首页
社区
课程
招聘
[原创]KCTF 第三题 绝境逢生 WriteUP
发表于: 2024-8-21 15:09 4696

[原创]KCTF 第三题 绝境逢生 WriteUP

2024-8-21 15:09
4696

得到题目后发现进行了混淆

考虑使用idapython去除混淆, 先写去混淆的基础代码:

之后对代码patten实现对应的CodeMatcher.

还有一个去除rdtsc指令的CodeMatcher, 因为代码中总是计算rdtsc的结果差值, 所以尝试直接nop发现就可以

进行完上面几种基础的去混淆操作后检查去混淆结果, 还发现如下patten:

之后还有些许混淆代码残留.
分析发现代码正常逻辑总是会引用esi, 所以在出现混淆代码时向下查找esi就可以定位到下一段代码逻辑
并在去除rdstc后可以使用WinDbg的Time Travel Debugging用提供的序列号得到正确的结果, 于是开始分析程序逻辑.
sub_426260为输出函数, sub_4265C0为输入函数;
程序在A62622处call sub_4265C0完成第二次输入; 以此为起点,
在之后遇到代码引用[esi+...]内存时检查该内存如果为输入内容可以判断为在处理输入;
遇到向[esi+...]中写入值后, 使用ba r4 [esi+...]; g可运行至读取该内存的部分;
遇到从未知的[esi+...]中读取值时, 使用ba w4 [esi+...]; g-可反向运行至写入该内存的部分;
记录windbgX显示的 Time Travel Position ... 当作书签可以使用!ttdext.tt ... 跳转.
分析时发现存在混淆代码一串push pop操作后在引用esi前pop一个常数值, 该常数值可以通过断点跳转得到.
在使用Time Travel Debugging时可以忽略分析使用name进行计算的过程, 因为name已知固定则计算结果不会变, 只要分析使用序列号的计算过程并在内存中查看与其比较的结果即可.
之后是一段难以准确回想起的分析过程, 只总结一下分析的结果:
第一段

转成代码并写出其逆向代码为:

解密后的结果会与'Qi0Qi04IuUm0Qm40IqQi0Qi4\x00\x00\x00\x00\x00\x00\x00\x00'比较.
启动另一个windbgX输入名称为KCTF跳转至相同位置可发现在和'Qm1Rm00JuQi1Qm00JuQm0Qi4\x00\x00\x00\x00\x00\x00\x00\x00'进行比较.
使用其进行反向计算:

得到第一段320573B1122A8582B1970D3CDD69B52A981FC982E92FE1FD030902797D248AD6
之后序列号中的21E7FFFF会与内存中固定值比较, 名称为KCTF时会与D806FEFF进行比较,此时序列号为
320573B1122A8582B1970D3CDD69B52A981FC982E92FE1FD030902797D248AD6D806FEFF

在之后分析代码时发现与第一段相同, 直接找内存比较的内容, 发现在b3e1ea处可以找到,
bp b3e1ea;g后读取[esi+8E2E8h]中指针所指内容可以看到 'Ri0Rm10JqUj1Rm11JuRi0Qi0\x00\x00\x00\x00\x00\x00\x00\x00'
得到第二段8FA7B1A2308DDA080F61FFAD060E925E0FAB2B3C85B81BC17D7E36DD2BF46679
之后的ECFBFFFF同上找到相同位置的比较内容为FAFBFFFF.

再之后的分析中遇到了对.text函数的调用, 分析发现为std::bitset相关函数;
具体过程分析如下:

对照写出反向代码:

端序转换得到最后一段F246AA251DD7A39E
则KCTF的序列号为320573B1122A8582B1970D3CDD69B52A981FC982E92FE1FD030902797D248AD6 D806FEFF 8FA7B1A2308DDA080F61FFAD060E925E0FAB2B3C85B81BC17D7E36DD2BF46679 FAFBFFFF F246AA251DD7A39E删去空格

obf:00A41000                 push    ds:dword_433088
obf:00A41006                 mov     [esp], eax
obf:00A41009                 mov     eax, ds:dword_433038
obf:00A4100E                 push    ds:dword_433090
obf:00A41014                 mov     [esp], eax
obf:00A41017                 mov     eax, [esp+4]
obf:00A4101B                 pop     dword ptr [esp]
obf:00A4101E                 mov     ds:dword_433088, 4720h
obf:00A41028                 mov     [esp], eax
obf:00A4102B                 mov     eax, ds:dword_433018
obf:00A41030                 push    ds:dword_433090
obf:00A41036                 mov     [esp], eax
obf:00A41039                 mov     eax, ds:dword_433040
obf:00A4103E                 push    ds:dword_433098
obf:00A41044                 mov     [esp], eax
obf:00A41047                 mov     eax, [esp+4]
obf:00A4104B                 pop     dword ptr [esp]
obf:00A4104E                 mov     ds:dword_433090, 39703h
obf:00A41058                 mov     [esp], eax
obf:00A4105B                 push    dword ptr [esp+4]
obf:00A4105F                 pop     eax
obf:00A41060                 pop     dword ptr [esp]
obf:00A41063                 pushf
obf:00A41064                 push    ds:dword_433098
obf:00A4106A                 pop     ds:dword_433038
obf:00A41070                 popf
obf:00A41071                 mov     [esp], eax
obf:00A41074                 mov     eax, ds:dword_433008
obf:00A41079                 push    ds:dword_4330A0
obf:00A41000                 push    ds:dword_433088
obf:00A41006                 mov     [esp], eax
obf:00A41009                 mov     eax, ds:dword_433038
obf:00A4100E                 push    ds:dword_433090
obf:00A41014                 mov     [esp], eax
obf:00A41017                 mov     eax, [esp+4]
obf:00A4101B                 pop     dword ptr [esp]
obf:00A4101E                 mov     ds:dword_433088, 4720h
obf:00A41028                 mov     [esp], eax
obf:00A4102B                 mov     eax, ds:dword_433018
obf:00A41030                 push    ds:dword_433090
obf:00A41036                 mov     [esp], eax
obf:00A41039                 mov     eax, ds:dword_433040
obf:00A4103E                 push    ds:dword_433098
obf:00A41044                 mov     [esp], eax
obf:00A41047                 mov     eax, [esp+4]
obf:00A4104B                 pop     dword ptr [esp]
obf:00A4104E                 mov     ds:dword_433090, 39703h
obf:00A41058                 mov     [esp], eax
obf:00A4105B                 push    dword ptr [esp+4]
obf:00A4105F                 pop     eax
obf:00A41060                 pop     dword ptr [esp]
obf:00A41063                 pushf
obf:00A41064                 push    ds:dword_433098
obf:00A4106A                 pop     ds:dword_433038
obf:00A41070                 popf
obf:00A41071                 mov     [esp], eax
obf:00A41074                 mov     eax, ds:dword_433008
obf:00A41079                 push    ds:dword_4330A0
from typing import List
import idc, idaapi
import keystone
ks = keystone.Ks(keystone.KS_ARCH_X86, keystone.KS_MODE_LITTLE_ENDIAN | keystone.KS_MODE_32)
 
obf_start = 0xA41000
obf_end = 0xBE3541
 
class CodeMatcher():
    def __init__(self) -> None:
        self.start_ea = 0
        self.inst_count = 0
        self.size = 0
        self.matched = 0
     
    def feed(self, insn: idaapi.insn_t):
        raise NotImplementedError('')
     
    def patch(self):
        raise NotImplementedError('')
 
def get_dism(ea):
    r = idc.generate_disasm_line(ea, idc.GENDSM_FORCE_CODE)
    while r.find('seg _00cfg') > 0:
        idaapi.auto_make_code(ea)
        idc.auto_wait()
        r = idc.generate_disasm_line(ea, 0)
        # print(hex(ea), r)
    p = r.find(';')
    if p >= 0:
        r = r[:p]
    return r
deobfers: List[CodeMatcher] = []
 
def do_deobf():
    cur_ea = obf_start
    cur_insn = idaapi.insn_t()
    last_ea = 0
    while cur_ea < obf_end:
        idaapi.auto_make_code(cur_ea)
        insn_size = idaapi.decode_insn(cur_insn, cur_ea)
        if insn_size == 0:
            raise Exception('error decoding inst at', hex(cur_ea))
        cur_ea += insn_size
        for deobfer in deobfers:
            if deobfer.feed(cur_insn):
                cur_ea = deobfer.patch()
                continue
from typing import List
import idc, idaapi
import keystone
ks = keystone.Ks(keystone.KS_ARCH_X86, keystone.KS_MODE_LITTLE_ENDIAN | keystone.KS_MODE_32)
 
obf_start = 0xA41000
obf_end = 0xBE3541
 
class CodeMatcher():
    def __init__(self) -> None:
        self.start_ea = 0
        self.inst_count = 0
        self.size = 0
        self.matched = 0
     
    def feed(self, insn: idaapi.insn_t):
        raise NotImplementedError('')
     
    def patch(self):
        raise NotImplementedError('')
 
def get_dism(ea):
    r = idc.generate_disasm_line(ea, idc.GENDSM_FORCE_CODE)
    while r.find('seg _00cfg') > 0:
        idaapi.auto_make_code(ea)
        idc.auto_wait()
        r = idc.generate_disasm_line(ea, 0)
        # print(hex(ea), r)
    p = r.find(';')
    if p >= 0:
        r = r[:p]
    return r
deobfers: List[CodeMatcher] = []
 
def do_deobf():
    cur_ea = obf_start
    cur_insn = idaapi.insn_t()
    last_ea = 0
    while cur_ea < obf_end:
        idaapi.auto_make_code(cur_ea)
        insn_size = idaapi.decode_insn(cur_insn, cur_ea)
        if insn_size == 0:
            raise Exception('error decoding inst at', hex(cur_ea))
        cur_ea += insn_size
        for deobfer in deobfers:
            if deobfer.feed(cur_insn):
                cur_ea = deobfer.patch()
                continue
# push $1
# mov [esp] $2
#    |
#    v
# push $2
class Push(CodeMatcher):
    def __init__(self) -> None:
        super().__init__()
        self.inst_count = 2
        self.size = 0
 
    def feed(self, insn: idaapi.insn_t):
        if self.matched != 0 and insn.itype == idaapi.NN_nop:
            self.size += insn.size
            return False
        if self.matched == 0:
            # push ??
            if insn.itype == idaapi.NN_push:
                self.start_ea = insn.ea
                self.size += insn.size
                self.matched += 1
                return False
        elif self.matched == 1:
            # mov [esp] ??
            if insn.itype == idaapi.NN_mov and insn.Op1.type == idaapi.o_phrase and idaapi.get_reg_name(insn.Op1.phrase, 4) == 'esp' and insn.Op1.addr == 0:
                self.size += insn.size
                self.matched += 1
                self.opstr = get_dism(insn.ea)
                self.opstr = self.opstr[self.opstr.find(", ")+2:]
                return True
        self.matched = 0
        self.size = 0
        return False
 
    def patch(self):
        self.matched = 0
        try:
            insn_bytes, size = ks.asm('push ' + self.opstr)
            size = len(insn_bytes)
            insn_bytes += b'\x90'*(self.size - size)
            self.size = 0
            idaapi.patch_bytes(self.start_ea, bytes(insn_bytes))
            return self.start_ea
        except Exception as e:
            print(hex(self.start_ea), 'push ' + self.opstr)
            raise e
# push $1
# mov [esp] $2
#    |
#    v
# push $2
class Push(CodeMatcher):
    def __init__(self) -> None:
        super().__init__()
        self.inst_count = 2
        self.size = 0
 
    def feed(self, insn: idaapi.insn_t):
        if self.matched != 0 and insn.itype == idaapi.NN_nop:
            self.size += insn.size
            return False
        if self.matched == 0:
            # push ??
            if insn.itype == idaapi.NN_push:
                self.start_ea = insn.ea
                self.size += insn.size
                self.matched += 1
                return False
        elif self.matched == 1:
            # mov [esp] ??
            if insn.itype == idaapi.NN_mov and insn.Op1.type == idaapi.o_phrase and idaapi.get_reg_name(insn.Op1.phrase, 4) == 'esp' and insn.Op1.addr == 0:
                self.size += insn.size
                self.matched += 1
                self.opstr = get_dism(insn.ea)
                self.opstr = self.opstr[self.opstr.find(", ")+2:]
                return True
        self.matched = 0
        self.size = 0
        return False
 
    def patch(self):
        self.matched = 0
        try:
            insn_bytes, size = ks.asm('push ' + self.opstr)
            size = len(insn_bytes)
            insn_bytes += b'\x90'*(self.size - size)
            self.size = 0
            idaapi.patch_bytes(self.start_ea, bytes(insn_bytes))
            return self.start_ea
        except Exception as e:
            print(hex(self.start_ea), 'push ' + self.opstr)
            raise e
# push $1
# pop $2 ; (reg)
#    |
#    v
# mov $2, $1
class MovByStack(CodeMatcher):
    def __init__(self) -> None:
        super().__init__()
        self.inst_count = 2
        self.size = 0
 
    def feed(self, insn: idaapi.insn_t):
        if self.matched != 0 and insn.itype == idaapi.NN_nop:
            self.size += insn.size
            return False
        if self.matched == 0:
            # push ??
            if insn.itype == idaapi.NN_push:
                self.start_ea = insn.ea
                self.size += insn.size
                self.op1str = get_dism(insn.ea)
                self.op1str = self.op1str[self.op1str.find(" ")+1:].strip()
                self.matched += 1
                return False
        elif self.matched == 1:
            # pop reg
            if insn.itype == idaapi.NN_pop and insn.Op1.type == idaapi.o_reg:
                self.size += insn.size
                self.matched += 1
                self.op2str = idaapi.get_reg_name(insn.Op1.reg, 4)
                return True
        self.matched = 0
        self.size = 0
        return False
 
    def patch(self):
        self.matched = 0
        try:
            if self.op2str == self.op1str:
                insn_bytes = b'\x90'*self.size
            else:
                insn_bytes, size = ks.asm('mov ' + self.op2str + ', ' + self.op1str)
                size = len(insn_bytes)
                insn_bytes += b'\x90'*(self.size - size)
            self.size = 0
            idaapi.patch_bytes(self.start_ea, bytes(insn_bytes))
            return self.start_ea
        except Exception as e:
            print(hex(self.start_ea), 'mov ' + self.op2str + ', ' + self.op1str)
            raise e
# push $1
# pop $2 ; (reg)
#    |
#    v
# mov $2, $1
class MovByStack(CodeMatcher):
    def __init__(self) -> None:
        super().__init__()
        self.inst_count = 2
        self.size = 0
 
    def feed(self, insn: idaapi.insn_t):
        if self.matched != 0 and insn.itype == idaapi.NN_nop:
            self.size += insn.size
            return False
        if self.matched == 0:
            # push ??
            if insn.itype == idaapi.NN_push:
                self.start_ea = insn.ea
                self.size += insn.size
                self.op1str = get_dism(insn.ea)
                self.op1str = self.op1str[self.op1str.find(" ")+1:].strip()
                self.matched += 1
                return False
        elif self.matched == 1:
            # pop reg
            if insn.itype == idaapi.NN_pop and insn.Op1.type == idaapi.o_reg:
                self.size += insn.size
                self.matched += 1
                self.op2str = idaapi.get_reg_name(insn.Op1.reg, 4)
                return True
        self.matched = 0
        self.size = 0
        return False
 
    def patch(self):
        self.matched = 0
        try:
            if self.op2str == self.op1str:
                insn_bytes = b'\x90'*self.size
            else:
                insn_bytes, size = ks.asm('mov ' + self.op2str + ', ' + self.op1str)
                size = len(insn_bytes)
                insn_bytes += b'\x90'*(self.size - size)
            self.size = 0
            idaapi.patch_bytes(self.start_ea, bytes(insn_bytes))
            return self.start_ea
        except Exception as e:
            print(hex(self.start_ea), 'mov ' + self.op2str + ', ' + self.op1str)
            raise e
# push $1
# push $2
# xor byte ptr [esp], imm
# retn
#    |
#    v
# call ($2 xor imm) (ret $1)
 
# push $1
# jmp $2
#    |
#    v
# call $2 (ret $1)
class Call(CodeMatcher):
    def __init__(self) -> None:
        super().__init__()
        self.size = 0
 
    def feed(self, insn: idaapi.insn_t):
        if self.matched != 0 and insn.itype == idaapi.NN_nop:
            self.size += insn.size
            return False
        if self.matched == 0:
            # push ret addr
            if insn.itype == idaapi.NN_push and insn.Op1.type == idaapi.o_imm:
                self.start_ea = insn.ea
                self.end_ea = insn.Op1.value
                self.size += insn.size
                self.matched += 1
                return False
        elif self.matched == 1:
            # push call addr
            if insn.itype == idaapi.NN_push and insn.Op1.type == idaapi.o_mem:
                self.size += insn.size
                self.matched += 1
                self.target = idaapi.get_dword(insn.Op1.addr)
                return False
            # jmp target
            elif insn.itype == idaapi.NN_jmpni and self.start_ea + self.size + insn.size == self.end_ea:
                self.size += insn.size
                self.matched += 1
                if insn.Op1.type == idaapi.o_mem:
                    self.target = 'dword ptr ['+hex(insn.Op1.addr) + ']'
                elif insn.Op1.type == idaapi.o_reg:
                    self.target = idaapi.get_reg_name(insn.Op1.reg, 4)
                elif insn.Op1.type == idaapi.o_displ:
                    self.target = 'dword ptr ['+idaapi.get_reg_name(insn.Op1.phrase, 4)+'+'+hex(insn.Op1.addr) + ']'
                return True
        elif self.matched == 2:
            # xor byte ptr [esp], imm
            if insn.itype == idaapi.NN_xor and insn.Op1.type == idaapi.o_phrase and idaapi.get_reg_name(insn.Op1.phrase, 4) == 'esp' and insn.Op1.addr == 0:
                self.size += insn.size
                self.matched += 1
                self.target = hex(self.target ^ insn.Op2.value)
                return False
        elif self.matched == 3:
            # retn
            if insn.itype == idaapi.NN_retn:
                self.size += insn.size
                self.matched += 1
                return True
        self.matched = 0
        self.size = 0
        return False
 
    def patch(self):
        self.matched = 0
        try:
            # assert(self.start_ea + self.size == self.end_ea)
            insn_bytes, size = ks.asm('call ' + self.target, self.end_ea-5)
            size = len(insn_bytes)
            # print(hex(self.start_ea), 'call ' + self.target, self.end_ea-5)
            insn_bytes = b'\x90'*(self.size - size) + bytes(insn_bytes)
            self.size = 0
            idaapi.patch_bytes(self.start_ea, insn_bytes)
            return self.start_ea
        except Exception as e:
            print(hex(self.start_ea), 'call ' + self.target)
            raise e
# push $1
# push $2
# xor byte ptr [esp], imm
# retn
#    |
#    v
# call ($2 xor imm) (ret $1)
 
# push $1
# jmp $2
#    |
#    v
# call $2 (ret $1)
class Call(CodeMatcher):
    def __init__(self) -> None:
        super().__init__()
        self.size = 0
 
    def feed(self, insn: idaapi.insn_t):
        if self.matched != 0 and insn.itype == idaapi.NN_nop:
            self.size += insn.size
            return False
        if self.matched == 0:
            # push ret addr
            if insn.itype == idaapi.NN_push and insn.Op1.type == idaapi.o_imm:
                self.start_ea = insn.ea
                self.end_ea = insn.Op1.value
                self.size += insn.size
                self.matched += 1
                return False
        elif self.matched == 1:
            # push call addr
            if insn.itype == idaapi.NN_push and insn.Op1.type == idaapi.o_mem:
                self.size += insn.size
                self.matched += 1
                self.target = idaapi.get_dword(insn.Op1.addr)
                return False
            # jmp target
            elif insn.itype == idaapi.NN_jmpni and self.start_ea + self.size + insn.size == self.end_ea:
                self.size += insn.size
                self.matched += 1
                if insn.Op1.type == idaapi.o_mem:
                    self.target = 'dword ptr ['+hex(insn.Op1.addr) + ']'
                elif insn.Op1.type == idaapi.o_reg:
                    self.target = idaapi.get_reg_name(insn.Op1.reg, 4)
                elif insn.Op1.type == idaapi.o_displ:
                    self.target = 'dword ptr ['+idaapi.get_reg_name(insn.Op1.phrase, 4)+'+'+hex(insn.Op1.addr) + ']'
                return True
        elif self.matched == 2:
            # xor byte ptr [esp], imm
            if insn.itype == idaapi.NN_xor and insn.Op1.type == idaapi.o_phrase and idaapi.get_reg_name(insn.Op1.phrase, 4) == 'esp' and insn.Op1.addr == 0:
                self.size += insn.size
                self.matched += 1
                self.target = hex(self.target ^ insn.Op2.value)
                return False
        elif self.matched == 3:
            # retn
            if insn.itype == idaapi.NN_retn:
                self.size += insn.size
                self.matched += 1
                return True
        self.matched = 0
        self.size = 0
        return False
 
    def patch(self):
        self.matched = 0
        try:
            # assert(self.start_ea + self.size == self.end_ea)
            insn_bytes, size = ks.asm('call ' + self.target, self.end_ea-5)
            size = len(insn_bytes)
            # print(hex(self.start_ea), 'call ' + self.target, self.end_ea-5)
            insn_bytes = b'\x90'*(self.size - size) + bytes(insn_bytes)
            self.size = 0
            idaapi.patch_bytes(self.start_ea, insn_bytes)
            return self.start_ea
        except Exception as e:
            print(hex(self.start_ea), 'call ' + self.target)
            raise e
class Rdtsc(CodeMatcher):
    def __init__(self) -> None:
        super().__init__()
        self.size = 0
 
    def feed(self, insn: idaapi.insn_t):
        if insn.itype == idaapi.NN_rdtsc:
            self.ea = insn.ea
            self.size += insn.size
            return True
        self.size = 0
        return False
 
    def patch(self):
        self.matched = 0
        insn_bytes = b'\x90'*self.size
        self.size = 0
        idaapi.patch_bytes(self.ea, insn_bytes)
        return self.ea + len(insn_bytes)
class Rdtsc(CodeMatcher):
    def __init__(self) -> None:
        super().__init__()
        self.size = 0
 
    def feed(self, insn: idaapi.insn_t):
        if insn.itype == idaapi.NN_rdtsc:
            self.ea = insn.ea
            self.size += insn.size
            return True
        self.size = 0
        return False
 
    def patch(self):
        self.matched = 0
        insn_bytes = b'\x90'*self.size
        self.size = 0
        idaapi.patch_bytes(self.ea, insn_bytes)
        return self.ea + len(insn_bytes)
# push eax
# ... didnt read eax or [esp]
# mov eax, $1
# push eax
# ... didnt read eax or [esp]
# mov eax, [esp+4]
# pop dword ptr [esp]
# ... didnt read [esp]
# mov [esp], eax
#    |
#    v
# push eax
# ...
accesses = idaapi.reg_accesses_t()
def insn_reg_access(insn, regid):
    idaapi.ph_get_reg_accesses(accesses, insn, 0)
    for i in range(accesses.size()):
        access = accesses.at(i)
        if access.regnum == regid:
            return access.access_type
    return None
class StackDeobf(CodeMatcher):
    def __init__(self) -> None:
        super().__init__()
        self.reset()
        # self.parent = None
     
    def reset(self):
        self.insns = []
        self.matched = 0
        self.stackn = 0
        self.interrupt_ea = 0
        self.unrefed = False
 
    def match(self, insn, idx):
        if idx == 1:
            # mov eax, $1
            if insn.itype == idaapi.NN_mov and insn.Op1.type == idaapi.o_reg and insn.Op2.type == idaapi.o_mem and insn.Op1.reg == 0:
                return True
        elif idx == 2 or idx == 0:
            # push eax
            if insn.itype == idaapi.NN_push and insn.Op1.type == idaapi.o_reg and insn.Op1.reg == 0:
                return True
        elif idx == 3:
            # mov eax, [esp+4]
            if insn.itype == idaapi.NN_mov and insn.Op1.type == idaapi.o_reg and insn.Op1.reg == 0 and insn.Op2.type == idaapi.o_displ and idaapi.get_reg_name(insn.Op2.phrase, 4) == 'esp' and insn.Op2.addr == 4:
                return True
        elif idx == 4:
            # pop dword ptr [esp]
            if insn.itype == idaapi.NN_pop and insn.Op1.type == idaapi.o_phrase and idaapi.get_reg_name(insn.Op1.phrase, 4) == 'esp' and insn.Op1.addr == 0:
                return True
        elif idx == 5:
            # mov     [esp], eax
            if insn.itype == idaapi.NN_mov and insn.Op2.type == idaapi.o_reg and insn.Op2.reg == 0 and insn.Op1.type == idaapi.o_phrase and idaapi.get_reg_name(insn.Op1.phrase, 4) == 'esp' and insn.Op1.addr == 0:
                return True
        return False
    def feed(self, insn: idaapi.insn_t) -> bool:
        if insn.itype == idaapi.NN_nop:
            return False
         
        if self.match(insn, self.matched):
            if self.matched != 0:
                self.insns.append([insn.ea, insn.size])
            else:
                self.interrupt_ea = insn.ea
            self.stackn = 0
            self.unrefed = False
            self.matched += 1
            if self.matched == 3 or self.matched == 5:
                self.interrupt_ea = insn.ea + insn.size
            # print(self.matched, hex(insn.ea))
            return self.matched == 6
        else:
            if self.matched == 1 and self.match(insn, 0):
                # self.reset()
                # self.insns.append([insn.ea, insn.size])
                # self.matched = 1
                return False
            if self.matched == 3 or self.matched == 1 or self.matched == 5:
                if self.matched == 3 or self.matched == 1:
                    # access eax
                    access = insn_reg_access(insn, 0)
                    if access != None and access & idaapi.READ_ACCESS:
                        if self.matched == 1:
                            self.reset()
                            return False
                        return True
                if insn.itype == idaapi.NN_pop:
                    if self.stackn == 0:
                        if self.matched == 1:
                            self.reset()
                            return False
                        return True
                    self.stackn -= 1
                    return False
                elif insn.itype == idaapi.NN_push:
                    self.stackn += 1
                    return False
                # access esp
                access = insn_reg_access(insn, 4)
                if access != None and access & idaapi.READ_ACCESS:
                    # TODO: detect if read from [esp+stackn*4]
                    if self.matched == 1:
                        self.reset()
                        return False
                    return True
                return False
            if self.matched > 3:
                return True
            # if self.parent != None:
            #     self.parent = None
            self.reset()
            return False
 
    def patch(self):
        if self.matched != 6:
            r = self.interrupt_ea
            self.reset()
            return r
        try:
            for i in self.insns:
                ea = i[0]
                size = i[1]
                idaapi.patch_bytes(ea, b'\x90'*size)
            r = self.interrupt_ea
            self.reset()
            return r
        except Exception as e:
            raise e
# push eax
# ... didnt read eax or [esp]
# mov eax, $1
# push eax
# ... didnt read eax or [esp]
# mov eax, [esp+4]
# pop dword ptr [esp]
# ... didnt read [esp]
# mov [esp], eax
#    |
#    v
# push eax
# ...
accesses = idaapi.reg_accesses_t()
def insn_reg_access(insn, regid):
    idaapi.ph_get_reg_accesses(accesses, insn, 0)
    for i in range(accesses.size()):
        access = accesses.at(i)
        if access.regnum == regid:
            return access.access_type
    return None
class StackDeobf(CodeMatcher):
    def __init__(self) -> None:
        super().__init__()
        self.reset()
        # self.parent = None
     
    def reset(self):
        self.insns = []
        self.matched = 0
        self.stackn = 0
        self.interrupt_ea = 0
        self.unrefed = False
 
    def match(self, insn, idx):
        if idx == 1:
            # mov eax, $1
            if insn.itype == idaapi.NN_mov and insn.Op1.type == idaapi.o_reg and insn.Op2.type == idaapi.o_mem and insn.Op1.reg == 0:
                return True
        elif idx == 2 or idx == 0:
            # push eax
            if insn.itype == idaapi.NN_push and insn.Op1.type == idaapi.o_reg and insn.Op1.reg == 0:
                return True
        elif idx == 3:
            # mov eax, [esp+4]
            if insn.itype == idaapi.NN_mov and insn.Op1.type == idaapi.o_reg and insn.Op1.reg == 0 and insn.Op2.type == idaapi.o_displ and idaapi.get_reg_name(insn.Op2.phrase, 4) == 'esp' and insn.Op2.addr == 4:
                return True
        elif idx == 4:
            # pop dword ptr [esp]
            if insn.itype == idaapi.NN_pop and insn.Op1.type == idaapi.o_phrase and idaapi.get_reg_name(insn.Op1.phrase, 4) == 'esp' and insn.Op1.addr == 0:
                return True
        elif idx == 5:
            # mov     [esp], eax
            if insn.itype == idaapi.NN_mov and insn.Op2.type == idaapi.o_reg and insn.Op2.reg == 0 and insn.Op1.type == idaapi.o_phrase and idaapi.get_reg_name(insn.Op1.phrase, 4) == 'esp' and insn.Op1.addr == 0:
                return True
        return False
    def feed(self, insn: idaapi.insn_t) -> bool:
        if insn.itype == idaapi.NN_nop:
            return False
         
        if self.match(insn, self.matched):
            if self.matched != 0:
                self.insns.append([insn.ea, insn.size])
            else:
                self.interrupt_ea = insn.ea
            self.stackn = 0
            self.unrefed = False
            self.matched += 1
            if self.matched == 3 or self.matched == 5:
                self.interrupt_ea = insn.ea + insn.size
            # print(self.matched, hex(insn.ea))
            return self.matched == 6
        else:
            if self.matched == 1 and self.match(insn, 0):
                # self.reset()
                # self.insns.append([insn.ea, insn.size])
                # self.matched = 1
                return False
            if self.matched == 3 or self.matched == 1 or self.matched == 5:
                if self.matched == 3 or self.matched == 1:
                    # access eax
                    access = insn_reg_access(insn, 0)
                    if access != None and access & idaapi.READ_ACCESS:
                        if self.matched == 1:
                            self.reset()
                            return False
                        return True
                if insn.itype == idaapi.NN_pop:
                    if self.stackn == 0:
                        if self.matched == 1:
                            self.reset()
                            return False
                        return True
                    self.stackn -= 1
                    return False
                elif insn.itype == idaapi.NN_push:
                    self.stackn += 1
                    return False
                # access esp
                access = insn_reg_access(insn, 4)
                if access != None and access & idaapi.READ_ACCESS:
                    # TODO: detect if read from [esp+stackn*4]
                    if self.matched == 1:
                        self.reset()
                        return False
                    return True
                return False
            if self.matched > 3:
                return True
            # if self.parent != None:
            #     self.parent = None
            self.reset()
            return False
 
    def patch(self):
        if self.matched != 6:

[注意]APP应用上架合规检测服务,协助应用顺利上架!

最后于 2024-8-21 18:02 被tacesrever编辑 ,原因:
收藏
免费 3
支持
分享
最新回复 (2)
雪    币: 264
活跃值: (704)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
2

 给师傅你提点建议,程序的混淆引入了内存的概念,可以看到是有很多全局变量参与函数执行流程的。若从这些全局变量出发来反混淆会比 “人工提取代码特征还原” 方便很多。

 仅仅是提取特征码并 patch 人工还原代码不是 IDAPython 该干的活(X64Dbg 提供的 Script 接口就已经能方便地满足这样的需求),既然用到了 IDAPython 这样的工具,就应该发挥它的长处。

 可以看到,这个全局变量有三次引用,但是可以很明显地注意到,它的 r 操作只与 push 指令相关。

 转到这个 r 操作地址,其汇编格式如下,这样的汇编代码,可以直接优化成 push eax,不是吗?而 w 操作,仅仅是给它赋值了一个 随机常量

push    ds:dword_4AA100
mov     [esp], eax

 于是,我们可以断定,这个全局变量是无效变量,而 IDAPython 正好提供了操作 AST树 的接口,编写代码将与这个全局变量相关的指令删除即可。

 当然,也可以注意到这样的全局变量,它的 r 操作与寄存器相联系了(即与算法有具体的联系),给这样的全局变量保存即可。

 去看看 HexRays 的文档,应该能学到很多关于工具的使用技巧。虽然我是本题作者,不过我还没提交自己的去混淆代码,hiahia。(本届赛期结束前一定交出来)

最后于 2024-8-28 02:44 被zZhouQing编辑 ,原因:
2024-8-28 02:34
2
雪    币: 264
活跃值: (704)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
3
2024年KCTF水泊梁山-WriteUp-反混淆
https://bbs.kanxue.com/thread-283190.htm
2024-8-30 23:50
0
游客
登录 | 注册 方可回帖
返回
// // 统计代码