ISCC2019 Reverse Rev01 writeup
TOC
1. 分析 2. 思路 2.1. 找到输入点 2.2. 硬件断点 2.2.1. 进行int数组扩展 2.2.2. 进行检查 2.2.3. 进行flag判断 2.2.3.1. 提取处理过的flag 2.2.3.2. 还原 2.2.3.3. 运行实例 3. 总结
源文件、IDA分析文件下载,百度网盘:Rev01.zip
本题主要考察的是动态调试能力。
分析 拿到程序,光看到程序文件的大小都有点夸张,拖到IDA分析,更是一些奇奇怪怪的函数,而且字符串查找也找不到明显的正确、错误的回显,让我们很难进行静态分析。
思路 根据我们的输入进行硬件断点分析。
找到输入点 .text: 000000000000F470 loc_F470: .text: 000000000000F470 mov rdx , [rbp +18h ].text: 000000000000F474 mov rsi , [rbp +10h ] .text: 000000000000F478 test rdx , rdx .text: 000000000000F47B mov rax , 7FFFFFFFFFFFFFFFh .text: 000000000000F485 cmovs rdx , rax .text: 000000000000F489 xor edi , edi .text: 000000000000F48B call _read.text: 000000000000F490 mov rbx , rax .text: 000000000000F493 cmp rax , 0FFFFFFFFFFFFFFFFh .text: 000000000000F497 jz loc_F5AB
通过进行int终端程序,然后查看栈的返回地址,我们确定了是在0xF48B
这个地址读取我们的输入,所以在0xF490
下断点,读取输入后对于我们输入的字符串下硬件断点,进行动态调试。
硬件断点 首先触发的是下面的硬件断点(在0xF4D3
处),但是通过后期的调试,会发现这个断点不重要。
.text: 000000000000F4BC loc_F4BC: .text: 000000000000F4BC sub rbx , r13 .text: 000000000000F4BF add r13 , [rbp +10h ].text: 000000000000F4C3 mov esi , 0Ah .text: 000000000000F4C8 mov rdi , r13 .text: 000000000000F4CB mov rdx , rbx .text: 000000000000F4CE call _memchr.text: 000000000000F4D3 test rax , rax .text: 000000000000F4D6 jz short loc_F530
第二个停下来的硬断点是0xFB98
,这里程序将我们输入的字符串转移到了一块新的内存上了,所以对于新内存继续下硬件断点。
.text: 000000000000FB8D mov rsi , rdx .text: 000000000000FB90 mov rdx , rax .text: 000000000000FB93 call _memcpy.text: 000000000000FB98 add rsp , 0B8h .text: 000000000000FB9F retn
继续运行,新内存的硬件断点被触发(0x4CB94
),但这里并没有很大的用处,仅仅是判断是否达到了字符末尾。
.text: 000000000004CB90 loc_4CB90: .text: 000000000004CB90 .text: 000000000004CB90 movzx ebx , byte ptr [rsi +rax ].text: 000000000004CB94 test bl , bl .text: 000000000004CB96 js short loc_4CBF0
继续运行,又是一个 NULL 判断(0x4CBDD
),这里通过动态调试,也能确定为不重要的节点。
.text: 000000000004CBD9 cmp byte ptr [rsi +rax ], 0 .text: 000000000004CBDD jns short loc_4CBD0
进行int数组扩展 继续运行,在0x6876
处停下,这里是一个重点
.text: 0000000000006870 loc_6870: .text: 0000000000006870 movzx r12d , byte ptr [r13 +rbp +0 ].text: 0000000000006876 mov rbx , rsi .text: 0000000000006879 cmp rbp , rsi .text: 000000000000687C jnz short loc_68FD
反汇编代码:
do { temp = input[v16]; v18 = v14; if ( v16 == v14 ) { v18 = v14 + 1 ; if ( v14 >= 0xFFFFFFFFFFFFFFFF LL ) goto LABEL_68; if ( v18 < 2 * v14 ) v18 = 2 * v14; if ( !is_mul_ok (4uLL , v18) ) LABEL_68: alloc::raw_vec::capacity_overflow::hbc659f170a622eae (); if ( v14 ) { _rust_realloc(); int_array = (signed int *)v19; v13 = v32; if ( !v19 ) goto LABEL_63; } else { _rust_alloc(); int_array = (signed int *)v20; v13 = v32; if ( !v20 ) LABEL_63: alloc::alloc::handle_alloc_error::h9e3787e5722c870d (); } *(_QWORD *)&v30 = int_array; *((_QWORD *)&v30 + 1 ) = v18; v14 = v18; } int_array[v16++] = temp; v31 = v16; } while ( v13 != v16 );
可以通过动态调试,结合它的反汇编代码,确定这里将我们的字符串数组扩展为int型数组。
重点是这两句:
temp = input[v16]; int_array[v16++] = temp;
确定功能之后继续对扩展的int数组下硬件断点。
进行检查 之后继续运行,触发int数组
的硬件断点(0x6939
)。这个断点很重要。
.text: 0000000000006935 mov edx , [r14 +rcx ].text: 0000000000006939 add edx , 0FFFFFFE0h .text: 000000000000693C add rcx , 4 .text: 0000000000006940 cmp edx , 5Fh .text: 0000000000006943 jb short loc_6930
反汇编代码:
while ( v16 != v21 ){ v22 = int_array[v21] - 32 ; ++v21; if ( v22 >= 0x5F ) std::panicking::begin_panic::h770c088eb8f42530 ( "an error occuredSubmit this and get you'r points!\n" , 16LL , &off_64F10, v21 * 4 ); }
这里主要是检查我们的int数组
是否合理,不合理直接报错。
进行flag判断 继续运行,程序在0x6995
出因为硬件断点而停下,这里是重中之重。
.text: 0000000000006985 mov edi , [r15 +rsi *4 ].text: 0000000000006989 sar edi , 2 .text: 000000000000698C xor edi , 0Ah .text: 000000000000698F xor eax , eax .text: 0000000000006991 cmp edi , [r14 +rsi *4 ].text: 0000000000006995 lea rsi , [rsi +1 ].text: 0000000000006999 setz al .text: 000000000000699C add rcx , rax .text: 000000000000699F add rdx , 0FFFFFFFFFFFFFFFCh .text: 00000000000069A3 cmp rsi , rbp .text: 00000000000069A6 jb short loc_6980
反汇编代码:
result = ((flag[i] >> 2 ) ^ 0xA ) == int_array[i];
做到这里flag
已经很明显了,我们只要把flag的内容提取出来,然后进行还原就可以得到flag。
提取处理过的flag 下面的0x00007F8D0DA29000
为动态调试时,flag的地址,注意这里每次调试地址都是不同的,需要自己计算。而0x00007F8D0DA29000
就是该处理过的flag的结尾。
Python>get_bytes(0x00007F8D0DA29000, 0x00007F8D0DA29088 - 0x00007F8D0DA29000).encode('hex') 5401000080010000fc010000e4010000f80100005401000090010000bc010000bc010000b801000054010000f80100009401000054010000b4010000bc010000f801000054010000f401000088010000ac010000f8010000540100008c010000e40100005401000090010000bc010000bc010000b8010000bc010000b80100005401000090000000
还原 利用脚本进行还原:
import binasciiimport structdata = binascii.a2b_hex('5401000080010000fc010000e4010000f80100005401000090010000bc010000bc010000b801000054010000f80100009401000054010000b4010000bc010000f801000054010000f401000088010000ac010000f8010000540100008c010000e40100005401000090010000bc010000bc010000b8010000bc010000b80100005401000090000000' ) out = '' while (data): value = struct.unpack('i' , data[:4 ])[0 ] new_value = ((value >> 2 ) ^ 0xA ) out += chr (new_value) data = data[4 :] print (out)
运行实例 ex@Ex:~/test$ python3 main.py _just_need_to_get_what_is_needed_. ex@Ex:~/test$ python main.py _just_need_to_get_what_is_needed_. ex@Ex:~/test$ ./reverse _just_need_to_get_what_is_needed_. Submit this and get you'r points!
到此,我们拿到了flag。
总结 这题很考验我们的调试能力,其实刚开始的时候我只是调试了一下就觉得这个思路不太行,但是还好有ZhouYetao
师傅的指点,我才能继续调试下去。与其说是考验调试能力,其实我觉得更是在考验耐心。所以说耐心也很重要,特别是面对难题的时候。