本题主要考察的是动态调试能力。
目录
分析
拿到程序,光看到程序文件的大小都有点夸张,拖到IDA分析,更是一些奇奇怪怪的函数,而且字符串查找也找不到明显的正确、错误的回显,让我们很难进行静态分析。
思路
根据我们的输入进行硬件断点分析。
找到输入点
.text:000000000000F470 loc_F470: ; CODE XREF: std::io::append_to_string::he82ae2a7713c4960+64↑j
.text:000000000000F470 mov rdx, [rbp+18h]
.text:000000000000F474 mov rsi, [rbp+10h] ; buf
.text:000000000000F478 test rdx, rdx
.text:000000000000F47B mov rax, 7FFFFFFFFFFFFFFFh
.text:000000000000F485 cmovs rdx, rax ; nbytes
.text:000000000000F489 xor edi, edi ; fd
.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: ; CODE XREF: std::io::append_to_string::he82ae2a7713c4960:loc_F64C↓j
.text:000000000000F4BC sub rbx, r13
.text:000000000000F4BF add r13, [rbp+10h]
.text:000000000000F4C3 mov esi, 0Ah ; c
.text:000000000000F4C8 mov rdi, r13 ; s
.text:000000000000F4CB mov rdx, rbx ; n
.text:000000000000F4CE call _memchr
.text:000000000000F4D3 test rax, rax ; 不重要
.text:000000000000F4D6 jz short loc_F530
第二个停下来的硬断点是0xFB98
,这里程序将我们输入的字符串转移到了一块新的内存上了,所以对于新内存继续下硬件断点。
.text:000000000000FB8D mov rsi, rdx ; src
.text:000000000000FB90 mov rdx, rax ; n
.text:000000000000FB93 call _memcpy
.text:000000000000FB98 add rsp, 0B8h ; memcpy() 进行字符转移,继续下断点
.text:000000000000FB9F retn
继续运行,新内存的硬件断点被触发(0x4CB94
),但这里并没有很大的用处,仅仅是判断是否达到了字符末尾。
.text:000000000004CB90 loc_4CB90: ; CODE XREF: core::str::run_utf8_validation::h2a9edbcb1794d209+48↓j
.text:000000000004CB90 ; core::str::run_utf8_validation::h2a9edbcb1794d209+83↓j ...
.text:000000000004CB90 movzx ebx, byte ptr [rsi+rax]
.text:000000000004CB94 test bl, bl ; 这里进行 ‘\0’ 判断,不重要
.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: ; CODE XREF: beginer_reverse::main::h80fa15281f646bc1+26D↓j
.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 >= 0xFFFFFFFFFFFFFFFFLL )
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
还原
利用脚本进行还原:
#!/usr/bin/python
# -*- coding:utf-8 -*-
import binascii
import struct
data = 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
师傅的指点,我才能继续调试下去。与其说是考验调试能力,其实我觉得更是在考验耐心。所以说耐心也很重要,特别是面对难题的时候。