<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    android so文件攻防實戰-libDexHelper.so反混淆

    VSole2022-08-08 16:13:58

    計劃是寫一個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.結束

    字符串函數函數調用
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    UAC Bypass 技術學習
    2022-07-01 16:22:43
    通過在這些操作啟動前對其進行驗證,UAC 可以幫助防止惡意軟件和間諜軟件在未經許可的情況下在計算機上進行安裝或對計算機進行更改。提升的應用程序以 High 完整性級別運行,普通進程以 Medium 完整性級別運行,低權限進程以 Low 完整性級別運行。
    二進制程序分析
    2021-09-25 17:18:46
    分析惡意軟件的第一步是收集二進制程序在主機上執行的行為事件,研究人員根據這些行為大體形成一個思路來描述惡意軟件的功能。 這包含應用釋放或者從互聯網下下載的文件,寫入什么樣的注冊表、訪問了什么網絡地址,修改讀寫本地的什么文件等等。那么研究人員通過行為會確定惡意樣本的類型。通常類型如下:
    本篇針對該JS中的字符串混淆進行還原。字符串是如何混淆的解密方式想要對字符串反混淆就要先分析該樣本是如何對字符串進行混淆的。而處于全局作用域的_0x1f1a68實際上也是對另一個函數的調用。
    上一篇文章介紹了xorstr的原理和最小化驗證概念的代碼,這篇文章來看下這種已經被廣泛應用于各惡意樣本以及安全組件中的技術如何還原,如果還沒看上篇建議先看下了解其實現后再看本篇文章。
    開放虛擬機格式(Open Virtual Machine Format,OVF)是一種虛擬機分配格式,能夠支持不同產品與組織之間共享虛擬機。 VMware OVF Tool是由VMware免費提供的一款支持虛擬的導入導出工具,支持以命令提示符的方式運行。
    這里根據紅日安全PHP-Audit-Labs對一些函數缺陷的分析,從PHP內核層面來分析一些函數的可利用的地方,標題所說的函數缺陷并不一定是函數本身的缺陷,也可能是函數在使用過程中存在某些問題,造成了漏洞,以下是對部分函數的分析
    php中的disable_function是EG(ini_directives)來獲得的,而phpinfo根據 EG(ini_directives) 中獲取信息并打印。
    因為程序肯定是病毒,我就不上傳殺毒網去查殺了。正常我們在分析一個未知惡意程序的時候,流程都是要先上傳殺毒網看看。 用PEID進行查殼,顯示未加殼,程序采用Delphi語言開發。
    寫一個android中so文件反混淆的系列文章,目前這是第三篇。根據其他人的分析可知,libDexHelper.so是指令抽取的實現,libdexjni.so是VMP的實現。在android so文件攻防實戰-百度加固免費版libbaiduprotect.so反混淆中我們是交叉引用拿到加密后的字符串和它對應的解密函數的表然后frida主動調用得到的解密后的字符串,但是在這里這個方法就不太好用了。
    大廠基本為了程序的安全,會使用大量內聯SVC去調用系統函數,以此來保護程序的安全。如何實現SVC指令的IO重定向,成為最大的問題。內核態是當Linux需要處理文件,或者進行中斷IO等操作的時候就會進入內核態。當arm系列cpu發現svc指令的時候,就會陷入中斷,簡稱0x80中斷。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类