首页
社区
课程
招聘
[原创]某外卖软件libmtguard混淆分析
发表于: 2024-9-12 02:04 2726

[原创]某外卖软件libmtguard混淆分析

2024-9-12 02:04
2726

sub_25CDC这个函数就是字符串解密函数,byte_218F01是标记有没有解密的
图片描述
字符串解密函数就是一个异或,参数第一个存放解密后字符串,第二个是加密的字符串,第三个是密钥,第四个是长度
图片描述

字符串还是挺多的,需要批量处理一下
图片描述
解密算法已经知道了,直接从解密函数调用前的10个指令开始模拟执行到解密函数,得到解密函数的4个参数
找解密函数调用处的前10个指令时,需要判断一下是不是bxx之类的分支指令,是的话就不往前找了,避免模拟执行跑飞
然后根据参数解密出字符串,patch到第一个参数的地址

最后会有十几个字符串参数识别有问题,手动处理一下就可以
图片描述
如果是把字符串解密函数直接内联到使用字符串的函数代码中可能混淆效果好一些

直接看JNI_Onload,先是给W0赋了个值
图片描述
然后来到0x24980调用sub_25d0c
图片描述
函数从x30加上w0<<2的内存位置取了一个dword,然后加到x30
图片描述
函数调用时会把返回地址放到link register(x30),所以取数据的位置就是0x24980下面
上面是取了[0x24984 + 2<<2],也就是0x78
图片描述
加到x30之后ret来到0x0249FC
图片描述
查看0x24980的地方发现还有另外几个,并且sub_25D0C也不止有0x24980调用
可以猜测是每个函数都有有类似0x24980这种类似分发器的东西,通过上面的运算分发到函数内的基本块上
图片描述
图片描述
还是要用脚本批量处理一下
找到BL sub_25d0c的所有调用处,然后每个调用处向前找3条指令开始模拟执行,找到取数据时的下标,取出对应的偏移加回去得到正确的跳转位置,然后用keystone汇编一下指令,再patch回BL sub_25d0c的调用处
当然也不一定非要去模拟执行,这个so中BL sub_25d0c的所有调用处的前一条指令刚好都是赋值下标的指令,也就是LDR W0, xxx ,所以直接匹配也是可以
图片描述

patch完成之后,应用到so文件,重新用ida打开就可以看到函数都可以正常分析了
反混淆前
反混淆后

看JNI_Onload
图片描述
sub_24A74里分配了一些空间,然后调用sub_24D20
图片描述
sub_24D20中对JNINativeInterface里的函数全部重新映射了一遍,我们直接建个新的struct映射一下就好
图片描述
最后映射完会发现4个reserve没有映射,所以出现了部分方法出现2次,这里直接把其中一个方法加个前缀保留
图片描述
导入
图片描述
修改类型重新反编译
图片描述

import flare_emu
from idc import *
from idaapi import *
import idaapi
import ida_allins
from ida_ida import *
from idautils import *
import unicorn
from keystone import *
 
from unicorn import arm64_const
 
my_EH = flare_emu.EmuHelper()
 
def emu_last_x_insn(ea, insn_cnt=10, skip_ea=True, stop_in_brach=True, reg_info={}):
    count = 0
    curr_addr = ea
    while count < insn_cnt:
        curr_addr = prev_head(curr_addr, 0)
        insn = idaapi.insn_t()
        length = idaapi.decode_insn(insn, curr_addr)
        if stop_in_brach:
            if (insn.get_canon_feature() & CF_JUMP) != 0 or (insn.get_canon_feature() & CF_CALL) != 0 or insn.itype in [
                ARM_b, ARM_bl, ARM_br, ARM_bx, ARM_blx1, ARM_blx2, ARM_cbz, ARM_cbnz, ARM_blr,
                ARM_tbz, ARM_tbnz, ARM_ldrpc, ARM_ret] :
                    curr_addr = next_head(curr_addr, BADADDR)
                    break
        count += 1
    start_addr = curr_addr
    end_addr = ea
    my_EH.emulateRange(start_addr, end_addr, registers=reg_info, count=insn_cnt)
    return my_EH.uc
 
 
def is_valid_address(addr, segment_name = ".data"):
    seg = get_segm_by_name(segment_name)
    return addr >= seg.start_ea and addr <= seg.end_ea
 
def is_valid_string(string):
    for c in string:
        if c not in string.printable:
            return False
    return True
 
def dec_str():
    func_addr = 0x25CDC
    all_used_addr = list(CodeRefsTo(func_addr, False))
    error_process_addr = []
    for used_addr in all_used_addr:
        try:
            uc_stata = emu_last_x_insn(used_addr, reg_info={"X0": 0, "X1": 0, "X2": 0, "X3": 0})
            x0 = uc_stata.reg_read(arm64_const.UC_ARM64_REG_X0)
            x1 = uc_stata.reg_read(arm64_const.UC_ARM64_REG_X1)
            x2 = uc_stata.reg_read(arm64_const.UC_ARM64_REG_X2)
            x3 = uc_stata.reg_read(arm64_const.UC_ARM64_REG_X3)
 
            out_addr, enc_addr, key, lens = x0, x1, x2, x3
            if is_valid_address(enc_addr):
                curr_str_bytes = []
                for i in range(lens):
                    curr_char = get_wide_byte(enc_addr + i)
                    curr_char ^= key
                    key += 3
                    key %= 256
                    curr_str_bytes.append(curr_char)
                curr_str = bytearray(curr_str_bytes).decode('utf-8')
                if is_valid_address(out_addr):
                    for k in range(lens):
                        patch_byte(out_addr + k, curr_str_bytes[k])
                    create_strlit(out_addr,0,STRTYPE_C)
                    print(f"patched {hex(out_addr)} with   {curr_str}")
                else:
                    error_process_addr.append(used_addr)
            else:
                error_process_addr.append(used_addr)
        except Exception as e:
            error_process_addr.append(used_addr)
        print()
 
    for addr in error_process_addr:
        print(hex(addr),end=' ')
import flare_emu
from idc import *
from idaapi import *
import idaapi
import ida_allins
from ida_ida import *
from idautils import *
import unicorn
from keystone import *
 
from unicorn import arm64_const
 
my_EH = flare_emu.EmuHelper()
 
def emu_last_x_insn(ea, insn_cnt=10, skip_ea=True, stop_in_brach=True, reg_info={}):
    count = 0
    curr_addr = ea
    while count < insn_cnt:
        curr_addr = prev_head(curr_addr, 0)
        insn = idaapi.insn_t()
        length = idaapi.decode_insn(insn, curr_addr)
        if stop_in_brach:
            if (insn.get_canon_feature() & CF_JUMP) != 0 or (insn.get_canon_feature() & CF_CALL) != 0 or insn.itype in [
                ARM_b, ARM_bl, ARM_br, ARM_bx, ARM_blx1, ARM_blx2, ARM_cbz, ARM_cbnz, ARM_blr,
                ARM_tbz, ARM_tbnz, ARM_ldrpc, ARM_ret] :
                    curr_addr = next_head(curr_addr, BADADDR)
                    break
        count += 1
    start_addr = curr_addr
    end_addr = ea
    my_EH.emulateRange(start_addr, end_addr, registers=reg_info, count=insn_cnt)
    return my_EH.uc
 
 
def is_valid_address(addr, segment_name = ".data"):
    seg = get_segm_by_name(segment_name)
    return addr >= seg.start_ea and addr <= seg.end_ea
 
def is_valid_string(string):
    for c in string:
        if c not in string.printable:
            return False
    return True
 
def dec_str():
    func_addr = 0x25CDC
    all_used_addr = list(CodeRefsTo(func_addr, False))
    error_process_addr = []
    for used_addr in all_used_addr:
        try:
            uc_stata = emu_last_x_insn(used_addr, reg_info={"X0": 0, "X1": 0, "X2": 0, "X3": 0})
            x0 = uc_stata.reg_read(arm64_const.UC_ARM64_REG_X0)
            x1 = uc_stata.reg_read(arm64_const.UC_ARM64_REG_X1)
            x2 = uc_stata.reg_read(arm64_const.UC_ARM64_REG_X2)
            x3 = uc_stata.reg_read(arm64_const.UC_ARM64_REG_X3)

[注意]看雪招聘,专注安全领域的专业人才平台!

最后于 2024-9-29 01:49 被k423编辑 ,原因: 更新
收藏
免费 6
支持
分享
最新回复 (3)
雪    币: 1238
活跃值: (1810)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
标准 indbr 混淆 很好patch
2024-9-14 20:15
0
雪    币: 107
活跃值: (1889)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
3

分析的很好。JNI interface remap 楼主可以把新的结构定义贴出来?

最后于 2025-2-11 15:11 被frozenrain编辑 ,原因:
2025-2-11 15:10
0
雪    币: 318
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
好帖 不过我懒 直接选择trace
2025-2-11 16:20
0
游客
登录 | 注册 方可回帖
返回