0x00 前言

纯动调,这玩意IDA看不了一点

因为用了iTruth版本的x64dbg,整合了不少插件(好多不知道啥用的)所以

听说有反调试?无駄!

0x01 解

IDA看不了一点(好像是ollvm),上x64dbg一步步调

从输入下手,运行直到要求输入然后暂停,随便输点东西就会停下在syscall后面,回到用户空间后一步步ret回去,直到找到类似scanf参数的地方

之后一步步调,同时开启追踪记录,字节为单位,重点查看访问多次的内容,注意因为有大量花指令的存在,所以一定要用步进而不是步过来调,要不然随便哪个带call的花就会退出

首先可以发现这里调用了输入的内容

之后进了一个函数(其他花指令的call都是很近的,但是这个call跳转地址很远)

根据返回的值可以知道这是获取输入长度的(可能是strlen?)

继续跟踪发现这里被调用了三次之多,下个断点重启程序看看

重新执行直到调用strlen,然后看看输入长度被存到了哪里

发现存到了下面两个地址里面

下个两个硬断看看啥时候调用

发现丢到rdx里面去了

跟下去,发现rdx被拿去作比较了

估计这就是flag的长度了,试试发现确实是

然后应该可以推进了,找到存输入的位置(栈里面可以找到)下硬断,然后会断在这

往下走发现rdx的值先加了0x2400D5A6,然后加了0x40,最后又减去了0x2400D5A6,所以最后结果就是加了0x40

然后存回了这个地方

之后继续走发现每个字节都做了这个加0x40的操作

全部走完之后再给第一个下硬断,发现接下来的操作是

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
#读一字节给到edx
mov r8d, edx # 设传入为00 edx = 00, r8d = 00
xor r8d, 0xFFFFFFFF # edx = FFFF FFFF, r8d = FFFF FFFF
and edx, 0x16030876 # edx = 00, r8d = FFFF FFFF
and r8d, 0xE9FCF789 # edx = 00, r8d = E9FC F789
xor edx, 0xFFFFFFFF # edx = FFFF FFFF, r8d = E9FC F789
xor r8d, 0xFFFFFFFF # edx = FFFF FFFF, r8d = 1603 0876
and r8d, edx # edx = FFFF FFFF, r8d = 1603 0876
xor r8d, 0xFFFFFFFF # edx = FFFF FFFF, r8d = E9FC F789
mov edx, r8d # edx = E9FC F789, r8d = E9FC F789
xor edx, 0xFFFFFFFF # edx = 1603 0876, r8d = E9FC F789
and r8d, 0xF49D22FF # edx = 1603 0876, r8d = E09C 2289
and edx, 0xB62DD00 # edx = 0202 0800, r8d = E09C 2289
xor r8d, 0xFFFFFFFF # edx = 0202 0800, r8d = 1F63 DD76
xor edx, 0xFFFFFFFF # edx = FDFD F7FF, r8d = 1F63 DD76
and edx, r8d # edx = 1D61 D576, r8d = 1F63 DD76
xor edx, 0xFFFFFFFF # edx = E29E 2A89, r8d = 1F63 DD76
mov r8d, edx # edx = E29E 2A89, r8d = E29E 2A89
xor r8d, 0xFFFFFFFF # edx = E29E 2A89, r8d = 1D61 D576
and r8d, 0xE29E2AF6 # edx = E29E 2A89, r8d = 76
and edx, 0x1D61D509 # edx = 9, r8d = 76
xor r8d, 0xFFFFFFFF # edx = 9, r8d = FFFF FF89
xor edx, 0xFFFFFFFF # edx = FFFF FFF6, r8d = FFFF FF89
and edx, r8d # edx = FFFF FF80, r8d = FFFF FF89
xor edx, 0xFFFFFFFF # edx = 7F
mov r8, 0xFFFF4AC263967EF6
sub r10,r8
#将edx最低位放回

好恐怖,这么多的操作加起来只有一个^0x7F

之后故技重施,全部加密完之后再在第一个下硬断

之后断在这

慢慢调,发现每次加密会先取8个字节存起来,之后才加密

然后调到这会发现rax的值很是熟悉

看来是TEA相关算法,8字节一组来加密

那么是那个TEA呢,继续调,特别注意调用内存地址的地方

到这里

[rsp+0xC]里面的内容就是0x9E3779B9而根据shr r8d, 0xB可以知道这里是右移了11位,而将加密中出现右移11位操作的是XTEA,所以可以知道这边的加密算法用的是XTEA

继续调,可以发现r8d经过一系列运算之后拿去做rax的索引了,所以上图中赋值给rax的内容即上面的这个[rsp+0x28]存的就是密钥了

1
2
# 注意在内存窗口里面是little-endian
0xEF6FD9DB, 0xD2C273D3, 0x6F97E412, 0x72BFD624

至于轮数,在这块内存地址周围找找应该是能找到的,或者在这可以找到,内存选中的为index,后面那个就是轮数,下面一点就可以看到密钥

至此基本都知道了

提一下这块内存地址什么都有

标出来的是密钥,向上那个0x9DD3FFFDF0是key的索引,其左边0x7FF74C194CF8是原数据的索引,之后0x66是轮数,0x10是index,0x63F13C25是v0,0x7F0B9E5F是v1,0x81AF1549是sum,0x9E3779B9是delta

XTEA相关内容出来之后就去找密文了,继续走,在取最后8Bytes的时候在第一个下硬件断点(要不然直接就退了)

断在这

image-20241026225815741

之后一步步走,发现在这有个比较

反向跟一下可以找到r8d的来源在这

估计就是密文了,提取一下

1
0x9851E3A1, 0x49765686, 0x812B6B6F, 0X9612CECF, 0X3C3570A2, 0XF15C6231, 0XAA6B77FA, 0XBE056D9E, 0XF8A424E8, 0X0B3A23DB, 0X03CC2016, 0XA92BB5AD, 0X1D789F34, 0X9EF9B92E

后面就是写脚本解密了

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
import struct

def decrypt(rounds, v, k):
v0 = v[0]
v1 = v[1]
delta = 0x9E3779B9
x = delta * rounds
for i in range(rounds):
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (x + k[(x >> 11) & 3])
v1 = v1 & 0xFFFFFFFF
x -= delta
x = x & 0xFFFFFFFF
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (x + k[x & 3])
v0 = v0 & 0xFFFFFFFF
v[0] = v0
v[1] = v1
return v

if __name__ == '__main__':
k = [0xEF6FD9DB, 0xD2C273D3, 0x6F97E412, 0x72BFD624]
rounds = 0x66
enctxt = [0x9851E3A1, 0x49765686, 0x812B6B6F, 0X9612CECF, 0X3C3570A2, 0XF15C6231, 0XAA6B77FA, 0XBE056D9E, 0XF8A424E8, 0X0B3A23DB, 0X03CC2016, 0XA92BB5AD, 0X1D789F34, 0X9EF9B92E]
i = 0
for j in range(int(len(enctxt)/2)):
tmp = [0]*2
tmp[0] = enctxt[i]
tmp[1] = enctxt[i+1]
tmp = decrypt(rounds, tmp, k)
enctxt[i] = tmp[0]
enctxt[i+1] = tmp[1]
i += 2
for i in range(len(enctxt)):
enctxt[i]^=0x7F7F7F7F
enctxt[i]-=0x40404040
tmp = b''
for i in range(len(enctxt)):
tmp += struct.pack('<L', enctxt[i])
print(tmp)
# b'flag{u_ar3_re@11y_g00d_@t_011vm_de0bf_and_anti_debugger}'

附一下断点地址