本文为看雪论坛优秀文章
看雪论坛作者ID:si1enceZ
Overview
学了这么一段时间的Android,难得见到的一道比较对口的逆向题。不过比较遗憾的是,题目用的函数抽取的方法的文章我刚好看过,还研究了蛮久,不过当时就一直没有怎么看懂,只死磕出了在抽取指令时的操作,但是却不明白他是通过什么以及如何将指令给填充回去的。
不过经过实打实分析了这个题目后,对指令填充有了一定的理解,虽然这个题目填充的方式比较简易,但是至少知道了核心思想。果然知识表面上看懂了都还不是自己的,只有真正上手实践了才能理解的更加深刻。
一
StubApp-一代壳分析
attachBaseContext
首先通过AndroidManifest.xml找到入口Activity:com.mrctf.android2022.StubApp
StubApp类里先看attachBaseContext。
判断了e类的a函数返回值,如果True则取e.d()的ClassLoader。
e.a()
设置了两个so文件的路径、API的检查以及
e.f() => e.e() => e.b() e.c()
e.b()函数通过在su常见路径下创建su文件来检查是否被root。
e.c()通过执行which su命令后读取输出来检查是否有su文件。
如果有su则会输出:
如果没有su则没有输出:
所以总的来说 attachBaseContext的e.a函数加载了两个数据文件夹下so文件的路径以及运行环境和root的检测。
e.d()
e.d()中分别读取并解密了assets文件夹下的三个文件res.dat、libc++_shared.so和build.json。
其中res.dat和libc++_shared.so作为两个so文件。
build.json作为Dex文件通过InMemoryDexClassLoader加载。
InMemoryDexClassLoader能够通过从一个包含着DEX文件的缓冲区中加载类。可以用来执行没有被写进本地文件系统的代码。
尔后调用了e.g()来将当前的Activity的classLoader替换成我们加载了自定义类的classLoader。
因为当前的Activity的mClassLoader已经是Activity默认的PathClassLoader,而该PathClassLoader并没有加载我们的自定义类,而且我们接下来要启动的MainActivity也是由PathClassLoader加载的,而且我们的InMemoryDexClassLoader和PathClassLoader都是继承于BaseDexClassLoader,所以后续如果要调用自定义类的话就会找不到。
接下来分析解密几个so。
二
libstub.so
res.dat
首先是由res.dat解密得到libnative.so。
读取res.dat文件后调用了decodeSo函数进行解密存放在应用的数据目录下的libnative.so,而decodeSo是libstub.so里的native函数。
但是在libstub.so里却没有直接找到decodeSo函数,因此应该是JNI_OnLoad里动态注册的。
decode String
另外libstub.so使用了Ollvm的字符串加密和控制流平坦化。
字符串在加载时调用了.init_array里的以.datadiv_decode开头的函数对字符串进行解密。
因此对于字符串解密有两种方法:
① 手写idapython脚本执行这几个函数,对字符串进行解密。
② 使用unicorn引擎模拟执行,在进行写操作时对操作数的地址进行Hook,即可获取初始化后的so文件。
这里参考官方给的WP中使用了AndroidNativeEmu框架。
import logging
import sys
from unicorn import *
import struct
from androidemu.emulator import Emulator
from UnicornTraceDebugger import udbg
logging.basicConfig(
stream=sys.stdout,
level=logging.DEBUG,
format='%(asctime)s %(levelname)7s %(name)34s | %(message)s'
)
logger = logging.getLogger(__name__)
emulator = Emulator(vfp_inst_set=True, vfs_root='vfs')
str_datas = {}
def hook_mem_write(uc,type,address,size,value,userdata):
try:
curdata = struct.pack("I", value)[:size]
str_datas[address] = curdata
except:
print(size)
emulator.mu.hook_add(UC_HOOK_MEM_WRITE,hook_mem_write)
emulator.load_library('lib/libc.so',do_init=False)
lib_module = emulator.load_library('lib/libstub.so',do_init=True)
base_addr = lib_module.base
sodata = open('lib/libstub.so', 'rb').read()
for address,value in str_datas.items():
if base_addr < address < base_addr + lib_module.size:
offset = address - base_addr - 0x1000
print('address:0x%x data:%s offset:0x%x ' % (address, value, offset))
sodata = sodata[:offset] + value + sodata[offset+len(value):]
with open('lib/libstub_new.so','wb') as file:
file.write(sodata)
解密前:
解密后:
decodeSo
sub_3F44里调用了sub_4D06是RegisterNatives动态注册函数。
RegisterNatives的定义
jint RegisterNatives(JNIEnv *env, jclass clazz,
const JNINativeMethod *methods, jint nMethods);
所以可以看到sub_3A74就是实际调用的decodeSo函数。
因为
a & ~b | ~a & b = a ^ b
实际上干的内容就是逐字节异或0x22,然后保存到文件,即可得到libnative.so
JNI_OnLoad
但是后面JNI_OnLoad还没有分析完。后面调用了sub_2A2C和sub_2DEC。
后面看了源码后才知道这两个函数分别是通过inLine Hook来Hook libc.so的execve和Hook libart.so的loadMethod。
Inline hook
项目地址:https://github.com/ele7enxxh/Android-Inline-Hook
大致使用方法:
if(registerInlineHook(target_func,read_func,tmp_func) == ELE7EN_OK){
if(inlineHook(target_func != ELE7EN_OK)){
return -1;
}
else{
return 0;
}
}
三
libnative.so
分析MainActivity
MainActivity存在的dex由build.json异或49即可得到。
MainActivity里检查了输入长度然后调用了Utils.nativeCheck。
Utils加载了libnative.so。
我们直接搜索发现好像有nativeCheck,但是查看了函数内容提示是个错误的flag。
仔细看发现这里的函数在Utils前有两个下划线:
因此实际上的函数又是一个动态注册的。
使用以下脚本去除一些混淆:
for addr,name in Names():
if name.startswith('x.') or name.startswith('y.'):
patch_dword(addr,0)
sub_3D74
我们可以看到是有注册nativeCheck和check函数,但是注册的函数处的参数的地址没有有用的东西。后来看了源码之后,在sub_3D74又是一个Inline Hook,dword_1B37C是在JNI_OnLoad里赋了JNIEnv*类型。
而+860偏移是RegisterNatives函数(前面分析的是有看到过,所以还算比较熟悉,或者是查看Structures里的JNINativeInterface的定义)。
sub_3834
因此sub_3D74函数里Hook了RegisterNatives函数,实际调用的是sub_3834
由前面定义知道a3是JNINativeMethod。
typedef struct{
const char* name;
const char* signature;
void* fnPtr;
}JNINativeMethod;
所以干的事情就是把传进去的函数减了2022才是实际的函数地址。
nativeCheck
nativeCheck函数的地址为0x29BC+3-2022=0x21D8。
nativeCheck内对输入的参数调用了Utils的test函数,但是我们看到的函数只有一个return true。
这涉及到nativeCheck里的其他操作。
四
函数抽取壳-二代壳分析
myloadmethod
首先我们先回到libstub里对loadMethod的Hook上分析。
Hook后的函数将dexfile偏移0xB24的地址给读取了出来并存到了shm文件下。
我们从dex中可以看到该处地址刚好是存放test函数地址的地址。
getPath
然后在libnative.so的JNI_OnLoad函数读取了shm文件里的内容,赋给了methodAddr。
nativeCheck
尔后在nativeCheck里给该地址赋了值,即把指令填了回去。
再调用test函数。
然后再把test函数里的指令给清空。
recovery
因此只需将指令填回去即可恢复test函数的内容。
对输入进行异或后,又调用了check函数。
五
解题
check函数里对输入进行了填充,然后又调用了Encrypto函数后和签名进行异或,最后与目标数据进行比较。
enc = [38,43,44,115,20,17,22,19,32,119,42,41,19,68,19,26,117,112,38,33,18,67,19,18,32,38,35,38,19,69,17,23,117,112,44,112,19,27,20,21,39,32,37,39,23,76,21,20,47,32,32,120,31,24,67,70,35,39,34,39,23,74,23,16,32,115,32,37,21,20,18,27,36,38,16,67,36,39,16,26]
signByte = [0x33,0x31,0x3a,0x31,0x34,0x36,0x34,0x61,0x3b,0x39,0x32,0x39,0x3c,0x3f,0x3f,0x38,0x71,0x21,0x22,0x20,0x24,0x27,0x26,0x26,0x28,0x2b,0x2a,0x29,0x2c,0x29,0x29,0x2d,0x44,0x43,0x1b,0x47,0x16,0x15,0x15,0x17,0x18,0x4d,0x1a,0x36,0x31,0x3b,0x31,0x65,0x3d,0x30,0x33,0x30,0x31,0x3c,0x6d,0x3b,0x3d,0x6a,0x3f,0x21,0x21,0x23,0x23,0x76,0x25,0x23,0x27,0x28,0x2a,0x2a,0x2f,0x24,0x2e,0x2f,0x2f,0x41,0x12,0x12,0x13,0x1c,0x15,0x10,0x17,0x1b,0x1c,0x1f,0x30,0x35,0x32,0x35,0x35,0x36,0x36,0x36,0x3f,0x3a,0x39,0x3a,0x3c,0x6c,0x3d,0x3f,0x20,0x29,0x22,0x25,0x24,0x26,0x23,0x22,0x28,0x2d,0x2a,0x23,0x2d,0x2e,0x2e,0x2e,0x17,0x12,0x11,0x12,0x14,0x44,0x15,0x17,0x18,0x11,0x1a,0x36,0x31,0x31,0x36,0x31,0x35,0x32,0x37,0x3f,0x38,0x39,0x3b,0x3d,0x3a,0x3d,0x3c,0x21,0x21,0x73,0x20,0x24,0x25,0x2e,0x27,0x2e,0x29,0x29,0x2e,0x29,0x2d,0x2a,0x2f,0x41,0x10,0x11,0x13,0x15,0x12,0x15,0x14,0x19,0x19,0x4b,0x33,0x31,0x32,0x3b,0x34,0x33,0x36,0x34,0x3d,0x3c,0x3a,0x3f,0x3c,0x6f,0x3f,0x3c,0x20,0x20,0x25,0x20,0x27,0x24,0x26,0x76,0x2b,0x29,0x2a,0x23,0x2c,0x2b,0x2e,0x2c,0x15,0x14,0x12,0x17,0x14,0x16,0x17,0x14,0x18,0x18,0x1d,0x33,0x32,0x32,0x32,0x61,0x34,0x31,0x37,0x6c,0x3a,0x38,0x38,0x3d,0x3e,0x3e,0x3c,0x25,0x22,0x23,0x20,0x27,0x26,0x26,0x24,0x2a,0x2a,0x2b,0x28,0x25,0x2e,0x2d,0x2c,0x10,0x14,0x43,0x12,0x13,0x15,0x42,0x14,0x1c,0x1a,0x1c,0x33,0x31,0x31,0x36,0x37,0x35,0x35,0x30,0x3b,0x39,0x39,0x39,0x3f,0x3c,0x3d,0x36,0x23,0x22,0x21,0x23,0x21,0x74,0x25,0x27,0x2c,0x21,0x29,0x2a,0x2c,0x7c,0x2d,0x2f,0x10,0x19,0x12,0x15,0x14,0x16,0x13,0x12,0x18,0x1d,0x1a,0x36,0x30,0x31,0x33,0x35,0x32,0x35,0x34,0x39,0x39,0x6b,0x38,0x3c,0x3d,0x36,0x3f,0x26,0x21,0x21,0x26,0x21,0x25,0x22,0x27,0x20,0x28,0x29,0x2b,0x2d,0x2a,0x2d,0x2c,0x11,0x11,0x43,0x10,0x14,0x15,0x1e,0x17,0x1e,0x19,0x19,0x35,0x34,0x32,0x37,0x34,0x32,0x37,0x34,0x38,0x38,0x3d,0x38,0x3f,0x3c,0x3e,0x6e,0x23,0x21,0x22,0x2b,0x24,0x23,0x26,0x24,0x2d,0x2c,0x2a,0x2f,0x2c,0x7c,0x2f,0x2c,0x10,0x10,0x15,0x10,0x17,0x14,0x16,0x46,0x1b,0x19,0x1a,0x38,0x31,0x34,0x33,0x37,0x30,0x33,0x37,0x3c,0x39,0x68,0x3a,0x3f,0x3d,0x3f,0x38,0x23,0x22,0x23,0x23,0x75,0x26,0x26,0x27,0x20,0x29,0x2c,0x2b,0x2f,0x28,0x2b,0x2f,0x14,0x11,0x11,0x12,0x17,0x15,0x17,0x10,0x1b,0x1a,0x1a,0x38,0x33,0x32,0x32,0x36,0x37,0x35,0x37,0x38,0x6d,0x3a,0x3d,0x3c,0x34,0x3c,0x6e,0x28,0x27,0x26,0x2b,0x2c,0x23,0x70,0x20,0x28,0x7d,0x2a,0x2a,0x2c,0x2c,0x2e,0x2e,0x10,0x14,0x12,0x13,0x14,0x16,0x1e,0x15,0x18,0x18,0x1a,0x66,0x31,0x32,0x30,0x34,0x3d,0x34,0x37,0x39,0x39,0x6b,0x3b,0x3e,0x35,0x3c,0x3f,0x21,0x21,0x23,0x23,0x24,0x2d,0x27,0x73,0x7b,0x7c,0x2a,0x2e,0x2a,0x2d,0x27,0x7a,0x17,0x44,0x12,0x47,0x14,0x16,0x13,0x1f,0x4b,0x1a,0x18,0x38,0x33,0x31,0x65,0x60,0x67,0x67,0x35,0x38,0x3e,0x39,0x39,0x39,0x3d,0x6f,0x39,0x27,0x77,0x71,0x71,0x2c,0x71,0x74,0x2f,0x21,0x7f,0x2f,0x79,0x28,0x7b,0x2c,0x79,0x41,0x47,0x1b,0x13,0x42,0x16,0x14,0x13,0x19,0x10,0x48,0x37,0x31,0x3a,0x30,0x62,0x30,0x30,0x36,0x31,0x6c,0x6c,0x39,0x3e,0x39,0x3d,0x69,0x22,0x25,0x2a,0x2a,0x71,0x23,0x2f,0x26,0x21,0x2b,0x2b,0x2c,0x7e,0x25,0x2c,0x7d,0x46,0x10,0x15,0x15,0x10,0x47,0x42,0x1e,0x4c,0x11,0x18,0x39,0x63,0x36,0x36,0x34,0x34,0x31,0x66,0x6a,0x3e,0x32,0x68,0x6a,0x3e,0x6c,0x3b,0x29,0x23,0x24,0x70,0x23,0x70,0x72,0x75,0x7e,0x2c,0x7e,0x2f,0x2b,0x29,0x29,0x29,0x41,0x13,0x16,0x45,0x15,0x47,0x1e,0x17,0x4b,0x11,0x1c,0x65,0x30,0x37,0x34,0x32,0x37,0x32,0x64,0x6c,0x6f,0x6b,0x39,0x6f,0x3a,0x3b,0x3b,0x23,0x25,0x73,0x22,0x20,0x2d,0x26,0x75,0x2e,0x2d,0x28,0x29,0x2b,0x29,0x2e,0x2a,0x16,0x44,0x43,0x41,0x15,0x41,0x42,0x44,0x19,0x4f,0x13,0x61,0x31,0x31,0x36,0x33,0x34,0x32,0x64,0x6c,0x3d,0x33,0x6e,0x6e,0x3b,0x3e,0x3a,0x27,0x26,0x24,0x25,0x70,0x70,0x72,0x23,0x2b,0x2c,0x7e,0x2d,0x25,0x7f,0x2d,0x7c,0x44,0x16,0x10,0x17,0x47,0x12,0x1f,0x1f,0x1b,0x4c,0x18,0x38,0x38,0x63,0x32,0x31,0x64,0x35,0x33,0x3c,0x3b,0x39,0x6e,0x6d,0x3f,0x3b,0x69,0x76,0x20,0x2a,0x23,0x75,0x77,0x24,0x23,0x2d,0x7f,0x28,0x7a,0x2a,0x2b,0x2f,0x7d,0x18,0x18,0x41,0x47,0x10,0x11,0x16,0x1e,0x1f,0x4c,0x19,0x64,0x36,0x30,0x61,0x60,0x34,0x60,0x31,0x30,0x6a,0x3a,0x33,0x38,0x6c,0x3d,0x6e,0x26,0x28,0x71,0x21,0x2d,0x76,0x2f,0x73,0x29,0x2b,0x78,0x7d,0x7f,0x7e,0x27,0x79,0x11,0x17,0x12,0x17,0x15,0x43,0x13,0x13,0x18,0x4c,0x18,0x39,0x30,0x61,0x65,0x30,0x30,0x60,0x64,0x31,0x38,0x3d,0x6f,0x3c,0x6b,0x6b,0x3e,0x21,0x21,0x23,0x22,0x2d,0x73,0x20,0x20,0x7e,0x7d,0x2c,0x79,0x2d,0x2e,0x28,0x2d,0x41,0x19,0x40,0x13,0x45,0x12,0x15,0x17,0x11,0x4d,0x49,0x34,0x32,0x34,0x34,0x3d,0x35,0x63,0x33,0x3e,0x6c,0x3c,0x3c,0x6f,0x3f,0x3d,0x3d,0x27,0x70,0x77,0x20,0x26,0x23,0x72,0x74,0x2c,0x21,0x7b,0x23,0x29,0x2d,0x7f,0x2e,0x12,0x13,0x16,0x16,0x47,0x43,0x1f,0x1f,0x4a,0x1c,0x1b,0x36,0x32,0x34,0x36,0x34,0x60,0x30,0x64,0x6a,0x31,0x33,0x69,0x38,0x3e,0x3a,0x3e,0x27,0x73,0x25,0x20,0x70,0x23,0x26,0x27,0x2c,0x2d,0x2e,0x2d,0x7f,0x2f,0x26,0x7a,0x18,0x17,0x47,0x47,0x13,0x11,0x47,0x10,0x10,0x1a,0x48,0x64,0x62,0x67,0x66,0x3d,0x64,0x34,0x3e,0x3e,0x39,0x38,0x3f,0x34,0x3e,0x3a,0x39,0x73,0x26,0x20,0x76,0x71,0x70,0x73,0x74,0x7d,0x2a,0x23,0x2b,0x79,0x7b,0x29,0x26,0x13,0x19,0x46,0x46,0x41,0x1d,0x11,0x17,0x1e,0x4a,0x48,0x34,0x31,0x33,0x60,0x67,0x32,0x33,0x32,0x3a,0x3e,0x3d,0x3d,0x3c,0x39,0x6d,0x39,0x27,0x21,0x20,0x23,0x27,0x25,0x27,0x27,0x28,0x29,0x2b,0x7a,0x2f,0x2f,0x2f,0x2c,0x10,0x10]
sign =[]
for i in range(1023):
sign.append(signByte[i]^(i%43))
data = []
for i in range(len(enc)):
data.append(enc[i]^sign[i])
key =0x20222022
for j in range(0,80,8):
left = data[j] << 24 | data[j + 1] << 16 | data[j + 2] << 8 | data[j + 3]
right = data[j + 4] << 24 | data[j + 5] << 16 | data[j + 6] << 8 | data[j + 7]
right = (right ^ left)&0xffffffff
left = (left ^ key)&0xffffffff
data[j] = (left >> 24) & 0xff
data[j + 1] = (left >> 16) & 0xff
data[j + 2] = (left >> 8) & 0xff
data[j + 3] = left & 0xff
data[j + 4] = (right >> 24) & 0xff
data[j + 5] = (right >> 16) & 0xff
data[j + 6] = (right >> 8) & 0xff
data[j + 7] = right & 0xff
data = [chr(i) for i in data if i !=0]
flag = []
for i in range(0,len(data),2):
if data[i]!=0:
flag.append(int(data[i]+data[i+1],16))
for i in range(len(flag)-2,0,-1):
flag[i] = flag[i] ^ flag[i+1] ^i
flag = [chr(i) for i in flag if i !=0]
print(''.join(flag))
看雪ID:si1enceZ
https://bbs.kanxue.com/user-home-926331.htm
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!
原文始发于微信公众号(看雪学苑):MRCTF2022 stuuuuub 题解