首页
社区
课程
招聘
[原创]看雪.深信服 2021 KCTF 春季赛 第七题 千里寻根
2021-5-23 06:19 10028

[原创]看雪.深信服 2021 KCTF 春季赛 第七题 千里寻根

2021-5-23 06:19
10028

多层自解密,每一层会先清空上一层,解密下一层,然后跳过去。每层的解密逻辑都不完全相同
真实指令分散在每一层中,还有call-pop-add之类的混淆

 

有反调试,不能直接调试器启动,但可以启动到输入之后再x64dbg附加。

 

发现输入之后会进入0x140001000的函数对serial做hexdecode,这个函数位置总是固定的。
在它的ret指令处(0x140001084)下断点,r10寄存器指示了serial保存的地方,在这里下硬件读写断点。

 

一次F9到达断点,是一句rep movsq指令,在此时开始运行下面的脚本:跟踪rep movsq指令对serial的复制,并对复制的目标位置下硬件断点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
loop:
 
find rip, "F348A5", 3
cmp $result, 0
je out
 
bphc
sti
sti
find rdi-0x60, "7D4DBA7A", 0xc0
bph $result+0xc, r, 1
 
run
 
jmp loop
 
out:

(注:$result+0xc是调整后的结果,因为真正处理serial的代码读的并不是第0个字节,而是第0xc个字节)

 

几秒钟后脚本停下来,是movzx r12d, byte ptr [r13 + 0x25]指令,这是真正开始处理serial的地方。

 

dump内存,记录下所有寄存器的值(以及gs:[0x8]和gs:[0x10]两个位置的值)

 

考虑用unicorn模拟执行+capstone反汇编,目的是方便的跟踪程序执行流程以及后续的分析。
直接写了一个基础版本,发现根本跑不完(毕竟原始程序直接执行都需要几秒钟)

 

基于模拟执行的分析肯定要反复尝试,速度必须要足够快,因此做了很多努力来加速:

  1. 先在x64dbg执行到真实验证逻辑第一次读取serial时再dump内存,而不是从程序开头就模拟执行。
  2. unicorn跑起来后,首先发现卡在了rep movsq指令中(作用是复制代码块)(UC_HOOK_CODE相当于F7单步执行,会在rep的每次重复都断下来,因此需要中断的次数等于rcx寄存器的值,大约几十万到几百万),可以主动读取rdi、rsi、rcx寄存器进行内存复制然后跳过rep movsq指令的执行。对rep stosq同理。
  3. 之后发现卡在了一组指令的循环中(这组指令的作用是解密代码块,因此每层壳都会有),循环次数与之前rep movsq复制的长度差不多。循环模式相对固定,从sub rcx, 1 / dec rcx开始(紧跟jne),然后是mov al, [reg],对al做简单运算,然后mov [reg], al,最后是inc reg / dec reg,进入下一轮循环。这里的麻烦是每个循环对al做的运算都不一样,因此不仅需要在hook函数中识别循环,还要找出对al寄存器的运算。之后同样主动计算,然后把rcx赋值为1跳过剩余的循环。
  4. (此时还没有加UC_HOOK_MEM)现在大约能在7-8分钟左右跑完一轮trace(从真实逻辑首次读取serial,到最终计算出的name与输入的name进行比较),还是有些慢,发现瓶颈在上一步,用Python做循环解密的速度不够快(python的循环性能确实不高。当然如果完全靠unicorn模拟执行的性能根本跑不出来),对此可以用C写一个dll然后用Python加载。

现在,程序能够在1分钟以内生成完整的trace,这个速度差不多可以接受了。

 

trace有大约80万行,关键指令又分散在垃圾指令中,还是不能人工分析。
下一步考虑基于trace做数据流跟踪(受到 https://bbs.pediy.com/thread-258262.htm 这篇文章启发),看输入的serial从内存中被读出来以及后续计算的流程。
对于任何一条语句,都有来源操作数(内存/寄存器/常量)和目的操作数(内存/寄存器),如果任何一个来源操作数与serial有关("tainted"),则相关的目的操作数也要标记为"tainted",相反的,如果所有来源操作数都没有tainted,则所有目的操作数的tainted标记都要取消。
实现的难度很大,capstone好像没有办法直接获得一条指令相关的源寄存器和目的寄存器(也可能有办法,只是我不知道),只好手动处理部分指令,目标是零漏报的基础上尽可能减少误报。

 

代码如下:

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
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
# https://github.com/unicorn-engine/unicorn/blob/master/bindings/python/sample_x86.py
# https://github.com/aquynh/capstone/blob/master/bindings/python/capstone/x86.py
# https://github.com/aquynh/capstone/blob/master/bindings/python/capstone/x86_const.py
 
from unicorn import *
from unicorn.x86_const import *
from capstone import *
from capstone.x86_const import *
import traceback
import ctypes
 
 
'''
// cl /O2 -c dodecryptblock.c
// link -DLL -out:libdodecryptblock.dll dodecryptblock.obj
 
__declspec(dllexport) void dodecryptblock(unsigned char *buf, int len, unsigned char table[256]) {
    for (int i = 0; i < len; i++) {
        buf[i] = table[buf[i]];
    }
}
'''
libdodecryptblock = ctypes.CDLL("./libdodecryptblock.dll")
 
#global_serial_start = None
#global_serial_len = 32
 
global_tainted_memlist = set()
global_tainted_reglist = set()
 
global_last_inst = None
 
class LoopContext:
    __slots__ = ["tracing", "tracecount", "operations", "direction", "address", "memreg"]
    def __init__(self):
        self.clear()
    def clear(self):
        self.tracing = False
        self.tracecount = 0
        self.operations = []
        self.direction = None
        self.address = None
        self.memreg = None
 
class HoopPrintContext:
    __slots__ = ["initial_rsp", "last_rsp", "last_address", "last_inst"]
    def __init__(self):
        self.initial_rsp = None
        self.last_rsp = None
        self.last_address = None
        self.last_inst = None
 
def handlekeyboardinterupt(func):
    def wrapper_func(*args, **kwargs):
        try:
            r = func(*args, **kwargs)
        except KeyboardInterrupt:
            import os
            os._exit(0)
        except Exception:
            traceback.print_exc()
            import os
            import sys
            sys.stdout.flush()
            sys.stderr.flush()
            os._exit(0)
        return r
    return wrapper_func
 
def capstone_reg_to_unicorn_reg(i):
    d = {
        X86_REG_RAX : UC_X86_REG_RAX,
        X86_REG_RBX : UC_X86_REG_RBX,
        X86_REG_RCX : UC_X86_REG_RCX,
        X86_REG_RDX : UC_X86_REG_RDX,
        X86_REG_RBP : UC_X86_REG_RBP,
        X86_REG_RSP : UC_X86_REG_RSP,
        X86_REG_RSI : UC_X86_REG_RSI,
        X86_REG_RDI : UC_X86_REG_RDI,
        X86_REG_R8 : UC_X86_REG_R8,
        X86_REG_R9 : UC_X86_REG_R9,
        X86_REG_R10 : UC_X86_REG_R10,
        X86_REG_R11 : UC_X86_REG_R11,
        X86_REG_R12 : UC_X86_REG_R12,
        X86_REG_R13 : UC_X86_REG_R13,
        X86_REG_R14 : UC_X86_REG_R14,
        X86_REG_R15 : UC_X86_REG_R15,
 
        X86_REG_AL : UC_X86_REG_AL,
        X86_REG_BL : UC_X86_REG_BL,
        X86_REG_CL : UC_X86_REG_CL,
        X86_REG_DL : UC_X86_REG_DL,
        X86_REG_SIL : UC_X86_REG_SIL,
        X86_REG_DIL : UC_X86_REG_DIL,
 
        X86_REG_RIP : UC_X86_REG_RIP,
    }
    return d[i]
 
def capstone_reg_to_normal_reg(i):
    d = {
        X86_REG_RAX : X86_REG_RAX,
        X86_REG_RBX : X86_REG_RBX,
        X86_REG_RCX : X86_REG_RCX,
        X86_REG_RDX : X86_REG_RDX,
        X86_REG_RBP : X86_REG_RBP,
        X86_REG_RSP : X86_REG_RSP,
        X86_REG_RSI : X86_REG_RSI,
        X86_REG_RDI : X86_REG_RDI,
        X86_REG_R8 : X86_REG_R8,
        X86_REG_R9 : X86_REG_R9,
        X86_REG_R10 : X86_REG_R10,
        X86_REG_R11 : X86_REG_R11,
        X86_REG_R12 : X86_REG_R12,
        X86_REG_R13 : X86_REG_R13,
        X86_REG_R14 : X86_REG_R14,
        X86_REG_R15 : X86_REG_R15,
 
        X86_REG_EAX : X86_REG_RAX,
        X86_REG_EBX : X86_REG_RBX,
        X86_REG_ECX : X86_REG_RCX,
        X86_REG_EDX : X86_REG_RDX,
        X86_REG_EBP : X86_REG_RBP,
        X86_REG_ESP : X86_REG_RSP,
        X86_REG_ESI : X86_REG_RSI,
        X86_REG_EDI : X86_REG_RDI,
        X86_REG_R8D : X86_REG_R8,
        X86_REG_R9D : X86_REG_R9,
        X86_REG_R10D : X86_REG_R10,
        X86_REG_R11D : X86_REG_R11,
        X86_REG_R12D : X86_REG_R12,
        X86_REG_R13D : X86_REG_R13,
        X86_REG_R14D : X86_REG_R14,
        X86_REG_R15D : X86_REG_R15,
 
        X86_REG_AX : X86_REG_RAX,
        X86_REG_BX : X86_REG_RBX,
        X86_REG_CX : X86_REG_RCX,
        X86_REG_DX : X86_REG_RDX,
        X86_REG_BP : X86_REG_RBP,
        X86_REG_SP : X86_REG_RSP,
        X86_REG_SI : X86_REG_RSI,
        X86_REG_DI : X86_REG_RDI,
        X86_REG_R8W : X86_REG_R8,
        X86_REG_R9W : X86_REG_R9,
        X86_REG_R10W : X86_REG_R10,
        X86_REG_R11W : X86_REG_R11,
        X86_REG_R12W : X86_REG_R12,
        X86_REG_R13W : X86_REG_R13,
        X86_REG_R14W : X86_REG_R14,
        X86_REG_R15W : X86_REG_R15,
 
        X86_REG_AL : X86_REG_RAX,
        X86_REG_AH : X86_REG_RAX,
        X86_REG_BL : X86_REG_RBX,
        X86_REG_BH : X86_REG_RBX,
        X86_REG_CL : X86_REG_RCX,
        X86_REG_CH : X86_REG_RCX,
        X86_REG_DL : X86_REG_RDX,
        X86_REG_DH : X86_REG_RDX,
        X86_REG_BPL : X86_REG_RBP,
        X86_REG_SIL : X86_REG_RSI,
        X86_REG_DIL : X86_REG_RDI,
        X86_REG_R8B : X86_REG_R8,
        X86_REG_R9B : X86_REG_R9,
        X86_REG_R10B : X86_REG_R10,
        X86_REG_R11B : X86_REG_R11,
        X86_REG_R12B : X86_REG_R12,
        X86_REG_R13B : X86_REG_R13,
        X86_REG_R14B : X86_REG_R14,
        X86_REG_R15B : X86_REG_R15,
 
        X86_REG_RIP : X86_REG_RIP,
        X86_REG_EFLAGS : X86_REG_EFLAGS,
    }
    return d[i]
 
def p64(n):
    n &= 0xffffffffffffffff
    return n.to_bytes(8, 'little')
 
def u64(s):
    assert(len(s) == 8)
    return int.from_bytes(s, 'little')
 
def ror8(n, c):
    return ((n>>c)|(n<<(8-c))) & 0xff
 
def rol8(n, c):
    return ((n<<c)|(n>>(8-c))) & 0xff
 
def sar8(n, c):
    if n & 0x80:
        return n >> c
    else:
        return ((n >> c) | (0xff << (8-c))) & 0xff
 
with open("MEM5_143948F5D_0000000140000000_0691E000.mem", "rb") as f:
    X86_CODE64 = f.read()
    assert(len(X86_CODE64) == 0x691E000)
 
md = Cs(CS_ARCH_X86, CS_MODE_64)
md.detail = True
 
global_rdtsc_value = 0
 
@handlekeyboardinterupt
def hook_code64(uc, address, size, user_data):
    global global_rdtsc_value
    global global_serial_start
    #print(hex(address), hex(size), uc.mem_read(address, 16).hex())
    instbytes = uc.mem_read(address, size)
    inst = list(md.disasm(instbytes, address))[0]
    #print(">>> Tracing instruction at 0x%x, instruction size = 0x%x, inst %s %s" %(address, size, inst.mnemonic, op_str))
    #print("0x%x\t%s %s" %(address, inst.mnemonic, inst.op_str) + "\t\t\t"+instbytes.hex()+"\t"+hex(uc.reg_read(UC_X86_REG_RDX)))
    print("0x%x\t%s %s" %(address, inst.mnemonic, inst.op_str))
    rip = address
    #rip = uc.reg_read(UC_X86_REG_RIP)
    #print(">>> RIP is 0x%x" %rip)
 
    if instbytes == b"\x0f\x31":    # rdtsc
        uc.reg_write(UC_X86_REG_RDX, 0)
        uc.reg_write(UC_X86_REG_RAX, global_rdtsc_value)
        global_rdtsc_value += 1000000
        uc.reg_write(UC_X86_REG_RIP, rip+size)
    elif instbytes == b"\xF3\x48\xA5":    # rep movsq
        rsi = uc.reg_read(UC_X86_REG_RSI)
        rdi = uc.reg_read(UC_X86_REG_RDI)
        rcx = uc.reg_read(UC_X86_REG_RCX)
#        print("rep movsq: ", hex(rip), hex(rsi), hex(rdi), hex(rcx))
        old_global_tainted_memlist = set(global_tainted_memlist)
        for a in old_global_tainted_memlist:
            if rdi <= a < (rdi+rcx*8):
                global_tainted_memlist.remove(a)
        for a in old_global_tainted_memlist:     # cannot merge the two loops
            if rsi <= a < (rsi+rcx*8):
                global_tainted_memlist.add(a + rdi-rsi)
                #print("\tchangeed: ", hex(a+(rdi-rsi)), "->", hex(a))
        uc.mem_write(rdi, bytes(uc.mem_read(rsi, rcx*8)))
        uc.reg_write(UC_X86_REG_RCX, 0)
        uc.reg_write(UC_X86_REG_RDI, rdi+rcx*8)
        uc.reg_write(UC_X86_REG_RSI, rsi+rcx*8)
        uc.reg_write(UC_X86_REG_RIP, rip+size)
    elif instbytes == b"\xF3\x48\xAB":    # rep stosq
        rax = uc.reg_read(UC_X86_REG_RAX)
        rdi = uc.reg_read(UC_X86_REG_RDI)
        rcx = uc.reg_read(UC_X86_REG_RCX)
#        print("rep stosq: ", hex(rip), hex(rax), hex(rdi), hex(rcx))
        for a in set(global_tainted_memlist):
            if rdi <= a < rdi+rcx*8:
                global_tainted_memlist.remove(a)
        uc.mem_write(rdi, p64(rax)*rcx)
        uc.reg_write(UC_X86_REG_RDI, rdi+rcx*8)
        uc.reg_write(UC_X86_REG_RIP, rip+size)
    elif instbytes in [b"\x48\x83\xe9\x01", b"\x83\xe9\x01", b"\x48\xff\xc9", b"\xff\xc9"]:    # sub rcx, 1    sub ecx, 1    dec rcx    dec ecx
        if not user_data.tracing:
            user_data.clear()
            user_data.tracing = True
            user_data.address = address
        else:
            assert(address == user_data.address)
            user_data.tracing = False
            rcx = uc.reg_read(UC_X86_REG_RCX)
#            print(user_data.memreg)
            memreg = uc.reg_read(capstone_reg_to_unicorn_reg(user_data.memreg))
            tmp = uc.mem_read(memreg, rcx-1)
#            print("\n".join(user_data.operations))
            jitcode = compile("\n".join(user_data.operations), "", mode="exec")
            '''
            for i in range(len(tmp)):
                c = tmp[i]
                g = {"c":c, "ror8":ror8, "rol8":rol8}
                exec(jitcode, g)
                c = g["c"]
                if (i == 0):
                    print(tmp[i], c)
                tmp[i] = c
            '''
            buf = bytearray(256)
            for i in range(256):
                c = i
                g = {"c":c, "ror8":ror8, "rol8":rol8}
                exec(jitcode, g)
                c = g["c"]
                buf[i] = c
            '''
            for i in range(len(tmp)):
                tmp[i] = buf[tmp[i]]
            '''
            tmpb = ctypes.create_string_buffer(bytes(tmp), len(tmp))
            libdodecryptblock.dodecryptblock(ctypes.byref(tmpb), len(tmp), ctypes.byref(ctypes.create_string_buffer(bytes(buf),len(buf))))
            tmp = tmpb.raw
 
            uc.mem_write(memreg, bytes(tmp))
            uc.reg_write(capstone_reg_to_unicorn_reg(user_data.memreg), memreg+user_data.direction*(rcx-1))
            uc.reg_write(UC_X86_REG_RCX, 1)    # let  sub rcx, 1  to execute
    elif user_data.tracing:
        print("tracing")
        if user_data.tracecount >= 100:
            user_data.tracing = False
        else:
            user_data.tracecount += 1
            inst = list(md.disasm(instbytes, address))[0]
            operands = list(inst.operands)
            if inst.id == X86_INS_MOV:
                if operands[0].type == X86_OP_REG and operands[1].type == X86_OP_MEM:
                    if operands[0].reg == X86_REG_AL:
                        # find the load inst
                        user_data.memreg = operands[1].mem.base
            elif inst.id in [X86_INS_ADD, X86_INS_SUB, X86_INS_XOR, X86_INS_AND, X86_INS_OR, X86_INS_ROR, X86_INS_ROL]:
                if operands[0].type == X86_OP_REG and operands[0].reg == X86_REG_AL:
                    if operands[1].type == X86_OP_REG:
                        v = uc.reg_read(capstone_reg_to_unicorn_reg(operands[1].reg))
                    elif operands[1].type == X86_OP_IMM:
                        v = operands[1].imm
                    else:
                        assert(0)
                    if inst.id == X86_INS_ADD:
                        pyinst = f"c = (c + {v}) & 0xff"
                    elif inst.id == X86_INS_SUB:
                        pyinst = f"c = (c - {v}) & 0xff"
                    elif inst.id == X86_INS_XOR:
                        pyinst = f"c = (c ^ {v}) & 0xff"
                    elif inst.id == X86_INS_AND:
                        pyinst = f"c = (c & {v}) & 0xff"
                    elif inst.id == X86_INS_OR:
                        pyinst = f"c = (c | {v}) & 0xff"
                    elif inst.id == X86_INS_ROR:
                        pyinst = f"c = ror8(c, {v})"
                    elif inst.id == X86_INS_ROL:
                        pyinst = f"c = rol8(c, {v})"
                    else:
                        assert(0)
                    user_data.operations.append(pyinst)
            elif inst.id in [X86_INS_NOT, X86_INS_NEG, X86_INS_INC, X86_INS_DEC]:
                if operands[0].type == X86_OP_REG and operands[0].reg == X86_REG_AL:
                    if inst.id == X86_INS_NOT:
                        pyinst = f"c = (~c) & 0xff"
                    elif inst.id == X86_INS_NEG:
                        pyinst = f"c = (-c) & 0xff"
                    elif inst.id == X86_INS_INC:
                        pyinst = f"c = (c + 1) & 0xff"
                    elif inst.id == X86_INS_DEC:
                        pyinst = f"c = (c - 1) & 0xff"
                    else:
                        assert(0)
                    user_data.operations.append(pyinst)
                elif operands[0].type == X86_OP_REG and operands[0].reg == user_data.memreg:
                    if inst.id == X86_INS_INC:
                        user_data.direction = 1
                    elif inst.id == X86_INS_DEC:
                        user_data.direction = -1
                    else:
                        #assert(0)
                        pass
            else:
                assert("al" not in inst.op_str.split(',')[0])
    pass
 
 
@handlekeyboardinterupt
def hook_code64_2(uc, address, size, user_data):
    global global_last_inst
    instbytes = uc.mem_read(address, size)
    inst = list(md.disasm(instbytes, address))[0]
    operands = list(inst.operands)
    global_last_inst = inst
 
    src_regs = set()
    target_regs = set()
    if len(operands) > 0:
        leftopreand = operands[0]
        rightopreand = operands[1] if len(operands) > 1 else operands[0]
        tainted = False
        if rightopreand.type == X86_OP_REG:
            rr = rightopreand.reg
            src_regs.add(rr)
        elif rightopreand.type == X86_OP_MEM:
            segment = rightopreand.mem.segment
            base = rightopreand.mem.base
            index = rightopreand.mem.index
            scale = rightopreand.mem.scale
            disp = rightopreand.mem.disp
            if base != 0:
                src_regs.add(base)
            if index != 0:
                src_regs.add(index)
            memaddr = disp + (uc.reg_read(capstone_reg_to_unicorn_reg(base)) if base != 0 else 0) + (uc.reg_read(capstone_reg_to_unicorn_reg(index)) if index != 0 else 0) * scale
            memvalue = u64(uc.mem_read(memaddr, 8)) if inst.id != X86_INS_LEA and segment == 0 else memaddr
            if inst.id != X86_INS_LEA and ((base != 0 and capstone_reg_to_normal_reg(base) in global_tainted_reglist) or (index != 0 and capstone_reg_to_normal_reg(index) in global_tainted_reglist)) and memaddr not in global_tainted_memlist:
                global_tainted_memlist.add(memaddr)    # important!
        if leftopreand.type == X86_OP_REG:
            rw = leftopreand.reg
            target_regs.add(rw)
            if inst.id != X86_INS_MOV and inst.id != X86_INS_LEA:
                src_regs.add(rw)
 
    tainted = False
    for rr in src_regs:
        rr_n = capstone_reg_to_normal_reg(rr)
        if rr_n in global_tainted_reglist:
            tainted = True
            break
 
    # special for xor rdi, rdi    sub rdi, rdi    and rdi, rdi
    if (inst.id in [X86_INS_XOR, X86_INS_SUB, X86_INS_AND]) and operands[0].type == X86_OP_REG and operands[1].type == X86_OP_REG and operands[0].reg == operands[1].reg:
        tainted = False
        if inst.id == X86_INS_AND:
            target_regs = set()
    # special for rsp
    if len(operands) > 0 and operands[0].type == X86_OP_REG and operands[0].reg == X86_REG_RSP:
        tainted = False
        target_regs = set()
    # special for xchg nottainted, tainted
    if inst.id == X86_INS_XCHG and operands[0].type == X86_OP_REG and operands[1].type == X86_OP_REG:
        tainted = False
        target_regs = set()
        r1 = operands[0].reg
        r2 = operands[1].reg
        if (not r1 in global_tainted_reglist) and r2 in global_tainted_reglist:
            r1, r2 = r2, r1
 
        if r1 in global_tainted_reglist and not r2 in global_tainted_reglist:
            tainted = False
            if r1 in global_tainted_reglist:
                global_tainted_reglist.remove(r1)
            if r2 not in global_tainted_reglist:
                global_tainted_reglist.add(r2)
        elif r1 in global_tainted_reglist and r2 in global_tainted_reglist:
            tainted = True
            target_regs.add(r1, r2)
    if inst.id in [X86_INS_CMP, X86_INS_NOT, X86_INS_POP, X86_INS_PUSH] or "cmov" in inst.mnemonic:
        tainted = False
        target_regs = set()
 
    for rw in target_regs:
        rw_n = capstone_reg_to_normal_reg(rw)
        if tainted:
            lv = uc.reg_read(leftopreand.reg) if leftopreand.type == X86_OP_REG and inst.id != X86_INS_MOV else 0xdeadbeefdeadbeefdeadbeef
            rv = uc.reg_read(rightopreand.reg) if rightopreand.type == X86_OP_REG else (rightopreand.imm if rightopreand.type == X86_OP_IMM else memvalue)
            print("\ttainted: 0x%x, 0x%x" % (lv, rv))
            if rw_n not in global_tainted_reglist:
                global_tainted_reglist.add(rw_n)
        else:
            if rw_n in global_tainted_reglist:
                global_tainted_reglist.remove(rw_n)
 
@handlekeyboardinterupt
def hook_mem_access(uc, access, address, size, value, user_data):
    #print(global_tainted_reglist)
    #print(len(global_tainted_memlist))
    global global_last_inst
    inst = global_last_inst
    operands = list(inst.operands)
    if access == UC_MEM_WRITE:
        rr = None
        if inst.id == X86_INS_PUSH:
            if operands[0].type == X86_OP_REG:
                rr = operands[0].reg
        elif inst.id == X86_INS_MOV:
            if operands[1].type == X86_OP_REG:
                rr = operands[1].reg
        tainted = False
 
        #print(rr, tainted, global_tainted_reglist)
        if rr and capstone_reg_to_normal_reg(rr) in global_tainted_reglist:
            tainted = True
            print("\ttainted >>> Memory is being WRITE at 0x%x, data size = %u, data value = 0x%x" \
                    %(address, size, value & 0xffffffffffffffff))
        else:
            pass
            #print("\tnottainted >>> Memory is being WRITE at 0x%x, data size = %u, data value = 0x%x" \
            #        %(address, size, value))
 
        for i in range(size):
            if tainted:
                if (address+i) not in global_tainted_memlist:
                    global_tainted_memlist.add(address+i)
            else:
                if (address+i) in global_tainted_memlist:
                    global_tainted_memlist.remove(address+i)
 
    else:   # READ
        tainted = False
        if address in global_tainted_memlist:
            tainted = True
 
        rw = None
        if len(operands) > 0 and operands[0].type == X86_OP_REG:
            rw = operands[0].reg
 
        if rw:
            rw_n = capstone_reg_to_normal_reg(rw)
 
            if tainted:
                print("\ttainted >>> Memory is being READ at 0x%x, data size = %u, data value = 0x%x" \
                        %(address, size, int.from_bytes(uc.mem_read(address, size), 'little')))
                if rw_n not in global_tainted_reglist:
                        global_tainted_reglist.add(rw_n)
            else:
                if rw_n in global_tainted_reglist:
                        global_tainted_reglist.remove(rw_n)
 
ADDRESS = 0x140000000
ADDRESS_SPACESIZE = 0x691E000
GS_BASE = 0x1000
 
mu = Uc(UC_ARCH_X86, UC_MODE_64)
mu.mem_map(ADDRESS, ADDRESS_SPACESIZE)
mu.mem_write(ADDRESS, X86_CODE64)
 
mu.reg_write(UC_X86_REG_GS_BASE, 0x1000)
mu.mem_map(GS_BASE, 0x1000)
mu.mem_write(GS_BASE+8, p64(0x1443BB3000))
mu.mem_write(GS_BASE+0x10, p64(0x143BB0000))
 
mu.reg_write(UC_X86_REG_RAX, 0xC3C3C3C3C3C3C3DF)
mu.reg_write(UC_X86_REG_RBX, 0x0000000140277000)
mu.reg_write(UC_X86_REG_RCX, 0x0000000000000000)
mu.reg_write(UC_X86_REG_RDX, 0x000000000027BA61)
mu.reg_write(UC_X86_REG_RBP, 0x000000014032C4DE)
mu.reg_write(UC_X86_REG_RSP, 0x0000000143BB2EC0)
mu.reg_write(UC_X86_REG_RSI, 0x000000014027A000)
mu.reg_write(UC_X86_REG_RDI, 0x000000014059DE7A)
mu.reg_write(UC_X86_REG_R8, 0x000000004032C4DE)
mu.reg_write(UC_X86_REG_R9, 0x000000014027E065)
mu.reg_write(UC_X86_REG_R10, 0x0000000140001000)
mu.reg_write(UC_X86_REG_R11, 0x0000000146680000)
mu.reg_write(UC_X86_REG_R12, 0x0000000000000080)
mu.reg_write(UC_X86_REG_R13, 0x0000000143BAF4F3)
mu.reg_write(UC_X86_REG_R14, 0x0000000000000000)
mu.reg_write(UC_X86_REG_R15, 0x000000000000000)
 
mu.reg_write(UC_X86_REG_EFLAGS, 0x0000000000000014)
mu.reg_write(UC_X86_REG_RIP, 0x0000000143948F58)
 
lc = LoopContext()
hpc = HoopPrintContext()
mu.hook_add(UC_HOOK_CODE, hook_code64, lc)#, ADDRESS, ADDRESS+ADDRESS_SPACESIZE)
mu.hook_add(UC_HOOK_CODE, hook_code64_2, None)
mu.hook_add(UC_HOOK_MEM_READ | UC_HOOK_MEM_WRITE, hook_mem_access)
 
for i in range(32):
    global_tainted_memlist.add(0x143BAF50C+i)
#global_serial_start = 0x143BAF50C
#mu.mem_write(0x143BAF50C, bytes.fromhex("B91AE5FCDA57D87406968CBDB8829799790A77302D7E8754B705894489B37A10"))
 
try:
    mu.emu_start(0x0000000143948F58, ADDRESS + ADDRESS_SPACESIZE)
except KeyboardInterrupt:
    import os
    os._exit(0)

代码写的不好,修了很久的bug,但是生成的trace还存在问题。

 

加上数据流跟踪之后,跑一遍trace大约需要2分钟。
附件trace.rar里的t6.txt文件是以KCTF为name、以给出的公开serial为serial作为输入得到的结果。用下面的代码筛选出trace到的指令:

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
with open("t6.txt", "r") as f:
    lines = f.readlines()
 
smallregs = [
    "ax", "bx", "cx", "dx", "si", "di", "bp", "sp", "r8w", "r9w", "r10w", "r11w", "r12w", "r13w", "r14w", "r15w",
#    "al", "ah", "bl", "bh", "cl", "ch", "dl", "dh", "sil", "dil", "bpl", "r8b", "r9b", "r10b", "r11b", "r12b", "r13b", "r14b", "r15b",
]
 
for i in range(len(lines)-3):
    #if lines[i].startswith("0x") and "rsp" not in lines[i] and "pop" not in lines[i] and "push" not in lines[i] and "cmov" not in lines[i]:
    if lines[i].startswith("0x"):
 
        line = lines[i]
        #print(line)
        inst_str = line.split('\t')[1]
        mnemonic = inst_str[:inst_str.find(' ')]
        op_str = inst_str[inst_str.find(' ')+1:]
        tmp = op_str.split(",")
        if len(tmp) >= 2:
            leftop = tmp[0].strip()
            rightop = tmp[1].strip()
        else:
            leftop = None
            rightop = None
 
        #if lines[i+1].startswith("    tainted"):
        #if (lines[i+1].startswith("    tainted:") and leftop in smallregs) \
        if (lines[i+1].startswith("    tainted:") and "rsp" not in op_str) \
               or ("ptr [r13" in lines[i] and (lines[i+1].startswith("    tainted >>>") or lines[i+2].startswith("    tainted >>>"))):
            print(lines[i], end="")
            print(lines[i+1], end="")
            if lines[i+2].startswith("    tainted"):
                print(lines[i+2], end="")

筛选结果在tt6.txt中,大约2500行,这个长度人工分析是可以接受的。

 

基于数据流分析的结果是可以做死代码消除的,由于没有做,还是有很多垃圾指令。(希望有一种方便的办法能够生成可执行的程序让ida帮忙做这件事)
突破口是读写内存的指令,从这些地方逐渐向上找。

 

最终分析下来的流程是:

  1. serial的前16字节和后16字节分别做一次变换
  2. 一个类似base64的解码过程,每4字节通过查表和移位得到3字节
  3. 上一步的得到24字节,前8字节做一些变换之后判断是否与name的前8字节相等;后16字节做一些变换(与第一步的变换相同)之后判断是否与name的16字节相等(如果输入的name不足16字节,内存中有一些原始数据用来补齐,可以直接提取出来)

(数据流跟踪的代码有bug导致这里有些指令的trace丢失了(特别是第3步里对前8字节的处理),只好根据上下文去猜)

 

serial的验证以及从name生成serial的程序如下:

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
def p16(n):
    n &= 0xffff
    return n.to_bytes(2, 'little')
 
def u16(s):
    assert(len(s) == 2)
    return int.from_bytes(s, 'little')
 
def p64(n):
    n &= 0xffffffffffffffff
    return n.to_bytes(8, 'little')
 
def u64(s):
    assert(len(s) == 8)
    return int.from_bytes(s, 'little')
 
def rol16(n, k):
    n &= 0xffff
    return ((n << k) | (n >> (16-k))) & 0xffff
 
def ror16(n, k):
    n &= 0xffff
    return ((n >> k) | (n << (16-k))) & 0xffff
 
def doencrypt1(buf, indextable):
    assert(len(buf) == 16)
 
    buf1 = bytearray(buf)
 
    v1 = u16(buf1[0:2])    # s1 ^ k4
    v2 = u16(buf1[2:4])    # s2 ^ k4
    v3 = u16(buf1[4:6])    # s3 + k3
    v4 = u16(buf1[6:8])    # s4 - k3
    v5 = u16(buf1[8:0xa])   # s5 + k2
    v6 = u16(buf1[0xa:0xc])    # s6 + k2
    v7 = u16(buf1[0xc:0xe])    # s7 ^ k1
    v8 = u16(buf1[0xe:0x10])    # s8 ^ k1
 
    t1 = v2 ^ v1    # s2 ^ s1
    t2 = (v4 + v3) & 0xffff    # s4 + s3
    t3 = (v5 - v6) & 0xffff    # s5 - s6
    cl = bin(v8 ^ v7).lstrip("0b").count('1')    # s8 ^ s7# hamming distance
 
    v7 = ror16(v7, cl)
    v8 = ror16(v8, cl)
 
    k1 = ( (t2 & t1) | ((~t1) & t3) ) & 0xffff
    k2 = ( ((k1 * t1) >> cl) + 0x18) & 0xffff
    k3 = ( k2 ^ t3 ) & 0xffff    # 0x79c6
    k4 = ( (k2 & k1) | (k3 & (k2 | k1)) ) & 0xffff
 
    s1 = p16(v1 ^ k4)
    s2 = p16(v2 ^ k4)
    s3 = p16(v3 - k3)
    s4 = p16(v4 + k3)
    s5 = p16(v5 - k2)
    s6 = p16(v6 - k2)
    s7 = p16(v7 ^ k1)
    s8 = p16(v8 ^ k1)
 
    buf1[indextable[0]] = s1[1]
    buf1[indextable[8]] = s1[0]
    buf1[indextable[1]] = s2[1]
    buf1[indextable[9]] = s2[0]
    buf1[indextable[2]] = s3[1]
    buf1[indextable[10]] = s3[0]
    buf1[indextable[3]] = s4[1]
    buf1[indextable[11]] = s4[0]
    buf1[indextable[4]] = s5[1]
    buf1[indextable[12]] = s5[0]
    buf1[indextable[5]] = s6[1]
    buf1[indextable[13]] = s6[0]
    buf1[indextable[6]] = s7[1]
    buf1[indextable[14]] = s7[0]
    buf1[indextable[7]] = s8[1]
    buf1[indextable[15]] = s8[0]
 
    return buf1
 
 
def dodecrypt1(serial, indextable):
    assert(len(serial) == 16)
 
    buf1 = bytearray(serial)
 
    s1 = (buf1[indextable[0]] << 8) | buf1[indextable[8]]
    s2 = (buf1[indextable[1]] << 8) | buf1[indextable[9]]
    s3 = (buf1[indextable[2]] << 8) | buf1[indextable[10]]
    s4 = (buf1[indextable[3]] << 8) | buf1[indextable[11]]
    s5 = (buf1[indextable[4]] << 8) | buf1[indextable[12]]
    s6 = (buf1[indextable[5]] << 8) | buf1[indextable[13]]
    s7 = (buf1[indextable[6]] << 8) | buf1[indextable[14]]
    s8 = (buf1[indextable[7]] << 8) | buf1[indextable[15]]
 
    '''
    s1 = u16(buf1[0xb:0xd])    # 0x80c8
    s2 = u16(buf1[9:0xb])    # 0x8b84
    s3 = u16(buf1[7:9])    # 0xefa8
    s4 = u16(buf1[5:7])    # 0xf12e
    s5 = u16(buf1[3:5])    # 0x0x7a
    s6 = u16(buf1[1:3])    # 0xba4d
    s7 = (buf1[0] << 8) | buf1[0xf]    # 0x7d70
    s8 = u16(buf1[0xd:0xf])    # 0x31b8
    '''
 
    t1 = s2 ^ s1    # 0xb4c
    t2 = (s4 + s3) & 0xffff    # 0xe0d6
    t3 = (s5 - s6) & 0xffff    # 0xe22d
    cl = bin(s8 ^ s7).lstrip("0b").count('1')    # 6    # hamming distance
 
    print(hex(s1), hex(s2), hex(s3), hex(s4), hex(s5), hex(s6), hex(s7), hex(s8))
    print(hex(t1), hex(t2), hex(t3), hex(cl))
 
    k1 = ( (t2 & t1) | ((~t1) & t3) ) & 0xffff    # 0xe065
    buf1[0xc:0xc+2] = p16(rol16(s7 ^ k1, cl))
    buf1[0xe:0xe+2] = p16(rol16(s8 ^ k1, cl))
 
    k2 = ( ((k1 * t1) >> cl) + 0x18) & 0xffff    # 0x9beb
    buf1[0x8:0x8+2] = p16(s5 + k2)
    buf1[0xa:0xa+2] = p16(s6 + k2)
 
    k3 = ( k2 ^ t3 ) & 0xffff    # 0x79c6
    buf1[0x6:0x6+2] = p16(s4 - k3)
    buf1[0x4:0x4+2] = p16(s3 + k3)
 
    k4 = ( (k2 & k1) | (k3 & (k2 | k1)) ) & 0xffff
    buf1[0x0:0x0+2] = p16(s1 ^ k4)
    buf1[0x2:0x2+2] = p16(s2 ^ k4)
 
    print(hex(k1), hex(k2), hex(k3), hex(k4))
 
    return buf1
 
def doencode2(s):
    assert(len(s) % 3 == 0)
    chartable = [149, 226, 128, 198, 234, 195, 213, 141, 158, 197, 179, 98, 100, 77, 118, 186, 146, 253, 222, 127, 66, 114, 129, 173, 121, 84, 115, 133, 134, 94, 241, 132, 106, 245, 99, 216, 254, 168, 192, 200, 79, 201, 199, 3, 123, 229, 223, 2, 0, 13, 31, 60, 19, 34, 37, 59, 43, 23, 170, 160, 246, 151, 89, 88, 109, 15, 12, 6, 61, 27, 14, 33, 20, 38, 45, 4, 18, 28, 9, 1, 46, 51, 53, 35, 32, 11, 56, 26, 50, 44, 57, 124, 209, 242, 92, 117, 161, 8, 36, 16, 40, 41, 63, 49, 7, 10, 47, 17, 25, 30, 62, 21, 29, 42, 58, 52, 22, 5, 54, 55, 39, 48, 24, 108, 74, 122, 68, 152, 150, 105, 196, 235, 204, 73, 190, 181, 72, 113, 148, 225, 163, 177, 120, 250, 83, 70, 64, 203, 188, 71, 131, 193, 238, 249, 232, 97, 169, 251, 194, 210, 76, 85, 218, 247, 126, 217, 143, 172, 227, 82, 96, 155, 233, 86, 156, 137, 87, 180, 81, 125, 176, 116, 142, 162, 157, 237, 182, 224, 95, 252, 75, 110, 165, 65, 208, 167, 187, 240, 140, 145, 101, 185, 212, 230, 135, 184, 191, 248, 236, 159, 154, 211, 111, 147, 93, 102, 136, 67, 91, 80, 243, 130, 183, 206, 103, 231, 244, 255, 175, 205, 214, 220, 171, 104, 90, 138, 221, 239, 228, 189, 78, 164, 119, 178, 69, 166, 153, 112, 219, 107, 215, 144, 207, 174, 139, 202]
    rchartable = [None]*256
    for i, c in enumerate(chartable):
        rchartable[c] = i
 
    r = b""
    for i in range(0, len(s), 3):
        x, y, z = s[i], s[i+1], s[i+2]
        a = (x & 0x3) | ((z & 0xf) << 2)
        b = (y & 0x3c) | ((x & 0xc) >> 2)
        c = (x >> 4) | ((y & 0x3) << 4)
        d = ((y & 0xc0) >> 2) | (z >> 4)
        r += bytes([rchartable[a], rchartable[b], rchartable[c], rchartable[d]])
    return r
 
 
def dodecode2(s):
    assert(len(s) % 4 == 0)
    chartable = [149, 226, 128, 198, 234, 195, 213, 141, 158, 197, 179, 98, 100, 77, 118, 186, 146, 253, 222, 127, 66, 114, 129, 173, 121, 84, 115, 133, 134, 94, 241, 132, 106, 245, 99, 216, 254, 168, 192, 200, 79, 201, 199, 3, 123, 229, 223, 2, 0, 13, 31, 60, 19, 34, 37, 59, 43, 23, 170, 160, 246, 151, 89, 88, 109, 15, 12, 6, 61, 27, 14, 33, 20, 38, 45, 4, 18, 28, 9, 1, 46, 51, 53, 35, 32, 11, 56, 26, 50, 44, 57, 124, 209, 242, 92, 117, 161, 8, 36, 16, 40, 41, 63, 49, 7, 10, 47, 17, 25, 30, 62, 21, 29, 42, 58, 52, 22, 5, 54, 55, 39, 48, 24, 108, 74, 122, 68, 152, 150, 105, 196, 235, 204, 73, 190, 181, 72, 113, 148, 225, 163, 177, 120, 250, 83, 70, 64, 203, 188, 71, 131, 193, 238, 249, 232, 97, 169, 251, 194, 210, 76, 85, 218, 247, 126, 217, 143, 172, 227, 82, 96, 155, 233, 86, 156, 137, 87, 180, 81, 125, 176, 116, 142, 162, 157, 237, 182, 224, 95, 252, 75, 110, 165, 65, 208, 167, 187, 240, 140, 145, 101, 185, 212, 230, 135, 184, 191, 248, 236, 159, 154, 211, 111, 147, 93, 102, 136, 67, 91, 80, 243, 130, 183, 206, 103, 231, 244, 255, 175, 205, 214, 220, 171, 104, 90, 138, 221, 239, 228, 189, 78, 164, 119, 178, 69, 166, 153, 112, 219, 107, 215, 144, 207, 174, 139, 202]
 
    tmp = bytearray(len(s))
    for i, c in enumerate(s):
        tmp[i] = chartable[c]
 
    for c in tmp:
        assert(c < 0x40)
 
    r = b""
    for i in range(0, len(tmp), 4):
        a, b, c, d = tmp[i], tmp[i+1], tmp[i+2], tmp[i+3]
        x = ((c << 4) & 0xff) | ((b & 0x3) << 2) | (a & 0x3)
        y = ((d & 0xf0) << 2) | (b & 0x3c) | ((c & 0x3f) >> 4)
        z = ((d << 4) & 0xff) | ((a & 0x3f) >> 2)
        r += bytes([x, y, z])
    return r
 
def doencode3(r):
    assert(len(r) == 8)
    r = p64(u64(r)^0x8267f5d9b0ea143c)
    s = bytearray(8)
    s[3] = r[0]
    s[2] = r[2] ^ s[3]
    s[1] = r[1] ^ s[2]
    s[0] = r[3] ^ s[1]
    s[7] = r[4]
    s[6] = r[6] ^ s[7]
    s[5] = r[5] ^ s[6]
    s[4] = r[7] ^ s[5]
    return s
 
def dodecode3(s):
    assert(len(s) == 8)
    r = bytearray(8)
    r[0] = s[3]
    r[1] = s[1] ^ s[2]
    r[2] = s[2] ^ s[3]
    r[3] = s[0] ^ s[1]
    r[4] = s[7]
    r[5] = s[5] ^ s[6]
    r[6] = s[6] ^ s[7]
    r[7] = s[4] ^ s[5]
    return p64(u64(r)^0x8267f5d9b0ea143c)
 
def name_to_serial(name):
    assert(len(name) <= 16)
 
    buf5 = bytearray(b"\x9a\x1e\x1d\x1c\x1b\x1a\x19\x18\x17\x16\x15\x14\x13\x12\x11\x10")
    buf5[:len(name)] = name
    if len(name) < 16:
        buf5[len(name)] = 0
    print(buf5.hex())
 
    # stage4
 
    indextable3 = [0xb, 0xd, 0xf, 0x1, 0x3, 0x5, 0x7, 0x9, 0xc, 0xe, 0x0, 0x2, 0x4, 0x6, 0x8, 0xa]
    buf3_2 = doencrypt1(buf5, indextable3)
    print(buf3_2.hex())
 
    # stage3
 
    print("buf5", buf5[:8])
    buf3_1 = doencode3(buf5[:8])
    print(buf3_1.hex())
 
    buf3 = buf3_1 + buf3_2
 
    # stage2
 
    buf2 = doencode2(buf3)
    print(buf2.hex())
 
    # stage1
 
    indextable1 = [0xc, 0xa, 0x8, 0x6, 0x4, 0x2, 0x0, 0xe, 0xb, 0x9, 0x7, 0x5, 0x3, 0x1, 0xf, 0xd]
    indextable2 = [0xb, 0x9, 0x7, 0x5, 0x3, 0x1, 0xf, 0xd, 0x8, 0x6, 0x4, 0x2, 0x0, 0xe, 0xc, 0xa]
 
    serial = doencrypt1(buf2[:16], indextable1) + doencrypt1(buf2[16:], indextable2)
    print("serial")
    print(serial.hex())
 
    return serial
 
def serial_to_name(serial):
    assert(len(serial) == 32)
 
    # stage1
 
    indextable1 = [0xc, 0xa, 0x8, 0x6, 0x4, 0x2, 0x0, 0xe, 0xb, 0x9, 0x7, 0x5, 0x3, 0x1, 0xf, 0xd]
    indextable2 = [0xb, 0x9, 0x7, 0x5, 0x3, 0x1, 0xf, 0xd, 0x8, 0x6, 0x4, 0x2, 0x0, 0xe, 0xc, 0xa]
 
    buf2 = dodecrypt1(serial[:16], indextable1) + dodecrypt1(serial[16:], indextable2)
    print("buf2:", buf2.hex(), buf2)
 
    # stage2
 
    buf3 = dodecode2(buf2)
    print("buf3", buf3.hex())
 
    # stage3
 
    print(buf3[:8].hex())
    buf4 = dodecode3(buf3[:8])
 
    # stage4
 
    indextable3 = [0xb, 0xd, 0xf, 0x1, 0x3, 0x5, 0x7, 0x9, 0xc, 0xe, 0x0, 0x2, 0x4, 0x6, 0x8, 0xa]
    buf5 = dodecrypt1(buf3[8:], indextable3)
 
    print(buf5)
 
    # stage5
 
    assert(buf4 == buf5[:8])
 
    return bytes(buf5)
 
test_serial = bytes.fromhex(("7D 4D BA 7A    9C 2E F1 A8    EF 84 8B C8    80 B8 31 70" + "43 1F 37 E5    99 04 8D BB    88 C3 06 BC    40 35 79 D1").replace(" ", ""))
 
#print(name_to_serial(b"FE0C37052AED0E33"))
#serial_to_name(test_serial)
 
serial = name_to_serial(b"KCTF")
print(serial_to_name(serial))
 
print(serial.hex().upper())    # B91AE5FCDA57D87406968CBDB8829799790A77302D7E8754B705894489B37A10

附件:
memdump.rar:运行到真正的验证逻辑首次开始读取serial时的内存状态以及寄存器状态,用于模拟执行
trace.rar:基于模拟执行生成的完整trace文件以及过滤结果。t6.txt和tt6.txt直接对应memdump,输入为KCTF / 公开的序列号;t7.txt和tt7.txt则是在unicorn中把serial对应的内存覆盖为真正的serial后得到的结果。


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

上传的附件:
收藏
点赞7
打赏
分享
最新回复 (4)
雪    币: 163
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
本人鹿之川 2021-5-25 10:45
2
0
雪    币: 1467
活跃值: (2197)
能力值: ( LV9,RANK:156 )
在线值:
发帖
回帖
粉丝
whydbg 2021-5-25 15:34
3
0
可以把 dodecryptblock.c 分享一下吗?
雪    币: 19662
活跃值: (10428)
能力值: ( LV15,RANK:1454 )
在线值:
发帖
回帖
粉丝
mb_mgodlfyn 12 2021-5-25 23:05
4
0
whydbg 可以把 dodecryptblock.c 分享一下吗?
注释里就是这个文件的代码
 
__declspec(dllexport) void dodecryptblock(unsigned char *buf, int len, unsigned char table[256]) {
    for (int i = 0; i < len; i++) {
        buf[i] = table[buf[i]];
    }
}
雪    币: 1467
活跃值: (2197)
能力值: ( LV9,RANK:156 )
在线值:
发帖
回帖
粉丝
whydbg 2021-5-26 08:32
5
0
谢谢          
游客
登录 | 注册 方可回帖
返回