android so文件攻防實戰-libDexHelper.so反混淆
計劃是寫一個android中so文件反混淆的系列文章,目前這是第三篇。
第一篇:android so文件攻防實戰-百度加固免費版libbaiduprotect.so反混淆(https://bbs.pediy.com/thread-271388.htm)
第二篇:android so文件攻防實戰-某團libmtguard.so反混淆(https://bbs.pediy.com/thread-271853.htm)
今天分析的是企業版64位,我用LibChecker查了一下手機上的APP找到的,時間也還比較新。根據其他人的分析可知,libDexHelper.so是指令抽取的實現,libdexjni.so是VMP的實現。
去除混淆
首先因為加密過,肯定是不能直接反編譯的,可以在libart.so下斷點,進入JNI_onLoad以后就可以dump下來。

不過此時也不能直接F5,還存在以下混淆方式:
1.垃圾指令

這些垃圾指令是在switch的一個永遠不會被執行到的分支里面,可以直接將IDA不能MakeCode的地方patch成NOP再MakeCode。
2.字符串加密
有好幾個解密字符串的函數,0x186C4,0x7783C,0x95B9C。在android so文件攻防實戰-百度加固免費版libbaiduprotect.so反混淆(https://bbs.pediy.com/thread-271388.htm)中我們是交叉引用拿到加密后的字符串和它對應的解密函數的表然后frida主動調用得到的解密后的字符串,但是在這里這個方法就不太好用了。因為這里加密后的字符串是在棧上一個byte一個byte拼起來的,和最后調用解密函數之間可能隔了很多條指令,甚至都不在一個block。
我最后用的是下面這種方案:以0x40110處調用0x186C4處的解密函數為例,這里面字符串解密的邏輯比較簡單,需要三個參數。我們可以自己實現也可以用unicorn,我就用unicorn了。


import sysimport unicornimport binasciiimport threadingimport subprocess from capstone import *from capstone.arm64 import * with open("C:\\Users\\hjy\\Downloads\\out1.fix.so","rb") as f: sodata = f.read() uc = unicorn.Uc(unicorn.UC_ARCH_ARM64, unicorn.UC_MODE_ARM)code_addr = 0x0code_size = 8*0x1000*0x1000uc.mem_map(code_addr, code_size)stack_addr = code_addr + code_sizestack_size = 0x1000000stack_top = stack_addr + stack_size - 0x8uc.mem_map(stack_addr, stack_size)uc.mem_write(code_addr, sodata)uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X29, stack_addr)uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X28, stack_addr)uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X27, stack_addr)uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X26, stack_addr)uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X25, stack_addr)uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X24, stack_addr)uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X23, stack_addr)uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X22, stack_addr)uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X21, stack_addr)uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X20, stack_addr)uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X19, stack_addr)uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X18, stack_addr)uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X17, stack_addr)uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X16, stack_addr)uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X15, stack_addr)uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X14, stack_addr)uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X13, stack_addr)uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X12, stack_addr)uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X11, stack_addr)uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X10, stack_addr)uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X9, stack_addr)uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X8, stack_addr)uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X7, stack_addr)uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X6, stack_addr)uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X5, stack_addr)uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X4, stack_addr)uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X3, stack_addr)uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X2, stack_addr)uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X1, stack_addr)uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X0, stack_addr)uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_SP, stack_top)X0 = uc.reg_read(unicorn.arm64_const.UC_ARM64_REG_X0) uc.mem_write(X0, bytes.fromhex(sys.argv[1]))uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X1, int(sys.argv[2], 16))uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X2, int(sys.argv[3], 16)) uc.emu_start(0x1777C, 0x17780) X0 = uc.reg_read(unicorn.arm64_const.UC_ARM64_REG_X0)decstr = uc.mem_read(X0, 80) print("decstr:", decstr)uc.mem_unmap(stack_addr, stack_size)uc.mem_unmap(code_addr, code_size)
總共有幾百處調用,不可能全部人工去這樣解出來,我寫了另外以一個腳本去調用decstr.py。首先通過交叉引用找到所有調用解密函數的地方,然后把起始地址設為該block的起始地址,結束地址設為調用解密函數的地址,通過unicorn跑出decstr.py需要的三個參數之后調用decstr.py。
遇到unicorn.unicorn.UcError也有兩個處理策略,一個是跳過該地址( loop_call_prepare_arg1),起始地址不變;一個是將起始地址設為下一條地址(loop_call_prepare_arg2)。當然這套方案還有優化的空間,比如生成調用解密函數需要的參數的代碼和最后調用解密函數的代碼不在一個block,就處理不了。
import unicornimport binasciiimport threadingimport subprocess from capstone import *from capstone.arm64 import * inscnt = 0start_addr = 0end_addr = 0stop_addr = 0stop_addr_list = [] def hook_code(uc, address, size, user_data): global inscnt global end_addr global stop_addr global stop_addr_list md = Cs(CS_ARCH_ARM64, CS_MODE_ARM) for ins in md.disasm(sodata[address:address + size], address): #rint(">>> 0x%x:\t%s\t%s" % (ins.address, ins.mnemonic, ins.op_str)) stop_addr = ins.address if ins.address in stop_addr_list: #print("will pass 0x%x:\t%s\t%s" %(ins.address, ins.mnemonic, ins.op_str)) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_PC, address + size) return inscnt = inscnt + 1 if (inscnt > 500): uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_PC, 0xffffffff) return if ins.mnemonic.find("b.") != -1: print("will pass 0x%x:\t%s\t%s" %(ins.address, ins.mnemonic, ins.op_str)) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_PC, address + size) return if ins.mnemonic.find("bl") != -1: print("will pass 0x%x:\t%s\t%s" %(ins.address, ins.mnemonic, ins.op_str)) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_PC, address + size) return if ins.op_str in ["x0","x1","x2","x3"]: X1 = uc.reg_read(unicorn.arm64_const.UC_ARM64_REG_X1) if X1 > 0x105A88: print("will pass 0x%x:\t%s\t%s" %(ins.address, ins.mnemonic, ins.op_str)) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_PC, address + size) return if ins.op_str.startswith("#0x"): addr = int(ins.op_str[3:],16) if (addr > 0x14E50 and addr < 0x15820) \ or addr == 0x186C4 \ or addr > 0x105A88: print("will pass 0x%x:\t%s\t%s" %(ins.address, ins.mnemonic, ins.op_str)) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_PC, address + size) return def call_prepare_arg(): global inscnt global start_addr global end_addr global stop_addr global stop_addr_list inscnt = 0 uc = unicorn.Uc(unicorn.UC_ARCH_ARM64, unicorn.UC_MODE_ARM) code_addr = 0x0 code_size = 8*0x1000*0x1000 uc.mem_map(code_addr, code_size) stack_addr = code_addr + code_size stack_size = 0x1000000 stack_top = stack_addr + stack_size - 0x8 uc.mem_map(stack_addr, stack_size) uc.hook_add(unicorn.UC_HOOK_CODE, hook_code) uc.mem_write(code_addr, sodata) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X29, stack_addr) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X28, stack_addr) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X27, stack_addr) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X26, stack_addr) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X25, stack_addr) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X24, stack_addr) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X23, stack_addr) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X22, stack_addr) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X21, stack_addr) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X20, stack_addr) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X19, stack_addr) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X18, stack_addr) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X17, stack_addr) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X16, stack_addr) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X15, stack_addr) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X14, stack_addr) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X13, stack_addr) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X12, stack_addr) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X11, stack_addr) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X10, stack_addr) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X9, stack_addr) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X8, stack_addr) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X7, stack_addr) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X6, stack_addr) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X5, stack_addr) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X4, stack_addr) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X3, stack_addr) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X2, stack_addr) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X1, stack_addr) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_X0, stack_addr) uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_SP, stack_top) uc.emu_start(start_addr, end_addr) X0 = uc.reg_read(unicorn.arm64_const.UC_ARM64_REG_X0) decstr = uc.mem_read(X0, 80) end_index = decstr.find(bytearray(b'\x00'), 1) decstr = decstr[:end_index] decstr = binascii.b2a_hex(decstr) decstr = decstr.decode('utf-8') X1 = uc.reg_read(unicorn.arm64_const.UC_ARM64_REG_X1) X2 = uc.reg_read(unicorn.arm64_const.UC_ARM64_REG_X2) pi = subprocess.Popen(['C:\\Python38\\python.exe', 'decstr.py', decstr, hex(X1), hex(X2)], stdout=subprocess.PIPE) output = pi.stdout.read() print(output) def loop_call_prepare_arg1(): global inscnt global end_addr global stop_addr global stop_addr_list loopcnt = 0 stop_addr_list = [] while True: try: loopcnt = loopcnt + 1 if(loopcnt > 200): break call_prepare_arg() except unicorn.unicorn.UcError: print("adding....") print(hex(stop_addr)) stop_addr_list.append(stop_addr) else: break def loop_call_prepare_arg2(): global inscnt global end_addr global stop_addr global stop_addr_list global start_addr loopcnt = 0 stop_addr_list = [] while True: try: loopcnt = loopcnt + 1 if(loopcnt > 200): break call_prepare_arg() except unicorn.unicorn.UcError: start_addr = stop_addr + 4 else: break with open("C:\\Users\\hjy\\Downloads\\out1.fix.so","rb") as f: sodata = f.read() all_addr = []with open('xref_decstr.txt', 'r', encoding='utf-8') as f: for line in f: addr = "0x" + line[2:] addr = int(addr, 16) all_addr.append(addr) for i in all_addr: print("i:") print(hex(i)) end_addr = i CODE = sodata[i - 4:i] md = Cs(CS_ARCH_ARM64, CS_MODE_ARM) for x in md.disasm(CODE, i - 4): mnemonic = x.mnemonic while mnemonic != "ret" \ and mnemonic != "b" \ and mnemonic != "br" \ and mnemonic != "cbz" \ and mnemonic != "cbnz": i = i - 4 CODE = sodata[i - 4:i] for x in md.disasm(CODE, i - 4): mnemonic = x.mnemonic start_addr = i print("start_addr:") print(hex(start_addr)) print("end_addr:") print(hex(end_addr)) loop_call_prepare_arg1() loop_call_prepare_arg2()
更惡心的是還有很多字符串是自己在函數內解密的,這種情況我也沒想到有什么好的方法。
3.控制流混淆
第一種是把正常順序執行的指令打亂成switch的形式,這個影響倒不是太大:

第二種是動態計算跳轉地址,基本上類似于在android so文件攻防實戰-某團libmtguard.so反混淆(https://bbs.pediy.com/thread-271853.htm)見過的那種,但是要更復雜。

比如這里的指令,在0x1DA0C處給X2賦值,X2此時為.data段中的一個地址,W0為偏移,取出值后在0x1DA18處乘4加上0x1DA20,最后的值就是0x1DA1C處X0的值。那么需要解決這么幾個問題:
如何確定0x1DA0C處給X2賦的值;
將0x1DA00處的指令改成跳轉指令,0x1DA00這個地址又該如何確定;
找到所有會跳轉到0x1DA1C的指令,將跳轉地址改成計算出來的X0的值。
第一個問題,其實和字符串解密面臨的情況是類似的,比如這里需要找到和"LDR X2, [X29,#0x190+var_118]"對應的"STR XX, [X29,#0x190+var_118]"這條指令,然后再找給XX寄存器賦值的指令,然而這兩條指令很可能和BR X0隔了好幾個block。我的解決方法是通過IDA提供的idaapi.FlowChar功能,遞歸前面的block查找。不足之處在于前提條件是IDA正確識別了函數的起始地址,否則會出現我們需要的指令和BR X0不在同一個函數的情況,這樣就處理不了。
第二個問題,在遞歸前面的block的時候就先找到0x1D9D4處這條給W0賦值的指令,然后從0x1D9D4處開始直到0x1DA1C,找到第一個存在交叉引用的地址,也就是0x1DA04。它的前一條指令0x1DA00就是需要改成跳轉指令的地方。
第三個問題,確定了0x1DA00之后,那么從0x1DA00到0x1DA1C所有存在交叉引用的地址都要去交叉引用的地方修改跳轉地址。不過這里有很多細節。
(1)如果W0是由CSEL,CSET,CSINC這些指令賦值的,像下面這種情況,那么需要把0x1DE80和0x1DE84修改成 B.GE和B.LT。
patch前:

patch后:

(2)0x1DE80處的CSEL W0, WZR, W8, LT,這里W8的值是在0x1D9DC MOV W8, #5賦值的,所以我的代碼中有一個register_value_dict,在改掉0x1DA00處的指令之后會讀取0x1DA00所在的block到0x1DA1C所在的block的所有指令,找到給寄存器賦值的指令然后把值存起來。


(3)有些地方還會有一條sub指令,這個也要考慮進去,比如下面這種情況0x33394處跳轉的地址就應該按照W8為4計算。


最后的腳本放附件了。當然還有一些腳本處理不了的地方,不過問題已經不算太大了,需要的話可以動態調試確定。
4.函數地址動態計算



這個在IDA里面是能看清楚的,v35其實就是off_12EB80[0],即調用0x80FE0處的p329AAB59961F6410ABA963EF972FE303。
接下來我們就來分析libDexHelper.so,來看看它都干了些什么。精力有限,很多地方沒能很詳細去分析。有些地方分析的可能也不一定對,將就看吧。
功能分析
JNI_OnLoad(0x3EA68)的分析在最后。
0x15960
讀/proc/self/maps,特征字符串:
libDexHelper.solibDexHelper-x86.solibDexHelper-x86_64.so/system/lib64/libart.so/system/lib64/libLLVM.so/system/framework/arm64/boot-framework.oat/system/lib64/libskia.so/system/lib64/libhwui.so.oatff c3 01 d1 f3 03 04 aa f4 03 02 aa f5 03 01 aa e8 03 00 aaGumInvocationListenerGSocketListenerEvent
0x16A30
獲取系統屬性,讀/proc/%d/cmdline,特征字符串:
ro.yunos.versionro.yunos.version.releasepersist.sys.dalvik.vm.libpersist.sys.dalvik.vm.lib.2/system/bin/dex2oatLD_OPT_PACKAGENAMELD_OPT_ENFORCE_V1
off_12EF10:為2表示yunos,art模式;為1表示yunos,dalvik模式;為0表示非yunos。
0x17A70
md5。
0x186C4
字符串解密函數。
0x19674
返回字符串rw。
0x19778
返回字符串su。
0x1987C
返回字符串mount。
0x19998
寫classes.dve文件。
0x19b48
讀取目錄中的文件。
0x19E08
創建String類型的數組,第一個參數是String列表,第二個參數是數組長度。
0x1A058
調用0x19E08創建數組:
/etc/sbin/system/system/bin/vendor/bin/system/sbin/system/xbin
0x1A740
調用0x19E08創建數組:
com.yellowes.sueu.chainfire.supersucom.noshufou.android.sucom.thirdparty.superusercom.koushikdutta.superusercom.noshufou.android.su.elite
0x1AF1C
調用0x19E08創建數組:
com.chelpus.lackypatchcom.ramdroid.appquarantinecom.koushikdutta.rommanagercom.dimonvideo.luckypatchercom.ramdroid.appquarantineprocom.koushikdutta.rommanager.license
0x1B7D0
調用0x19E08創建數組:
com.saurik.substratecom.formyhm.hiderootcom.amphoras.hidemyrootcom.devadvance.rootcloakcom.formyhm.hiderootPremiumcom.devadvance.rootcloakpluscom.amphoras.hidemyrootadfreecom.zachspong.temprootremovejbde.robv.android.xposed.installer
0x1C40C
system_property_get ro.product.cpu.abi和讀/system/lib/libc.so判斷是不是x86架構。
0x1C61C
查看classes.dve是否存在。
0x1C8D8
調用0x19E08創建數組:
/sbin//su/bin//data/local//system/bin//system/xbin//data/local/bin//system/sd/xbin//data/local/xbin//system/bin/.ext//system/bin/failsafe//system/usr/we-need-root/
0x1D518
初始化一些路徑,特征字符串:
.cacheoat.payloadv1filter.jarclasses.odexclasses.vdexclasses.dexassets/classes.jar.cache/classes.jar.cache/classes.dex.cache/classes.odex.cache/classes.vdex
0x1E520
將libc中的一些函數的地址放到.DATA。
0x137BB0 fopen0x137BB8 fclose0x137BC0 fgets0x137BC8 fwrite0x137BD0 fread0x137BD8 sprintf0x137BE0 pthread_create
0x1F250
讀/proc/self/cmdline,判斷是否含有com.miui.packageinstaller從而判斷是否由小米應用包管理組件啟動。
0x1F710
先system_property_get ro.product.manufacturer和system_property_get ro.product.model判斷是否是samsung,然后system_property_get ro.build.characteristics是否為emulator。
0x1FDC8
注冊如下native函數:
RegisterNative(com/secneo/apkwrapper/H, attach(Landroid/app/Application;Landroid/content/Context;)V, RX@0x4002f6e4[libDexHelper.so]0x2f6e4)RegisterNative(com/secneo/apkwrapper/H, b(Landroid/content/Context;Landroid/app/Application;)V, RX@0x400247c0[libDexHelper.so]0x247c0)RegisterNative(com/secneo/apkwrapper/H, c()V, RX@0x40024c08[libDexHelper.so]0x24c08)RegisterNative(com/secneo/apkwrapper/H, d(Ljava/lang/String;)Ljava/lang/String;, RX@0x40023d04[libDexHelper.so]0x23d04)RegisterNative(com/secneo/apkwrapper/H, e(Ljava/lang/Object;Ljava/util/List;Ljava/lang/String;)[Ljava/lang/Object;, RX@0x40035ab0[libDexHelper.so]0x35ab0)RegisterNative(com/secneo/apkwrapper/H, f()[Ljava/lang/String;, RX@0x4001a740[libDexHelper.so]0x1a740)RegisterNative(com/secneo/apkwrapper/H, g()[Ljava/lang/String;, RX@0x4001af1c[libDexHelper.so]0x1af1c)RegisterNative(com/secneo/apkwrapper/H, h()[Ljava/lang/String;, RX@0x4001b7d0[libDexHelper.so]0x1b7d0)RegisterNative(com/secneo/apkwrapper/H, n()[Ljava/lang/String;, RX@0x4001c8d8[libDexHelper.so]0x1c8d8)RegisterNative(com/secneo/apkwrapper/H, j()[Ljava/lang/String;, RX@0x4001a058[libDexHelper.so]0x1a058)RegisterNative(com/secneo/apkwrapper/H, k()Ljava/lang/String;, RX@0x40019778[libDexHelper.so]0x19778)RegisterNative(com/secneo/apkwrapper/H, l()Ljava/lang/String;, RX@0x4001987c[libDexHelper.so]0x1987c)RegisterNative(com/secneo/apkwrapper/H, m()Ljava/lang/String;, RX@0x40019674[libDexHelper.so]0x19674)RegisterNative(com/secneo/apkwrapper/H, bb(Landroid/content/Context;Landroid/app/Application;Landroid/app/Application;)V, RX@0x4002921c[libDexHelper.so]0x2921c)RegisterNative(com/secneo/apkwrapper/H, o(Landroid/content/Context;)I, RX@0x4002f158[libDexHelper.so]0x2f158)RegisterNative(com/secneo/apkwrapper/H, p()V, RX@0x4001875c[libDexHelper.so]0x1875c)RegisterNative(com/secneo/apkwrapper/H, q()I, RX@0x40023568[libDexHelper.so]0x23568)RegisterNative(com/secneo/apkwrapper/H, mu()I, RX@0x4001f250[libDexHelper.so]0x1f250)
0x218A8
system_property_get ro.build.version.release/ro.build.version.sdk/ro.build.version.codename,最終返回sdkversion。
0x22068
創建一些目錄:
/data/usr/0/包名/.cache/oat/data/usr/0/包名/.cache/oat/arm64/data/usr/0/包名/.payload
0x22a90
模擬器檢測,特征字符串:
vboxsf/mnt/shared/install_apknemusf/mnt/shell/emulated/0/Music sharefolder/sdcard/windows/BstSharedFolder
0x23568
讀proc/pid/cmdline找字符串":bbs",沒搞懂這是什么意思。這個函數名是is_magisk_check_process。
0x247C0
調用setOuterContext。
0x24C08
system_property_get ro.product.brand,針對華為/榮耀機型,調用startLoadFromDisk。
0x26278
getDeclaredFields獲取field對象數組之后調用equals,返回查找的指定的field對象。
0x27290
修改mInitialApplication和mClassLoader。
0x2921C
修改mAllApplications(remove和add)。
0x29CE8
模擬器檢測,特征字符串:
com.bignox.app.store.hdcom.bluestacks.appguidancecom.bluestacks.settingscom.bluestacks.homecom.bluestack.BstCommandProcessorcom.bluestacks.appmart
0x2B670
通過FLAG_DEBUGGABLE判斷是debug還是release。
0x2CAE0
通過android.content.pm.Signature獲取簽名的md5。
0x2F158
通過access以下文件判斷是否被root:
/sbin/.magisk//sbin/.core/img/sbin/.core/mirror/sbin/.core/db-0/magisk.db
0x2F6E4
讀/proc/self/cmdline,調用java層的com.secneo.apkwrapper.H.j,調用bindService,獲取android_id,調用android.app.Application.attach,如果包名是com.huawei.irportalapp.uat調用setOuterContext。
0x31474
調用java層的com.secneo.apkwrapper.H.f(ff)加載v1filter.jar和原始dex。
查看/proc/self/maps:
anon:dalvik-classes.dex extracted in memory from v1filter.jar
anon:dalvik-DEX data
把多出來這樣的段dump下來。
原始dex:

指令虛擬化是調用JniLib.cV解析執行的,最后一個參數是一個函數code索引,用來查找被虛擬化后的指令,其它是方法參數:

v1filter.jar:

0x3371C
hook libcutils.so/liblog.so中的android_log_write和android_log_buf_write,使其返回0。
0x339FC
currentActivityThread-mPackages-LoadedApk-mResources-getAssets。
0x34A00
調用android.content.res.Resources.getAssets,失敗再調用0x339FC。
0x351DC
讀取assets文件。
0x35AB0
對傳入參數調用makeInMemoryDexElements,修改dalvik.system.DexFile.mFileName。
0x3766C
初始化下列字符串:
/data/user/0/cn.missfresh.application/.cache/classes.jar/data/user/0/cn.missfresh.application/.cache/classes.dex/data/user/0/cn.missfresh.application/.cache/v1filter.jar
調用0x80458計算包名hash,調用0x75AA8。
調用AAssetManager_open讀取assets/resthird.data寫入v1filter.jar,調用0x31474。
(看別人的分析應該讀assets下面兩個文件:classes0.jar是被加密的dex,classes.dgc是被加密的抽取后的指令。不過我分析的這個樣本中沒有classes0.jar和classes.dgc,可能是名字變了)
0x398F8/0x3A08C
檢測dexhunter,dumpclass好像是dexhunter里面的吧。特征字符串:
_Z16hprofDumpClassesP15hprof_context_t_Z12dvmDumpClassPK11ClassObjecti_Z9dumpClassP7DexFileidumpclassdump_class
0x3BF10
參數是文件名,返回文件是否存在。
0x3BF7C
模擬器檢測,特征字符串:
ueventd.ttVM_x86.rcinit.ttVM_x86.rcfstab.ttVM_x86bluestacksBlueStacks
0x3CE14
system_property_get ro.debuggable,調用檢測模擬器的函數。
0x3D814
通過android.hardware.usb.action.USB_STATE監聽USB狀態。
0x42378
md5。
0x44708
hook下列函數(反調試):
vmDebug::notifyDebuggerActivityStart(hook后:0x446C0)art::Dbg::GoActive(hook后:0x446E4)art::Runtime::AttachAgent(hook后:0x45CF8)
0x46194
system_property_get ro.yunos.version。
0x4C2F0
hook下列函數(指令抽取還原):
art::ClassLinker::DefineClass(hook后:0x46BB8)art::ClassLinker::LoadMethod(hook后:0x46ED4/0x47BB8/0x488C0/0x491F8/0x49B0C)art::OatFile::OatMethod::LinkMethod(hook后:0x46BD8/0x46DB0)
0x4DB80
md5。
0x50280
讀/proc/self/maps找到含有包名的段。
0x5074C
調用java層的com.secneo.apkwrapper.H1.find_dexfile。
0x50B60
調用java.lang.StackTraceElement.getMethodName和java.lang.StackTraceElement.getClassName。
0x57424
加載assets中的classes.dgg。
0x598FC
讀/proc/self/maps找到libDexHelper.so。
0x59CE8
設置dex2oat的參數,--zip-fd/--oat-fd/--zip-location/--oat-location/--oat-file/--instruction-set。
0x5C600
hook libdvm.so中的函數(類似于0x67544),具體沒仔細看,0x5BAA8-0x5BEF8都是被hook后的實現。
0x61E3C
hook libc中的下列函數:
fstatat64(hook后:0x5E778)stat(hook后:0x5E858)close(hook后:0x5EA20)openat(hook后:0x5ED20)open(hook后:0x5ED9C)pread(hook后:0x5FAB8)read(hook后:0x5FC14)mmap64(hook后:0x5FDDC)__openat_2(hook后:0x5FEF4)__open_2(hook后:0x5FF74)
0x64AE8
根據不同SDK版本返回Name Mangling之后的art::DexFileLoader::open。
0x65FE4
根據不同SDK版本返回Name Mangling之后的art::OatFileManager::OpenDexFilesFromOat。
0x67544
hook下列函數:
art::DexFileLoader::open(hook后:0x6D39C/0x6D3E8)art::OatFileManager::OpenDexFilesFromOat(hook后:0x6A2C0/0x6AF14/0x6B9B0/0x6C188/0x6CB5C)
0x6D4A0
patch掉art::Runtime::IsVerificationEnabled。
0x6DAD8
hook art::DexFileVerifier::Verify(hook后:0x6D38C/0x6D394,直接返回1)。
0x6E40C
hook art::DexFileLoader::open(hook后:0x6D39C/0x6D3E8)。
0x70410
hook下列函數:
art::DexFileVerifier::Verify(hook后:0x6EB04/0x6EB0C/0x6EB14,直接返回1)art::DexFile::OpenMemory(hook后:0x74EE8/0x74E90/0x74F38)Art::DexFile(hook后:0x74E30/0x74F88)
0x75054
hook libdvm.so中的函數,具體沒仔細看,0x6EB1C/0x74DEC/0x6FFBC都是被hook后的實現。
0x75AA8
讀java.lang.DexCache.dexfile(這個dexfile就是解壓apk之后根目錄的那個classes.dex)。
0x767F8
參數是so文件路徑,打開該so文件。
0x76C8C
參數是libart.so中的一個函數,返回該函數地址。
0x76CCC
第一個參數是so中的函數名,第二個參數是so的相對路徑,返回該函數在so中的地址。
0x76D90
參數是libdexfile.so中的一個函數,返回該函數地址。
0x76DD4
參數是libjdwp.so中的一個函數,返回該函數地址。
0x76E18
md5。
0x7783C
字符串解密函數。
0x79270
計算傳入字符串的hash(不完全是md5)。
0x7A240
熱補丁檢測,特征字符串:
nuwaandfixhotfix.RiskStutinker
0x80458
調用0x79270。
0x804A8
hook libc中的下列函數:
msync(hook后:0x78470)close(hook后:0x7AF50)munmap(hook后:0x7A568)openat64(hook后:0x7DC48)__open_2(hook后:0x7DC80)_open64(hook后:0x7DCB8)_openat_2(hook后:0x7DCF0)ftruncate64(hook后:0x7DD30)mmap64(hook后:0x7EF60)pread64(hook后:0x7F5D0)read(hook后:0x7F7DC)write(hook后:0x8022C)
0x87F98
hook libdvm.so中的函數(類似于0x44708),具體沒仔細看,0x856C0/0x87F00/0x87F4C都是被hook后的實現。
0x889B0
patch掉art::Runtime::UseJitCompilation。
0x8A794/0x8B71C/0x8B890
hook函數實現。
0x917E8
讀/proc/sys/fs/inotify/max_queued_watches。
0x91848
讀/proc/sys/fs/inotify/max_user_instances。
0x918A8
讀/proc/sys/fs/inotify/max_user_watches。
0x95778
看起來好像是通過判斷時間實現的反調試。
0x95A28
字符串查找函數。
0x95B9C
字符串解密函數。
0x95D60
socket連接。
0x96398
frida檢測,讀/proc/self/task,特征字符串:gum-js-loop;讀/proc/self/fd,特征字符串linjector。
0x995D0
xposed檢測,特征字符串:
.xposed.xposedbridgexposed_art
0x99D28
hook框架檢測,特征字符串:
fridaddi_hookdexposedsubstrateadbi_hookMSFindSymbolhook_precallhook_postcallMSHookFunctionDexposedBridgeMSCloseFunctiondexstuff_loaddexdexposedIsHookedALLINONEs_arthookdexstuff_resolv_dvmdexposedCallHandlerart_java_method_hookartQuickToDispatcherdexstuff_defineclassdalvik_java_method_hookart_quick_call_entrypointfrida_agent_main
0x9C0BC
調用0x96398檢測frida,system_property_get ro.product.model,調用0x9FD88檢測xposed和自動脫殼機,hook dlopen(hook后:0x9B89C)和ptrace(hook后:0x95BF8)。
0x9CFCC
通過讀取/proc/%d/status判斷TracerPid等實現反調試。
0x9D878
通過讀取/proc/%d/wchan判斷是不是ptrace_stop實現反調試。
0x9DCF4
通過讀取/proc/%ld/task/%ld/status判斷TracerPid等實現反調試。
0x9ED44
通過java.lang.StackTraceElement.getClassName打印函數調用棧進行xposed檢測。
0x9F770
通過java.lang.ClassLoader.getSystemClassLoader.loadClass打印類加載器進行xposed檢測。
0x9FD88
調用0x9ED44和0x9F770,通過判斷ServiceManager里是否有user.xposed.system進行xposed檢測,然后檢測自動脫殼機:
fart(https://github.com/hanbinglengyue/FART)
FUPK3(https://github.com/F8LEFT/FUPK3)
Youpk(https://github.com/Youlor/Youpk)
檢測方法是判斷下列類或者方法是否存在:
dumpMethodCodefartthreadfartandroid/app/fupk3/Fupkandroid/app/fupk3/Globalandroid/app/fupk3/UpkConfigandroid/app/fupk3/FRefInvokecn/youlor/Unpacker
0xA18D4
getInstalledApplications獲取系統中安裝的APP信息。
0xA7D3C
解密出字符串Java和JNI_OnLoad,hook了幾個函數,被hook的原地址未知,新地址:0xA43A0/0xA485C/0xA48F4/0xA54B0;hook dlsym(hook后:0xA4554)和dlopen(hook后:0xA4D30)。
0xB4B94
hook libc中的下列函數:
write(hook后:0xAA2CC)pwrite64(hook后:0xAA51C)close(hook后:0xAA774)read64(hook后:0xAAA9C)openat64(hook后:0xAACB8)__openat_2(hook后:0xAB6D4)__open_2(hook后:0xAC0F4)open64(hook后:0xACB10)read(hook后:0xAFE18)mmap64(hook后:0xB1C54)
system_property_get debug.atrace.tags.enableflags,hook bionic_trace_begin和bionic_trace_end(hook后:0xA8EF4和0xA8EF8,直接返回),沒有找到則hook g_trace_marker_fd(hook后:0xA8EFC,返回-1)。
這些hook是為了透明加密,具體沒仔細看,之前論壇也有人分析過,估計應該沒有太大的變化:梆梆加固之透明加密分析。
0xB9BEC
sha1。
0xBAE64
md5init。
0xC3378
base64。
0xC5DDC
base64。
0xC7B18
APK簽名相關。
0xD024C
sha1。
0xD1C04
sha1init。
0xD2E98
md5。
0xD6484
調用0xD75A0。
0xD5CDC
讀/proc/self/cmdline。
0xD6578
hook libdvm.so中的函數(hook后:0xD6988)。
0xD68A8
根據off_12EF10處的值判斷調用0xD6484還是0xD6578。
0xD75A0
hook libaoc.so中的函數(hook后:0xD69BC)。
0x3EA68
JNI_OnLoad。分析環境pixel4 android10,動態分析過程中一些沒有被調用的函數不再分析。
1.初始化cpuabi字符串(arm64)于0x12E7C8
2.初始化so名字符串(libDexHelper)于0x12EC38
3.初始化字符串com/secneo/apkwrapper/H于0x137B10
4.調用0x1E520
JNIEnv->FindClass(com/secneo/apkwrapper/H)JNIEnv->GetStaticFieldID(com/secneo/apkwrapper/H.PKGNAMELjava/lang/String;)JNIEnv->GetStaticObjectField(class com/secneo/apkwrapper/H, PKGNAME Ljava/lang/String; => "cn.missfresh.application")JNIEnv->GetStringUtfChars("cn.missfresh.application")
6.將包名存于0x138040
JNIEnv->FindClass(android/app/ActivityThread)JNIEnv->GetStaticMethodID(android/app/ActivityThread.currentActivityThread()Landroid/app/ActivityThread;)JNIEnv->CallStaticObjectMethodV(class android/app/ActivityThread, currentActivityThread())JNIEnv->GetMethodID(android/app/ActivityThread.getSystemContext()Landroid/app/ContextImpl;)JNIEnv->CallObjectMethodV(android.app.ActivityThread, getSystemContext())JNIEnv->FindClass(android/app/ContextImpl)JNIEnv->GetMethodID(android/app/ContextImpl.getPackageManager()Landroid/content/pm/PackageManager;)JNIEnv->CallObjectMethodV(android.app.ContextImpl, getPackageManager())JNIEnv->GetMethodID(android/content/pm/PackageManager.getPackageInfo(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;)JNIEnv->NewStringUTF("cn.missfresh.application")JNIEnv->CallObjectMethodV(android.content.pm.PackageManager, getPackageInfo("cn.missfresh.application", 0x0))JNIEnv->GetFieldID(android/content/pm/PackageInfo.applicationInfo Landroid/content/pm/ApplicationInfo;)JNIEnv->GetObjectField(android.content.pm.PackageInfo, applicationInfo Landroid/content/pm/ApplicationInfo;)JNIEnv->GetFieldID(android/content/pm/ApplicationInfo.sourceDir Ljava/lang/String;)JNIEnv->GetObjectField(android.content.pm.ApplicationInfo, sourceDir Ljava/lang/String; => "/data/app/cn.missfresh.application-1")JNIEnv->GetStringUtfChars("/data/app/cn.missfresh.application-1")JNIEnv->GetFieldID(android/content/pm/ApplicationInfo.dataDir Ljava/lang/String;)JNIEnv->GetObjectField(android.content.pm.ApplicationInfo, dataDir Ljava/lang/String; => "/data/data/cn.missfresh.application")JNIEnv->GetStringUtfChars("/data/data/cn.missfresh.application")
8.調用0x218A8
JNIEnv->GetFieldID(android/content/pm/ApplicationInfo.nativeLibraryDir Ljava/lang/String;)JNIEnv->GetObjectField(android.content.pm.ApplicationInfo@36d64342, nativeLibraryDir Ljava/lang/String; => "/data/app/cn.missfresh.application-1/lib/arm64")JNIEnv->GetStringUtfChars("/data/app/cn.missfresh.application-1/lib/arm64")
10.讀/proc/pid/fd,匹配包名+base.apk,0x12EA38存放指向base.apk完整路徑的指針的指針
JNIEnv->FindClass(com/secneo/apkwrapper/H)JNIEnv->GetStaticFieldID(com/secneo/apkwrapper/H.ISMPAASLjava/lang/String;)JNIEnv->GetStaticObjectField(class com/secneo/apkwrapper/H, ISMPAAS Ljava/lang/String; => "###MPAAS###")JNIEnv->GetStringUtfChars("###MPAAS###")
12.將得到的結果和###MPAAS###比較,0x12E7F8指向0x137D9C,0x137D9C存放比較結果
13.調用0x22068
14.調用0x23568
15.調用0x1F250
16.將字符串lib/libart.so存放于0x1378A8
17.讀/proc/self/maps,找權限為"r-xp"的lib/libart.so
18.初始化下列字符串:
/data/user/0/cn.missfresh.application/.cache/data/user/0/cn.missfresh.application/.cache/oat/arm64/data/user/0/cn.missfresh.application/.cache/classes.dve/data/app/cn.missfresh.application-xxx/oat/arm64/base.odex
19.fstat /data/app/cn.missfresh.application-xxx/oat/arm64/base.odex
20.計算md5(不太清楚具體算的什么),0x12EC98指向0x130080,0x130080存放計算結果
21.access /data/user/0/cn.missfresh.application/.cache/classes.dve,不存在則把之前算的md5寫入該文件;存在則讀取其中的值和之前算的比較,不相等則寫入新計算的值
22.調用0x3371C(根據標記位決定是否調用)
23.調用0x1FDC8
24.初始化下列字符串:
/data/user/0/cn.missfresh.application/.cache/libDexHelper32/lib/armeabi-v7a/libDexHelper.so/lib/armeabi/libDexHelper.soassets/libDexHelper32
25.調用0x3766C
26.調用0xB4B94
27.調用0x9C0BC
28.調用0xD5CDC
29.調用0x24C08
30.調用0x1C40C
31.調用0xA7D3C
32.結束