某程xxmain.so花指令去除记录

某程xxmain.so花指令去除记录

前言

最近想要练习练习脚本去花,去混淆,解密字符串

但是感觉自己加花/混淆出来的样本太简单了

在龙哥星球看到xccccccc师傅发了一篇某程xxmain.so的去花和算法还原的文章
记一次xxmain.so从去花到魔改算法还原

so里的花指令强度还算可以,模式比较固定,非常适合练习

xccccccc师傅文章里App版本是8.0.65,可以说是很老,笔者在豌豆荚也下不到,于是用的8.74.6版本

好在目标方法仍被保留,其在so中的偏移是0xc2794

Frida检测绕过

绕过思路和之前某书Frida检测绕过记录一模一样,这里不再赘述

花指令分析

函数开头一眼花指令,不是正常函数开头的序言

STP压X0和X30

LDR给W0一个立即数

BL调用sub_C2C64,同时设置X30/LR为返回地址即0xC27A0

sub_C2C64前两条指令通过X0和立即数计算新的X0

(单独说明是因为其他类似sub_C2C64的函数前两条指令的运算方式会不同,还有存在第三条运算指令的情况)

第三条指令X0加1,第四条指令X0先左移2位,再作为偏移,以X30为基址,去tb读个新的DWORD赋给W0

第五条指令X30加X0,改返回地址,最后RET返回,也就是说sub_C2C64根据X0进行动态跳转

上面最后会跳转到0xC2CA0(unidbg模拟执行+traceCode可以很方便地找到跳转地址)

LDP和开头的STP对应,也会平衡堆栈

后面两条STP就是正常函数开头的序言了

然后又是STP+LDR+BL的组合,只是W0的值不同

而且这里BL并不是直接调用sub_C2C64,而是先跳转到loc_C279C再通过BL调用sub_C2C64

这就和前面不一样了,区别在于STP+LDR+BL后的第四条指令仍是BL还是运算指令EOR/SUB等等

总结一下,函数开头的STP+LDR+BL会跳转到一个类似sub_C2C64的通过X0和X30计算返回地址的函数calcfunc

但是这个计算返回地址的函数calcfunc有很多,指令条数和运算方式都不太一样

比如这里sub_31C4C前三条指令都是X0和立即数的运算,从第四条指令开始才符合上面的模式

笔者的解决思路是每当函数开头进入一个新calcfunc的时候(calcfunc作为键),对calcfunc的指令进行反汇编并保存

然后实现一个汇编指令解释器去模拟每条指令,以计算最终的跳转地址

对于STP+LDR+BL后第四条指令仍是BL的情况,通过loc_xxxxx确定唯一的calcfunc,得到跳转地址后加入值列表

去花

这里需要说明的一点是,跳转地址的第一条指令是LDP,要和STP对应,所以patch的地址得是LDR的地址

完整脚本如下

python 复制代码
import idautils
import ida_bytes
import idc
import struct
from capstone import *
from keystone import *


# 反汇编引擎
cs = Cs(CS_ARCH_ARM64, CS_MODE_ARM)
# 汇编引擎
ks = Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN)

# 字典
# 条目结构
# {"通过w0和x30计算目标跳转地址的函数名称":[x30, [函数汇编指令], (目标patch地址,目标跳转地址), (,), ...]}
amap = dict()


def mydisasm(codebin, codeaddr):
    # 反汇编
    asm = []
    for insn in cs.disasm(codebin, codeaddr):
        if insn.mnemonic != "":
            # 助记符
            asm.append(insn.mnemonic)
            # 操作数
            asm.append(insn.op_str)
    return asm


def getfuncinsns(startaddr):
    # 获取函数汇编指令
    funcinsns = []
    addr = startaddr
    while True:
        addrcode = idc.get_wide_dword(addr)
        addrcode = struct.pack("<I", addrcode)
        ins = mydisasm(addrcode, addr)
        funcinsns.append(ins)
        addr += 4
        if ins[0] == "ret":
            break
    return funcinsns


def execfuncinsns(w0, x30, insns: list[list[str]]):
    # 解释执行函数汇编指令
    # 返回目标跳转地址
    maybewrong = False
    # 处理第一条指令
    insn0 = insns[0]
    assert insn0[1].startswith("x0, x0, #")
    insn0oprand = insn0[1].replace("x0, x0, #", "")
    insn0oprand = int(insn0oprand, 16)
    if insn0[0] == "add":
        w0 += insn0oprand
    elif insn0[0] == "sub":
        w0 -= insn0oprand
    elif insn0[0] == "eor":
        w0 ^= insn0oprand
    elif insn0[0] == "lsr":
        w0 >>= insn0oprand
    else:
        maybewrong = True
    # 处理第二条指令
    insn1 = insns[1]
    assert insn1[1].startswith("x0, x0, #")
    insn1oprand = insn1[1].replace("x0, x0, #", "")
    insn1oprand = int(insn1oprand, 16)
    if insn1[0] == "add":
        w0 += insn1oprand
    elif insn1[0] == "sub":
        w0 -= insn1oprand
    elif insn1[0] == "eor":
        w0 ^= insn1oprand
    elif insn1[0] == "lsr":
        w0 >>= insn1oprand
    else:
        maybewrong = True
    if len(insns) == 6:
        # 处理第三条指令
        assert insns[2][0] == "add"
        assert insns[2][1] == "x0, x0, #1"
        w0 += 1
        # 处理第四条指令
        assert insns[3][0] == "ldr"
        assert insns[3][1] == "w0, [x30, x0, sxtx #2]"
        w0 <<= 2
        w0 = idc.get_wide_dword(x30+w0)
        # 处理第五条指令
        assert insns[4][0] == "add"
        assert insns[4][1] == "x30, x30, x0"
        w0 = x30+w0
        # 处理第六条指令
        assert insns[5][0] == "ret"
    elif len(insns) == 7:
        # 处理第三条指令
        insn2 = insns[2]
        assert insn2[1].startswith("x0, x0, #")
        insn2oprand = insn2[1].replace("x0, x0, #", "")
        insn2oprand = int(insn2oprand, 16)
        if insn2[0] == "add":
            w0 += insn2oprand
        elif insn2[0] == "sub":
            w0 -= insn2oprand
        elif insn2[0] == "eor":
            w0 ^= insn2oprand
        elif insn2[0] == "lsr":
            w0 >>= insn2oprand
        else:
            maybewrong = True
        # 处理第四条指令
        assert insns[3][0] == "add"
        assert insns[3][1] == "x0, x0, #1"
        w0 += 1
        # 处理第五条指令
        assert insns[4][0] == "ldr"
        assert insns[4][1] == "w0, [x30, x0, sxtx #2]"
        w0 <<= 2
        w0 = idc.get_wide_dword(x30+w0)
        # 处理第六条指令
        assert insns[5][0] == "add"
        assert insns[5][1] == "x30, x30, x0"
        w0 = x30+w0
        # 处理第七条指令
        assert insns[6][0] == "ret"
    else:
        maybewrong = True
    if maybewrong:
        w0 = -1
    return w0


def search(addr):
    global amap
    # 第一条指令
    # STP             X0, X30, [SP,#-0x10]!
    firstinsaddr = addr
    if idc.get_wide_dword(firstinsaddr) == 0xA9BF7BE0:
        # 第二条指令
        # LDR             W0, =
        secondinsaddr = addr + 4
        if idc.get_wide_dword(secondinsaddr) == 0x18000040:
            # 第三条指令
            thirdinsaddr = addr + 8
            thirdinscode = idc.get_wide_dword(thirdinsaddr)
            thirdinscode = struct.pack("<I", thirdinscode)
            thirdinsasm = mydisasm(thirdinscode, thirdinsaddr)
            # 第三条指令一定是bl
            if len(thirdinsasm) != 0 and thirdinsasm[0] == "bl":
                # 第四条指令
                fourthinsaddr = int(thirdinsasm[1][1:], 16)
                fourthinscode = idc.get_wide_dword(fourthinsaddr)
                fourthinscode = struct.pack("<I", fourthinscode)
                fourthinsasm = mydisasm(fourthinscode, fourthinsaddr)
                # 第四条指令不是bl,说明直接进入计算函数,向amap插入条目
                if len(fourthinsasm) != 0 and fourthinsasm[0] != "bl":
                    calcfunc = f"sub_{hex(fourthinsaddr)[2:]}"
                    calcbasex30 = thirdinsaddr+4
                    if calcfunc not in amap.keys():
                        calcfuncinsns = getfuncinsns(fourthinsaddr)
                        amap[calcfunc] = [calcbasex30, calcfuncinsns]
                # 第四条指令仍是bl,说明间接进入计算函数,验证计算函数是否存在
                elif len(fourthinsasm) != 0 and fourthinsasm[0] == "bl":
                    calcfunc = f"sub_{fourthinsasm[1][3:]}"
                    if calcfunc not in amap.keys():
                        print(f"wrong at {hex(firstinsaddr)}")
                # 第二条指令
                secondinscode = idc.get_wide_dword(secondinsaddr)
                secondinscode = struct.pack("<I", secondinscode)
                secondinsasm = mydisasm(secondinscode, secondinsaddr)
                # 第二条指令一定是ldr
                if len(secondinsasm) != 0 and secondinsasm[0] == "ldr":
                    # 获取动态的w0
                    w0addr = secondinsasm[1].replace("w0, #", "")
                    w0 = idc.get_wide_dword(int(w0addr, 16))
                    # 获取相对计算函数静态的x30和汇编指令
                    calcfunclist = amap[calcfunc]
                    x30 = calcfunclist[0]
                    calcfuncinsns = calcfunclist[1]
                    # 计算目标跳转地址
                    w0jmpaddr = execfuncinsns(w0, x30, calcfuncinsns)
                    if w0jmpaddr != -1:
                        calcfunclist.append((secondinsaddr, w0jmpaddr))
                    else:
                        print(f"bad func at {calcfunc}")
                        exit()


def gencode(srcaddr, jmpcode):
    # 生成机器码
    code, _ = ks.asm(jmpcode, srcaddr)
    return code


# 遍历段,找.text段
textsegstart = -1
textsegend = -1
for segea in idautils.Segments():
    if ".text" in idc.get_segm_name(segea):
        textsegstart = segea
        textsegend = idc.get_segm_end(segea)
        break
if textsegstart != -1 and textsegend != -1:
    while textsegstart <= textsegend:
        search(textsegstart)
        textsegstart += 4

for value in amap.values():
    # [(目标patch地址,目标跳转地址)]
    patchlist = value[2:]
    for srcaddr, jmpaddr in patchlist:
        jmpcode = f"B {hex(jmpaddr)}"
        code = gencode(srcaddr, jmpcode)
        if code != None:
            ida_bytes.patch_bytes(srcaddr, bytes(code))

print("Done")

最终patch了231616字节,去花后反编译,堆栈还是有点问题

还有点混淆,d810能去个大概

后记

后续的算法还原就没做了,xccccccc师傅文章里分析出来是魔改的MD5和SHA256

感觉再跟一遍有点折磨(懒狗本狗了属于是

去花脚本有待改进,就像上面说的堆栈还有点问题

还有就是去混淆,不透明谓词的话,理论上改成const类型即可,但是量太大,脚本改的话还得学习学习

相关推荐
安科瑞刘鸿鹏2 小时前
校园建筑用电安全监测装置 电气火灾监测预防设备功能介绍
运维·服务器·网络·嵌入式硬件·安全·能源
茶颜悦色vv4 小时前
网络搜索引擎Shodan(2)
网络·安全·web安全·搜索引擎·网络安全
Channing Lewis4 小时前
salesforce developer console 匿名执行是以什么身份执行的
数据库·安全·salesforce
黑龙江亿林等级保护测评6 小时前
做等保二级备案需要准备哪些材料
网络·安全·金融·智能路由器·ddos
星海幻影7 小时前
网络安全知识见闻终章 ?
网络·安全·web安全
kinlon.liu7 小时前
安全日志记录的重要性
服务器·网络·安全·安全架构·1024程序员节
马戏团小丑7 小时前
fastjson/jackson对getter,setter和constructor的区分
java·安全
hanniuniu138 小时前
动态威胁场景下赋能企业安全,F5推出BIG-IP Next Web应用防火墙
网络协议·tcp/ip·安全