某程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类型即可,但是量太大,脚本改的话还得学习学习

相关推荐
EasyNVR4 小时前
NVR管理平台EasyNVR多个NVR同时管理:全方位安防监控视频融合云平台方案
安全·音视频·监控·视频监控
黑客Ash7 小时前
【D01】网络安全概论
网络·安全·web安全·php
阿龟在奔跑8 小时前
引用类型的局部变量线程安全问题分析——以多线程对方法局部变量List类型对象实例的add、remove操作为例
java·jvm·安全·list
.Ayang8 小时前
SSRF漏洞利用
网络·安全·web安全·网络安全·系统安全·网络攻击模型·安全架构
.Ayang8 小时前
SSRF 漏洞全解析(概述、攻击流程、危害、挖掘与相关函数)
安全·web安全·网络安全·系统安全·网络攻击模型·安全威胁分析·安全架构
好想打kuo碎9 小时前
1、HCIP之RSTP协议与STP相关安全配置
网络·安全
周全全9 小时前
Spring Boot + Vue 基于 RSA 的用户身份认证加密机制实现
java·vue.js·spring boot·安全·php
Mr.Pascal10 小时前
刚学php序列化/反序列化遇到的坑(攻防世界:Web_php_unserialize)
开发语言·安全·web安全·php
风间琉璃""10 小时前
二进制与网络安全的关系
安全·机器学习·网络安全·逆向·二进制