android so檔案攻防實戰-libDexHelper.so反混淆
本文為看雪論壇精華文章
看雪論壇作者ID:houjingyi
計劃是寫一個android中so檔案反混淆的系列文章,目前這是第三篇。
第一篇:android so檔案攻防實戰-百度加固免費版libbaiduprotect.so反混淆( http://bbs.pediy.com/thread-271388.htm )
第二篇: android so檔案攻防實戰-某團libmtguard.so反混淆( http://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反混淆( http://bbs.pediy.com/thread-271388.htm )中我們是交叉引用拿到加密後的字串和它對應的解密函式的表然後frida主動呼叫得到的解密後的字串,但是在這裡這個方法就不太好用了。因為這裡加密後的字串是在棧上一個byte一個byte拼起來的,和最後呼叫解密函式之間可能隔了很多條指令,甚至都不在一個block。
我最後用的是下面這種方案:以0x40110處呼叫0x186C4處的解密函式為例,這裡面字串解密的邏輯比較簡單,需要三個引數。我們可以自己實現也可以用unicorn,我就用unicorn了。
import sys
import unicorn
import binascii
import threading
import 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 = 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.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 unicorn
import binascii
import threading
import subprocess
from capstone import *
from capstone.arm64 import *
inscnt = 0
start_addr = 0
end_addr = 0
stop_addr = 0
stop_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反混淆( http://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.so
libDexHelper-x86.so
libDexHelper-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
.oat
ff c3 01 d1 f3 03 04 aa f4 03 02 aa f5 03 01 aa e8 03 00 aa
GumInvocationListener
GSocketListenerEvent
0x16A30
獲取系統屬性,讀/proc/%d/cmdline,特徵字串:
ro.yunos.version
ro.yunos.version.release
persist.sys.dalvik.vm.lib
persist.sys.dalvik.vm.lib.2
/system/bin/dex2oat
LD_OPT_PACKAGENAME
LD_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.su
eu.chainfire.supersu
com.noshufou.android.su
com.thirdparty.superuser
com.koushikdutta.superuser
com.noshufou.android.su.elite
0x1AF1C
呼叫0x19E08建立陣列:
com.chelpus.lackypatch
com.ramdroid.appquarantine
com.koushikdutta.rommanager
com.dimonvideo.luckypatcher
com.ramdroid.appquarantinepro
com.koushikdutta.rommanager.license
0x1B7D0
呼叫0x19E08建立陣列:
com.saurik.substrate
com.formyhm.hideroot
com.amphoras.hidemyroot
com.devadvance.rootcloak
com.formyhm.hiderootPremium
com.devadvance.rootcloakplus
com.amphoras.hidemyrootadfree
com.zachspong.temprootremovejb
de.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
初始化一些路徑,特徵字串:
.cache
oat
.payload
v1filter.jar
classes.odex
classes.vdex
classes.dex
assets/classes.jar
.cache/classes.jar
.cache/classes.dex
.cache/classes.odex
.cache/classes.vdex
0x1E520
將libc中的一些函式的地址放到.DATA。
0x137BB0 fopen
0x137BB8 fclose
0x137BC0 fgets
0x137BC8 fwrite
0x137BD0 fread
0x137BD8 sprintf
0x137BE0 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, [email protected][libDexHelper.so]0x2f6e4)
RegisterNative(com/secneo/apkwrapper/H, b(Landroid/content/Context;Landroid/app/Application;)V, [email protected][libDexHelper.so]0x247c0)
RegisterNative(com/secneo/apkwrapper/H, c()V, [email protected][libDexHelper.so]0x24c08)
RegisterNative(com/secneo/apkwrapper/H, d(Ljava/lang/String;)Ljava/lang/String;, [email protected][libDexHelper.so]0x23d04)
RegisterNative(com/secneo/apkwrapper/H, e(Ljava/lang/Object;Ljava/util/List;Ljava/lang/String;)[Ljava/lang/Object;, [email protected][libDexHelper.so]0x35ab0)
RegisterNative(com/secneo/apkwrapper/H, f()[Ljava/lang/String;, [email protected][libDexHelper.so]0x1a740)
RegisterNative(com/secneo/apkwrapper/H, g()[Ljava/lang/String;, [email protected][libDexHelper.so]0x1af1c)
RegisterNative(com/secneo/apkwrapper/H, h()[Ljava/lang/String;, [email protected][libDexHelper.so]0x1b7d0)
RegisterNative(com/secneo/apkwrapper/H, n()[Ljava/lang/String;, [email protected][libDexHelper.so]0x1c8d8)
RegisterNative(com/secneo/apkwrapper/H, j()[Ljava/lang/String;, [email protected][libDexHelper.so]0x1a058)
RegisterNative(com/secneo/apkwrapper/H, k()Ljava/lang/String;, [email protected][libDexHelper.so]0x19778)
RegisterNative(com/secneo/apkwrapper/H, l()Ljava/lang/String;, [email protected][libDexHelper.so]0x1987c)
RegisterNative(com/secneo/apkwrapper/H, m()Ljava/lang/String;, [email protected][libDexHelper.so]0x19674)
RegisterNative(com/secneo/apkwrapper/H, bb(Landroid/content/Context;Landroid/app/Application;Landroid/app/Application;)V, [email protected][libDexHelper.so]0x2921c)
RegisterNative(com/secneo/apkwrapper/H, o(Landroid/content/Context;)I, [email protected][libDexHelper.so]0x2f158)
RegisterNative(com/secneo/apkwrapper/H, p()V, [email protected][libDexHelper.so]0x1875c)
RegisterNative(com/secneo/apkwrapper/H, q()I, [email protected][libDexHelper.so]0x23568)
RegisterNative(com/secneo/apkwrapper/H, mu()I, [email protected][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_apk
nemusf
/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.hd
com.bluestacks.appguidance
com.bluestacks.settings
com.bluestacks.home
com.bluestack.BstCommandProcessor
com.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
_Z9dumpClassP7DexFilei
dumpclass
dump_class
0x3BF10
引數是檔名,返回檔案是否存在。
0x3BF7C
模擬器檢測,特徵字串:
ueventd.ttVM_x86.rc
init.ttVM_x86.rc
fstab.ttVM_x86
bluestacks
BlueStacks
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
熱補丁檢測,特徵字串:
nuwa
andfix
hotfix
.RiskStu
tinker
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.
xposedbridge
xposed_art
0x99D28
hook框架檢測,特徵字串:
frida
ddi_hook
dexposed
substrate
adbi_hook
MSFindSymbol
hook_precall
hook_postcall
MSHookFunction
DexposedBridge
MSCloseFunction
dexstuff_loaddex
dexposedIsHooked
ALLINONEs_arthook
dexstuff_resolv_dvm
dexposedCallHandler
art_java_method_hook
artQuickToDispatcher
dexstuff_defineclass
dalvik_java_method_hook
art_quick_call_entrypoint
frida_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檢測,然後檢測自動脫殼機:
)
FUPK3(
http://github.com/F8LEFT/FUPK3)
Youpk(
h ttps://github.com/Youlor/Youpk )檢測方法是判斷下列類或者方法是否存在:
dumpMethodCode
fartthread
fart
android/app/fupk3/Fupk
android/app/fupk3/Global
android/app/fupk3/UpkConfig
android/app/fupk3/FRefInvoke
cn/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
5.
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
7.
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
9.
JNIEnv->GetFieldID(android/content/pm/ApplicationInfo.nativeLibraryDir Ljava/lang/String;)
JNIEnv->GetObjectField([email protected]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完整路徑的指標的指標
11.
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.so
assets/libDexHelper32
25.呼叫0x3766C
26.呼叫0xB4B94
27.呼叫0x9C0BC
28.呼叫0xD5CDC
29.呼叫0x24C08
30.呼叫0x1C40C
31.呼叫0xA7D3C
32.結束
參考
梆梆APP加固產品方案淺析: http://www.cnblogs.com/2014asm/p/14547218.html
某加固詳細分析總結,另附該加固脫殼機:
http://bbs.pediy.com/thread-252828.htm總結
樣本混淆強度還是比較大的,比前兩篇文章中的樣本要複雜很多。不過分析過程中也是有一些技巧:比如位置相鄰的函式之前其實是有聯絡的,和另外某些殼的程式碼有類似的地方,可以網上搜一下舊版本的分析部落格,有一些函式名和字串沒有被抹去。
看雪ID:houjingyi
http://bbs.pediy.com/user-home-734571.htm
*本文由看雪論壇 houjingyi 原創,轉載請註明來自看雪社群
往期推薦
2. sql注入學習筆記
球分享
球點贊
球在看
點選“閱讀原文”,瞭解更多!
- CVE-2022-21882提權漏洞學習筆記
- wibu證書 - 初探
- 2022羊城杯競賽 Web題目解析
- CVE-2020-1054提權漏洞學習筆記
- 逆向分析sign演算法
- CVE-2021-38001漏洞利用
- Frida-objection 基礎使用獲取FLAG
- CVE-2013-3660提權漏洞學習筆記
- 利用AndroidNativeEmu完成多層jni呼叫的模擬
- 因優化而導致的溢位與CVE-2020-16040
- LLVM PASS PWN 總結
- win10 1909逆向之APIC中斷和實驗
- sql注入學習分享
- CVE-2021-3493復現
- 逆向篇:解決快牙不能掃碼問題
- 基於某釘探索針對CEF框架的一些逆向思路
- Android 10屬性系統原理,檢測與定製原始碼反檢測
- 太猖獗!近期勒索攻擊事件頻發,業內人士發表見解
- House of cat新型glibc中IO利用手法解析 & 第六屆強網杯House of cat詳解
- 實現一個壓縮殼,並給它加點“料”