[UniCTF 2026] ezobf-基于rdtsc指令反调试+控制流平坦化+变异aes

Posted by Qmeimei10086 on January 29, 2026

前言

这题写了快10个小时了,也是挺难的,或许是我太菜了

定位主函数

通过定位字符串找到主函数,长这样,有点哈人
CFG1

去花

在ida里只看到

.text:0000000140008778                 lea     rax, Buffer     ; "wrong"
.text:000000014000877F                 mov     rcx, rax        ; Buffer
.text:0000000140008782                 call    puts
.text:0000000140008787                 mov     [rbp+8F0h+var_944], 0
.text:000000014000878E                 mov     rax, [rbp+8F0h+var_950]
.text:0000000140008792                 xor     al, 0FFh
.text:0000000140008794                 mov     [rbp+8F0h+var_60], rax
.text:000000014000879B                 jmp     loc_140008983   ; jumptable 00000001400019BC case 255

字符串列表理由right,但是却没看到?
往下滑一点发现
Flower
由于上面有关retn,而且没有交叉应用关系,ida把他识别为数据了
按c重建可以看到代码块,但是没有交叉引用挺诡异的,估计是经过一个跳转表过来的
全部重建后(差不多130个,耐心点)
CFG2
明显发现上面那一大串就是真实块,这是一道明显的控制流平坦化问题
可以先看我之前写的:https://qmeimei10086.github.io/2026/01/19/angr%E7%AC%A6%E5%8F%B7%E6%89%A7%E8%A1%8C%E5%AF%B9%E6%8A%97ollvm/

反调试

在序言下个断点,运行一下发现直接跑飞了?说明是有反调试的
找了半天没找到beingdebug之类的函数,最后发现了一段大量存在的代码

rdtsc
shl     rdx, 20h
or      rax, rdx
nop
mov     [rbp+8F0h+var_68], rax
mov     rax, [rbp+8F0h+var_950]
xor     rax, 0Dh
mov     [rbp+8F0h+var_60], rax

上网搜一下发现rdtsc是获取当前时间戳,那就很明显了,大概率是获取当前时间戳然后与开始的相减,通过时间判断是否被调试
我发现3处是校验时间的
第一处

.text:0000000140008983 loc_140008983:          ; jumptable 00000001400019BC case 255
.text:0000000140008983 mov     rax, [rbp+8F0h+var_950]
.text:0000000140008987 xor     rax, [rbp+8F0h+var_60]
.text:000000014000898E cmp     rax, 0FFh
.text:0000000140008994 setnz   al
.text:0000000140008997 test    al, al
.text:0000000140008999 jnz     loc_1400017AE

这里发现如果不跳转到loc_1400017AE就会跑到retn的地方,所以这里patch为jmp loc_1400017AE
第二处

.text:00000001400017AE
.text:00000001400017AE loc_1400017AE:
.text:00000001400017AE rdtsc
.text:00000001400017B0 shl     rdx, 20h
.text:00000001400017B4 or      rax, rdx
.text:00000001400017B7 nop
.text:00000001400017B8 sub     rax, [rbp+8F0h+var_70]
.text:00000001400017BF mov     rdx, 1DCD65000h
.text:00000001400017C9 cmp     rdx, rax
.text:00000001400017CC setb    al
.text:00000001400017CF test    al, al
.text:00000001400017D1 jz      short loc_1400017DF

这里发现必须要跳转到loc_1400017DF,否则会跑飞,所以也patch为jmp
第三处

.text:00000001400017DF
.text:00000001400017DF loc_1400017DF:
.text:00000001400017DF mov     rax, [rbp+8F0h+var_950]
.text:00000001400017E3 xor     rax, [rbp+8F0h+var_60]
.text:00000001400017EA mov     edx, 0FEEDF00Dh
.text:00000001400017EF cmp     rax, rdx
.text:00000001400017F2 jz      loc_140008960

这里发现跳转到loc_140008960会跑飞,所以也patch为nop
然后生成一个新文件,就可以正常调试了

寻找真实块

请先阅读我的上一篇博客(
CFG3
发现所有的真实块都有一个共同后继0x140008983,所以我们只要找到0x140008983的所有前驱即可
主要有三种真实块
CFG4
第一种我们正常
CFG5 第二种我们要找上面那块当真实块地址 CFG6 第三种要找上面数第二个块
ida脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import idaapi
import idc


def get_basic_block(ea):
    func = idaapi.get_func(ea)
    if not func:
        return None
    f = idaapi.FlowChart(func) # 获取函数的控制流图
    for block in f:
        if block.start_ea <= ea < block.end_ea:
            return block
    return None
def get_block_size(block):
    return block.end_ea - block.start_ea


def find_all_real_blocks(fun_ea):

    real_blocks_has_branch = []
    real_blocks_has_no_branch = []
    real_blocks_has_no_branch_but_has_pred = []
    blocks = idaapi.FlowChart(idaapi.get_func(fun_ea))
    loop_head_addr= 0x000000140008983
    loop_head_block = get_basic_block(loop_head_addr)
    blocks =  list(loop_head_block.preds())
    for block in blocks:
        size = get_block_size(block)
        if size != 25 and size != 20:
            start_ea = block.start_ea
            if start_ea != 5368715678:
                preds = list(block.preds())
                if len(preds) == 0:
                    real_blocks_has_no_branch.append(start_ea)
                if len(preds) == 1:
                    pred = preds[0]
                    real_blocks_has_branch.append(pred.start_ea)
                if len(preds) == 2:
                    for pred in preds:
                        preds_1 = list(pred.preds())
                        pred_1 = preds_1[0]
                        real_blocks_has_no_branch.append(pred_1.start_ea)
                        real_blocks_has_no_branch_but_has_pred.append(pred_1.start_ea)

    real_blocks_has_branch = list(set(real_blocks_has_branch))
    real_blocks_has_no_branch = list(set(real_blocks_has_no_branch))
    real_blocks_has_no_branch_but_has_pred = list(set(real_blocks_has_no_branch_but_has_pred))
    real_blocks = real_blocks_has_branch + real_blocks_has_no_branch
    
    print("所有有跳转真实块地址真实块地址:", [hex(x) for x in real_blocks_has_branch])
    print("所有无跳转真实块地址真实块地址:", [hex(x) for x in real_blocks_has_no_branch])
    print("所有真实块地址真实块地址:", [hex(x) for x in real_blocks])
    print("所有无跳转但有前驱真实块地址真实块地址:", [hex(x) for x in real_blocks_has_no_branch_but_has_pred])
    print('-----------------------------------------------------------------------------------------')
    print("所有有跳转真实块地址真实块地址:",real_blocks_has_branch)
    print("所有无跳转真实块地址真实块地址:", real_blocks_has_no_branch)
    print("所有真实块地址真实块地址:", real_blocks)
    print("所有无跳转但有前驱真实块地址真实块地址:", real_blocks_has_no_branch_but_has_pred)
    
    
find_all_real_blocks(0x1400016D0)

jmp_table = {
    0x1400065df:0x14000661F,
    0x140006d53:0x140006D93,
    0x140006ad7:0x140006B17,
    0x14000685b:0x14000689B,
}


'''
所有有跳转真实块地址真实块地址: [5368731680, 5368721346, 5368720899, 5368733699, 5368731240, 5368743560, 5368716810, 5368730347, 5368733259, 5368718318, 5368719375, 5368729678, 5368716082, 5368743991, 5368742232, 5368744189, 5368716574]
所有无跳转真实块地址真实块地址: [5368736265, 5368739855, 5368725012, 5368715284, 5368742939, 5368724001, 5368730146, 5368737327, 5368740917, 5368734780, 5368723006, 5368721991, 5368742471, 5368738377, 5368727626, 5368716363, 5368717907, 5368728661, 5368735835, 5368726621, 5368739423, 5368715878, 5368732782, 5368720496, 5368725619, 5368724611, 5368718980, 5368736901, 5368740491, 5368723598, 5368742031, 5368734354, 5368722595, 5368737957, 5368727214, 5368721583, 5368720050, 5368741554, 5368717496, 5368735419, 5368728252, 5368732352, 5368729281, 5368739009, 5368726214, 5368714960, 5368736471, 5368725209, 5368730841, 5368740059, 5368718558, 5368743137, 5368721136, 5368733938, 5368724213, 5368719610, 5368741115, 5368737537, 5368717059, 5368723208, 5368722187, 5368731919, 5368734993, 5368738587, 5368728860, 5368727843, 5368726821, 5368718119, 5368725813, 5368736055, 5368733498, 5368744250, 5368739645, 5368720703, 5368729922, 5368719176, 5368724810, 5368737107, 5368742741, 5368731479, 5368740695, 5368723802, 5368733027, 5368734567, 5368722794, 5368727413, 5368738167, 5368743799, 5368721787, 5368717701, 5368729477, 5368732550, 5368726411, 5368728459, 5368735629, 5368720273, 5368739219, 5368731039, 5368741799, 5368725419, 5368718770, 5368736691, 5368740281, 5368724415, 5368734144, 5368743362, 5368723397, 5368741322, 5368719827, 5368737747, 5368722394, 5368717275, 5368730586, 5368735199, 5368715231, 5368732129, 5368727015, 5368738791, 5368729068, 5368728045, 5368726012]
所有真实块地址真实块地址: [5368731680, 5368721346, 5368720899, 5368733699, 5368731240, 5368743560, 5368716810, 5368730347, 5368733259, 5368718318, 5368719375, 5368729678, 5368716082, 5368743991, 5368742232, 5368744189, 5368716574, 5368736265, 5368739855, 5368725012, 5368715284, 5368742939, 5368724001, 5368730146, 5368737327, 5368740917, 5368734780, 5368723006, 5368721991, 5368742471, 5368738377, 5368727626, 5368716363, 5368717907, 5368728661, 5368735835, 5368726621, 5368739423, 5368715878, 5368732782, 5368720496, 5368725619, 5368724611, 5368718980, 5368736901, 5368740491, 5368723598, 5368742031, 5368734354, 5368722595, 5368737957, 5368727214, 5368721583, 5368720050, 5368741554, 5368717496, 5368735419, 5368728252, 5368732352, 5368729281, 5368739009, 5368726214, 5368714960, 5368736471, 5368725209, 5368730841, 5368740059, 5368718558, 5368743137, 5368721136, 5368733938, 5368724213, 5368719610, 5368741115, 5368737537, 5368717059, 5368723208, 5368722187, 5368731919, 5368734993, 5368738587, 5368728860, 5368727843, 5368726821, 5368718119, 5368725813, 5368736055, 5368733498, 5368744250, 5368739645, 5368720703, 5368729922, 5368719176, 5368724810, 5368737107, 5368742741, 5368731479, 5368740695, 5368723802, 5368733027, 5368734567, 5368722794, 5368727413, 5368738167, 5368743799, 5368721787, 5368717701, 5368729477, 5368732550, 5368726411, 5368728459, 5368735629, 5368720273, 5368739219, 5368731039, 5368741799, 5368725419, 5368718770, 5368736691, 5368740281, 5368724415, 5368734144, 5368743362, 5368723397, 5368741322, 5368719827, 5368737747, 5368722394, 5368717275, 5368730586, 5368735199, 5368715231, 5368732129, 5368727015, 5368738791, 5368729068, 5368728045, 5368726012]
所有无跳转但有前驱真实块地址真实块地址: [5368735199, 5368737107, 5368715284, 5368736471, 5368735835, 5368715231]
'''

有两个不在idacfg里不在上面的,但是脚本会输出,我们把他剔除掉(但是我忘了哪一个OwO),这些是分发器,不过也不影响后面

重建控制流

我们先做过小实验,在ida里试一下执行到个块是否是不依赖除了前驱块的快执行结果的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import idaapi
import ida_dbg
import ida_bytes
import idc
import ida_ua


i = 0
real_blocks: list[int] =[5368720899, 5368733699, 5368736265, 5368716810, 5368719375, 5368739855, 5368725012, 5368742939, 5368731680, 5368724001, 5368730146, 5368737327, 5368740917, 5368743991, 5368734780, 5368723006, 5368721991, 5368742471, 5368738377, 5368727626, 5368716363, 5368733259, 5368729678, 5368717907, 5368728661, 5368735835, 5368726621, 5368739423, 5368715878, 5368731240, 5368732782, 5368720496, 5368725619, 5368724611, 5368718980, 5368736901, 5368743560, 5368740491, 5368723598, 5368742031, 5368734354, 5368722595, 5368737957, 5368727214, 5368721583, 5368720050, 5368741554, 5368717496, 5368735419, 5368728252, 5368732352, 5368729281, 5368739009, 5368726214, 5368714960, 5368736471, 5368725209, 5368730841, 5368740059, 5368718558, 5368743137, 5368730347, 5368721136, 5368733938, 5368724213, 5368719610, 5368741115, 5368744189, 5368737537, 5368717059, 5368723208, 5368722187, 5368731919, 5368734993, 5368738587, 5368728860, 5368716574, 5368727843, 5368726821, 5368718119, 5368716082, 5368725813, 5368736055, 5368733498, 5368744250, 5368739645, 5368720703, 5368729922, 5368719176, 5368724810, 5368737107, 5368742741, 5368731479, 5368740695, 5368742232, 5368723802, 5368733027, 5368734567, 5368722794, 5368727413, 5368738167, 5368743799, 5368721787, 5368717701, 5368729477, 5368732550, 5368726411, 5368728459, 5368735629, 5368720273, 5368739219, 5368731039, 5368741799, 5368725419, 5368718770, 5368736691, 5368740281, 5368724415, 5368734144, 5368721346, 5368743362, 5368723397, 5368741322, 5368719827, 5368737747, 5368722394, 5368717275, 5368730586, 5368735199, 5368732129, 5368727015, 5368738791, 5368729068, 5368728045, 5368718318, 5368726012]

addr_list = []

def add_breakpoint():
    # Anti-debug bypass breakpoints
    # ida_dbg.add_bpt(0x1400017D1)
    # ida_dbg.add_bpt(0x1400017F2)
    # ida_dbg.add_bpt(0x140008999)
    print("Anti-debug breakpoints set at 0x1400017D1, 0x1400017F2")

    for addr in real_blocks:
        insn = ida_ua.insn_t()
        next_ea = idc.next_head(addr)
        ida_dbg.add_bpt(next_ea)
        addr_list.append(next_ea)
        print(f"Breakpoint set at {hex(next_ea)}")


import ida_ua
import ida_idp
import ida_allins

# 日志文件路径
LOG_FILE = r"d:\reverse\MCP\ida_trace_log.txt"

# 清空/初始化日志
with open(LOG_FILE, "w") as f:
    f.write("IDA Trace Log Started\n")

def log_current_block(start_ea):
    """
    反汇编当前块直到遇到控制流改变指令
    """
    with open(LOG_FILE, "a") as f:
        f.write(f"\n------------------------------------------------\n")
        f.write(f"Block Execution: {hex(start_ea)}\n")
        
        curr = start_ea
        # 防止无限循环,设置最大指令数
        for _ in range(200):
            # 获取反汇编文本
            disasm = idc.generate_disasm_line(curr, 0)
            f.write(f"{hex(curr)}: {disasm}\n")
            
            # 解码指令判断是否结束
            insn = ida_ua.insn_t()
            if ida_ua.decode_insn(insn, curr) == 0:
                break
                
            # 常见的块结束指令类型
            # 注意: call 一般不算块结束,但在某些流图中算。OLLVM平坦化里通常遇到 jmp/jcc 就结束了
            if ida_idp.is_ret_insn(insn) or \
               ida_idp.is_indirect_jump_insn(insn) or \
               (insn.itype == ida_allins.NN_jmp) or \
               (insn.itype >= ida_allins.NN_ja and insn.itype <= ida_allins.NN_jz):
                break
                
            curr = idc.next_head(curr)

class hook_all_opecode(ida_dbg.DBG_Hooks):

    def dbg_bpt(self, tid, ea):
        # if ea == 0x1400017D1:
        #     print(f"Hit 0x1400017D1, Force ZF=1")
        #     ida_dbg.set_reg_val("ZF", 1)
        #     ida_dbg.request_continue_process()
        #     return 0
        # if ea == 0x1400017F2:
        #     print(f"Hit 0x1400017F2, Force ZF=0")
        #     ida_dbg.set_reg_val("ZF", 0)
        #     ida_dbg.request_continue_process()
        #     return 0
        # if ea == 0x140008999:
        #     print(f"Hit 0x140008999, Force ZF=0")
        #     ida_dbg.set_reg_val("ZF", 0)
        #     ida_dbg.request_continue_process()
        #     return 0

        
        for addr in addr_list:
            if ea == addr:
                global i
                print(f"Hit breakpoint at {hex(ea)} - Real block #{i}")
                
                # 记录块汇编到文件
                log_current_block(ea)
                
                i+=1
        
        
        
        return 0
def install_hook():
    # 清理旧的 hook (如果存在于全局变量中)
    global my_hook
    try:
        if 'my_hook' in globals():
            my_hook.unhook()
            print("Removed old hook")
    except:
        pass

    # 安装新的 hook
    my_hook = hook_all_opecode()
    my_hook.hook()
    print("Hook installed. Please set the breakpoint manually.")


install_hook()
add_breakpoint()

在序言尾下个断点,运行,然后强制rip设置为一个块的的首地址,然后按f9,而且发现每次到哪个块是相同的
而且我们发现输出please input flag的下一个块有fget函数,这很符合逻辑
这里提一嘴,rdtsc指令好像断不下来,所以我都是设置他的下一个指令
对应第二种块,我们发现我们修改jz/jnz指令时的zf寄存器,到达的块时不同的,所以这是条件跳转相关的,待会模拟执行时候要分裂处理
对应第三种,我们发现怎么改jz/jnz,虽然会到不同的代码,但是到的下一个块都是一样的所以和第一种一样

模拟执行

我拷打ai写了两个unicorn模拟执行的脚本,思路参考angr那一篇,主要都是为了找后继
遇到会分裂的,手动分裂两种情况去执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
import unicorn
from unicorn import *
from unicorn.x86_const import *
import pefile
import sys
import struct

# Configuration
EXE_PATH = "D:\\reverse\\MCP\\ezobf1.exe"
STACK_BASE = 0x00100000
STACK_SIZE = 0x00200000

# Constants from analysis
PROLOGUE_START = 0x1400016D0
PROLOGUE_END = 0x1400017A9

# Lists from hook_rebuild_by_ida.py
real_blocks_has_branch = [5368731680, 5368721346, 5368720899, 5368733699, 5368731240, 5368743560, 5368716810, 5368730347, 5368733259, 5368718318, 5368719375, 5368729678, 5368716082, 5368743991, 5368742232, 5368744189, 5368716574]
real_blocks_has_no_branch = [5368736265, 5368739855, 5368725012, 5368742939, 5368724001, 5368730146, 5368737327, 5368740917, 5368734780, 5368723006, 5368721991, 5368742471, 5368738377, 5368727626, 5368716363, 5368717907, 5368728661, 5368735835, 5368726621, 5368739423, 5368715878, 5368732782, 5368720496, 5368725619, 5368724611, 5368718980, 5368736901, 5368740491, 5368723598, 5368742031, 5368734354, 5368722595, 5368737957, 5368727214, 5368721583, 5368720050, 5368741554, 5368717496, 5368735419, 5368728252, 5368732352, 5368729281, 5368739009, 5368726214, 5368714960, 5368736471, 5368725209, 5368730841, 5368740059, 5368718558, 5368743137, 5368721136, 5368733938, 5368724213, 5368719610, 5368741115, 5368737537, 5368717059, 5368723208, 5368722187, 5368731919, 5368734993, 5368738587, 5368728860, 5368727843, 5368726821, 5368718119, 5368725813, 5368736055, 5368733498, 5368744250, 5368739645, 5368720703, 5368729922, 5368719176, 5368724810, 5368737107, 5368742741, 5368731479, 5368740695, 5368723802, 5368733027, 5368734567, 5368722794, 5368727413, 5368738167, 5368743799, 5368721787, 5368717701, 5368729477, 5368732550, 5368726411, 5368728459, 5368735629, 5368720273, 5368739219, 5368731039, 5368741799, 5368725419, 5368718770, 5368736691, 5368740281, 5368724415, 5368734144, 5368743362, 5368723397, 5368741322, 5368719827, 5368737747, 5368722394, 5368717275, 5368730586, 5368735199, 5368732129, 5368727015, 5368738791, 5368729068, 5368728045, 5368726012]

all_real_block = real_blocks_has_branch + real_blocks_has_no_branch

# Helpers
all_real_block_set = set(all_real_block)
found_successor = {}

def load_pe(uc):
    try:
        pe = pefile.PE(EXE_PATH)
    except Exception as e:
        print(f"Error loading PE: {e}")
        sys.exit(1)

    image_base = pe.OPTIONAL_HEADER.ImageBase
    # Align size up to 4KB
    header_size = (pe.OPTIONAL_HEADER.SizeOfHeaders + 0xFFF) & ~0xFFF
    uc.mem_map(image_base, header_size)
    uc.mem_write(image_base, pe.header)
    
    max_addr = image_base + header_size
    
    for section in pe.sections:
        va = section.VirtualAddress
        vsize = section.Misc_VirtualSize
        data = section.get_data()
        
        map_addr = image_base + va
        map_start = map_addr & ~0xFFF
        map_end = (map_addr + vsize + 0xFFF) & ~0xFFF
        
        if map_end > max_addr:
             addr_to_map = max(map_start, max_addr)
             size_to_map = map_end - addr_to_map
             if size_to_map > 0:
                 uc.mem_map(addr_to_map, size_to_map)
                 max_addr = map_end
        
        uc.mem_write(image_base + va, data)

try:
    from capstone import *
    cs = Cs(CS_ARCH_X86, CS_MODE_64)
    def disassemble(code, address):
        for i in cs.disasm(code, address):
            return f"{i.mnemonic}\t{i.op_str}"
        return ""
except ImportError:
    def disassemble(code, address):
        return code.hex()

def hook_code_verbose(uc, address, size):
    try:
        code = uc.mem_read(address, size)
        asm = disassemble(code, address)
        print(f"0x{address:x}: {asm}")
    except:
        print(f"0x{address:x}: ???")

def hook_code(uc, address, size, user_data):
    # hook_code_verbose(uc, address, size)
    try:
        code = uc.mem_read(address, 2)
        if code[0] == 0xE8: # call relative
            # Calculate target address
            offset = struct.unpack("<i", uc.mem_read(address + 1, 4))[0]
            target = address + 5 + offset
            
            # Check if target matches sub_140008A67 or sub_14000A090
            # 0x140008A67: Initialization check
            # 0x14000A090: printf/vfprintf
            if target == 0x140008A67 or target == 0x14000A090:
                 # print(f"Skipping CALL to {hex(target)} at {hex(address)}")
                 uc.reg_write(UC_X86_REG_RAX, 1) # Return success/1
                 uc.reg_write(UC_X86_REG_RIP, address + 5)
            
        elif code[0] == 0xFF and code[1] == 0x15: # call qword ptr [rip+disp] (imports)
            # print(f"Skipping CALL FF 15 at {hex(address)}")
            uc.reg_write(UC_X86_REG_RAX, 1)
            uc.reg_write(UC_X86_REG_RIP, address + 6)
        # RET handling removed to allow execution to continue via stack manipulation jumps

    except Exception:
        pass

def hook_mem_invalid(uc, access, address, size, value, user_data):
    # Simply map memory to allow execution to continue
    # print(f"Invalid access at {hex(address)}")
    page_start = address & ~0xFFF
    try:
        uc.mem_map(page_start, 0x1000)
        return True
    except Exception as e:
        # print(f"Map fail: {e}")
        return False

def hook_block(uc, address, size, user_data):
        found = None
        if address in all_real_block_set:
            found = address
        elif (address - 1) in all_real_block_set:
            found = address - 1
            
        if found:
            if found != user_data['current_start']:
                print(f"Successor found: {hex(user_data['current_start'])} -> {hex(found)}")
                found_successor[user_data['current_start']] = found
                uc.emu_stop()

def main():
    uc = Uc(UC_ARCH_X86, UC_MODE_64)
    
    # 1. Load PE
    load_pe(uc)
    
    # 2. Setup Stack
    uc.mem_map(STACK_BASE, STACK_SIZE)
    uc.reg_write(UC_X86_REG_RSP, STACK_BASE + STACK_SIZE - 0x1000)

    # Hook Invalid Memory
    uc.hook_add(UC_HOOK_MEM_READ_UNMAPPED | UC_HOOK_MEM_WRITE_UNMAPPED | UC_HOOK_MEM_FETCH_UNMAPPED, hook_mem_invalid)
    
    # 3. Save Initial State at Prologue Head
    # Run from entry? No, just manually set RIP to PROLOGUE_START and save context
    # Assuming PE is loaded correctly, registers are 0/defaults. 
    # Important: RSP is set.
    uc.reg_write(UC_X86_REG_RIP, PROLOGUE_START)
    
    # We might need to map a fake input flag if the code accesses it?
    # Based on solve_unicorn.py, there is FAKE_INPUT but it seems used later.
    # The prologue usually just does register saving.
    
    initial_context = uc.context_save()
    
    print("Starting search...")
    
    for block_addr in real_blocks_has_no_branch:
        # print(f"Checking {hex(block_addr)}")
        # a. Restore context
        uc.context_restore(initial_context)
        
        # Special handling for Prologue Start as per request
        if block_addr == PROLOGUE_START:
            # Just run it. It will hit end and go somewhere.
            # We hook blocks to see where it lands.
            user_data = {'current_start': block_addr}
            h = uc.hook_add(UC_HOOK_BLOCK, hook_block, user_data)
            h_code = uc.hook_add(UC_HOOK_CODE, hook_code, user_data)
            try:
                # We expect it to leave prologue and hit a real block
                uc.emu_start(PROLOGUE_START, -1, 0, 500) 
            except UcError:
                pass
            uc.hook_del(h)
            uc.hook_del(h_code)
            
            if block_addr not in found_successor:
                print(f"[-] No successor found for block {hex(block_addr)}")
            continue
            
        # b. Execute to prologue tail
        # We run from Start to End.
        try:
            h_code = uc.hook_add(UC_HOOK_CODE, hook_code, None)
            uc.emu_start(PROLOGUE_START, PROLOGUE_END)
            uc.hook_del(h_code)
            # print(f"RBP after prologue: {hex(uc.reg_read(UC_X86_REG_RBP))}")
        except UcError as e:
            print(f"Error running prologue: {e}")
            try:
                uc.hook_del(h_code)
            except:
                pass
            continue
            
        # c. Force Jump to current block
        uc.reg_write(UC_X86_REG_RIP, block_addr)
        
        # d. Simulate
        user_data = {'current_start': block_addr}
        h = uc.hook_add(UC_HOOK_BLOCK, hook_block, user_data)
        h_code = uc.hook_add(UC_HOOK_CODE, hook_code, user_data)
        
        try:
            # Run for a limited number of instructions to find next block
            # timeout=0 (infinite), count=50000 to prevent infinite loops
            uc.emu_start(block_addr, -1, 0, 50000)
        except UcError as e:
            # print(f"Error emulating block {hex(block_addr)}: {e}")
            pass
            
        uc.hook_del(h)
        uc.hook_del(h_code)

        if block_addr not in found_successor:
            print(f"[-] No successor found for block {hex(block_addr)}")

    print("Done.")

if __name__ == "__main__":
    main()

这是寻找直接跳转的,下一个时寻找条件跳转的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
import sys
import struct
import re
from unicorn import *
from unicorn.x86_const import *
from capstone import *

# Ensure we can import from current directory
sys.path.append('d:\\reverse\\MCP')

try:
    from find_succeed import load_pe, all_real_block, real_blocks_has_no_branch, hook_mem_invalid, hook_code as hook_code_base, PROLOGUE_START, PROLOGUE_END, STACK_BASE, STACK_SIZE
except ImportError as e:
    print(f"Error importing from find_succeed.py: {e}")
    sys.exit(1)

# Derive branch blocks: All Real Blocks - No-Branch Blocks
# We must use set operations
# real_blocks_has_branch = sorted(list(set(all_real_block) - set(real_blocks_has_no_branch)))
real_blocks_has_branch =  [5368731680, 5368721346, 5368720899, 5368733699, 5368731240, 5368743560, 5368716810, 5368730347, 5368733259, 5368718318, 5368719375, 5368729678, 5368716082, 5368743991, 5368742232, 5368744189, 5368716574]
real_blocks_has_branch = sorted(real_blocks_has_branch)

all_real_block_set = set(all_real_block)

def get_mnemonic(uc, address):
    try:
        md = Cs(CS_ARCH_X86, CS_MODE_64)
        code = uc.mem_read(address, 15)
        for i in md.disasm(code, address):
            return i.mnemonic
    except:
        return ""

# Wrapper to reuse hook_code logic or just duplicat it to be safe
def hook_code_skip(uc, address, size, user_data):
    # Reuse the logic from find_succeed but avoiding dependency on its globals if any
    try:
        code = uc.mem_read(address, 2)
        if code[0] == 0xE8: # call relative
             offset = struct.unpack("<i", uc.mem_read(address + 1, 4))[0]
             target = address + 5 + offset
             if target == 0x140008A67 or target == 0x14000A090:
                 uc.reg_write(UC_X86_REG_RAX, 1) # Return success/1
                 uc.reg_write(UC_X86_REG_RIP, address + 5)
        elif code[0] == 0xFF and code[1] == 0x15: # call qword ptr [rip+disp] (imports)
            uc.reg_write(UC_X86_REG_RAX, 1)
            uc.reg_write(UC_X86_REG_RIP, address + 6)
    except:
        pass

def hook_find_jcc(uc, address, size, user_data):
    # 1. Skip calls logic first
    hook_code_skip(uc, address, size, user_data)
    
    # Check if we already skipped (RIP changed) - but UC hooks run before execution. Use memory check?
    # Usually hook code runs, then instruction executes. If hook modifies RIP, execution continues at new RIP?
    # Unicorn documentation says hook runs BEFORE instruction.
    # If we change RIP, we effectively skip ONLY IF we stop or if Unicorn handles it immediately.
    # With uc.reg_write(RIP, new), the current instruction is NOT skipped unless we stop or return?
    # Actually, changing RIP in hook causes execution to resume at new RIP *after* the hook returns? 
    # Whatever find_succeed.py did worked. It modified RIP.
    
    # We should detect if it's a call before disassembly JCC.
    # The code looks fine.

    # 2. Check JCC
    mnemonic = get_mnemonic(uc, address)
    if mnemonic.startswith('j') and mnemonic != 'jmp':
        user_data['jcc_addr'] = address
        user_data['mnemonic'] = mnemonic
        uc.emu_stop()

def hook_block_succ(uc, address, size, user_data):
    # Fuzzy match buffer
    found = None
    if address in all_real_block_set:
        found = address
    elif (address - 1) in all_real_block_set:
        found = address - 1
        
    if found:
        user_data['succ'] = found
        uc.emu_stop()

def find_jcc_by_disasm(uc, block_addr, max_ins=80, max_bytes=0x300):
    try:
        md = Cs(CS_ARCH_X86, CS_MODE_64)
        code = uc.mem_read(block_addr, max_bytes)
        count = 0
        for insn in md.disasm(code, block_addr):
            if insn.address != block_addr and insn.address in all_real_block_set:
                break
            if insn.mnemonic.startswith('j') and insn.mnemonic != 'jmp':
                return insn.address
            count += 1
            if count >= max_ins:
                break
    except:
        return None
    return None

def set_flags_for_cond(existing_flags, mnemonic, take_true):
    ZF = 1 << 6
    SF = 1 << 7
    OF = 1 << 11
    flags = existing_flags & ~(ZF | SF | OF)

    if mnemonic in ("jz", "je"):
        if take_true:
            flags |= ZF
    elif mnemonic in ("jnz", "jne"):
        if not take_true:
            flags |= ZF
    elif mnemonic in ("jg", "jnle"):
        if take_true:
            # ZF=0 and SF==OF -> choose SF=0, OF=0
            pass
        else:
            flags |= ZF
    elif mnemonic in ("jge", "jnl"):
        if take_true:
            # SF==OF -> choose SF=0, OF=0
            pass
        else:
            # SF!=OF
            flags |= SF
    elif mnemonic in ("jl", "jnge"):
        if take_true:
            # SF!=OF
            flags |= SF
        else:
            # SF==OF -> choose SF=0, OF=0
            pass
    elif mnemonic in ("jle", "jng"):
        if take_true:
            # ZF=1 satisfies condition
            flags |= ZF
        else:
            # ZF=0 and SF==OF -> choose SF=0, OF=0
            pass
    else:
        # Fallback to ZF-only split
        if take_true:
            flags |= ZF

    return flags

def main():
    print(f"Checking {len(real_blocks_has_branch)} blocks with branches...")
    
    uc = Uc(UC_ARCH_X86, UC_MODE_64)
    # Load PE
    load_pe(uc)
    # Setup Stack
    uc.mem_map(STACK_BASE, STACK_SIZE)
    uc.reg_write(UC_X86_REG_RSP, STACK_BASE + STACK_SIZE - 0x1000)
    # Handle unmapped
    uc.hook_add(UC_HOOK_MEM_READ_UNMAPPED | UC_HOOK_MEM_WRITE_UNMAPPED | UC_HOOK_MEM_FETCH_UNMAPPED, hook_mem_invalid)

    # 1. Run Prologue to get initial state
    print("Running prologue...")
    uc.reg_write(UC_X86_REG_RIP, PROLOGUE_START)
    try:
        # We use a code hook to skip calls in prologue too
        h_skip = uc.hook_add(UC_HOOK_CODE, hook_code_skip)
        uc.emu_start(PROLOGUE_START, PROLOGUE_END, 0, 50000)
        uc.hook_del(h_skip)
    except UcError as e:
        print(f"Error running prologue: {e}")
    
    # Save the 'ready' context
    ctx_ready = uc.context_save()
    
    simple_successors = {} # Unconditional
    cond_successors = {}   # Conditional
    cond_mnemonic = {}     # Conditional mnemonic

    # 0. Load trace
    try:
        with open(r"d:\reverse\MCP\trace_final.txt", "r", encoding="utf-16") as f:
            for line in f:
                m = re.search(r"Successor found: (0x[0-9a-fA-F]+) -> (0x[0-9a-fA-F]+)", line)
                if m:
                    simple_successors[int(m.group(1), 16)] = int(m.group(2), 16)
    except Exception as e:
        print(f"Warning load trace: {e}")

    print("Starting branch simulation...")
    
    for block_addr in real_blocks_has_branch:
        # 2. Restore state and run to find JCC
        uc.context_restore(ctx_ready)
        uc.reg_write(UC_X86_REG_RIP, block_addr)

        user_data = {'jcc_addr': None, 'mnemonic': ''}
        h_jcc = uc.hook_add(UC_HOOK_CODE, hook_find_jcc, user_data)

        try:
            # Run enough instructions to find the jump in the block
            uc.emu_start(block_addr, -1, 0, 5000)
        except UcError:
            pass
        uc.hook_del(h_jcc)

        jcc_addr = user_data['jcc_addr']
        jcc_mnemonic = user_data['mnemonic']
        if jcc_addr is None:
            # Fallback: try linear disassembly to locate JCC
            jcc_addr = find_jcc_by_disasm(uc, block_addr)
            if jcc_addr is not None:
                jcc_mnemonic = get_mnemonic(uc, jcc_addr)
        if jcc_addr is None:
            print(f"Warning: No JCC found in block {hex(block_addr)}")
            # Might be a RET block that ended up in the list, or implicit fallthrough?
            if block_addr == 0x140008837:
                cond_successors[block_addr] = (0x14000893A, 0x140008777)
                cond_mnemonic[block_addr] = "jnz"
            else:
                cond_successors[block_addr] = (None, None)
            continue

        # Ensure RIP is at JCC for a consistent split
        uc.reg_write(UC_X86_REG_RIP, jcc_addr)
        ctx_at_jcc = uc.context_save()
        existing_flags = uc.reg_read(UC_X86_REG_EFLAGS)
        
        # 3. Path TRUE (Left)
        uc.context_restore(ctx_at_jcc)
        uc.reg_write(UC_X86_REG_EFLAGS, set_flags_for_cond(existing_flags, jcc_mnemonic, True))
        
        ud_left = {'succ': None}
        h_succ = uc.hook_add(UC_HOOK_BLOCK, hook_block_succ, ud_left)
        h_skip = uc.hook_add(UC_HOOK_CODE, hook_code_skip)
        try:
            # Execute step (jump triggers) + run
            uc.emu_start(jcc_addr, -1, 0, 50000)
        except: pass
        uc.hook_del(h_succ)
        uc.hook_del(h_skip)
        
        # 4. Path FALSE (Right)
        uc.context_restore(ctx_at_jcc)
        uc.reg_write(UC_X86_REG_EFLAGS, set_flags_for_cond(existing_flags, jcc_mnemonic, False))
        
        ud_right = {'succ': None}
        h_succ = uc.hook_add(UC_HOOK_BLOCK, hook_block_succ, ud_right)
        h_skip = uc.hook_add(UC_HOOK_CODE, hook_code_skip)
        try:
            uc.emu_start(jcc_addr, -1, 0, 50000)
        except: pass
        uc.hook_del(h_succ)
        uc.hook_del(h_skip)
        
        left_val = ud_left['succ']
        right_val = ud_right['succ']

        if block_addr == 0x140008837 and left_val is None and right_val is None:
            left_val = 0x14000893A
            right_val = 0x140008777
        
        if left_val is None and right_val is None:
            cond_successors[block_addr] = (None, None)
            cond_mnemonic[block_addr] = jcc_mnemonic
            continue
            
        l_str = left_val if left_val else 0
        r_str = right_val if right_val else 0
        cond_successors[block_addr] = (l_str, r_str)
        cond_mnemonic[block_addr] = jcc_mnemonic

    # OUTPUT
    print("flow_patch = {")
    for k in sorted(simple_successors.keys()):
        v = simple_successors[k]
        print(f"    {hex(k)}: {hex(v)},")
    print("}")
    
    print("\nbranch_patch = {")
    for k in sorted(cond_successors.keys()):
        v = cond_successors[k]
        if v[0] is None and v[1] is None:
            print(f"    {hex(k)}: None,")
            continue
        l_str = hex(v[0]) if v[0] else "None"
        r_str = hex(v[1]) if v[1] else "None"
        print(f"    {hex(k)}: ({l_str}, {r_str}), # (Left:Cond=True, Right:Cond=False)")
    print("}")

    missing_successors = {k: None for k, v in cond_successors.items() if v[0] is None or v[1] is None}
    print("\nmissing_successors = {")
    for k in sorted(missing_successors.keys()):
        print(f"    {hex(k)}: None,")
    print("}")

    mnem_jz = ("jz", "je")
    mnem_jnz = ("jnz", "jne")
    mnem_jle = ("jle", "jng", "jl", "jnge")
    mnem_jg = ("jg", "jnle", "jge", "jnl")
    
    patch_jz = {k: cond_successors[k] for k, m in cond_mnemonic.items() if m in mnem_jz}
    patch_jnz = {k: cond_successors[k] for k, m in cond_mnemonic.items() if m in mnem_jnz}
    patch_jle = {k: cond_successors[k] for k, m in cond_mnemonic.items() if m in mnem_jle}
    patch_jg = {k: cond_successors[k] for k, m in cond_mnemonic.items() if m in mnem_jg}

    def print_patch_dict(name, d):
        print(f"\n{name} = " + "{")
        for k in sorted(d.keys()):
            v = d[k]
            l_str = hex(v[0]) if v[0] else "None"
            r_str = hex(v[1]) if v[1] else "None"
            print(f"    {hex(k)}: ({l_str}, {r_str}),")
        print("}")

    print_patch_dict("patch_jz", patch_jz)
    print_patch_dict("patch_jnz", patch_jnz)
    print_patch_dict("patch_jle", patch_jle)
    print_patch_dict("patch_jg", patch_jg)

if __name__ == "__main__":
    main()

运行下面那个就行,结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
Checking 17 blocks with branches...
Running prologue...
Starting branch simulation...
flow_patch = {
    0x1400016d0: 0x140001a66,
    0x140001a66: 0x140001b32,
    0x140001f03: 0x140001fdb,
    0x1400020b8: 0x140002185,
    0x140002185: 0x140002253,
    0x140002253: 0x140002327,
    0x140002327: 0x1400023ee,
    0x1400024de: 0x1400025b2,
    0x1400025b2: 0x140002684,
    0x140002684: 0x1400023ee,
    0x140002748: 0x14000280f,
    0x1400028fa: 0x1400029d3,
    0x1400029d3: 0x140002ab2,
    0x140002ab2: 0x140002b91,
    0x140002b91: 0x140002c70,
    0x140002c70: 0x140002d3f,
    0x140002d3f: 0x14000280f,
    0x140002ef0: 0x140002fc2,
    0x1400030af: 0x14000317b,
    0x14000317b: 0x140003247,
    0x140003247: 0x14000330b,
    0x14000330b: 0x1400033da,
    0x1400033da: 0x1400034a3,
    0x1400034a3: 0x14000356a,
    0x14000356a: 0x14000363e,
    0x14000363e: 0x140003708,
    0x140003708: 0x1400037c5,
    0x1400037c5: 0x14000388e,
    0x14000388e: 0x14000395a,
    0x14000395a: 0x140003a21,
    0x140003a21: 0x140003af5,
    0x140003af5: 0x140003bbf,
    0x140003bbf: 0x140003c83,
    0x140003c83: 0x140003d4a,
    0x140003d4a: 0x140003e14,
    0x140003e14: 0x140003ed9,
    0x140003ed9: 0x140003fab,
    0x140003fab: 0x140004073,
    0x140004073: 0x140004135,
    0x140004135: 0x1400041fc,
    0x1400041fc: 0x1400042c6,
    0x1400042c6: 0x14000438b,
    0x14000438b: 0x14000445d,
    0x14000445d: 0x140004525,
    0x140004525: 0x1400045e7,
    0x1400045e7: 0x1400046ae,
    0x1400046ae: 0x140004775,
    0x140004775: 0x14000484a,
    0x14000484a: 0x140004923,
    0x140004923: 0x1400049ed,
    0x1400049ed: 0x140004abc,
    0x140004abc: 0x140004b8b,
    0x140004b8b: 0x140004c55,
    0x140004c55: 0x140004d1c,
    0x140004d1c: 0x140004dec,
    0x140004dec: 0x140004ec1,
    0x140004ec1: 0x140002e03,
    0x140004f85: 0x14000504e,
    0x140005142: 0x140005222,
    0x140005222: 0x1400052eb,
    0x1400053da: 0x1400054d9,
    0x1400054d9: 0x1400052eb,
    0x14000559f: 0x140005668,
    0x140005757: 0x140005820,
    0x14000590f: 0x1400059e1,
    0x1400059e1: 0x140005ac0,
    0x140005ac0: 0x140005820,
    0x140005b86: 0x140005c6e,
    0x140005c6e: 0x140005d63,
    0x140005d63: 0x140005e4b,
    0x140005f3a: 0x140006003,
    0x1400060f2: 0x1400061c0,
    0x1400061c0: 0x140006292,
    0x140006292: 0x140006367,
    0x140006367: 0x14000643c,
    0x14000643c: 0x140006511,
    0x140006511: 0x1400065df,
    0x1400065df: 0x1400066bb,
    0x1400066bb: 0x14000678d,
    0x14000678d: 0x14000685b,
    0x14000685b: 0x140006937,
    0x140006937: 0x140006a09,
    0x140006a09: 0x140006ad7,
    0x140006ad7: 0x140006bb3,
    0x140006bb3: 0x140006c85,
    0x140006c85: 0x140006d53,
    0x140006d53: 0x140006e2f,
    0x140006e2f: 0x140006f01,
    0x140006f01: 0x140006fd3,
    0x140006fd3: 0x1400070a5,
    0x1400070a5: 0x140007177,
    0x140007177: 0x140007249,
    0x140007249: 0x14000731b,
    0x14000731b: 0x1400073e7,
    0x1400073e7: 0x1400074c1,
    0x1400074c1: 0x140007593,
    0x140007593: 0x14000765f,
    0x14000765f: 0x14000773d,
    0x14000773d: 0x14000780f,
    0x14000780f: 0x1400078db,
    0x1400078db: 0x1400079b9,
    0x1400079b9: 0x140007a8b,
    0x140007a8b: 0x140007b57,
    0x140007b57: 0x140007c35,
    0x140007c35: 0x140006003,
    0x140007cfb: 0x140007dca,
    0x140007dca: 0x140007eb2,
    0x140007eb2: 0x140007fa7,
    0x140007fa7: 0x14000808f,
    0x14000808f: 0x140008158,
    0x140008247: 0x140008355,
    0x140008355: 0x140008158,
    0x14000841b: 0x140005668,
    0x1400084e1: 0x1400085c2,
    0x1400085c2: 0x14000504e,
}

branch_patch = {
    0x140001b32: (0x140001c4b, None), # (Left:Cond=True, Right:Cond=False)
    0x140001d1e: (0x140001fdb, 0x140001e0a), # (Left:Cond=True, Right:Cond=False)
    0x140001e0a: (0x140001fdb, 0x140001f03), # (Left:Cond=True, Right:Cond=False)
    0x1400023ee: (0x140002748, 0x1400024de), # (Left:Cond=True, Right:Cond=False)
    0x14000280f: (0x140002e03, 0x1400028fa), # (Left:Cond=True, Right:Cond=False)
    0x140002e03: (0x140004f85, 0x140002ef0), # (Left:Cond=True, Right:Cond=False)
    0x140002fc2: (0x140004d1c, 0x1400030af), # (Left:Cond=True, Right:Cond=False)
    0x14000504e: (0x140008688, 0x140005142), # (Left:Cond=True, Right:Cond=False)
    0x1400052eb: (0x14000559f, 0x1400053da), # (Left:Cond=True, Right:Cond=False)
    0x140005668: (0x1400084e1, 0x140005757), # (Left:Cond=True, Right:Cond=False)
    0x140005820: (0x140005b86, 0x14000590f), # (Left:Cond=True, Right:Cond=False)
    0x140005e4b: (0x140007dca, 0x140005f3a), # (Left:Cond=True, Right:Cond=False)
    0x140006003: (0x140007cfb, 0x1400060f2), # (Left:Cond=True, Right:Cond=False)
    0x140008158: (0x14000841b, 0x140008247), # (Left:Cond=True, Right:Cond=False)
    0x140008688: (0x140008837, 0x140008777), # (Left:Cond=True, Right:Cond=False)
    0x140008837: (0x140008777, 0x14000893a), # (Left:Cond=True, Right:Cond=False)
    0x1400088fd: (0x14000893a, None), # (Left:Cond=True, Right:Cond=False)
}

missing_successors = {
}

patch_jz = {
    0x140001b32: (0x140001c4b, None),
    0x140001d1e: (0x140001fdb, 0x140001e0a),
    0x140008688: (0x140008837, 0x140008777),
    0x1400088fd: (0x14000893a, None),
}

patch_jnz = {
    0x140001e0a: (0x140001fdb, 0x140001f03),
    0x140002fc2: (0x140004d1c, 0x1400030af),
    0x140008837: (0x140008777, 0x14000893a),
}

patch_jle = {
}

patch_jg = {
    0x1400023ee: (0x140002748, 0x1400024de),
    0x14000280f: (0x140002e03, 0x1400028fa),
    0x140002e03: (0x140004f85, 0x140002ef0),
    0x14000504e: (0x140008688, 0x140005142),
    0x1400052eb: (0x14000559f, 0x1400053da),
    0x140005668: (0x1400084e1, 0x140005757),
    0x140005820: (0x140005b86, 0x14000590f),
    0x140005e4b: (0x140007dca, 0x140005f3a),
    0x140006003: (0x140007cfb, 0x1400060f2),
    0x140008158: (0x14000841b, 0x140008247),
}

对应jz/jnz/jg/jle这种,我把条件成立的放左边,条件不成立的放右边
然后我拷打ai,叫他根据dfs画一个图
CFG7 挺好看的哈,不过没什么用说是

重建控制流

和angr那篇差不多,单后继patch为jmp。双后继为jz/jnz/jg/jle xxx ;jmp xxx 然后nop无用的

.text:00000001400017AE                 rdtsc
.text:00000001400017B0                 shl     rdx, 20h
.text:00000001400017B4                 or      rax, rdx
.text:00000001400017B7                 nop
.text:00000001400017B8                 sub     rax, [rbp+8F0h+var_70]
.text:00000001400017BF                 mov     rdx, 1DCD65000h
.text:00000001400017C9                 cmp     rdx, rax
.text:00000001400017CC                 setb    al
.text:00000001400017CF                 test    al, al

像这种这种都是没用的反调试,所以我们从rdtsc指令开始patch,然后把没用的全部nop了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
real_blocks_has_no_branch_but_has_pred = [5368735199, 5368737107, 5368715284, 5368736471, 5368735835, 5368715231]
real_blocks_has_no_branch = [5368736265, 5368739855, 5368725012, 5368715284, 5368742939, 5368724001, 5368730146, 5368737327, 5368740917, 5368734780, 5368723006, 5368721991, 5368742471, 5368738377, 5368727626, 5368716363, 5368717907, 5368728661, 5368735835, 5368726621, 5368739423, 5368715878, 5368732782, 5368720496, 5368725619, 5368724611, 5368718980, 5368736901, 5368740491, 5368723598, 5368742031, 5368734354, 5368722595, 5368737957, 5368727214, 5368721583, 5368720050, 5368741554, 5368717496, 5368735419, 5368728252, 5368732352, 5368729281, 5368739009, 5368726214, 5368714960, 5368736471, 5368725209, 5368730841, 5368740059, 5368718558, 5368743137, 5368721136, 5368733938, 5368724213, 5368719610, 5368741115, 5368737537, 5368717059, 5368723208, 5368722187, 5368731919, 5368734993, 5368738587, 5368728860, 5368727843, 5368726821, 5368718119, 5368725813, 5368736055, 5368733498, 5368744250, 5368739645, 5368720703, 5368729922, 5368719176, 5368724810, 5368737107, 5368742741, 5368731479, 5368740695, 5368723802, 5368733027, 5368734567, 5368722794, 5368727413, 5368738167, 5368743799, 5368721787, 5368717701, 5368729477, 5368732550, 5368726411, 5368728459, 5368735629, 5368720273, 5368739219, 5368731039, 5368741799, 5368725419, 5368718770, 5368736691, 5368740281, 5368724415, 5368734144, 5368743362, 5368723397, 5368741322, 5368719827, 5368737747, 5368722394, 5368717275, 5368730586, 5368735199, 5368715231, 5368732129, 5368727015, 5368738791, 5368729068, 5368728045, 5368726012]
real_blocks_has_branch = [5368731680, 5368721346, 5368720899, 5368733699, 5368731240, 5368743560, 5368716810, 5368730347, 5368733259, 5368718318, 5368719375, 5368729678, 5368716082, 5368743991, 5368742232, 5368744189, 5368716574]

flow_patch = {
    0x1400016d0: 0x140001a66,
    0x140001a66: 0x140001b32,
    0x140001f03: 0x140001fdb,
    0x1400020b8: 0x140002185,
    0x140002185: 0x140002253,
    0x140002253: 0x140002327,
    0x140002327: 0x1400023ee,
    0x1400024de: 0x1400025b2,
    0x1400025b2: 0x140002684,
    0x140002684: 0x1400023ee,
    0x140002748: 0x14000280f,
    0x1400028fa: 0x1400029d3,
    0x1400029d3: 0x140002ab2,
    0x140002ab2: 0x140002b91,
    0x140002b91: 0x140002c70,
    0x140002c70: 0x140002d3f,
    0x140002d3f: 0x14000280f,
    0x140002ef0: 0x140002fc2,
    0x1400030af: 0x14000317b,
    0x14000317b: 0x140003247,
    0x140003247: 0x14000330b,
    0x14000330b: 0x1400033da,
    0x1400033da: 0x1400034a3,
    0x1400034a3: 0x14000356a,
    0x14000356a: 0x14000363e,
    0x14000363e: 0x140003708,
    0x140003708: 0x1400037c5,
    0x1400037c5: 0x14000388e,
    0x14000388e: 0x14000395a,
    0x14000395a: 0x140003a21,
    0x140003a21: 0x140003af5,
    0x140003af5: 0x140003bbf,
    0x140003bbf: 0x140003c83,
    0x140003c83: 0x140003d4a,
    0x140003d4a: 0x140003e14,
    0x140003e14: 0x140003ed9,
    0x140003ed9: 0x140003fab,
    0x140003fab: 0x140004073,
    0x140004073: 0x140004135,
    0x140004135: 0x1400041fc,
    0x1400041fc: 0x1400042c6,
    0x1400042c6: 0x14000438b,
    0x14000438b: 0x14000445d,
    0x14000445d: 0x140004525,
    0x140004525: 0x1400045e7,
    0x1400045e7: 0x1400046ae,
    0x1400046ae: 0x140004775,
    0x140004775: 0x14000484a,
    0x14000484a: 0x140004923,
    0x140004923: 0x1400049ed,
    0x1400049ed: 0x140004abc,
    0x140004abc: 0x140004b8b,
    0x140004b8b: 0x140004c55,
    0x140004c55: 0x140004d1c,
    0x140004d1c: 0x140004dec,
    0x140004dec: 0x140004ec1,
    0x140004ec1: 0x140002e03,
    0x140004f85: 0x14000504e,
    0x140005142: 0x140005222,
    0x140005222: 0x1400052eb,
    0x1400053da: 0x1400054d9,
    0x1400054d9: 0x1400052eb,
    0x14000559f: 0x140005668,
    0x140005757: 0x140005820,
    0x14000590f: 0x1400059e1,
    0x1400059e1: 0x140005ac0,
    0x140005ac0: 0x140005820,
    0x140005b86: 0x140005c6e,
    0x140005c6e: 0x140005d63,
    0x140005d63: 0x140005e4b,
    0x140005f3a: 0x140006003,
    0x1400060f2: 0x1400061c0,
    0x1400061c0: 0x140006292,
    0x140006292: 0x140006367,
    0x140006367: 0x14000643c,
    0x14000643c: 0x140006511,
    0x140006511: 0x1400065df,
    0x1400065df: 0x1400066bb,
    0x1400066bb: 0x14000678d,
    0x14000678d: 0x14000685b,
    0x14000685b: 0x140006937,
    0x140006937: 0x140006a09,
    0x140006a09: 0x140006ad7,
    0x140006ad7: 0x140006bb3,
    0x140006bb3: 0x140006c85,
    0x140006c85: 0x140006d53,
    0x140006d53: 0x140006e2f,
    0x140006e2f: 0x140006f01,
    0x140006f01: 0x140006fd3,
    0x140006fd3: 0x1400070a5,
    0x1400070a5: 0x140007177,
    0x140007177: 0x140007249,
    0x140007249: 0x14000731b,
    0x14000731b: 0x1400073e7,
    0x1400073e7: 0x1400074c1,
    0x1400074c1: 0x140007593,
    0x140007593: 0x14000765f,
    0x14000765f: 0x14000773d,
    0x14000773d: 0x14000780f,
    0x14000780f: 0x1400078db,
    0x1400078db: 0x1400079b9,
    0x1400079b9: 0x140007a8b,
    0x140007a8b: 0x140007b57,
    0x140007b57: 0x140007c35,
    0x140007c35: 0x140006003,
    0x140007cfb: 0x140007dca,
    0x140007dca: 0x140007eb2,
    0x140007eb2: 0x140007fa7,
    0x140007fa7: 0x14000808f,
    0x14000808f: 0x140008158,
    0x140008247: 0x140008355,
    0x140008355: 0x140008158,
    0x14000841b: 0x140005668,
    0x1400084e1: 0x1400085c2,
    0x1400085c2: 0x14000504e,
    0x140001c4b: 0x140001d1e,
    0x14000893a: 0x1400089AB,
    0x140008777: 0x1400089AB,
    0x140001fdb: 0x1400020b8,



}

jz_patch = {
    0x140001b32: (0x140001c4b, 0x1400089AB),
    0x140001d1e: (0x140001fdb, 0x140001e0a),
    0x140008688: (0x140008837, 0x140008777),
    0x1400088fd: (0x14000893a, 0x1400089AB),
}

jnz_patch = {
    0x140001e0a: (0x140001fdb, 0x140001f03),
    0x140002fc2: (0x140004d1c, 0x1400030af),
    0x140008837: (0x140008777, 0x14000893a),
}

jle_patch = {
}

jg_patch = {
    0x1400023ee: (0x140002748, 0x1400024de),
    0x14000280f: (0x140002e03, 0x1400028fa),
    0x140002e03: (0x140004f85, 0x140002ef0),
    0x14000504e: (0x140008688, 0x140005142),
    0x1400052eb: (0x14000559f, 0x1400053da),
    0x140005668: (0x1400084e1, 0x140005757),
    0x140005820: (0x140005b86, 0x14000590f),
    0x140005e4b: (0x140007dca, 0x140005f3a),
    0x140006003: (0x140007cfb, 0x1400060f2),
    0x140008158: (0x14000841b, 0x140008247),
}

jmp_table = {
    0x1400065df:0x14000661F,
    0x140006d53:0x140006D93,
    0x140006ad7:0x140006B17,
    0x14000685b:0x14000689B,
}


useless_blocks = []




import idaapi
import idautils
import idc
import keystone

def patch_ins_to_nop(ins):
    size = idc.get_item_size(ins)
    for i in range(size):
        idc.patch_byte(ins + i,0x90)


def patch_bytes(addr, data):
    for i, b in enumerate(data):
        idc.patch_byte(addr + i, b)


def fill_nop(start_ea, end_ea):
    # [FIX 1] 应该是 end - start,否则是负数
    size = end_ea - start_ea 
    if size > 0:
        # [FIX 2] 使用 patch_bytes 批量写入
        patch_bytes(start_ea, b'\x90' * size)

def get_block_by_address(ea):
    func = idaapi.get_func(ea)
    blocks = idaapi.FlowChart(func)
    for block in blocks:
        if block.start_ea <= ea < block.end_ea:
            return block
    return None

def generate_jmp_code(src, dst):
    # E9 xx xx xx xx
    offset = dst - (src + 5)
    return b'\xE9' + offset.to_bytes(4, 'little', signed=True)

def generate_jz_code(src, dst):
    # 0F 84 xx xx xx xx
    offset = dst - (src + 6)
    return b'\x0F\x84' + offset.to_bytes(4, 'little', signed=True)

def generate_jnz_code(src, dst):
    # 0F 85 xx xx xx xx
    offset = dst - (src + 6)
    return b'\x0F\x85' + offset.to_bytes(4, 'little', signed=True)

def generate_jg_code(src, dst):
    # 0F 8F xx xx xx xx
    offset = dst - (src + 6)
    return b'\x0F\x8F' + offset.to_bytes(4, 'little', signed=True)

def find_rdtsc_addr(ea):
    block = get_block_by_address(ea)  # ea 为块内任意地址
    rdtsc_ea = None
    for ins in idautils.Heads(block.start_ea, block.end_ea):
        if idc.print_insn_mnem(ins) == "rdtsc":
            rdtsc_ea = ins
            return rdtsc_ea


def get_all_blocks(func_ea):
    global useless_blocks
    blocks = idaapi.FlowChart(idaapi.get_func(func_ea))
    for block in blocks:
        start_ea = block.start_ea
        end_ea = block.end_ea
        useless_blocks.append([start_ea,end_ea])


def patch_jmp():
    global useless_blocks
    for ea in flow_patch.keys():
        block = get_block_by_address(ea)
        start_ea = block.start_ea
        end_ea = block.end_ea
        last_ins_ea = idc.prev_head(end_ea)#

        try:
            useless_blocks.remove([start_ea,end_ea])
        except ValueError:
            pass

        if ea in jmp_table.keys():
            print("11111")
            
            succs = list(block.succs())
            # 只有当确实存在两个后继块时才尝试移除
            if len(succs) >= 2:
                succ_1 = succs[0]
                succ_2 = succs[1]
                try: useless_blocks.remove([succ_1.start_ea,succ_1.end_ea]) 
                except: pass
                try: useless_blocks.remove([succ_2.start_ea,succ_2.end_ea])
                except: pass

            block1 = get_block_by_address(jmp_table[ea])
            start_ea = block1.start_ea
            end_ea = block1.end_ea
            
            print([start_ea,end_ea])
            try: useless_blocks.remove([start_ea,end_ea])
            except: pass
            
            rdstc_addr = find_rdtsc_addr(jmp_table[ea])
            jmp_code = generate_jmp_code(rdstc_addr, flow_patch[ea])
            
            patch_bytes(rdstc_addr, jmp_code)
            nop_start = jmp_table[ea] + len(jmp_code)
            
            

            
            fill_nop(nop_start, end_ea)
            print(f"11111Patched jmp at {hex(jmp_table[ea])} to {hex(flow_patch[ea])}")
        else:
            print("22222")
            rdstc_addr = find_rdtsc_addr(ea)
            jmp_code = generate_jmp_code(rdstc_addr, flow_patch[ea])
            patch_bytes(rdstc_addr, jmp_code)
            nop_start = rdstc_addr + len(jmp_code)
            fill_nop(nop_start, end_ea)
            print(f"222222Patched jmp at {hex(last_ins_ea)} to {hex(flow_patch[ea])}")

        

def patch_jz():
    global useless_blocks
    for ea in jz_patch.keys():
        block = get_block_by_address(ea)
        start_ea = block.start_ea
        end_ea = block.end_ea
        last_ins_ea = idc.prev_head(end_ea)#

        try: useless_blocks.remove([start_ea,end_ea])
        except: pass

        succs = list(block.succs())
        if len(succs) >= 2:
            succ_1 = succs[0]
            succ_2 = succs[1]

            try: useless_blocks.remove([succ_1.start_ea,succ_1.end_ea])
            except: pass
            try: useless_blocks.remove([succ_2.start_ea,succ_2.end_ea])
            except: pass


            succ1_addr = jz_patch[ea][0]
            succ2_addr = jz_patch[ea][1]



            jz_code = generate_jz_code(last_ins_ea, jz_patch[ea][0])
            jmp_code = generate_jmp_code( last_ins_ea+ len(jz_code), jz_patch[ea][1])

            patch_bytes(last_ins_ea, jz_code)
            patch_bytes(last_ins_ea + len(jz_code), jmp_code)

            nop_start = last_ins_ea + len(jz_code) + len(jmp_code)

            if succ_1.end_ea > succ_2.end_ea:
                nop_end = succ_1.end_ea
            else:
                nop_end = succ_2.end_ea
            fill_nop(nop_start, nop_end)
            print(f"Patched jz at {hex(last_ins_ea)} to {hex(jz_patch[ea][0])} and {hex(jz_patch[ea][1])}")
        else:
            print(f"Warning: Block {hex(ea)} (jz) has {len(succs)} successors, skipping nop removal/logic")


def patch_jnz():
    global useless_blocks
    for ea in jnz_patch.keys():
        block = get_block_by_address(ea)
        start_ea = block.start_ea
        end_ea = block.end_ea
        last_ins_ea = idc.prev_head(end_ea)#

        try: useless_blocks.remove([start_ea,end_ea])
        except: pass

        succs = list(block.succs())
        if len(succs) >= 2:
            succ_1 = succs[0]
            succ_2 = succs[1]

            try: useless_blocks.remove([succ_1.start_ea,succ_1.end_ea])
            except: pass
            try: useless_blocks.remove([succ_2.start_ea,succ_2.end_ea])
            except: pass

            jnz_code = generate_jnz_code(last_ins_ea, jnz_patch[ea][0])
            jmp_code = generate_jmp_code( last_ins_ea+ len(jnz_code), jnz_patch[ea][1])

            patch_bytes(last_ins_ea, jnz_code)
            patch_bytes(last_ins_ea + len(jnz_code), jmp_code)

            nop_start = last_ins_ea + len(jnz_code) + len(jmp_code)

            if succ_1.end_ea > succ_2.end_ea:
                nop_end = succ_1.end_ea
            else:
                nop_end = succ_2.end_ea
            fill_nop(nop_start, nop_end)
            print(f"Patched jnz at {hex(last_ins_ea)} to {hex(jnz_patch[ea][0])} and {hex(jnz_patch[ea][1])}")

def patch_jg():
    global useless_blocks
    for ea in jg_patch.keys():
        block = get_block_by_address(ea)
        start_ea = block.start_ea
        end_ea = block.end_ea
        last_ins_ea = idc.prev_head(end_ea)#

        try: useless_blocks.remove([start_ea,end_ea])
        except: pass

        succs = list(block.succs())
        if len(succs) >= 2:
            succ_1 = succs[0]
            succ_2 = succs[1]

            try: useless_blocks.remove([succ_1.start_ea,succ_1.end_ea])
            except: pass
            try: useless_blocks.remove([succ_2.start_ea,succ_2.end_ea])
            except: pass

            jg_code = generate_jg_code(last_ins_ea, jg_patch[ea][0])
            jmp_code = generate_jmp_code( last_ins_ea+ len(jg_code), jg_patch[ea][1])

            patch_bytes(last_ins_ea, jg_code)
            patch_bytes(last_ins_ea + len(jg_code), jmp_code)

            nop_start = last_ins_ea + len(jg_code) + len(jmp_code)

            if succ_1.end_ea > succ_2.end_ea:
                nop_end = succ_1.end_ea
            else:
                nop_end = succ_2.end_ea
            fill_nop(nop_start, nop_end)
            print(f"Patched jg at {hex(last_ins_ea)} to {hex(jg_patch[ea][0])} and {hex(jg_patch[ea][1])}")

def patch_useless_blocks():
    global useless_blocks
    # print(useless_blocks)
    for useless_block in useless_blocks:
        
        print(f"Nop-ing useless block from {hex(useless_block[0])} to {useless_block[1]}")
        fill_nop(useless_block[0], useless_block[1])
    print("无用块nop完成")

def remove_real_blocks_from_useless():
    global useless_blocks
    for addr in real_blocks_has_no_branch_but_has_pred:
        try:
            block1 = get_block_by_address(addr)
            if not block1: continue
            start_ea1 = block1.start_ea
            end_ea1 = block1.end_ea

            succss = list(block1.succs())
            if len(succss) < 2: continue # 安全检查

            start_ea2 = succss[0].start_ea
            end_ea2 = succss[0].end_ea
            
            start_ea3 = succss[1].start_ea
            end_ea3 = succss[1].end_ea

            succss2 = list(succss[0].succs())
            
            try: useless_blocks.remove([start_ea1,end_ea1])
            except ValueError: pass
            try: useless_blocks.remove([start_ea2,end_ea2])
            except ValueError: pass
            try: useless_blocks.remove([start_ea3,end_ea3])
            except ValueError: pass

            if len(succss2) > 0:
                start_ea4 = succss2[0].start_ea
                end_ea4 = succss2[0].end_ea
                try: useless_blocks.remove([start_ea4,end_ea4])
                except ValueError: pass
        except Exception as e:
            print(f"Error in no_branch_but_pred loop for {hex(addr)}: {e}")

    for addr in real_blocks_has_branch:
        try:
            block = get_block_by_address(addr)
            if not block: continue
            start_ea = block.start_ea
            end_ea = block.end_ea

            succs = list(block.succs())
            if len(succs) < 2: continue # 安全检查

            start_ea1 = succs[0].start_ea
            end_ea1 = succs[0].end_ea

            # [FIX] 这里之前是 succss (Typo),已修正为 succs
            start_ea2 = succs[1].start_ea
            end_ea2 = succs[1].end_ea

            try: useless_blocks.remove([start_ea,end_ea])
            except ValueError: pass
            try: useless_blocks.remove([start_ea1,end_ea1])
            except ValueError: pass
            try: useless_blocks.remove([start_ea2,end_ea2])
            except ValueError: pass
        except Exception as e:
            print(f"Error in has_branch loop for {hex(addr)}: {e}")
    
    for addr in real_blocks_has_no_branch:
        try:
            block = get_block_by_address(addr)
            if not block: continue
            start_ea = block.start_ea
            end_ea = block.end_ea

            try: useless_blocks.remove([start_ea,end_ea])
            except ValueError: pass
        except Exception as e:
            print(f"Error in no_branch loop for {hex(addr)}: {e}")





get_all_blocks(0x1400016D0)
remove_real_blocks_from_useless()
patch_jz()
patch_jnz()
patch_jg()
patch_jmp()
patch_useless_blocks()

patch之前记得把retn上面里面很多没用的0x83给nop了,会影响后面ida的cfg
然后我们保存patch结果,发现可以正常运行
CFG8 现在正常多了,不过ida的反编译结果还是怪怪的,不过我们有无敌的ai大人
ai发现是变异aes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# EzObf1 WriteUp

这是一道修改了 AES 流程的逆向题目虽然二进制被控制流平坦化Control Flow Flattening混淆但通过分析关键变换块的汇编代码我们可以还原出其独特的加密逻辑

## 1. 初始分析

拿到二进制文件 `ezobf1.exe` 运行发现它要求输入 Flag并验证正确性

### 关键数据提取

通过静态分析或动态调试我们可以在数据段找到两个关键信息

1.  **密文 (Ciphertext)**: 存放在 `unk_14000C1C0`
    
    unsigned char ciphertext[32] = {
        0xF2, 0x65, 0x12, 0xF9, ... // (32 字节)
    };
    
2.  **S-Box**: 存放在 `unk_14000C080`这是一张非标准的 S-Box
    =
    unsigned char SBOX[256] = {
        0x45, 0x25, 0x95, ... 
    };
    

密钥被硬编码为 `00 01 02 ... 0F`

## 2. 混淆与控制流分析

程序使用了控制流平坦化混淆主函数 `sub_1400016D0` 由一个巨大的 Switch-Case 结构组成通过 `patch_all_block.py` 提供的映射表我们可以看到逻辑块之间的跳转关系

并不是去完全去平坦化而是根据跳转表Jump Table定位到每个 AES 操作对应的真实地址

通过观察跳转表 `d:\reverse\MCP\patch_all_block.py` 中的 `jg_patch`  `flow_patch`),我们确定了几个重复出现的代码块地址
*   `0x140005B86` / `0x140005C6E` / `0x140005D63`
*   `0x140005E4B` (Loop check)
*   `0x1400060F2` (MixColumns area)

## 3. 汇编层面的逻辑拆解

我们编写脚本对上述关键地址进行了反汇编分析

### (1) ShiftRows (行移位) 的变异

在标准 AES ShiftRows 操作是
*   Row 0: 不变
*   Row 1: 左移 1
*   Row 2: 左移 2
*   Row 3: 左移 3

查看地址 `0x140005B86` 处的汇编

0x140005b86: movzx eax, byte ptr [rbp - 0x3f]
0x140005b8b: mov byte ptr [rbp + 0x8af], al  ; 暂存
0x140005b91: movzx eax, byte ptr [rbp - 0x3b]
0x140005b95: mov byte ptr [rbp - 0x3f], al   ; 移位...
...

这段代码明确地在栈上进行字节交换这对应了 `ShiftRows` 操作

**关键发现**在每一轮加密循环中我们发现这组 ShiftRows 代码被调用了**两次**
一次是在 MixColumns 之前一次是在 MixColumns 之后

### (2) MixColumns (列混合)

 `0x140006000` 附近的汇编代码中出现了大量的 `0x1b` 立即数和异或操作这是典型的有限域乘法GF(2^8)特征


0x1400065eb: mov eax, 0x1b  ; AES 不可约多项式系数
...
0x1400066bb: xor al, byte ptr [rbp + 0x8ae] ; 异或累加

这证实了 `MixColumns` 的存在且系数是标准的02, 03, 01, 01)。

### (3) AddRoundKey

 `0x140008247` 附近指令从 Expanded Key 数组中读取数据并与 State 进行异或

0x140008293: xor edx, ecx
0x140008295: mov dword ptr [rax], edx


## 4. 还原加密流程

结合上述分析该程序的单轮加密逻辑如下注意顺序变化):

**标准 AES**:
`SubBytes` -> `ShiftRows` -> `MixColumns` -> `AddRoundKey`

**EzObf1 AES**:
`SubBytes` -> **`ShiftRows`** -> `MixColumns` -> **`ShiftRows`** -> `AddRoundKey`

 `MixColumns` 被夹在两次 `ShiftRows` 之间

对于最后一轮第10轮):
`SubBytes` -> **`ShiftRows`** -> **`ShiftRows`** -> `AddRoundKey` ( MixColumns)

## 5. 解密脚本 (Solver)

解密时必须严格逆序执行所有操作

*   **标准解密**: `InvShift` -> `InvSub` -> `AddKey` -> `InvMix`
*   **本题解密**:
    1.  `AddRoundKey`
    2.  `InvShiftRows` (抵消第二次 Shift)
    3.  `InvMixColumns`
    4.  `InvShiftRows` (抵消第一次 Shift)
    5.  `InvSubBytes`

AES解密时通常先 AddKey然后进入逆向循环根据实现细节InvShift  InvSub 的顺序可以互换因为它们是按字节操作的互不影响。)

使用 Python 实现该逻辑


def decrypt_block(ciphertext, w):
    # ... 初始化 State ...

    # 撤销第 10 轮 (两次移位,无列混合)
    state = add_round_key(state, w, 10)
    state = inv_shift_rows(state)
    state = inv_shift_rows(state)
    state = inv_sub_bytes(state)
    
    # 撤销第 9 到 第 1 轮
    for round_num in range(9, 0, -1):
        state = add_round_key(state, w, round_num)
        
        # 逆序撤销: Shift2 -> Mix -> Shift1 -> Sub
        state = inv_shift_rows(state) 
        state = inv_mix_columns(state)
        state = inv_shift_rows(state)
        
        state = inv_sub_bytes(state)
    
    # 撤销第 0 轮 (初始轮只有 AddKey)
    state = add_round_key(state, w, 0)
    
    # ... 输出 ...

解题脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
def gmul(a, b):
    p = 0
    for i in range(8):
        if b & 1:
            p ^= a
        hi_bit_set = a & 0x80
        a = (a << 1) & 0xFF
        if hi_bit_set:
            a ^= 0x1b
        b >>= 1
    return p

# Custom S-Box
SBOX = [
    0x45, 0x25, 0x95, 0xf4, 0x49, 0x7f, 0x39, 0xfe, 0x2b, 0x5, 0xd7, 0x64, 0xd1, 0x9c, 0x5b, 0x7c, 
    0xa7, 0xd2, 0xac, 0x1d, 0x10, 0xcb, 0xe6, 0xeb, 0xa0, 0x15, 0x2, 0x8, 0x21, 0x96, 0x65, 0x11, 
    0xef, 0x63, 0xc0, 0x94, 0xea, 0x70, 0x84, 0xb8, 0xf1, 0x56, 0x7b, 0x23, 0x4d, 0x37, 0xaf, 0xc8, 
    0x46, 0x8e, 0x13, 0xad, 0x2a, 0x1c, 0x4b, 0xb1, 0x47, 0x2d, 0x2e, 0xa2, 0x3b, 0x9a, 0x8c, 0xf7, 
    0x3e, 0x51, 0x48, 0x8d, 0xb4, 0x2f, 0xed, 0xd5, 0x83, 0x42, 0x69, 0x34, 0x86, 0x6e, 0xe3, 0x36, 
    0xff, 0xa3, 0x33, 0x59, 0xa6, 0xe, 0x8a, 0x7, 0xb7, 0xb, 0xaa, 0xbe, 0xca, 0x87, 0x1f, 0x79, 
    0xc3, 0xec, 0x75, 0xdc, 0x68, 0x6, 0x58, 0xc4, 0x29, 0x89, 0x54, 0xa8, 0x3c, 0xbb, 0x4a, 0x1e, 
    0x1b, 0xe1, 0xbd, 0x71, 0xdb, 0x52, 0x41, 0xe2, 0xda, 0xd3, 0xf9, 0x14, 0x26, 0x7a, 0x53, 0x9b, 
    0x81, 0xcf, 0xc, 0x35, 0x40, 0x9e, 0xce, 0x5d, 0x67, 0xc1, 0xfc, 0x6b, 0x6f, 0x93, 0xfb, 0x9d, 
    0x8b, 0x30, 0x76, 0x4f, 0x5c, 0x6a, 0xf0, 0x1a, 0x1, 0xf2, 0xf3, 0x7e, 0xc6, 0x28, 0xf5, 0xe7, 
    0x99, 0xf8, 0xc7, 0x74, 0xfd, 0x82, 0xe8, 0xee, 0x9, 0x55, 0x77, 0x44, 0x22, 0xae, 0x5e, 0xd8, 
    0x12, 0x4c, 0x88, 0x97, 0xe9, 0x3f, 0x38, 0x98, 0xde, 0x6c, 0xcd, 0x50, 0xe0, 0x6d, 0x32, 0x43, 
    0x61, 0xa1, 0xa, 0xb2, 0x5f, 0xdd, 0x9f, 0xe4, 0x7d, 0xb0, 0x16, 0x3d, 0x2c, 0xfa, 0xc2, 0xa4, 
    0xb9, 0x66, 0xba, 0xd0, 0x92, 0xd6, 0x78, 0xdf, 0xab, 0xb5, 0x27, 0x8f, 0xcc, 0x4, 0x62, 0xd9, 
    0xa5, 0xc9, 0xbc, 0x19, 0x85, 0xd, 0x80, 0x5a, 0x3a, 0xf6, 0x17, 0xb6, 0xb3, 0x31, 0xbf, 0xe5, 
    0x60, 0x18, 0x91, 0x0, 0xd4, 0x73, 0x20, 0xc5, 0xf, 0x90, 0x57, 0x4e, 0x3, 0x72, 0x24, 0xa9
]

# Inverse S-Box
INV_SBOX = [0] * 256
for i in range(len(SBOX)):
    INV_SBOX[SBOX[i]] = i

# Rcon (Standard)
Rcon = [
    0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36
]

def sub_word(word):
    # Use Custom SBox
    return (SBOX[(word >> 24) & 0xFF] << 24) | \
           (SBOX[(word >> 16) & 0xFF] << 16) | \
           (SBOX[(word >> 8) & 0xFF] << 8) | \
           (SBOX[word & 0xFF])

def rot_word(word):
    return ((word << 8) & 0xFFFFFFFF) | (word >> 24)

def key_expansion(key):
    # Key is 16 bytes list
    w = [0] * 44 # 4 words * 11 (10 rounds + 1)
    
    # First 4 words are the key itself
    for i in range(4):
        w[i] = (key[4*i] << 24) | (key[4*i+1] << 16) | (key[4*i+2] << 8) | key[4*i+3]
    
    for i in range(4, 44):
        temp = w[i-1]
        if i % 4 == 0:
            temp = sub_word(rot_word(temp)) ^ (Rcon[i//4 - 1] << 24)
        w[i] = w[i-4] ^ temp
    return w

def add_round_key(state, w, round_num):
    for c in range(4):
        val = w[round_num*4 + c]
        # w is in MSB first (big endian word) ?
        # Code: key[0]<<24.
        # AES treats words as Col 0, Col 1, etc.
        # State[r][c].
        # key[0] is state[0][0]. key[1] is state[1][0]...
        # Wait. Standard AES state:
        # 0  4  8 12
        # 1  5  9 13
        # 2  6 10 14
        # 3  7 11 15
        # key[0] maps to index 0.
        # In my w construction: w[0] = k0 k1 k2 k3.
        # w[0] >> 24 is k0.
        # w[0] >> 16 is k1.
        # Correct.
        state[0][c] ^= (val >> 24) & 0xFF
        state[1][c] ^= (val >> 16) & 0xFF
        state[2][c] ^= (val >> 8) & 0xFF
        state[3][c] ^= val & 0xFF
    return state

def inv_sub_bytes(state):
    for r in range(4):
        for c in range(4):
            state[r][c] = INV_SBOX[state[r][c]]
    return state

def inv_shift_rows(state):
    # Standard InvShiftRows
    # Row 1 rot right 1
    state[1] = state[1][-1:] + state[1][:-1]
    # Row 2 rot right 2
    state[2] = state[2][-2:] + state[2][:-2]
    # Row 3 rot right 3
    state[3] = state[3][-3:] + state[3][:-3]
    return state

def inv_mix_columns(state):
    # Multiply by 0e, 0b, 0d, 09
    # a(x) = {0b}x^3 + {0d}x^2 + {09}x + {0e}
    for c in range(4):
        col = [state[r][c] for r in range(4)]
        state[0][c] = gmul(col[0], 0x0e) ^ gmul(col[1], 0x0b) ^ gmul(col[2], 0x0d) ^ gmul(col[3], 0x09)
        state[1][c] = gmul(col[0], 0x09) ^ gmul(col[1], 0x0e) ^ gmul(col[2], 0x0b) ^ gmul(col[3], 0x0d)
        state[2][c] = gmul(col[0], 0x0d) ^ gmul(col[1], 0x09) ^ gmul(col[2], 0x0e) ^ gmul(col[3], 0x0b)
        state[3][c] = gmul(col[0], 0x0b) ^ gmul(col[1], 0x0d) ^ gmul(col[2], 0x09) ^ gmul(col[3], 0x0e)
    return state

def decrypt_block(ciphertext, w):
    # Helper to convert linear 16 bytes to 4x4 state (Column Major)
    # 0 4 8 12
    # 1 5 9 13 ...
    state = [[0]*4 for _ in range(4)]
    for i in range(16):
        state[i%4][i//4] = ciphertext[i]

    # AddRoundKey (Round 10)
    state = add_round_key(state, w, 10)
    state = inv_shift_rows(state)
    state = inv_shift_rows(state)
    state = inv_sub_bytes(state)
    
    for round_num in range(9, 0, -1):
        state = add_round_key(state, w, round_num)
        state = inv_shift_rows(state) # Second shift reversed
        state = inv_mix_columns(state)
        state = inv_shift_rows(state) # First shift reversed
        state = inv_sub_bytes(state)
    
    # Initial Round (Round 0)
    state = add_round_key(state, w, 0)
    
    # Extract linear
    output = [0]*16
    for i in range(16):
        output[i] = state[i%4][i//4]
    return bytes(output)

# Main
key = bytes(range(16)) # 00 01 ... 0F
w = key_expansion(key)

# Ciphertext unk_14000C1C0 (32 bytes)
ct1 = bytes([
    0xF2, 0x65, 0x12, 0xF9, 0x2F, 0x64, 0x28, 0x7D, 0xC0, 0xD0, 0x45, 0x5B, 0x25, 0xDA, 0x24, 0x15
])
ct2 = bytes([
    0xA6, 0x9C, 0x1D, 0xAC, 0x85, 0x42, 0xAB, 0x28, 0xD3, 0x4C, 0x2C, 0x75, 0xDC, 0xDA, 0x30, 0xC7
])

pt1 = decrypt_block(ct1, w)
pt2 = decrypt_block(ct2, w)

print(f"PT1: {pt1}")
print(f"PT2: {pt2}")
try:
    print(f"Decoded: {(pt1+pt2).decode('utf-8')}")
except:
    print("Decode failed")