首页
论坛
课程
招聘
[原创]bang加固简单分析
2022-7-13 21:15 19024

[原创]bang加固简单分析

2022-7-13 21:15
19024

自己的一个demo随手就上传加固了一下,然后开始分析,是免费版的,应该不少人已经分析过了

dex

dex加固,可以使用frida-dexdump可以直接dump下来

可以看到加载了SecShell进行脱壳调用,这个libSecShell.so是32位的


libSecShell.so

export列表中看到了JNI_Onload,但是是加密的,分析不出来,修改代码的话一定会调用mprotect,在mprotect处交叉引用,找不到调用,于是猜测可能是svc调用,用脚本跑了一下,发现了mprotect,脚本是之前论坛上看到的

system call : 7d 70
addr : c0783
Func Name : __NR_mprotect
 c0 70 a0 e3 00 00 00 ef

在这里交叉引用发现都在sub_C0C30里调用
用frida去hook这个函数

var mprotect_cnt = 0
//frida -U --no-pause -f com.testlinker.ty -l hook.js
function sleep(delay) {
    var start = (new Date()).getTime();
    while ((new Date()).getTime() - start < delay) {
      continue;
    }
}
 
function hook_svc_mprotect() {
    let base_svc_mprotect = Module.findBaseAddress("libSecShell.so");
    if (base_svc_mprotect != null) {
        console.log("base_svc_mprotect : " + base_svc_mprotect)
    }else{
        return ;
    }
    let svc_mprotect = base_svc_mprotect.add(0xC0778);//32位
    Interceptor.attach(svc_mprotect, {
        onEnter: function(args) {
            console.log("==========================================")
           
            console.log("svc_mprotect: start = " + args[0] + " , len = " + args[1] + " , ATTRIBUTES = " + args[2])
            mprotect_cnt += 1
            console.log(hexdump(base_svc_mprotect.add(0x281B4)))
        },
        onLeave: function(){
            console.log("svc_mprotect leave")
            console.log("==========================================")
        }
    })
}
function dis(address, number) {
    for (var i = 0; i < number; i++) {
        var ins = Instruction.parse(address);
        console.log("address:" + address + "--dis:" + ins.toString());
        address = ins.next;
    }
}
//libc->strstr()  从linker里面找到call_function的地址
function hook() {
//call_function("DT_INIT", init_func_, get_realpath());
    var linkermodule
    if (Process.pointerSize == 4) {
        linkermodule = Process.findModuleByName("linker");
    }else if (Process.pointerSize == 8) {
        linkermodule = Process.findModuleByName("linker64");
    }
    // var linkermodule = Process.getModuleByName("linker");
    var call_function_addr = null;
    var symbols = linkermodule.enumerateSymbols();
    for (var i = 0; i < symbols.length; i++) {
        var symbol = symbols[i];
        //LogPrint(linkername + "->" + symbol.name + "---" + symbol.address);
        if (symbol.name.indexOf("__dl__ZL13call_functionPKcPFviPPcS2_ES0_") != -1) {
            call_function_addr = symbol.address;
            //LogPrint("linker->" + symbol.name + "---" + symbol.address)
        }
    }
    Interceptor.attach(call_function_addr, {
        onEnter: function (args) {
            var type = ptr(args[0]).readUtf8String();
            var address = args[1];
            var sopath = ptr(args[2]).readUtf8String();
            console.log("loadso:" + sopath + "--addr:" + address + "--type:" + type);
            if (sopath.indexOf("libSecShell.so") != -1) {
                var libnativemodule = Process.getModuleByName("libSecShell.so");//call_function正在加载目标so,这时就拦截下来
                var base = libnativemodule.base;
                hook_svc_mprotect()
            }
        }
    })
}
function main() {
    hook();
}
setImmediate(main)

可以发现经过mprotect一次后,对应地址的值发生了变化

[Pixel 3::com.example.cryptotest ]-> 
base_svc_mprotect : 0xcfaea000
==========================================
svc_mprotect: start = 0xcfaea000 , len = 0xa1000 , ATTRIBUTES = 0x7
           0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
cfb121b4  9b 66 a6 75 82 ab ba fb 1a 80 e6 75 d7 0e 7f 1b  .f.u.......u....
svc_mprotect leave
==========================================
==========================================
svc_mprotect: start = 0xcfb8b000 , len = 0x1f000 , ATTRIBUTES = 0x3
           0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
cfb121b4  2d e9 f0 4f ad f6 ac 4d df f8 44 4e df f8 44 3e  -..O...M..DN..D>
svc_mprotect leave
==========================================
==========================================
svc_mprotect: start = 0xcfaea000 , len = 0xa1000 , ATTRIBUTES = 0x7
           0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
cfb121b4  2d e9 f0 4f ad f6 ac 4d df f8 44 4e df f8 44 3e  -..O...M..DN..D>
svc_mprotect leave
==========================================

用memdumper64(github上有,速度挺快) dump出so,用Sofixer修复so文件

打开跳转到JNI_Onload(0x1E5DC)

发现ida没有自动创建函数,按p会报错The function has undefined instruction/data at the specified address

用idapython强制创建函数

ida_funcs.add_func(0x281B4,0x2A5CC)

随便打开一个函数,发现是这样的

.data:00085E24 DD F9 97 E4 off_85E24 DCD 0xE497F9DD ; DATA XREF: sub_DF08+8↑r

cat /proc/18395/maps | grep e49看一下这个地址

发现是libc.so,把这个libc.so拖出来放到ida分析
计算一下0xE497F9DD-0xe494b000 = 0x349dd
看一下libc.so,所以这个函数就是strcpy

感觉可以写一个idapython脚本去修复一下
然后就写了一下,先从libc.so中提取函数地址和函数名

from idautils import *
from idaapi import *
from idc import *
f = open("./func.txt",'w')
for func_addr in Functions(0,0x5B18BC):
    func_name = get_func_name(func_addr)
    print(func_addr , func_name)
    f.write(str(func_addr) + "," + func_name + "\n")
    # f.writelines()
f.close()

效果:

然后从.data段中找到相应地址,相减得到libc.so中地址的偏移,然后对应起来,去修改函数名

from idautils import *
from idaapi import *
from idc import *
f = open(r"CryptoTest_32\CryptoTest\lib\func.txt",'r')
func_info = {}
while True:
    info = f.readline().strip('\n')
    if not info:
        break
    addr, func_name = info.split(',')
    # print(addr + func_name)
    func_info[int(addr,10)] = func_name
# print(func_info)
f.close()
textStart = 0xA2984
textEnd = 0xC2000
# textStart = 0xA2DE0
# textEnd = 0xA2E04
libc_dump_base = 0xe494b000
for i in range(textStart,textEnd,4):
    dword_ = get_dword(i)
    if dword_ > libc_dump_base:
        libc_func = dword_ - libc_dump_base
        # print(dword_,libc_func)
        func_name = func_info.get(libc_func)
        if not func_name:
            func_name = func_info.get(libc_func-1)  #thumb
        if not func_name:
            continue
        raw_name_off = get_name(i)
        patch_name_off = func_name + "_ptr_" + raw_name_off
        set_name(i,patch_name_off)
        xrefaddrs = XrefsTo(i, flags=0)
        for xrefaddr in xrefaddrs:
            raw_name = get_func_name(xrefaddr.frm)          #拿到函数原名称
            patch_fun_addr = get_name_ea_simple(raw_name)   #拿到函数地址
            # print(get_func_name(xrefaddr.frm))
            if raw_name and patch_fun_addr:
                break
        if raw_name and patch_fun_addr:
            patch_name = func_name + "_" + raw_name
            print("patch_name : ",patch_name)
            set_name(patch_fun_addr,patch_name)
        print(dword_,func_name)

效果如下:

这样就容易分析得多,其实不止libc.so,还有libdl.so等,不过这个函数少,就手动恢复了


init_array

地址:0x11720

sub_13E48:打开libc.so,通过dlsym获取了mprotect、mmap、munmap、fopen、fclose、fgets、fwrite、fread、sprintf、pthread_create函数指针

接着跟着frida的log,程序运行到了case 2

流程是case 2 -> case 5 -> case 4(读cmdline) -> case 1 -> case 5 -> case 4循环读取

这里主要是记录包名的长度,存在v8里

最终执行到case 0,读包名,然后和/system/bin/dex2oat对比,这里我包名和/system/bin/dex2oat不匹配,不进入下面的步骤(这个过程看不懂它要干啥)

然后进入到JNI_Onload


JNI_Onload

字符串解密

刚看到JNI_Onload,发现用了sub_12B94函数大量解密字符串,

于是采用frida hook这个函数,打印出相应的信息(比如解密后的函数,返回地址),本来是只想解密字符串,但是字符串的解密顺序其实帮助了分析流程的过程,解密字符串的函数不止一个,具体的可以看看附件,写得很乱,需要注意的是这个hook的时机应该是在JNI_Onload解密之后,不然可能会出问题

function hook_decode_str(){
    let base_secShell = Module.findBaseAddress("libSecShell.so");
    let decode_str = base_secShell.add(0x12B94+1);
    Interceptor.attach(decode_str, {
        onEnter: function(args) {
            console.log("=======decode_str========="+ " size = " + args[1] + " op = " + args[2]," return addr = " + this.context.lr.sub(base_secShell))
            this.args0 = args[0]
            this.args1 = args[1]
        },
        onLeave: function(){
            console.log(hexdump(this.args0,{length:this.args1.toInt32()}))
            // console.log(hexdump(args[0],{length:0x10}))
        }
    })
}


function hook_svc_mprotect() {
    let base_secShell = Module.findBaseAddress("libSecShell.so");
    if (base_secShell != null) {
        console.log("base_secShell : " + base_secShell)
    }else{
        return ;
    }
    let svc_mprotect = base_secShell.add(0xC0778);//32位
    // let svc_mprotect = base_secShell.add(0x1541A0);//64位
    //private native void jniLoadScriptFromAssets(AssetManager assetManager, String assetURL, boolean loadSynchronously);
    Interceptor.attach(svc_mprotect, {
        onEnter: function(args) {
            console.log("==========================================")
           
            console.log("svc_mprotect: start = " + args[0] + " , len = " + args[1] + " , ATTRIBUTES = " + args[2])
            mprotect_cnt += 1
            console.log(hexdump(base_secShell.add(0x281B4)))
        },
        onLeave: function(){
            console.log("svc_mprotect leave")
            console.log("==========================================")
           
            if(mprotect_cnt == 2){
                hook_decode_str()
                // hook_elf_hook()
                // sleep(1000000)
            }
        }
    })
}

大概流程

先执行case 0:初始化JNIEnv,解密得到com/SecShell/SecShell/H字符串

然后case8(0x29e00):

跳到sub_13E48,获取libc.so一些函数指针,从java类获取PKGNAME = "com.example.cryptotest",

后面在case8里的case分支干了一些不知道在干啥,好像是在配置环境

然后是case9:

调用android/app/ActivityThread类的currentActivityThread方法

调用ActivityThread对象的getSystemContext方法

调用ContextImpl的getPackageManager方法

调用PackageManager的getPackageInfo方法

获取PackageInfo对象的applicationInfo字段

获取ApplicationInfo对象的sourceDir字段

获取ApplicationInfo对象的nativeLibraryDir字段

拼接出/proc/%d/fd/%d,遍历fd找到base.apk路径


然后是case2:对小米手机进行适配

然后是case3:创建了线程(没执行到),验证了签名

case1->case10

case10:打开/proc/self/maps,找到lib/libart.so,比较是否是r-xp权限,通过格式化字符串%lx-%lx读取地址

case11:把libart.so改为可读写,两个箭头前后对比

case13:拼接出各种字符串,比如/data/app/~~yqfNRTFBNC4L6gA2oycp-g==/com.example.cryptotest-VtwyTKkuWOlQLYKSpK7Z5Q==/oat/arm/base.odex

然后到case13里面的case10,打开这个base.odex

会打开打开classes.dve进行校验

然后会执行sub_260BC,这里会调用0x4D7DC(hook_libc_so_func),对hook部分说的那些函数进行hook,然后读classes.jar写到内存里的时候就调用这些函数进行解密(dex加载)

case4:把libart.so权限改回去

case12:弄了好多inlinehook,但是好像也没有执行(不知道是不是我系统版本过高)

具体见附件给的idb吧

dex加载

sub_1DFB0调用了com/SecShell/SecShell/H的f方法加载/data/user/0/com.example.cryptotest/.cache/classes.jar

调用com/SecShell/SecShell/H的ff加载/data/user/0/com.example.cryptotest/.cache/v1filter.jar

通过dump maps来比较加载前和加载后的差异

可以直接把这个直接dump下来,发现解析不了,有点尴尬

于是比较一下frida-dexdump dump下来的文件,发现后面多了几百个字节,删掉就可以解析了



inlineHook

地址:0x53E30是inline hook函数

交叉引用可以看到很多hook的地方

比如hook了libc.so的pread64、ftruncate64、write、read、munmap、msync、__open、__openat、__mmap2

运行的时候发现其他的hook没有触发,之后用ida动态调试了一下确实是只hook了这些,其他地方不知道是不是有啥其他办法能让我断不下来

这些没有执行的地方就不过多分析了

函数p208CA25EFD02F087E334CA562B3F8423:

检测

地址0x60C5C:(似乎没有执行,发现这些check函数好像都没有执行)

xposed检测,fart检测等

check_usb:0x25508
check_root:0x17D9C


其他

函数地址:0x53E30,对华为和荣耀手机进行适配
is_miuiinstaller_process对小米手机进行适配
JNI_Onload里兼容性适配:

JNI函数注册:sub_16028(通过字符串解密log很容易发现)


用ida动态调试的时候,可能会遇到函数不会自动解析成函数,在下面框框输入这段脚本,然后用createFunction函数就可以创建函数了

def createFunction(start,end):
    len_func = end - start
    begin = start
    del_items(start,0,len_func)         #先undefine
    while len_func:
        cnt = idc.create_insn(begin)
        if cnt == 0:
            break       #遇到比如off_31F40 DCD __stack_chk_guard_ptr - 0x31D78这种就不解析了,一般是连续的,如果不连续需要跳过,继续解析
        begin += cnt
        len_func -= cnt
        print(len_func)  
    #idc.create_insn(start)
    return idc.add_func(start,end)

【参考文献】分析一下梆x加固:https://bbs.pediy.com/thread-266247.htm


好像超过上传大小了,两个文件,apk传不上去,所有文件放在百度网盘:

链接:https://pan.baidu.com/s/1Wdjp431IhhoCbcICQCJRAg 

提取码:kxuc



看雪招聘平台创建简历并且简历完整度达到90%及以上可获得500看雪币~

最后于 2022-7-26 14:23 被falconnnn编辑 ,原因:
上传的附件:
收藏
点赞5
打赏
分享
打赏 + 50.00雪花
打赏次数 1 雪花 + 50.00
 
赞赏  Editor   +50.00 2022/08/01 恭喜您获得“雪花”奖励,安全圈有你而精彩!
最新回复 (5)
雪    币: 1250
活跃值: 活跃值 (767)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
hackdaliu 活跃值 1 2022-8-11 11:17
2
0
你好,这个trace svc的脚本用的哪个呀?之前看到的都是trace64位的
雪    币: 207
活跃值: 活跃值 (865)
能力值: ( LV5,RANK:73 )
在线值:
发帖
回帖
粉丝
falconnnn 活跃值 2022-8-11 20:32
3
0
hackdaliu 你好,这个trace svc的脚本用的哪个呀?之前看到的都是trace64位的

不是trace,是ida脚本找的,你也可以直接用ida搜"svc",因为是加壳程序最后也没几个有几个,挨个看一下就出来了,脚本是之前哪里看的,不是自己写的就没好意思贴出来

上传的附件:
雪    币: 4
活跃值: 活跃值 (402)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
菜鸟也想飞 活跃值 2022-8-13 15:19
4
0
感谢分享!
雪    币: 2718
活跃值: 活跃值 (1182)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mb_rjdrqvpa 活跃值 2022-11-8 11:33
5
0
图片挂了好多,麻烦楼主修复一下
雪    币: 207
活跃值: 活跃值 (865)
能力值: ( LV5,RANK:73 )
在线值:
发帖
回帖
粉丝
falconnnn 活跃值 2022-11-11 19:11
6
0
mb_rjdrqvpa 图片挂了好多,麻烦楼主修复一下
移步这里看吧,https://mp.weixin.qq.com/s/0i2y_1WHryXhGPqiltwDiw
游客
登录 | 注册 方可回帖
返回