靜態分析sscronet和metasec_ml走不通了,換個思路繼續搞!
1、直接打開從內存dump的so,報錯如下:
本以為只是簡單破壞了文件頭,於是用010editor比對內存dump的so和安裝包原始的so,看看哪些地方被破壞了,手動挨個恢復;結果發現兩個so長度都不同了(結構都被改了),而且差一點有幾十個,挨個恢復效率太低;
繼續從內存dump另一個mestasec_ml文件,用ida打開遇到同樣問題,只能用010editor看看,結果大失所望:
- 加密的字段還是沒解密
- 字段的位置都變了
- 文件長度、結構同樣都變了,這殼加的真是一絕!
按照以往so加密的經驗或套路:OLLVM對關鍵字符串加密,然后在init或init_array解密!這里從內存dump出來的應該是解密后的,但這里並未解密,可能的原因有:(1)使用時才解密,用完后又重新加密 (2)這些加密字段根本就不重要,是故意用來轉移注意力的! 這里暫時不繼續,換模擬器執行試試!
2、用模擬器的本意是想hook內存寫的代碼,一旦發現寫了內存就記錄寫的地址和內容,然后把這些內容寫回原so文件,相當於打個補丁!
(1)這里直接使用看雪hanbingle大佬的補丁腳本(我加了些注釋),如下:
import logging import sys import unicorn import struct from androidemu.emulator import Emulator import binascii # Configure logging logging.basicConfig( stream=sys.stdout, level=logging.DEBUG, format="%(asctime)s %(levelname)7s %(name)34s | %(message)s" ) logger = logging.getLogger(__name__) # Initialize emulator modify_map={} emulator = Emulator(vfp_inst_set=True) def UC_HOOK_MEM_WRITE(mu,type,address,size,value,user_data): #print("address:"+str(address)) bytearray=struct.pack("I",value)[:size]#寫入內存的內容轉換成字節數組 modify_map[address]=bytearray#放入map中 logger.info("address:" + str(hex(address)) + "-------content:" + str(bytearray)) if(size==1):#如果只改了一個字節,很有可能是解密字符串的,先打印看看 logger.info(chr(value)) return filepath="example_binaries/libmetasec_ml.so" #filepath="example_binaries/obf.so" try: emulator.mu.hook_add(unicorn.UC_HOOK_MEM_WRITE,UC_HOOK_MEM_WRITE)#一旦寫內存,就調用UC_HOOK_MEM_WRITE函數 emulator.load_library("./example_binaries/libc.so", do_init=False) emulator.load_library("./example_binaries/libdl.so", do_init=False) emulator.load_library("./example_binaries/libstdc++.so", do_init=False) emulator.load_library("./example_binaries/libm.so", do_init=False) lib_module = emulator.load_library(filepath, do_init=True) orisofile=open(filepath,'rb') content=orisofile.read()#讀取原始so的內容 orisofile.close() for i in modify_map: value=modify_map[i] base=lib_module.base if(i>=base and i<=(base+lib_module.size)): #代碼段和數據段並不是連續存放的,還有0x1000的偏移 offset=i-base-0x1000 #原始的內容打斷重新拼接 content=content[:offset]+value+content[offset+len(value):] #logger.info("offset:"+offset+"-------content:"+binascii.b2a_hex(value)) logger.info("offset:" + str(hex(offset)) + "-------content:" + str(value)) fixso=filepath+".fix" fixfile=open(fixso,'wb') fixfile.write(content) fixfile.close() except Exception as e: print(e)
從androidNativeEmu日志看,init_array的函數在挨個執行了;但是執行到第三個的時候報錯:unhandled syscall 0x142(322),原來還有系統調用的模擬還未覆蓋到!想要繼續執行,需要補全這個系統調用,這里暫時不管,換ExAndroidNativeEmu試試!
ExAndroidNativeEmu遇到了內存錯誤:
無奈繼續換unidbg試試,原理同樣是hook寫內存的代碼,一旦發現寫了內存,立即記錄寫的位置和內容;執行的腳本參考文章末尾鏈接1;從執行日志看來:
- 這兩個so還有調用關系,metasec_ml調用了sscronet的3個函數,分別是Cronet_Engine_Create、Cronet_Engine_SetOpaque和Cronet_Engine_Destroy,既然都被調用了,可以用frida跟蹤一下函數的調用棧
- 還是因為內存中的so結構已經被改變,簡單粗暴寫回原位置已經不行了,代碼一直報錯!
繼續分析上面提到的那3個函數。第一個Create函數,F5的代碼如下:函數沒有參數,返回了v1;從代碼看,v1貌似是個結構體,不同的偏移有不同的取值;
int Cronet_Engine_Create() { int v0; // r0 int v1; // r4 _QWORD *v2; // r0 _QWORD *v3; // r0 v0 = sub_143D48(356); v1 = v0; *(_DWORD *)(v0 + 256) = 30000; *(_BYTE *)(v0 + 252) = 1; *(_BYTE *)(v0 + 16) = 0; *(_BYTE *)(v0 + 12) = 0; *(_DWORD *)v0 = &off_277914; *(_DWORD *)(v0 + 4) = 0; *(_DWORD *)(v0 + 8) = 0; sub_1432BA(v0 + 260); *(_BYTE *)(v1 + 276) = 1; *(_DWORD *)(v1 + 264) = 0; *(_DWORD *)(v1 + 268) = 0; *(_DWORD *)(v1 + 272) = 0; sub_1432BA(v1 + 280); *(_DWORD *)(v1 + 296) = sub_F2DF6; *(_DWORD *)(v1 + 300) = &unk_67B9C; *(_DWORD *)(v1 + 284) = 0; sub_143302(v1 + 304, 0, 1); *(_BYTE *)(v1 + 312) = 0; sub_143302(v1 + 316, 0, 1); v2 = (_QWORD *)(v1 + 340); *v2 = 0LL; v2[1] = 0LL; v3 = (_QWORD *)(v1 + 324); *v3 = 0LL; v3[1] = 0LL; return v1; }
所以這里Create函數的hook代碼:
var Cronet_Engine_Create = Module.findExportByName("libsscronet.so","Cronet_Engine_Create") console.log("Cronet_Engine_Create in libsscronet.so addr:"+Cronet_Engine_Create) Interceptor.attach(Cronet_Engine_Create,{ onEnter: function (args) { console.log('called from:\n' +Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n') },onLeave: function (retval){ console.log("Cronet_Engine_Create retval:"+hexdump(retval, {//dump指定內存 offset: 0, //從何處開始 length: 400, //長度 header: true, ansi: true //是否是ansi })) } })
正當我得意洋洋,想着馬上就可以根據棧回溯找到metasec_ml中關鍵的函數位置時,上述這段代碼運行報錯,錯誤提示:Error: expected a pointer;於時逐行代碼檢查,發現函數地址是null;然后用這個代碼查找導出函數,發現一個都沒有,這就奇怪了:ida能找到導出函數,為啥frida就找不到了?
var exports = Module.enumerateExportsSync("libsscronet.so"); console.log("export:"+exports);
繼續用Module.findBaseAddress,發現連libsscronet.so的基址都是null;同理,libmetasec_ml.so的基址也是null;難道是我的api用錯了?用同樣的api,隨機選了其他so文件,比如libc、libutils、libgui、libLLVM、libstagefright、libinput等都能找到基址,唯獨這兩個so找不到,個人猜測是故意的,不知道基址找不到是不是因為內存中soinfo結構體被更改導致(感覺有點像windows下pe結構體斷鏈);這里我就不糾結了,直接手動通過cat /proc/pid/maps|grep sscronet方式獲取到libsscronet的基址是0x0cc00000,再從ida里查到Cronet_Engine_Create的偏移是0xF2D0C,所以hook代碼改為:
var Cronet_Engine=0x0cc00000+0xF2D0C var Cronet_Engine_Create = new NativePointer(Cronet_Engine) console.log("Cronet_Engine_Create in libsscronet.so addr:"+Cronet_Engine_Create) Interceptor.attach(Cronet_Engine_Create, { //Interceptor.attach(Create_addr, { onEnter: function (args) { console.log('called from:\n' +Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n') },onLeave: function (retval){ console.log("Cronet_Engine_Create retval:"+hexdump(retval, {//dump指定內存 offset: 0, //從何處開始 length: 400, //長度 header: true, ansi: true //是否是ansi })) } })
然鵝遺憾的是並未按照預期打印函數的調用棧,估計是時機不對;
回到unidbg:這3個函數調用的位置都在日志顯示了,分別如下:
.text:00012F0C .text:00012F0C loc_12F0C ; CODE XREF: sub_12EA0+40↑j .text:00012F0C 018 29 6A LDR R1, [R5,#(dword_9FD10 - 0x9FCF0)] ; name .text:00012F0E 018 20 46 MOV R0, R4 ; handle .text:00012F10 018 F4 F7 AE EC BLX dlsym .text:00012F14 018 69 6A LDR R1, [R5,#(dword_9FD14 - 0x9FCF0)] .text:00012F16 018 A8 60 STR R0, [R5,#(dword_9FCF8 - 0x9FCF0)] .text:00012F18 018 D1 B9 CBNZ R1, loc_12F50 .text:00012F1A 018 18 20 MOVS R0, #0x18 .text:00012F1C 018 64 F0 1A FE BL sub_77B54
.text:00012F50 loc_12F50 ; CODE XREF: sub_12EA0+78↑j .text:00012F50 018 69 6A LDR R1, [R5,#(dword_9FD14 - 0x9FCF0)] ; name .text:00012F52 018 20 46 MOV R0, R4 ; handle .text:00012F54 018 F4 F7 8C EC BLX dlsym .text:00012F58 018 A9 6A LDR R1, [R5,#(dword_9FD18 - 0x9FCF0)] .text:00012F5A 018 E8 60 STR R0, [R5,#(dword_9FCFC - 0x9FCF0)] .text:00012F5C 018 A9 B9 CBNZ R1, loc_12F8A .text:00012F5E 018 16 20 MOVS R0, #0x16 .text:00012F60 018 64 F0 F8 FD BL sub_77B54
text:00012F8A loc_12F8A ; CODE XREF: sub_12EA0+BC↑j .text:00012F8A 018 A9 6A LDR R1, [R5,#(dword_9FD18 - 0x9FCF0)] ; name .text:00012F8C 018 20 46 MOV R0, R4 ; handle .text:00012F8E 018 F4 F7 70 EC BLX dlsym .text:00012F92 018 28 61 STR R0, [R5,#(dword_9FD00 - 0x9FCF0)] .text:00012F94 018 5D F8 04 BB LDR.W R11, [SP+0x10+var_10],#4 .text:00012F98 014 F0 BD POP {R4-R7,PC}
無一例外都發生在dlsym之后的STR語句,把dlsym的返回值從R0存放到內存中;這里有兩個思路:(1)frida或ida看看寄存器的值 (2)unidbg來trace;這里選擇用unidbg來trace(trace代碼在文章末尾)!剛開始trace就遇到了問題:從日志看,代碼已經進入jni_onload函數執行(我是想看看registerNative在哪)是在新生成線程,然后就一直卡這里了,沒有任何新日志,不知道哪里出了問題!
這里新建一個類,implements CodeHook接口,完成 public void hook(Backend backend, long address, int size, Object user)方法,把每條執行的代碼地址都記錄在案;重新開始后一共執行了大約15分鍾,執行了約570萬行代碼,通過日志來看,發現卡住的元凶了:如下圖,從107萬行代碼開始,一直在重復:貌似在等待互斥鎖,如果沒有得到就一直等!怪不得代碼卡這里了;一不做二不休,直接NOP掉CMP和BNE這兩行代碼,然后繼續用unidbg來trace,這次果然一次性繞過了這個“死循環”!(經多方打探和證實,這是由於unidbg不支持多線程導致的;哎,模擬環境終究還是模擬環境,比不了真機......)
然后用參考鏈接1或2的idapython腳本給so的代碼作色和打補丁(腳本用到了sark),把沒用的代碼都nop掉,恢復so文件最初是的樣子,就能順利F5和找jni函數了,結果遇到了一堆的問題!先是找不到sark,發現是ida引用的路徑不對,所以手動用sys.path.append指定了sark的安裝路徑;接着又是dll load failed報錯,按照很多人建議重新安裝了python、然后用python3.dll替換,特么這次又報如下錯誤:
這個錯誤花了大半天時間也沒找到解決辦法,目前暫時卡這里了,后續要么解決這個bug,要么換其他的思路繼續跟蹤!這次就暫時先到這里吧,我繼續想想怎么搞idapyhton的這個bug!
from PyQt5 import QtCore, QtWidgets, QtGui ImportError: DLL load failed: %1 不是有效的 Win32 應用程序。
3、思路總結:
(1)既然原始的so有很多加密的字符串,好多重要函數的棧也不平衡,ida無法靜態分析,那就從內存dump唄!從內存dump出來后發現:
-
- 字符串還是沒解密(也有可能用的時候解密,用完后再次加密)
- elf文件頭也被破壞了,ida都沒法分析
- elf文件結構也變了(字符串的位置被挪動)
(2)原始so的文件結構還沒被破壞(如果改變,連操作系統的linker都不認識了,徹底沒法加載),所以想着從這里突破:先用unidbg來trace,記錄執行代碼的地址;然后用ida的腳本打patch,把沒有被執行過的代碼都nop掉,這樣來去掉OLLVM的混淆和被破壞的棧平衡!目前trace成功了,得到了代碼執行的log,但是用idapython執行patch腳本時報錯,需要解決這個bug!
(3)這里還有一絕要再次提起:frida的api找不到sscronet和metasec_ml這兩個so的基址,但是其他so卻能找到!以前在windows下做外掛,為了躲避搜查,外掛的dll或驅動經常采用斷鏈的方式讓自己的各種結構體脫離操作系統管理的鏈表,第三方調用系統api查找時會遍歷鏈表,這時就找不到了!不知道這里是不是用了同樣的方式!
(4)idapython代碼(末尾參考鏈接1和2都有,原理都一樣:通過sark標記)
# -*- coding: utf-8 -*- import sark import idc import sys #sys.path.append('D:\\BaiduNetdiskDownload\\douyin\\venv\\Lib\\site-packages') def patch_nop(addr): ''' 將指定地址的字節修改成nop ''' nop_code = [0x1f, 0x20, 0x03, 0xd5] for i in range(len(nop_code)): idc.patch_byte(addr+i, nop_code[i]) def patch_code(): # 設置需要處理代碼的起始地址和終止地址,並獲取范圍內所有匯編指令的地址 s = 0x10CEC # 目標方法起始地址 e = 0x10CEC+0xFEC # 目標方法結束地址 funcLines = sark.lines(s, e) for line in funcLines: # 判斷如果該行代碼的顏色是我們標記的顏色,則進入patch邏輯,將代碼patch為nop指令。 if line.type == "code": if line.color != 0xEE82EE: patch_nop(line.ea) def do_ollvm_bcf(): ''' ollvm虛假控制流處理 ''' log_file = "trace.log" trace_addrs = [] # 將trace指令的地址讀取到trace_addrs列表中 with open(log_file, "r") as f: lines = f.readlines() for line in lines: trace_addrs.append(int(line.replace("\n", ""), 16)) for addr in trace_addrs: line = sark.line.Line(addr) line.color = 0xEE82EE # 將執行過的指令通過修改顏色來標記出來。 if __name__ == "__main__": do_ollvm_bcf() # patch_code()
unidbg trace的代碼(大部分參考末尾鏈接1的):
package com.unicorncourse08; import capstone.Capstone; import com.github.unidbg.AndroidEmulator; import com.github.unidbg.Emulator; import com.github.unidbg.LibraryResolver; import com.github.unidbg.Module; import com.github.unidbg.arm.backend.Backend; import com.github.unidbg.arm.backend.CodeHook; import com.github.unidbg.arm.backend.WriteHook; import com.github.unidbg.arm.backend.dynarmic.DynarmicLoader; import com.github.unidbg.linux.android.AndroidARMEmulator; import com.github.unidbg.linux.android.AndroidResolver; import com.github.unidbg.linux.android.dvm.AbstractJni; import com.github.unidbg.linux.android.dvm.DalvikModule; import com.github.unidbg.linux.android.dvm.VM; import com.github.unidbg.listener.TraceCodeListener; import com.github.unidbg.memory.Memory; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import unicorn.Unicorn; import java.io.*; import java.util.Arrays; import java.util.Map; public class decryptOLLVM<mouldBase> extends AbstractJni { static { DynarmicLoader.useDynarmic(); } private AndroidEmulator emulator; private final VM vm; public DeStrWriteHook trace; public DeCodeHook codeTrace; public Module module; public static String filePath = " \\unidbg-master\\libs\\libmetasec_ml.so"; public static String sscronet_filePath = " \\unidbg-master\\libs\\libsscronet.so"; private static final Log log = LogFactory.getLog(DalvikModule.class); public decryptOLLVM(){ emulator=new AndroidARMEmulator("com.ss.android.ugc.aweme",null,null); vm = emulator.createDalvikVM(); vm.setVerbose(true); vm.setJni(this); try { trace = new DeStrWriteHook(false); final Memory memory=emulator.getMemory(); //這里的android版本指定后會自動load相關的so LibraryResolver resolver = new AndroidResolver(23); memory.setLibraryResolver(resolver); //設置內存寫入的監控 emulator.getBackend().hook_add_new(trace,1,0,emulator); emulator.loadLibrary(new File(sscronet_filePath),true); emulator.loadLibrary(new File(" \\unidbg-master\\libs\\libttboringssl.so")); emulator.loadLibrary(new File(" \\unidbg-master\\libs\\libttcrypto.so")); emulator.loadLibrary(new File(" \\unidbg-master\\libs\\libandroid.so")); module = emulator.loadLibrary(new File(filePath),true); codeTrace = new DeCodeHook(module.base); // 添加一個指令集hook回調,並輸出當前執行指令在so文件中的偏移地址 emulator.getBackend().hook_add_new(codeTrace,module.base+0x7c50,module.base+0x84170,emulator); /*emulator.traceCode(module.base + 0x7c50, module.base + 0x84170, new TraceCodeListener() { @Override public void onInstruction(Emulator<?> emulator, long address, Capstone.CsInsn insn) { DeCodeHook.write2file("\\unidbg-master\\libs\\traceA.log" ,Long.toHexString(address-module.base)); } });*/ log.info("---------------before call jni_onload"); vm.callJNI_OnLoad(emulator,module); log.info("---------------after call jni_onload"); } catch (Exception e) { e.printStackTrace(); } } public static byte[] readFile(String strFile){ try{ InputStream is = new FileInputStream(strFile); int iAvail = is.available(); byte[] bytes = new byte[iAvail]; is.read(bytes); is.close(); return bytes; }catch(Exception e){ e.printStackTrace(); } return null ; } public static void writeFile(byte[] data,String savefile){ try { FileOutputStream fos=new FileOutputStream(savefile); BufferedOutputStream bos=new BufferedOutputStream(fos); bos.write(data,0,data.length); bos.flush(); bos.close(); } catch (Exception e) { e.printStackTrace(); } } public static String bytetoString(byte[] bytearray) { String result = ""; char temp; int length = bytearray.length; for (int i = 0; i < length; i++) { if(bytearray[i]!=0) { temp = (char) bytearray[i]; result += temp; } } return result; } public static void main(String[] args){ decryptOLLVM destr=new decryptOLLVM(); //因為so文件的結構發生變化,簡單粗暴地寫回原so文件地址已經不行了,會報錯;所以這里以下的代碼都可以注釋掉,沒必要了(其他沒改文件結構的so是可以直接用來打patch的); String savepath="D:\\BaiduNetdiskDownload\\unidbg-master\\libs\\libnative-lib_new.so"; /* * sodata這個是從文件讀取的,但實際在內存中的so長度比文件讀取的長,造成了下面System.arraycopy(sodata,0,start,0,offset.intValue())報indexoutofbond錯誤; * */ byte[] sodata=readFile(filePath); long base_addr=destr.module.base; long module_size=destr.module.size; System.out.println(String.format("base_addr:0x%x module_size:%s end_addr:0x%x", base_addr, module_size, base_addr+module_size)); ByteArrayOutputStream baos = new ByteArrayOutputStream(); //遍歷保存的寫入地址和寫入數據 for(Map.Entry<Long, byte[]> item : destr.trace.dstr_datas.entrySet()){ //如果范圍是在模塊內的。則進行處理 if(item.getKey()>base_addr && item.getKey()<base_addr+module_size){ //獲取到正確的寫入的偏移位置 baos = new ByteArrayOutputStream(); Long offset=item.getKey()-base_addr-0x1000; System.out.println(String.format("offset:0x%x----data:%s----data:%s",offset,bytetoString(item.getValue()), Arrays.toString(item.getValue()))); //先把前半部分取出來 byte[] start=new byte[offset.intValue()]; //int diffLen = start.length - sodata.length;//得到磁盤so長度和內存so長度差值 //byte[] sodata = new byte[offset.intValue()]; System.arraycopy(sodata,0,start,0,offset.intValue()); //然后把后半部分的大小計算出來 int endsize=sodata.length-offset.intValue()-item.getValue().length; //把后半部分的數據填充上 byte[] end=new byte[endsize]; System.arraycopy(sodata,offset.intValue()+item.getValue().length,end,0,endsize); //將三塊位置的數據填充上 baos.write(start,0,start.length); baos.write(item.getValue(),0,item.getValue().length); baos.write(end,0,end.length); //最后把so保存起來 sodata=baos.toByteArray(); } } writeFile(baos.toByteArray(),savepath); } }
package com.unicorncourse08; import com.github.unidbg.Emulator; import com.github.unidbg.arm.backend.*; import com.github.unidbg.arm.backend.Backend; import com.github.unidbg.arm.backend.CodeHook; import com.github.unidbg.arm.context.RegisterContext; import com.github.unidbg.listener.TraceReadListener; import com.github.unidbg.listener.TraceWriteListener; import com.github.unidbg.listener.TraceWriteListener; import unicorn.Unicorn; import java.io.FileWriter; import java.io.IOException; import java.io.PrintStream; import java.util.Arrays; import java.util.HashMap; import java.util.Map; public class DeCodeHook implements CodeHook{ long base; public DeCodeHook(long base){ this.base=base; } public static void write2file(String fileName, String content) { try { // 打開一個寫文件器,構造函數中的第二個參數true表示以追加形式寫文件 FileWriter writer = new FileWriter(fileName, true); writer.write(content); writer.close(); } catch (IOException e) { e.printStackTrace(); } } @Override public void hook(Backend backend, long address, int size, Object user) { // 打印當前指令地址,注意需要將實際地址減去so基地址得到代碼在文件中的偏移 //System.out.println(String.format("0x%x",address-this.base)); write2file("\\unidbg-master\\libs\\trace.log",String.format("0x%x\r\n",address-this.base)); } @Override public void onAttach(Unicorn.UnHook unHook) { } @Override public void detach() { } }
package com.unicorncourse08; import com.github.unidbg.Emulator; import com.github.unidbg.arm.backend.Backend; import com.github.unidbg.arm.backend.BackendException; import com.github.unidbg.arm.backend.Backend; import com.github.unidbg.arm.backend.BackendException; import com.github.unidbg.arm.backend.ReadHook; import com.github.unidbg.arm.backend.WriteHook;//這才是正確的類,unicorn.WriteHook是錯的 import com.github.unidbg.arm.context.RegisterContext; import com.github.unidbg.listener.TraceReadListener; import com.github.unidbg.listener.TraceWriteListener; import com.github.unidbg.listener.TraceWriteListener; import unicorn.Unicorn; import java.io.PrintStream; import java.util.Arrays; import java.util.HashMap; import java.util.Map; /* * unidbg-master\\unidbg-api\\src\\main\\jav\a\com\\github\\unidbg\\TraceMemoryHook.java 有demo代碼 * */ class DeStrWriteHook implements WriteHook { private final boolean read; private Unicorn.UnHook unHook; DeStrWriteHook(boolean read) { this.read = read; } PrintStream redirect; TraceWriteListener traceWriteListener; //保存的寫入數據地址和寫入的數據 public Map<Long,byte[]> dstr_datas=new HashMap<Long,byte[]>(); /** * long類型轉byte[] (大端) * @param n * @return */ public static byte[] longToBytesBig(long n) { byte[] b = new byte[8]; b[7] = (byte) (n & 0xff); b[6] = (byte) (n >> 8 & 0xff); b[5] = (byte) (n >> 16 & 0xff); b[4] = (byte) (n >> 24 & 0xff); b[3] = (byte) (n >> 32 & 0xff); b[2] = (byte) (n >> 40 & 0xff); b[1] = (byte) (n >> 48 & 0xff); b[0] = (byte) (n >> 56 & 0xff); return b; } /** * long類型轉byte[] (小端) * @param n * @return */ public static byte[] longToBytesLittle(long n) { byte[] b = new byte[8]; b[0] = (byte) (n & 0xff); b[1] = (byte) (n >> 8 & 0xff); b[2] = (byte) (n >> 16 & 0xff); b[3] = (byte) (n >> 24 & 0xff); b[4] = (byte) (n >> 32 & 0xff); b[5] = (byte) (n >> 40 & 0xff); b[6] = (byte) (n >> 48 & 0xff); b[7] = (byte) (n >> 56 & 0xff); return b; } @Override public void hook(Backend backend, long address, int size, long value, Object user) { if (read) { return; } try { Emulator<?> emulator = (Emulator<?>) user; if (traceWriteListener == null || traceWriteListener.onWrite(emulator, address, size, value)) { //將寫入的地址和寫入的數據保存下來,先轉long為小端序 byte[] writedata=longToBytesLittle(value); byte[] resizeWriteData=new byte[size]; //將指定大小的數據保存 System.arraycopy(writedata,0,resizeWriteData,0,size); dstr_datas.put(address,resizeWriteData); /*if(size==1){ System.out.println(String.format("address:0x%x,content:%s",address,bytetoString(writedata))); }*/ //System.out.println("address:"+Long.toHexString(address)+"---------content:"+ Long.toString(value)); } } catch (BackendException e) { throw new IllegalStateException(e); } } @Override public void onAttach(Unicorn.UnHook unHook) { if (this.unHook != null) { throw new IllegalStateException(); } this.unHook = unHook; } @Override public void detach() { if (unHook != null) { unHook.unhook(); unHook = null; } } //https://www.cnblogs.com/Free-Thinker/p/4694854.html public static String bytetoString(byte[] bytearray) { String result = ""; char temp; int length = bytearray.length; for (int i = 0; i < length; i++) { if(bytearray[i]!=0) { temp = (char) bytearray[i]; result += temp; } } return result; } }
4. 最后:靜態分析查看metasec_ml的導入函數時發現了很多字符串相關的操作api,如下:好多都是字符串操作的系統api,會不會用這些api操作字符串了?
選了幾個常用的api hook,代碼如下:
var malloc_addr = Module.findExportByName("libc.so","malloc") console.log("malloc in libc.so addr:"+malloc_addr) Interceptor.attach(malloc_addr,{ onEnter: function (args) { var backtrace = Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') if(backtrace.indexOf("metasec") >= 0||backtrace.indexOf("sscronet") >= 0){ console.log('called from:\n' +backtrace + '\n') this.len=args[1] } //console.log('called from:\n' +Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n') },onLeave: function (retval){ console.log("retval:"+hexdump(retval, {//dump指定內存 offset: 0, //從何處開始 length: Number(this.len), //長度 header: true, ansi: false //是否是ansi })) } }) var memcmp_addr = Module.findExportByName("libc.so","memcmp") console.log("memcmp in libc.so addr:"+memcmp_addr) Interceptor.attach(memcmp_addr,{ onEnter: function (args) { console.log('called from:\n' +Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n') console.log("args[0]:"+ptr(args[0]).readCString()) console.log("args[1]:"+ptr(args[1]).readCString()) },onLeave: function (retval){ //console.log(hexdump(retval)) } }) var memcpy_addr = Module.findExportByName("libc.so","memcpy") console.log("memcpy in libc.so addr:"+memcpy_addr) Interceptor.attach(memcpy_addr,{ onEnter: function (args) { var backtrace = Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') if(backtrace.indexOf("metasec") >= 0||backtrace.indexOf("sscronet") >= 0){ console.log('called from:\n' +backtrace + '\n') //console.log("args[0]:"+ptr(args[0]).readCString()) console.log("args[1]:"+hexdump(args[1], {//dump指定內存 offset: 0, //從何處開始 length: args[2].toInt32(), //長度 header: true, ansi: true //是否是ansi })) } },onLeave: function (retval){ //console.log(hexdump(retval)) } })
很遺憾也沒發現那4個關鍵字段(也有可能是我hook的api少了,需要繼續嘗試其他的api);
參考:
1、http://missking.cc/2020/11/03/unicorn2/ unidbg執行so中的init_array
2、https://blog.csdn.net/weixin_46734340/article/details/118857038 Android逆向-實戰so分析-某洲_v3.5.8_unidbg學習
3、https://bbs.pediy.com/thread-266999.htm 分析unidbg(unidbgMutil)多線程機制