2023 Hitcon CTF The Blade Reverse
Jsjsj Lv2

前言:很有意思一道Reverse题,在位移加密有很多巧妙方法,这里记录下做题的过程

这道题学到了很多东西呜呜呜,本篇记录下调试当中的一些技巧方法和参照3cly师傅的博客一些加密的骚操作

此题是个rust 逆向题,是个socket类型的交互题,我们使用字符串定位的方法定位到关键部分,这里我定位到了如下代码位置(前部分找入口找到对应字符串直接交叉引用即可):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
_rust_dealloc();
goto LABEL_178;
}
}
break;
case 0x67616C66: // flag
v18 = 9LL;
v19 = "IncorrectCorrectError: portscan: too many argumentsnetcatError: netcat: too many argumentsError: netcat: invalid portError: netcat: no input file specifiedUnknown command '";
if ( v182 == 2 )
{
v51 = seccomp_shell::shell::verify::h898bf5fa26dafbab(
input, // flag
*(_QWORD *)(v175 + 24),
*(_QWORD *)(v175 + 40));
v53 = v52;
if ( v51 )
seccomp_shell::util::print_failed::h41a9d0b5672e2e2f(
"IncorrectCorrectError: portscan: too many argumentsnetcatError: netcat: too many argumentsError: netcat: invalid portError: netcat: no input file specifiedUnknown command '",
9LL);
else

seccomp_shell::shell::verify::h898bf5fa26dafbab 它的传参就是我们要输出的字符串(flag) 这个函数就是我们要逆的主要函数

我们进到里面发现代码很多,这里我列出关键部分代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
memcpy(dest, &unk_62920, 0x200uLL);
v52 = 64LL;
v53 = (__int64 *)dest + 1;
do
{
v54 = *(v53 - 1);
if ( v54 > 0x3F )
goto LABEL_53;
v55 = flag[v52 - 1];
flag[v52 - 1] = flag[v54];
flag[v54] = v55;
v56 = *v53;
if ( (unsigned __int64)*v53 > 0x3F )
goto LABEL_53;
v57 = flag[v52 - 2];
flag[v52 - 2] = flag[v56];
flag[v56] = v57;
v53 += 2;
v52 -= 2LL;
}
while ( v52 );

我截取了最后的换位加密的代码,前面还有好几个这样的换位加密,但它换位加密的表在整个while循环一次下来都是固定的,在最外层的while循环里是256次,如下代码:

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
  while ( v52 );
v58 = 0LL;
do
{
v59 = (unsigned __int8)flag[v58] + 1;
LOWORD(v51) = 1;
LOWORD(v52) = 257;
v60 = 0;
do
{
v62 = v52;
LOWORD(v52) = (unsigned __int16)v52 / (unsigned __int16)v59;
v61 = v62 % (unsigned __int16)v59;
v63 = v51;
v51 = v60 - v51 * v52;
LODWORD(v52) = v59;
v59 = (unsigned __int16)(v62 % (unsigned __int16)v59);
v60 = v63;
}
while ( v61 );
v64 = 0;
if ( (__int16)v63 > 0 )
v64 = v63;
flag[v58] = ((unsigned __int16)(v64 + ((__int16)v63 >> 15) - v63) / 0x101u
+ v63
+ ((unsigned __int16)v63 >> 15)
+ 113) ^ 0x89;
v52 = v58 + 1;
v58 = v52;
}
while ( v52 != 64 );
}
while ( v8 != 256 );

在经历完换位加密后又进行了一次除取余的操作64次,这里猜测一次只能处理64个字节(其实就是64字节)通过这段加密将整个输入的字符串进行了一个除+取余等操作

经过逆向分析以上进行换位位移加密都是用的固定的索引,在以上换位加密代码和第二次加密的行为与用户输入值没有任何关系,用 00-ff 的映射(引用师傅的一句话逆向加密算法比较复杂,就找出 00-ff 的映射就行了 因为是按位加密的伐然后前面的几次打乱顺序由于是固定的表所以可以看成一次打乱顺序 找出索引交换的顺序即可)

生成 0~ff 的 hex 序列:

1
2
for i in range(0, 256, 64):
print(bytearray(range(i, i+64)).hex())

这里先放这,我们测试的数据是flag 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{} 先把加密前的数据提取出来,我把断点下在了这个位置

image

提取flag变量里的数据

数据是HfVl{qPcCYNMoRi6D7Jr}espOL3FhwdWAtTGZba4Ugjvnx1QkKE2IS9yuz5BX08m

这是打乱后的字符串,也就是在经历下面加密前的字符串

下面我们用上面hex序列获取加密后的数据,为什么要生成256,因为整个wile循环是256所以我们要生成256个字字节分别进行64个字节的处理,总共调4次即可

我把断点下在了如下的位置:

image

image

image

我下了如图两个断点,一个是加密前(打乱后),另一个是整个执行完一次后的加密

我们动调的时候用idapython脚本去把flag变量值给改下,脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import idaapi
import idc
import struct

# 获取当前选中的地址
current_address = idc.get_screen_ea()

# 获取堆栈上的指针变量地址
stack_pointer = 0x556646C923E0

# 要写入的非常长的数据按小端序存储
data = bytes.fromhex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff")

# 逐字节将数据按小端序写入指定地址
for i in range(0, len(data), 4):
value = struct.unpack("<I", data[i:i+4])[0] # 小端序
idaapi.patch_dword(stack_pointer + i, value)

# 打印结果
idaapi.msg("数据已按小端序写入堆栈地址: 0x{:X}\n".format(stack_pointer))

f9运行到下面哪个断点提取的加密数据是

[0xFB, 0x7B, 0x4E, 0xBB, 0x51, 0x15, 0x8D, 0xDB, 0xB0, 0xAC, 0xA5, 0x8E, 0xAA, 0xB2, 0x60, 0xEB, 0x63, 0x5C, 0xDE, 0x42, 0x2B, 0xC6, 0xA6, 0x35, 0x30, 0x43, 0xD6, 0x5F, 0xBD, 0x24, 0xB1, 0xE3, 0x8C, 0xA7, 0xD5, 0x2A, 0x7C, 0x6D, 0x8B, 0x17, 0x9D, 0x83, 0xFE, 0x69, 0x10, 0x59, 0xA9, 0x9E, 0x0F, 0x1C, 0x66, 0x97, 0x5B, 0x61, 0xED, 0xAD, 0xE0, 0xDA, 0x27, 0x06, 0x25, 0xDC, 0x5E, 0xE7, 0x40, 0x41]

我们发现第一次提取到0x40前,因为一次加密只能处理64个字节,所以我们这里还要再进行3次处理剩余的序列,我们再在动调的时候把flag这个变量值改为0x40开始

第二次数据是4132D2D98FEEAF03933A00A2E1B3EC819FCA58B779FD3BA0020CCBA880C0164D2F75710A0439FFC19CABEFA4D8E214C26C641E6B7E992E090B86746AC42D4FF980

剩下再从80开始动调,直到00-255个数据加密完后就是我们动调后的加密密文

找到后,先还原下加密后的数据,这里参考了3cly师傅的脚本

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
def decrypt(data: list, table: dict):
tmp=[]
for i in data:
tmp.append(table[i])
return tmp

def reverse_order(data:list, table: list):
tmp = []
for i in range(64):
tmp.append(data[table[i]])
return tmp

#get crypto table
s0_255 = [0xFB, 0x7B, 0x4E, 0xBB, 0x51, 0x15, 0x8D, 0xDB, 0xB0, 0xAC, 0xA5, 0x8E, 0xAA, 0xB2, 0x60, 0xEB, 0x63, 0x5C, 0xDE, 0x42, 0x2B, 0xC6, 0xA6, 0x35, 0x30, 0x43, 0xD6, 0x5F, 0xBD, 0x24, 0xB1, 0xE3, 0x8C, 0xA7, 0xD5, 0x2A, 0x7C, 0x6D, 0x8B, 0x17, 0x9D, 0x83, 0xFE, 0x69, 0x10, 0x59, 0xA9, 0x9E, 0x0F, 0x1C, 0x66, 0x97, 0x5B, 0x61, 0xED, 0xAD, 0xE0, 0xDA, 0x27, 0x06, 0x25, 0xDC, 0x5E, 0xE7,
0x41, 0x32, 0xD2, 0xD9, 0x8F, 0xEE, 0xAF, 0x03, 0x93, 0x3A, 0x00, 0xA2, 0xE1, 0xB3, 0xEC, 0x81, 0x9F, 0xCA, 0x58, 0xB7, 0x79, 0xFD, 0x3B, 0xA0, 0x02, 0x0C, 0xCB, 0xA8, 0x80, 0xC0, 0x16, 0x4D, 0x2F, 0x75, 0x71, 0x0A, 0x04, 0x39, 0xFF, 0xC1, 0x9C, 0xAB, 0xEF, 0xA4, 0xD8, 0xE2, 0x14, 0xC2, 0x6C, 0x64, 0x1E, 0x6B, 0x7E, 0x99, 0x2E, 0x09, 0x0B, 0x86, 0x74, 0x6A, 0xC4, 0x2D, 0x4F, 0xF9,
0xFA, 0x94, 0xB6, 0x1F, 0x89, 0x6F, 0x5D, 0xE8, 0xEA, 0xB5, 0x5A, 0x65, 0x88, 0xC5, 0x7F, 0x77, 0x11, 0xCF, 0xF1, 0x1B, 0x3F, 0xF4, 0x48, 0x47, 0x12, 0xE4, 0xBA, 0xDF, 0xE9, 0x62, 0x6E, 0xB4, 0x96, 0xCD, 0x13, 0x53, 0x4B, 0x28, 0xD7, 0xD1, 0x33, 0xB8, 0xE6, 0x7A, 0x2C, 0x9B, 0x29, 0x44, 0x52, 0xF7, 0x20, 0xF2, 0x31, 0xD3, 0xB9, 0x40, 0xD0, 0x34, 0xF5, 0x54, 0x1A, 0x01, 0xA1, 0x92,
0xFC, 0x85, 0x07, 0xBE, 0xDD, 0xBC, 0x19, 0xF3, 0x36, 0xF6, 0x72, 0x98, 0x4C, 0x7D, 0xC7, 0xD4, 0x45, 0x4A, 0x9A, 0xC3, 0x8A, 0xE5, 0x50, 0x46, 0xCC, 0x68, 0x76, 0x67, 0xC9, 0x0E, 0x3C, 0x57, 0xF0, 0x22, 0xBF, 0x26, 0x84, 0x0D, 0x90, 0xA3, 0xAE, 0x3D, 0x1D, 0xC8, 0x91, 0x05, 0x87, 0x70, 0x08, 0x73, 0x21, 0x49, 0x55, 0x3E, 0x37, 0x23, 0x18, 0x56, 0xCE, 0x82, 0x38, 0x95, 0x78, 0xF8]

crypto_table = dict(zip(s0_255,range(0x100)))

#get order table
source = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}'
replaced = 'HfVl{qPcCYNMoRi6D7Jr}espOL3FhwdWAtTGZba4Ugjvnx1QkKE2IS9yuz5BX08m'
revsere_table = [] #index 是 source 在 replace 中的下标
for i in source:
revsere_table.append(replaced.find(i))

tmp = [0x52, 0xCB, 0x15, 0x10, 0x7E, 0xD3, 0x78, 0x26, 0xC2, 0x14, 0x09, 0x50, 0x55, 0xFA, 0xEE, 0xC3, 0x0A, 0x97, 0xB9, 0x38, 0x12, 0x3D, 0x0E, 0xE9, 0xBE, 0xF6, 0x2B, 0x66, 0x67, 0xA8, 0x87, 0xAE, 0x1D, 0x53, 0x62, 0xEC, 0xFC, 0x5C, 0x88, 0x68, 0x23, 0x5B, 0x36, 0x13, 0xFB, 0xD7, 0xCA, 0x7A, 0xBD, 0xD9, 0x69, 0x6A, 0xE4, 0x2A, 0x6C, 0x9D, 0x86, 0xE0, 0xA4, 0x01, 0xBA, 0x3B, 0x20, 0x92]
for i in range(256):
tmp = decrypt(tmp, crypto_table)
tmp = reverse_order(tmp, revsere_table)
print(''.join(chr(i) for i in tmp))
# 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}

通过以上脚本我们发现可以还原原来的明文

tmp是我们0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}整个流程加密后的数据动调就可以得到,这里是占时的值,因为后面还有加密的操作

然后利用字母对应表还原,(这个操作算是学到了呜呜呜tql3cly师傅)

下一步我们接着往下调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
while ( v8 != 256 );
v65 = (void *)alloc::raw_vec::RawVec$LT$T$C$A$GT$::allocate_in::h9362616e9151d1f3(255LL, 0LL);
v67 = v66;
memcpy(v65, sub_5625F9C2BB2B, 0xFFuLL);
v81 = v65;
v82 = v67;
v83 = 255LL;
alloc::vec::Vec$LT$T$C$A$GT$::resize::h7362553f00beaec8(&v81, 255LL, 0LL);
if ( *(_DWORD *)(v85 + 16) == -1 )
core::panicking::panic::h65157a6ac7f1357a();
v84 = v85 + 16;
dest[0] = xmmword_5625F9C29010;
dest[1] = xmmword_5625F9C29020;
dest[2] = xmmword_5625F9C29030;
dest[3] = xmmword_5625F9C29040;
v68 = v83;
if ( v83 < 0xCD )
goto LABEL_74;

sub_5625F9C2BB2B 这个里面一开始是opcode,我们复制出来看下它的汇编

这里用了python的一个脚本,将指令还原汇编指令:

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

def disassemble_hex_instructions(hex_instructions):
try:
# Create a Capstone engine
md = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_64)
instructions = []

# Convert the hexadecimal instructions to bytes
byte_data = bytes(hex_instructions)

# Disassemble each instruction
for insn in md.disasm(byte_data, 0x1000):
instructions.append("0x%x:\t%s\t%s" % (insn.address, insn.mnemonic, insn.op_str))

return instructions

except capstone.CsError as e:
print("Capstone Error: %s" % e)
return None

if __name__ == "__main__":
# Input hexadecimal instructions
hex_instructions = [0x54, 0x5D, 0x31, 0xF6, 0x48, 0xB9, 0xA1, 0x57, 0x06, 0xB8, 0x62, 0x3A, 0x9F, 0x37, 0x48, 0xBA, 0x8E, 0x35, 0x6F, 0xD6, 0x4D, 0x49, 0xF7, 0x37, 0x48, 0x31, 0xD1, 0x51, 0x54, 0x5F, 0x6A, 0x02, 0x58, 0x99, 0x0F, 0x05, 0x48, 0x97, 0x31, 0xC0, 0x50, 0x54, 0x5E, 0x6A, 0x04, 0x5A, 0x0F, 0x05, 0x41, 0x5C, 0x6A, 0x03, 0x58, 0x0F, 0x05, 0x31, 0xF6, 0x48, 0xB9, 0x3B, 0x3B, 0x6F, 0xC3, 0x63, 0x64, 0xC0, 0xAA, 0x48, 0xBA, 0x48, 0x4C, 0x0B, 0xC3, 0x63, 0x64, 0xC0, 0xAA, 0x48, 0x31, 0xD1, 0x51, 0x48, 0xB9, 0x8C, 0x57, 0x82, 0x75, 0xD6, 0xF8, 0xA9, 0x7D, 0x48, 0xBA, 0xA3, 0x32, 0xF6, 0x16, 0xF9, 0x88, 0xC8, 0x0E, 0x48, 0x31, 0xD1, 0x51, 0x54, 0x5F, 0x6A, 0x02, 0x58, 0x99, 0x0F, 0x05, 0x48, 0x97, 0x31, 0xC0, 0x50, 0x54, 0x5E, 0x6A, 0x04, 0x5A, 0x0F, 0x05, 0x41, 0x5D, 0x6A, 0x03, 0x58, 0x0F, 0x05, 0x31, 0xF6, 0x6A, 0x6F, 0x48, 0xB9, 0x59, 0xE5, 0x06, 0x0C, 0x2D, 0xF6, 0xD9, 0x77, 0x48, 0xBA, 0x76, 0x81, 0x63, 0x7A, 0x02, 0x8C, 0xBC, 0x05, 0x48, 0x31, 0xD1, 0x51, 0x54, 0x5F, 0x6A, 0x02, 0x58, 0x99, 0x0F, 0x05, 0x48, 0x97, 0x31, 0xC0, 0x50, 0x54, 0x5E, 0x6A, 0x04, 0x5A, 0x0F, 0x05, 0x58, 0x48, 0xF7, 0xD0, 0x48, 0xC1, 0xE8, 0x1D, 0x48, 0x99, 0x6A, 0x29, 0x59, 0x48, 0xF7, 0xF1, 0x49, 0x96, 0x6A, 0x03, 0x58, 0x0F, 0x05, 0xB8, 0xEF, 0xBE, 0xAD, 0xDE, 0x44, 0x01, 0xE0, 0x44, 0x31, 0xE8, 0xC1, 0xC8, 0x0B, 0xF7, 0xD0, 0x44, 0x31, 0xF0, 0x3D, 0xEF, 0xBE, 0xAD, 0xDE, 0x75, 0x05, 0x6A, 0x01, 0x58, 0xEB, 0x03, 0x48, 0x31, 0xC0, 0x50, 0x53, 0x5F, 0x54, 0x5E, 0x6A, 0x08, 0x5A, 0x6A, 0x01, 0x58, 0x0F, 0x05, 0x55, 0x5C, 0x41]
disassembled_instructions = disassemble_hex_instructions(bytearray(hex_instructions))

if disassembled_instructions:
for instruction in disassembled_instructions:
print(instruction)
#
from keystone import *

def assemble_call_instruction(call_address):
try:
ks = Ks(KS_ARCH_X86, KS_MODE_64)
call_instruction = f"call {call_address:#X}"
encoding, _ = ks.asm(call_instruction)
return bytes(encoding)
except KsError as e:
print(f"Keystone Error: {e}")
return None

if __name__ == "__main__":
# Address to call
call_address = 0x0000559395BE3430

# Assemble the CALL instruction
assembled_instruction = assemble_call_instruction(call_address)

if assembled_instruction:
print(f"Assembled CALL instruction: {assembled_instruction.hex()}")

生成了如下汇编

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
tconexp/hb.py
0x1000: push rsp
0x1001: pop rbp
0x1002: xor esi, esi
0x1004: movabs rcx, 0x379f3a62b80657a1
0x100e: movabs rdx, 0x37f7494dd66f358e
0x1018: xor rcx, rdx
0x101b: push rcx
0x101c: push rsp
0x101d: pop rdi
0x101e: push 2
0x1020: pop rax
0x1021: cdq
0x1022: syscall
0x1024: xchg rdi, rax
0x1026: xor eax, eax
0x1028: push rax
0x1029: push rsp
0x102a: pop rsi
0x102b: push 4
0x102d: pop rdx
0x102e: syscall
0x1030: pop r12
0x1032: push 3
0x1034: pop rax
0x1035: syscall
0x1037: xor esi, esi
0x1039: movabs rcx, 0xaac06463c36f3b3b
0x1043: movabs rdx, 0xaac06463c30b4c48
0x104d: xor rcx, rdx
0x1050: push rcx
0x1051: movabs rcx, 0x7da9f8d67582578c
0x105b: movabs rdx, 0xec888f916f632a3
0x1065: xor rcx, rdx
0x1068: push rcx
0x1069: push rsp
0x106a: pop rdi
0x106b: push 2
0x106d: pop rax
0x106e: cdq
0x106f: syscall
0x1071: xchg rdi, rax
0x1073: xor eax, eax
0x1075: push rax
0x1076: push rsp
0x1077: pop rsi
0x1078: push 4
0x107a: pop rdx
0x107b: syscall
0x107d: pop r13
0x107f: push 3
0x1081: pop rax
0x1082: syscall
0x1084: xor esi, esi
0x1086: push 0x6f
0x1088: movabs rcx, 0x77d9f62d0c06e559
0x1092: movabs rdx, 0x5bc8c027a638176
0x109c: xor rcx, rdx
0x109f: push rcx
0x10a0: push rsp
0x10a1: pop rdi
0x10a2: push 2
0x10a4: pop rax
0x10a5: cdq
0x10a6: syscall
0x10a8: xchg rdi, rax
0x10aa: xor eax, eax
0x10ac: push rax
0x10ad: push rsp
0x10ae: pop rsi
0x10af: push 4
0x10b1: pop rdx
0x10b2: syscall
0x10b4: pop rax
0x10b5: not rax
0x10b8: shr rax, 0x1d
0x10bc: cqo
0x10be: push 0x29
0x10c0: pop rcx
0x10c1: div rcx
0x10c4: xchg r14, rax
0x10c6: push 3
0x10c8: pop rax
0x10c9: syscall
0x10cb: mov eax, 0xdeadbeef
0x10d0: add eax, r12d
0x10d3: xor eax, r13d
0x10d6: ror eax, 0xb
0x10d9: not eax
0x10db: xor eax, r14d
0x10de: cmp eax, 0xdeadbeef
0x10e3: jne 0x10ea
0x10e5: push 1
0x10e7: pop rax
0x10e8: jmp 0x10ed
0x10ea: xor rax, rax
0x10ed: push rax
0x10ee: push rbx
0x10ef: pop rdi
0x10f0: push rsp
0x10f1: pop rsi
0x10f2: push 8
0x10f4: pop rdx
0x10f5: push 1
0x10f7: pop rax
0x10f8: syscall
0x10fa: push rbp
0x10fb: pop rsp

目前发现没什么用先放这,继续往下动调,

1
2
3
4
5
6
7
8
9
10
11
12
v81 = v65;
v82 = v67;
v83 = 255LL;
alloc::vec::Vec$LT$T$C$A$GT$::resize::h7362553f00beaec8(&v81, 255LL, 0LL);
if ( *(_DWORD *)(v85 + 16) == -1 )
core::panicking::panic::h65157a6ac7f1357a();
v84 = v85 + 16;
dest[0] = xmmword_5625F9C29010;
dest[1] = xmmword_5625F9C29020;
dest[2] = xmmword_5625F9C29030;
dest[3] = xmmword_5625F9C29040;
v68 = v83;

发现dest赋了新值,我们在这个代码执行完后面 的位置用ida patch到上面哪个shellcode位置

,发现这个位置改变了

1
2
3
4
5
6
7
0x10cb: mov     eax, 0xxxxx  这个变成了我们输入的数据前4个byte
0x10d0: add eax, r12d
0x10d3: xor eax, r13d
0x10d6: ror eax, 0xb
0x10d9: not eax
0x10db: xor eax, r14d
0x10de: cmp eax, 0xxxxxx 这里变成了我们的dest数据也就是比较的目的数据

嗯,分析这块关键代码我们发现这里进行了xor ror not xor操作,其中r12 r13 r14是未知的,所以我们要想办法获取这3个寄存器的值,我一开始想patch源程序到这个位置,发现到了这个位置就无法执行 了,所以使用另一种方法,就汇编写出asm文件,进行nasm编译,然后再动调把断点下在0x10cb就可以查看r12 13 14的值了

编译具体方法:

n.asm:

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
section .text
global _start

_start:
push rsp
pop rbp
xor esi, esi
mov rcx, 0x379f3a62b80657a1
mov rdx, 0x37f7494dd66f358e
xor rcx, rdx
push rcx
push rsp
pop rdi
push 2
pop rax
cdq
syscall
xchg rdi, rax
xor rax, rax
push rax
push rsp
pop rsi
push 4
pop rdx
syscall
pop r12
push 3
pop rax
syscall
xor esi, esi
mov rcx, 0xaac06463c36f3b3b
mov rdx, 0xaac06463c30b4c48
xor rcx, rdx
push rcx
mov rcx, 0x7da9f8d67582578c
mov rdx, 0xec888f916f632a3
xor rcx, rdx
push rcx
push rsp
pop rdi
push 2
pop rax
cdq
syscall
xchg rdi, rax
xor rax, rax
push rax
push rsp
pop rsi
push 4
pop rdx
syscall
pop r13
push 3
pop rax
syscall
xor esi, esi
push 0x6f
mov rcx, 0x77d9f62d0c06e559
mov rdx, 0x5bc8c027a638176
xor rcx, rdx
push rcx
push rsp
pop rdi
push 2
pop rax
cdq
syscall
xchg rdi, rax
xor rax, rax
push rax
push rsp
pop rsi
push 4
pop rdx
syscall
pop rax
not rax
shr rax, 0x1d
cqo
push 0x29
pop rcx
div rcx
xchg r14, rax
push 3
pop rax
syscall
mov eax, 0xdeadbeef
add eax, r12d
xor eax, r13d
ror eax, 0xb
not eax
xor eax, r14d
cmp eax, 0xdeadbeef
jne short not_equal
push 1
pop rax
jmp short done
not_equal:
xor rax, rax
done:
push rax
push rbx
pop rdi
push rsp
pop rsi
push 8
pop rdx
push 1
pop rax
syscall
push rbp
pop rsp

1
2
nasm -f elf64 -o vuln.o n.asm
ld -o my_program vuln.o

将上面的汇编进行编译生成elf文件,然后再使用ida动调得到如下

r12 = 0x0000000464C457F r13 = 0x0000000746F6F72 r14 = 0x000000031F3831F

分析完逻辑我们就可以写解密脚本了,参考3cly师傅的脚本

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
import numpy as np
def decrypt(data: list, table: dict):
tmp=[]
for i in data:
tmp.append(table[i])
return tmp

def reverse_order(data:list, table: list):
tmp = []
for i in range(64):
tmp.append(data[table[i]])
return tmp

'''
def de_cmp(x):
r12 = 0x0000000464C457F
r13 = 0x0000000746F6F72
r14 = 0x000000031F3831F
x ^= r14
x = ~x
x = (x << 11) | (x >> (32 - 11))
x ^= r13
x -= r12
print(hex(x))
return [x & 0xff, (x & 0xff00) >> 8, (x & 0xff0000) >> 16, (x & 0xff000000) >> 24]
'''

def de_cmp(x):
r12 = 0x0000000464C457F
r13 = 0x0000000746F6F72
r14 = 0x000000031F3831F

x = np.uint32(x)
x = np.uint32(x ^ r14)
x = np.uint32(~x)
x = np.uint32((x << 11) | (x >> (32 - 11)))
x = np.uint32(x ^ r13)
x = np.uint32(x - r12)
print(hex(x))
x = np.int32(x)
return [x & 0xff, (x & 0xff00) >> 8, (x & 0xff0000) >> 16, (x & 0xff000000) >> 24]

# get crypto table
s0_255 = [0xFB, 0x7B, 0x4E, 0xBB, 0x51, 0x15, 0x8D, 0xDB, 0xB0, 0xAC, 0xA5, 0x8E, 0xAA, 0xB2, 0x60, 0xEB, 0x63, 0x5C, 0xDE, 0x42, 0x2B, 0xC6, 0xA6, 0x35, 0x30, 0x43, 0xD6, 0x5F, 0xBD, 0x24, 0xB1, 0xE3, 0x8C, 0xA7, 0xD5, 0x2A, 0x7C, 0x6D, 0x8B, 0x17, 0x9D, 0x83, 0xFE, 0x69, 0x10, 0x59, 0xA9, 0x9E, 0x0F, 0x1C, 0x66, 0x97, 0x5B, 0x61, 0xED, 0xAD, 0xE0, 0xDA, 0x27, 0x06, 0x25, 0xDC, 0x5E, 0xE7,
0x41, 0x32, 0xD2, 0xD9, 0x8F, 0xEE, 0xAF, 0x03, 0x93, 0x3A, 0x00, 0xA2, 0xE1, 0xB3, 0xEC, 0x81, 0x9F, 0xCA, 0x58, 0xB7, 0x79, 0xFD, 0x3B, 0xA0, 0x02, 0x0C, 0xCB, 0xA8, 0x80, 0xC0, 0x16, 0x4D, 0x2F, 0x75, 0x71, 0x0A, 0x04, 0x39, 0xFF, 0xC1, 0x9C, 0xAB, 0xEF, 0xA4, 0xD8, 0xE2, 0x14, 0xC2, 0x6C, 0x64, 0x1E, 0x6B, 0x7E, 0x99, 0x2E, 0x09, 0x0B, 0x86, 0x74, 0x6A, 0xC4, 0x2D, 0x4F, 0xF9,
0xFA, 0x94, 0xB6, 0x1F, 0x89, 0x6F, 0x5D, 0xE8, 0xEA, 0xB5, 0x5A, 0x65, 0x88, 0xC5, 0x7F, 0x77, 0x11, 0xCF, 0xF1, 0x1B, 0x3F, 0xF4, 0x48, 0x47, 0x12, 0xE4, 0xBA, 0xDF, 0xE9, 0x62, 0x6E, 0xB4, 0x96, 0xCD, 0x13, 0x53, 0x4B, 0x28, 0xD7, 0xD1, 0x33, 0xB8, 0xE6, 0x7A, 0x2C, 0x9B, 0x29, 0x44, 0x52, 0xF7, 0x20, 0xF2, 0x31, 0xD3, 0xB9, 0x40, 0xD0, 0x34, 0xF5, 0x54, 0x1A, 0x01, 0xA1, 0x92,
0xFC, 0x85, 0x07, 0xBE, 0xDD, 0xBC, 0x19, 0xF3, 0x36, 0xF6, 0x72, 0x98, 0x4C, 0x7D, 0xC7, 0xD4, 0x45, 0x4A, 0x9A, 0xC3, 0x8A, 0xE5, 0x50, 0x46, 0xCC, 0x68, 0x76, 0x67, 0xC9, 0x0E, 0x3C, 0x57, 0xF0, 0x22, 0xBF, 0x26, 0x84, 0x0D, 0x90, 0xA3, 0xAE, 0x3D, 0x1D, 0xC8, 0x91, 0x05, 0x87, 0x70, 0x08, 0x73, 0x21, 0x49, 0x55, 0x3E, 0x37, 0x23, 0x18, 0x56, 0xCE, 0x82, 0x38, 0x95, 0x78, 0xF8]

crypto_table = dict(zip(s0_255,range(0x100)))

#get order table
source = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}'
replaced = 'HfVl{qPcCYNMoRi6D7Jr}espOL3FhwdWAtTGZba4Ugjvnx1QkKE2IS9yuz5BX08m'
revsere_table = [] #index是source在replace中的下标
for i in source:
revsere_table.append(replaced.find(i))

#print(crypto_table)
#print(revsere_table)

cmp_data = [0x526851A7, 0x31FF2785, 0xC7D28788, 0x523F23D3, 0xAF1F1055, 0x5C94F027, 0x797A3FCD, 0xE7F02F9F, 0x3C86F045, 0x6DEAB0F9, 0x91F74290, 0x7C9A3AED, 0xDC846B01, 0x0743C86C, 0xDFF7085C, 0xA4AEE3EB]

tmp = []
for i in cmp_data:
tmp+=de_cmp(i)
print(tmp)

for i in range(256):
tmp = decrypt(tmp, crypto_table)
tmp = reverse_order(tmp, revsere_table)
print(''.join(chr(i) for i in tmp))

cmp_data直接提取dest数据就可以了,因为比较数据是固定的

总结:这道题学到了不少,学到了flag换位加密用固定表紧跟着第二次加密(64),在第二次加密的时候输入的字符和代码行为没有任何关系,所以采用了映射字母表查询(第一次用到这个方法)

 Comments