样本只作为脱壳学习的一个样本,样本的内部逻辑并没有仔细分析,且样本较为简单,通用性差。本帖仅作为学习记录,欢迎大家一同交流。
首先先来官网看一下免费版包括的内容
 
 
 
上传一个Demo用于加固,Demo反编译结果如下
 
加固只针对Dex进行,且对类进行抽取,其效果如下
 
 
首先Shell创建了Application获取程序的起始控制权,之后在attachBaseContext通过Helper类加载manxi.so文件
 
 
 
之后的步骤都是在So层进行,所以需要对So层进行分析
So通过ollvm进行混淆,首先我们先通过Qiling模拟执行去除最简单的字符串混淆
首先我们需要提取到init_array所有函数起始地址和结束地址,这里我通过IDAPython进行提取
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      
      12
      
      13
      
      14
      
      15
      
      16
      
      17
      
      18
      
      19
      
      20
      
      21
      
      22
      
      23
      
      24
      
      25
      
      26
      
      27
      
      28
      
      29
      
      30
      
      31
      
      32
      
      33
      
      34
      
      35
      | importidaapiimportliefimportstructfrompwn importelf, pack, unpack, u64idaapi.msg_clear()elf_raw =elf.ELF("D:/new/frida-agent-example-main/agent/node_modules/libmanxi_copy.so")elf_patch =elf.ELF(    "D:/new/frida-agent-example-main/agent/node_modules/libmanxi_copy.so")print(elf_raw.entrypoint)init_array =Nonebinary =lief.parse(    "D:/new/frida-agent-example-main/agent/node_modules/libmanxi_copy.so")forsec inbinary.sections:    ifsec.name ==".init_array":        init_array =sec# Read Init_arrayfuncArray =[]funcArray_Address =[]index =0whileindex < init_array.size:    offset =u64(elf_raw.read(init_array.virtual_address +index, 8))    funcArray.append(offset)    index +=8funcArray.pop()forfunc infuncArray:    function =[]    function.append(func)    function.append(idaapi.get_func(func).end_ea)    funcArray_Address.append(function)print(funcArray_Address) | 
提取到地址之后即可进行模拟执行,监听内存读写,由于字符串解密函数通过OLLVM进行混淆,所以存在对于状态变量的写入,这会导致对内存写入的Hook产生多余的结果,只需要通过判断偏移是否在.data段或者.rodata段即可判断是否对加密字符串进行写入,过滤掉不需要修改的内容后把解密内存写入文件中即可(如果某个函数不能模拟执行只需要从列表中去除即可)
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      
      12
      
      13
      
      14
      
      15
      
      16
      
      17
      
      18
      
      19
      
      20
      
      21
      
      22
      
      23
      
      24
      
      25
      
      26
      
      27
      
      28
      
      29
      
      30
      
      31
      
      32
      
      33
      
      34
      
      35
      
      36
      
      37
      
      38
      
      39
      
      40
      
      41
      
      42
      
      43
      
      44
      
      45
      
      46
      
      47
      
      48
      
      49
      
      50
      
      51
      
      52
      
      53
      
      54
      
      55
      
      56
      
      57
      
      58
      
      59
      
      60
      
      61
      
      62
      
      63
      
      64
      
      65
      
      66
      
      67
      
      68
      
      69
      
      70
      
      71
      
      72
      
      73
      
      74
      
      75
      
      76
      
      77
      
      78
      
      79
      
      80
      
      81
      
      82
      
      83
      
      84
      
      85
      
      86
      
      87
      
      88
      
      89
      
      90
      
      91
      
      92
      
      93
      
      94
      
      95
      
      96
      
      97
      
      98
      
      99
      
      100
      
      101
      
      102
      
      103
      
      104
      
      105
      
      106
      
      107
      
      108
      
      109
      
      110
      
      111
      
      112
      
      113
      
      114
      
      115
      
      116
      
      117
      
      118
      
      119
      
      120
      
      121
      
      122
      
      123
      
      124
      
      125
      
      126
      
      127
      
      128
      
      129
      
      130
      
      131
      
      132
      
      133
      
      134
      
      135
      
      136
      
      137
      
      138
      
      139
      
      140
      
      141
      
      142
      
      143
      
      144
      
      145
      
      146
      
      147
      
      148
      
      149
      
      150
      
      151
      
      152
      
      153
      
      154
      
      155
      
      156
      
      157
      
      158
      
      159
      
      160
      | importlieffromqiling.os.const importPOINTER, UINT, STRINGfromqiling importQilingfromqiling.const importQL_VERBOSEfromqiling.const importQL_INTERCEPTfromqiling importosfromqiling.os.mapper importQlFsMappedObjectimportstructfrompwn importelf, pack, unpack, u64soData =lief.parse("libmanxi.so")datasec =Nonerodatasec =Noneforsec insoData.sections:    ifsec.name ==".data":        datasec =sec    elifsec.name ==".rodata":        rodatasec =secdata_str =""elf_patch =elf.ELF(    "D:/new/frida-agent-example-main/agent/node_modules/libmanxi_copy.so")defwrite_hook(ql: Qiling, access: int, address: int, size: int, value: int):    Addr =address -base_addr    if(        Addr >=datasec.virtual_address        andAddr <=datasec.virtual_address +datasec.size    ) or(        Addr >=rodatasec.virtual_address        andAddr <=rodatasec.virtual_address +rodatasec.size    ):        # if value >= 32 and value <= 128:        print(            f"Write Address: {hex(address-base_addr)} Value:{hex(value)} Size:{hex(size)}"        )        ifsize ==1:            data =ql.pack8(value)        elifsize ==2:            data =ql.pack16(value)        elifsize ==4:            data =ql.pack32(value)        elifsize ==8:            data =ql.pack64(value)        elf_patch.write(Addr, data)# Read Init_arrayfuncArray =[    [22616, 23116],    [28832, 29460],    [39664, 40264],    [42856, 43124],    [43932, 44020],    [46660, 46664],    [55196, 62940],    [63672, 63756],    [63756, 63840],    [63840, 63924],    [73424, 73428],    [73516, 73600],    [87628, 88064],    [122504, 122588],    [123876, 124208],    [124208, 124292],    [125200, 125288],    [127652, 128176],    [131420, 131828],    [135164, 135876],    [153412, 154708],    [164120, 167816],    [175060, 175332],    [183120, 183124],    [183124, 183128],    [186856, 186940],    [193592, 193732],    [215616, 216696],    [219564, 219648],    [232264, 232352],    [232920, 232924],    [233080, 233352],    [244248, 253964],    [255484, 256504],    [258116, 258424],    [259408, 262080],    [262080, 262432],    [264236, 264696],    [268872, 269224],    [269688, 270484],    [274292, 276648],    [283864, 284264],    [286580, 287280],    [288384, 288656],    [291236, 291596],    [292296, 293676],    [294012, 295272],    [295272, 295520],    [295976, 296248],    [299480, 299788],    [300236, 307192],    [307500, 307504],    [311600, 312672],    [313304, 313308],    [316048, 316052],    [324380, 325624],    [378800, 380220],    [382004, 382008],    [389160, 389520],    [390088, 390092],    [392360, 392444],    [407960, 407964],    [410060, 410064],    [414476, 415160],    [448668, 457116],    [464888, 464972],    [469092, 469444],    [471132, 471388],    [472156, 472160],    [472180, 472264],    [472512, 472596],    [472596, 472680],    [472680, 472684],    [472684, 472772],    [485160, 489068],    [491672, 493608],    [493608, 493692],    [506756, 506988],    [520552, 521200],    [523132, 523532],    [528344, 528576],    [533168, 539904],    [548616, 550592],    [568596, 569860],    [573168, 575808],    [579536, 579792],    [589616, 589620],    [610480, 610484],    [626444, 626916],    [636500, 636504],    [641936, 643184],    [644460, 644464],    [644816, 644820],    [42852, 42856],]# # Init Binary Fileql =Qiling(    ["D:/new/frida-agent-example-main/agent/node_modules/libmanxi.so"],    r"D:/new/rootfs/arm64_android",    verbose=QL_VERBOSE.DISASM,)base_addr =ql.mem.get_lib_base("libmanxi.so")str=f"Base Address: {hex(base_addr)}"ql.hook_mem_write(write_hook)forfunc infuncArray:    ql.run(func[0] +base_addr, func[1] +base_addr -4)elf_patch.save("aaa.so") | 
解密之后就可以看到很多敏感字符串,但实际上该程序没有检测Root、Fart,只在程序启动时对Frida检测,也就是说可通过Attach来进行附加
 
 
 
首先需要绕过启动时的Frida检测,奇怪的是我Hook dlopen和android_dlopen_ext都无法获取到libmanxi.so的加载,但是通过Process.enumerateModules()可以得到so,这也导致了后续难以定位到检测的位置和绕过。于是使用魔改Frida  (可绕过检测)
简单Hook了一下程序中的SVC指令,通过Radare2的/adj svc可导出所有SVC指令的地址
 
其中addr是导出的结果,这里只针对openat进行过滤
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      
      12
      
      13
      
      14
      
      15
      
      16
      
      17
      
      18
      | functionhook_svc() {    console.log("=====Hook SVC=====")    varbase_addr = Module.findBaseAddress("libmanxi.so");    addr.forEach(function(svc) {        varoffset = `0x` + svc.offset.toString(16)        Interceptor.attach(base_addr.add(offset), {            onEnter: function(args) {                if(this.context.x8 == 0x38) {                    console.log("Openat:"+ ptr(this.context.x1).readCString())                }            },            onLeave: function(retval) {            }        })    })} | 
发现其创建线程不断打开status文件进而查询TracerPID字段
 
首先尝试Hook LoadMethod进而进行脱壳
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      
      12
      
      13
      
      14
      
      15
      
      16
      
      17
      
      18
      
      19
      
      20
      
      21
      
      22
      
      23
      
      24
      
      25
      
      26
      
      27
      
      28
      
      29
      
      30
      
      31
      
      32
      
      33
      
      34
      
      35
      
      36
      
      37
      
      38
      
      39
      
      40
      
      41
      
      42
      
      43
      | functiondump_memory(base, size) {    Java.perform(function() {        varcurrentApplication = Java.use("android.app.ActivityThread").currentApplication();        vardir = "/data/local/tmp";        varfile_path = dir + "/mydump.so";        varfile_handle = newFile(file_path, "w+");        if(file_handle && file_handle != null) {            Memory.protect(ptr(base), size, 'rwx');            varlibso_buffer = ptr(base).readByteArray(size);            file_handle.write(libso_buffer);            file_handle.flush();            file_handle.close();            console.log("[dump]:", file_path);        }    });}varsize = 0vardump_addr = 0functionhook_libart() {    Java.perform(function() {        varloadMethod = Module.findExportByName("libart.so", "_ZN3art11ClassLinker10LoadMethodERKNS_7DexFileERKNS_13ClassAccessor6MethodENS_6HandleINS_6mirror5ClassEEEPNS_9ArtMethodE")        vardumped = 0        Interceptor.attach(loadMethod, {            onEnter: function(args) {                if(dumped == 0) {                    dump_addr = ptr(args[1].add(8).readPointer())                    size = args[1].add(16).readInt()                    dump_memory(dump_addr, size)                    dumped = 1                }            },            onLeave: function(retval) {}        })    })}functionmain() {    hook_libart()}setImmediate(main) | 
然而脱下来的壳依旧只有Shell代码,即便我们通过延迟脱壳(即首先获取DexFile的begin和size,之后通过命令行调用脱壳函数)依旧如此
 
所以尝试别的办法,首先我们知道整体加固的原理是替换ClassLoader来加载原Dex,而Shell Dex尾部恰好存在着大量数据,可以猜测这就是被加密的Dex。由于Shell加载完成之后会将程序控制权归还给原程序,所以我们可以Hook performLaunchActivity,在Activity启动之前获取系统ClassLoader进而得到其加载的Dex文件进行Dump。经过测试,最后一个覆盖的Dex文件为原Dex
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      
      12
      
      13
      
      14
      
      15
      
      16
      
      17
      
      18
      
      19
      
      20
      
      21
      
      22
      
      23
      
      24
      
      25
      
      26
      
      27
      
      28
      
      29
      
      30
      
      31
      
      32
      
      33
      
      34
      
      35
      
      36
      
      37
      
      38
      
      39
      
      40
      
      41
      
      42
      
      43
      
      44
      
      45
      
      46
      
      47
      
      48
      
      49
      
      50
      
      51
      
      52
      
      53
      
      54
      
      55
      
      56
      
      57
      
      58
      
      59
      
      60
      
      61
      
      62
      | functiondump_memory(base, size) {    Java.perform(function() {        varcurrentApplication = Java.use("android.app.ActivityThread").currentApplication();        vardir = "/data/local/tmp";        varfile_path = dir + "/mydump.so";        varfile_handle = newFile(file_path, "w+");        if(file_handle && file_handle != null) {            Memory.protect(ptr(base), size, 'rwx');            varlibso_buffer = ptr(base).readByteArray(size);            file_handle.write(libso_buffer);            file_handle.flush();            file_handle.close();            console.log("[dump]:", file_path);        }    });}functiongetDexFile() {    Hook_Invoke()    Java.perform(function() {        varActivityThread = Java.use("android.app.ActivityThread")        ActivityThread["performLaunchActivity"].implementation = function(args) {            varenv = Java.vm.getEnv()            varclassloader = this.mInitialApplication.value.getClassLoader()            varBaseDexClassLoader = Java.use("dalvik.system.BaseDexClassLoader")            varelementsClass = Java.use("dalvik.system.DexPathList$Element")            classloader = Java.cast(classloader, BaseDexClassLoader)            varpathList = classloader.pathList.value            varelements = pathList.dexElements.value            console.log(elements)            //console.log(elements.value)            for(vari inelements) {                varelement;                try{                    element = Java.cast(elements[i], elementsClass);                } catch(e) {                    console.log(e)                }                //之后需要获取DexFile                vardexFile = element.dexFile.value                //getMethod(dexFile, classloader)                varmCookie = dexFile.mCookie.value                //$h获取句柄                varlength = env.getArrayLength(mCookie.$h, 0)                //console.log(length)                varArray = env.getLongArrayElements(mCookie.$h, 0)                //第一个不是DexFile                for(vari = 1; i < length; ++i) {                    varDexFilePtr = Memory.readPointer(ptr(Array).add(i * 8))                    varDexFileBegin = ptr(DexFilePtr).add(Process.pointerSize).readPointer()                    varDexFileSize = ptr(DexFilePtr).add(Process.pointerSize * 2).readU32()                    console.log(hexdump(DexFileBegin))                    console.log("Size => "+ DexFileSize.toString(16))                    dump_memory(DexFileBegin,DexFileSize)                    //dex_begin = DexFileBegin                    //dex_size = DexFileSize                }            }            returnthis.performLaunchActivity(arguments[0], arguments[1])        }    })} | 
反编译结果如下,可以发现onCreate的方法内容未被填充
 
 
所以尝试延迟Dump的时机,通过Hook ArtMethod::Invoke来Dump,最终代码如下
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      
      12
      
      13
      
      14
      
      15
      
      16
      
      17
      
      18
      
      19
      
      20
      
      21
      
      22
      
      23
      
      24
      
      25
      
      26
      
      27
      
      28
      
      29
      
      30
      
      31
      
      32
      
      33
      
      34
      
      35
      
      36
      
      37
      
      38
      
      39
      
      40
      
      41
      
      42
      
      43
      
      44
      
      45
      
      46
      
      47
      
      48
      
      49
      
      50
      
      51
      
      52
      
      53
      
      54
      
      55
      
      56
      
      57
      
      58
      
      59
      
      60
      
      61
      
      62
      
      63
      
      64
      
      65
      
      66
      
      67
      
      68
      
      69
      
      70
      
      71
      
      72
      
      73
      
      74
      
      75
      
      76
      
      77
      
      78
      
      79
      | functionHook_Invoke() {    varInvokeFunc = Module.findExportByName("libart.so", "_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc")    Interceptor.attach(InvokeFunc, {        onEnter: function(args) {            console.log(args[1])            // args[0].add(8).readInt().toString(16) == 0            console.log("Method Idx -> "+ args[0].add(8).readInt().toString(16) + " "+ args[0].add(12).readInt().toString(16))            dump_memory(dex_begin, dex_size)            //dump_memory(dex_begin, dex_size)        },        onLeave: function(retval) {}    })}vardex_begin = 0vardex_size = 0functiondump_memory(base, size) {    Java.perform(function() {        varcurrentApplication = Java.use("android.app.ActivityThread").currentApplication();        vardir = "/data/local/tmp";        varfile_path = dir + "/mydump.so";        varfile_handle = newFile(file_path, "w+");        if(file_handle && file_handle != null) {            Memory.protect(ptr(base), size, 'rwx');            varlibso_buffer = ptr(base).readByteArray(size);            file_handle.write(libso_buffer);            file_handle.flush();            file_handle.close();            console.log("[dump]:", file_path);        }    });}functiongetDexFile() {    Hook_Invoke()    Java.perform(function() {        varActivityThread = Java.use("android.app.ActivityThread")        ActivityThread["performLaunchActivity"].implementation = function(args) {            varenv = Java.vm.getEnv()            varclassloader = this.mInitialApplication.value.getClassLoader()            varBaseDexClassLoader = Java.use("dalvik.system.BaseDexClassLoader")            varelementsClass = Java.use("dalvik.system.DexPathList$Element")            classloader = Java.cast(classloader, BaseDexClassLoader)            varpathList = classloader.pathList.value            varelements = pathList.dexElements.value            console.log(elements)            //console.log(elements.value)            for(vari inelements) {                varelement;                try{                    element = Java.cast(elements[i], elementsClass);                } catch(e) {                    console.log(e)                }                //之后需要获取DexFile                vardexFile = element.dexFile.value                //getMethod(dexFile, classloader)                varmCookie = dexFile.mCookie.value                //$h获取句柄                varlength = env.getArrayLength(mCookie.$h, 0)                //console.log(length)                varArray = env.getLongArrayElements(mCookie.$h, 0)                //第一个不是DexFile                for(vari = 1; i < length; ++i) {                    varDexFilePtr = Memory.readPointer(ptr(Array).add(i * 8))                    varDexFileBegin = ptr(DexFilePtr).add(Process.pointerSize).readPointer()                    varDexFileSize = ptr(DexFilePtr).add(Process.pointerSize * 2).readU32()                    console.log(hexdump(DexFileBegin))                    console.log("Size => "+ DexFileSize.toString(16))                    dex_begin = DexFileBegin                    dex_size = DexFileSize                }            }            returnthis.performLaunchActivity(arguments[0], arguments[1])        }    })} | 
反编译效果如下
 
更多【蛮犀加固免费版脱壳】相关视频教程:www.yxfzedu.com