MRCTF2022 stuuuuub 題解
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 loggingimport sys from unicorn import *import struct from androidemu.emulator import Emulatorfrom 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.basesodata = 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 =0x20222022for 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 & 0xffdata = [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] ^iflag = [chr(i) for i in flag if i !=0]print(''.join(flag))