最近去打了一次鹏城杯的线下赛,赛场上碰到了一个木马 DinodasRAT, 这个 C2 客户端 使用了一坨比较复杂的加密函数,因此我们这个地方解密流量可以使用 unicorn 进行解密
首先是数据包的格式,因为程序过于复杂,比赛结束了也没分析出来,这边直接拿网络上 的分析结论:首先会有 0x30 开头的一个固定字节,然后接下来 4 个字节是数据包的长度 最后就是加密数据了。来源: 先知社区: 以中国为目标的DinodasRAT Linux后门剖析及通信解密尝试
接下来就是调用 unicorn 进行解密,我们可以使用 unicorn 直接模拟执行函数代码。
但是缺陷就是如果遇到系统调用,这个工具没法直接运行,还有就是函数运行的时候因为 是使用寄存器传入参数的,因此我们需要手动模拟寄存器赋值,这样来进行模拟函数传参。
然后我们还需要把密钥和密文数据模拟传入内存中, 最后还要传入加密函数相关的代码进入内存。
接下来以这个木马文件为例演示一下:
我们需要用 unicorn 来调用解密函数,我们可以看到这个函数的地址为 0x426090-0x4262BB
同时还调用了一个 tea 解密函数,地址为 0x426010-0x426085
我们使用如下代码来把几个函数加载进 unicorn 模拟器的内存
# Create the simulatorbase = 0x400000uc = Uc(UC_ARCH_X86, UC_MODE_64)uc.mem_map(base, 1024 * 1024)
def write_code(base, start, end):    func_start_offset = start - base    func_end_offset = end - base    func_code = code[func_start_offset : func_end_offset + 1]    uc.mem_write(start, func_code)
write_code(base, 0x426090, 0x4262BB)  # tea decode callerwrite_code(base, 0x426010, 0x426085)  # tea decode然后可以看到这个地方需要依次传入 v 密文, 密文长度,k 密钥, 结果,和结果长度
我们可以在内存中对这几个变量开辟对应的内存来进行存储,然后把这些内存地址和变量值依次写入
rdi, rsi, rdx, rcx, r8 寄存器, 用来模拟 x64 Linux 系统的函数调用传参过程
data_start = 0x431F00
# Write the datadata_addr = data_startuc.mem_write(data_addr, bytes(data))
# Write the keykey_addr = data_addr + len(data)uc.mem_write(key_addr, bytes(key))
# Calc the result buffer addressres_addr = key_addr + len(key)
# Calc the end number addrend_number_addr = res_addr + len(data) + 1end_number = 0x1000uc.mem_write(end_number_addr, end_number.to_bytes(4, "little"))
# Pass the parametersuc.reg_write(UC_X86_REG_RDI, data_addr)uc.reg_write(UC_X86_REG_RSI, len(data))uc.reg_write(UC_X86_REG_RDX, key_addr)uc.reg_write(UC_X86_REG_RCX, res_addr)uc.reg_write(UC_X86_REG_R8, end_number_addr)最后设置栈空间, 和 RIP 寄存器
# Calc the stack addressstack_len = 0x1000stack_addr = end_number_addr + 8 + stack_len
uc.reg_write(UC_X86_REG_RIP, func_start)uc.reg_write(UC_X86_REG_RSP, stack_addr)uc.reg_write(UC_X86_REG_RBP, stack_addr)最后所有代码如下:
from os import _exit
from unicorn import UC_ARCH_X86, UC_MODE_64, Ucfrom unicorn.unicorn import UcErrorfrom unicorn.x86_const import (    UC_X86_REG_R8,    UC_X86_REG_RBP,    UC_X86_REG_RCX,    UC_X86_REG_RDI,    UC_X86_REG_RDX,    UC_X86_REG_RIP,    UC_X86_REG_RSI,    UC_X86_REG_RSP,)
data = []  # 这个地方填入加密数据data = data[0x30:]data = data[5:]
key = [    0xA1,    0xA1,    0x18,    0xAA,    0x10,    0xF0,    0xFA,    0x16,    0x06,    0x71,    0xB3,    0x08,    0xAA,    0xAF,    0x31,    0xA1,]
# Get the codewith open("./config", "rb") as f:    code = f.read()
# Create the simulatorbase = 0x400000uc = Uc(UC_ARCH_X86, UC_MODE_64)uc.mem_map(base, 1024 * 1024)
def write_code(base, start, end):    func_start_offset = start - base    func_end_offset = end - base    func_code = code[func_start_offset : func_end_offset + 1]    uc.mem_write(start, func_code)
write_code(base, 0x426090, 0x4262BB)  # tea decode callerwrite_code(base, 0x426010, 0x426085)  # tea decode
func_start = 0x426090func_end = 0x4262BBdata_start = 0x431F00
# Write the datadata_addr = data_startuc.mem_write(data_addr, bytes(data))
# Write the keykey_addr = data_addr + len(data)uc.mem_write(key_addr, bytes(key))
# Calc the result buffer addressres_addr = key_addr + len(key)
# Calc the end number addrend_number_addr = res_addr + len(data) + 1end_number = 0x1000uc.mem_write(end_number_addr, end_number.to_bytes(4, "little"))
# Calc the stack addressstack_len = 0x1000stack_addr = end_number_addr + 8 + stack_len
uc.reg_write(UC_X86_REG_RIP, func_start)uc.reg_write(UC_X86_REG_RSP, stack_addr)uc.reg_write(UC_X86_REG_RBP, stack_addr)
# Pass the parametersuc.reg_write(UC_X86_REG_RDI, data_addr)uc.reg_write(UC_X86_REG_RSI, len(data))uc.reg_write(UC_X86_REG_RDX, key_addr)uc.reg_write(UC_X86_REG_RCX, res_addr)uc.reg_write(UC_X86_REG_R8, end_number_addr)
# Excute the codetry:    uc.emu_start(func_start, func_end)except UcError as e:    print(e)    print(uc.mem_read(res_addr, len(data)))    _exit(-1)print(uc.mem_read(res_addr, len(data)))