0x01 逆向分析
题目是一个64位的elf二进制文件,使用radare2对其进行分析:
$ r2 reee.bin [0x00400430]> aaa [0x00400430]> i fd 3 file reee.bin size 0x19d8 humansz 6.5K mode r-x format elf64 iorw false blksz 0x0 block 0x100 type EXEC (Executable file) arch x86 baddr 0x400000 binsz 4753 bintype elf bits 64 canary false class ELF64 compiler GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609 crypto false endian little havecode true intrp /lib64/ld-linux-x86-64.so.2 laddr 0x0 lang c linenum false lsyms false machine AMD x86-64 architecture maxopsz 16 minopsz 1 nx true os linux pcalign 0 pic false relocs false relro partial rpath NONE sanitiz false static false stripped true subsys linux va true [0x00400430]> afl 0x00400430 1 41 entry0 0x00400410 1 6 sym.imp.__libc_start_main 0x00400400 1 6 sym.imp.puts 0x0040064e 14 747 -> 195 main 0x00400500 8 134 -> 90 entry.init0 0x004004e0 3 28 entry.fini0 0x00400460 4 50 -> 41 fcn.00400460 0x00400526 1 296 fcn.00400526 0x004006e5 6 176 -> 60 fcn.004006e5 0x004003c8 3 26 fcn.004003c8
它是二进制文件,不是PIE可执行文件,这是它的主要逻辑图:
反编译结果如下:
void main(char *argc, char **argv) { char *placeholder_2; code cVar1; int64_t iVar2; uint32_t in_ECX; char **arg2; char **var_40h; undefined8 var_34h; int32_t second_index; int32_t index; int64_t var_20h; uint32_t var_18h; arg2 = argv; if ((int32_t)argc < 2) { argc = "need a flag!"; sym.imp.puts(); } placeholder_2 = argv[1]; index = 0; while (index < 0x7a69) { second_index = 0; while (second_index < 0x228) { argc = (char *)(uint32_t)fcn.004006e5[second_index]; cVar1 = (code)fcn.00400526((int64_t)argc); fcn.004006e5[second_index] = cVar1; second_index = second_index + 1; } index = index + 1; } iVar2 = fcn.004006e5(argc, (int64_t)arg2, placeholder_2, in_ECX); if (iVar2 == 0) { sym.imp.puts("Wrong!"); } else { sym.imp.puts("Correct!"); } return; }
从反编译版本中,我们可以推断出三件事:
1. 程序接受输入参数(flag)。
2. 将参数传递给函数fcn.004006e5。
3. 使用函数fcn.00400526在嵌套中修改了函数fcn.004006e5。
实际上,检查fcn.004006e5的asm 是否是混乱的:
┌ 60: fcn.004006e5 (int64_t arg2, uint32_t arg4, int64_t arg_58ae7f6ah); │ ╎╎╎ ; arg int64_t arg_58ae7f6ah @ rbp+0x58ae7f6a │ ╎╎╎ ; arg int64_t arg2 @ rsi │ ╎╎╎ ; arg uint32_t arg4 @ rcx │ ╎╎╎ 0x004006e5 stc │ ╎╎╎ 0x004006e6 xchg eax, ebx │ ┌────< 0x004006e7 jne 0x400716 │ │╎╎╎ 0x004006e9 fcmovnb st(0), st(6) │ │╎╎╎ 0x004006eb stosd dword [rdi], eax │ │╎╎└─< 0x004006ec loopne 0x400690 │ │╎╎ 0x004006ee cmp ecx, dword [rcx - 0x63] ; arg4 │ │╎╎ 0x004006f1 mov word [rsi + rax*4 - 0x23], ds ; arg2 │╎╎ 0x004006f5 invalid │╎└──< 0x004006f6 jae 0x4006c5 ; main+0x77 │╎ 0x004006f8 stc │╎ 0x004006f9 invalid │╎ 0x004006fa imul ebx, dword [rax], 0x2f592932 │╎ 0x00400700 invalid │╎ 0x00400701 cmpsd dword [rsi], dword ptr [rdi] │╎ 0x00400702 invalid │╎ ; CODE XREF from fcn.004006e5 @ +0x90 │╎ ┌─< 0x00400703 jns 0x40070e │╎ │ 0x00400705 adc byte [rdi + 0x5bcbfed6], cl │╎ │ 0x0040070b ret │└───< 0x0040070c jo 0x4006d1 ; main+0x83 │ │ ; CODE XREF from fcn.004006e5 @ +0x1e │ └─> 0x0040070e invalid │ 0x0040070f invalid │ 0x00400710 lodsb al, byte [rsi] │ 0x00400711 xchg dword [rdx - 0x61], eax │ ┌─< 0x00400714 jns 0x400760 │ │ │ ; CODE XREF from fcn.004006e5 @ 0x4006e7 │ └────> 0x00400716 xor eax, 0xe0bb9da8 │ │ 0x0040071c sar dword [arg_58ae7f6ah], 0xeb │ │ 0x00400723 pop rsp │ │ 0x00400724 add dword [rcx + 0x1dad7797], esi ; arg4 │ ╎│ 0x0040072a adc esp, ebp │ ╎│ 0x0040072c adc byte [0x6e340251], bh │ ┌────< 0x00400732 loop 0x40079b │ │╎╎│ 0x00400734 mov cl, 0x79 ; 'y' ; 121 │ │╎╎│ 0x00400736 sahf │ │╎╎│ 0x00400737 pop rsi │ │╎╎│ 0x00400738 cmp al, 0x14 ; 20 │ │╎╎│ 0x0040073a xor cl, cl │ │╎╎│ 0x0040073c cld │ │╎╎│ 0x0040073d mov bh, bh │ │╎╎│ 0x0040073f int3 │╎╎│ 0x00400740 call 0xffffffff8987d4da │╎╎│ 0x00400745 fnstenv [rcx + 1] │╎╎│ 0x00400748 mov bh, 0x25 ; '%' ; 37 │╎╎│ 0x0040074a and al, 0x6a ; 106 │╎╎│ 0x0040074c invalid │╎╎│ 0x0040074d mov dh, 0xe2 ; 226 │╎╎│ 0x0040074f int1 │╎╎│ 0x00400750 sbb al, 0x5c │╎╎│ 0x00400752 fidiv dword [rax + rdi*4 + 0x2b] │╎╎│ 0x00400756 invalid │╎╎│ 0x00400757 pop rbp │╎╎│ 0x00400759 and bl, byte [rax + 0x60299194] │╎└──< 0x0040075f jns 0x400729 ; fcn.004006e5+0x44 │└───< 0x00400761 jge 0x400730 ; fcn.004006e5+0x4b │ 0x00400763 sub eax, 0x8e88118d │ 0x00400768 rol byte [rdx + 0x66], cl │ 0x0040076b sti │ 0x0040076c xlatb │ 0x0040076d hlt │ 0x0040076e mov al, byte [rdx]
有很多无效的指令,我们可以尝试检查fcn.00400526:
int32_t fcn.00400526(int64_t arg1) { int64_t var_4h; *(uint8_t *)0x601161 = *(char *)0x601161 + 1; *(uint8_t *)0x601162 = *(char *)0x601162 + *(char *)((int64_t)*(uint8_t *)0x601161 + 0x601060); *(uint8_t *)((int64_t)*(uint8_t *)0x601161 + 0x601060) = *(uint8_t *)((int64_t)*(uint8_t *)0x601161 + 0x601060) ^ *(uint8_t *)((int64_t)*(uint8_t *)0x601162 + 0x601060); *(uint8_t *)((int64_t)*(uint8_t *)0x601162 + 0x601060) = *(uint8_t *)((int64_t)*(uint8_t *)0x601162 + 0x601060) ^ *(uint8_t *)((int64_t)*(uint8_t *)0x601161 + 0x601060); *(uint8_t *)((int64_t)*(uint8_t *)0x601161 + 0x601060) = *(uint8_t *)((int64_t)*(uint8_t *)0x601161 + 0x601060) ^ *(uint8_t *)((int64_t)*(uint8_t *)0x601162 + 0x601060); return arg1 + *(uint8_t *) ((int64_t) (*(char *)((int64_t)*(uint8_t *)(0x601162 + 0x601060) + *(char *)((int64_t)*(uint8_t *)(0x601161 + 0x601060) + (0x601060); }
这个函数不是不可能理解的,但是我们可以采取另一种方法。我们可以在以下位置设置断点:
iVar2 = fcn.004006e5(argc, (int64_t)arg2, placeholder_2, in_ECX);
调试二进制文件
[0x0040064e]> db 0x004006db [0x0040064e]> ood PCTF{fake_flag} Process with PID 6619 started... [0x7f2400442100]> dc hit breakpoint at: 4006db [0x004006db]> v!
现在函数更加清晰了,我们还可以反编译该函数:
[0x004006e5]> pdg undefined fcn.004006e5(uint32_t arg4) { undefined uVar1; undefined4 in_RDI; uVar1 = func_0x00400702(CONCAT44(in_RDI, arg4)); return uVar1; }
函数0x00400702的asm代码很长,但是我们可以很容易地反编译它。由于未在radare2上定义此函数,因此我们需要对其进行定义。在可视模式下,查找0x00400702并按df,然后pdg:
bool fcn.00400702(int64_t arg1) { char cVar1; char *in_RAX; int32_t iVar2; int64_t iVar3; char *pcVar4; uint8_t uVar5; int32_t iVar6; int32_t iVar7; bool bVar8; iVar3 = -1; // in_RAX = our argument pcVar4 = in_RAX; do { if (iVar3 == 0) break; iVar3 = iVar3 + -1; cVar1 = *pcVar4; pcVar4 = pcVar4 + 1; } while (cVar1 != '\0'); // compute the length of the argument iVar2 = ~(uint32_t)iVar3 - 1; uVar5 = 0x50; iVar6 = 0; // apply this function to our flag // (xor every character with the precedent one) while (iVar6 < 0x539) { iVar7 = 0; while (iVar7 < iVar2) { in_RAX[iVar7] = in_RAX[iVar7] ^ uVar5; uVar5 = uVar5 ^ in_RAX[iVar7]; iVar7 = iVar7 + 1; } iVar6 = iVar6 + 1; } bVar8 = true; iVar6 = 0; while (iVar6 < iVar2) { if (bVar8 == false) { bVar8 = false; } else { bVar8 = in_RAX[iVar6] == *(char *)((int64_t)iVar6 + 0x4008eb); } iVar6 = iVar6 + 1; } return bVar8; }
这段代码现在非常清楚。fcn.00400702在我们的输入中应用了argv[1]一个函数:将每个字符与前一个字符进行异或运算,并重复操作0x539次。
最后,该函数检查结果是否等于0x4008eb中的数组。
我们可以转储0x4008eb的内容:
[0x0040071a]> pxj 100@0x4008eb [ 72,95,54,53,53,37,20,44,29,1,3,45,12,111,53,97,126,52,10,68,36,44,74,70,25,89, 91,14,120,116,41,19,44,0,72,137,194,72,137,85,232,72,131,125,232,0,116,12,191, 209,9,64,0,232,219,250,255,255,235,10,191,218,9,64,0,232,207,250,255,255,144, 72,131,196,56,91,93,195,15,31,128,0,0,0,0,65,87,65,86,65,137,255,65,85,65,84, 76,141,37,190 ]
现在我们有一个问题,我们不知道flag的长度。
我们可以:
1. 暴力破解。
2. 猜解。
3. 数学运算。
我们使用bruteforce做到了这一点,但是我们可以猜测,直到\x00的输出中的第一个空终止符。
现在是时候计算flag了,我个人选择使用z3。
0x02 漏洞利用
from z3 import * data = [ 0x48, 0x5f, 0x36, 0x35, 0x35, 0x25, 0x14, 0x2c, 0x1d, 0x01, 0x03, 0x2d, 0x0c, 0x6f, 0x35, 0x61, 0x7e, 0x34, 0x0a, 0x44, 0x24, 0x2c, 0x4a, 0x46, 0x19, 0x59, 0x5b, 0x0e, 0x78, 0x74, 0x29, 0x13, 0x2c ] flag = [BitVec(f'{i:2}', 8) for i in range(len(data))] s = Solver() # flag mut be a printable character for f in flag: s.add(f > 0x20, f <= 0x7f) # just copy the code decompiled and rename some variables xored = 0x50 indx = 0 while (indx < 0x539): index = 0 while (index < len(flag)): flag[index] = flag[index] ^ xored xored = xored ^ flag[index] index = index + 1 indx += 1 for f, d in zip(flag, data): s.add(f == d) print(s.check()) m = s.model() model = sorted([(d, m[d]) for d in m], key = lambda x: str(x[0])) for m in model: print(chr(m[1].as_long()), end='')
0x03 flag
pctf{ok_nothing_too_fancy_there!}
https://twitter.com/PatchFriday/status/1043136025781055489
本文翻译自:https://meowmeowxw.gitlab.io/ctf/plaid-2020-reee/如若转载,请注明原文地址: