部分参考题解:http://eternal.red/2017/dont_panic-writeup/
方便起见,题目文件名改成go
运行题目程序,发现flag是跟在命令行参数之后的。用IDA打开后发现没有找到main函数,因此选择在Linux下查看段信息:
supergate@ubuntu:~/Desktop/task1$ readelf -S go
得到信息如下:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000401000 00001000
000000000007ae40 0000000000000000 AX 0 0 16
[ 2] .rodata PROGBITS 000000000047c000 0007c000
0000000000033f5b 0000000000000000 A 0 0 32
[ 3] .typelink PROGBITS 00000000004b0080 000b0080
0000000000000b4c 0000000000000000 A 0 0 32
[ 4] .itablink PROGBITS 00000000004b0bd0 000b0bd0
0000000000000038 0000000000000000 A 0 0 8
[ 5] .gosymtab PROGBITS 00000000004b0c08 000b0c08
0000000000000000 0000000000000000 A 0 0 1
[ 6] .gopclntab PROGBITS 00000000004b0c20 000b0c20
0000000000044d5d 0000000000000000 A 0 0 32
[ 7] .noptrdata PROGBITS 00000000004f6000 000f6000
0000000000002608 0000000000000000 WA 0 0 32
[ 8] .data PROGBITS 00000000004f8620 000f8620
0000000000001cf0 0000000000000000 WA 0 0 32
[ 9] .bss NOBITS 00000000004fa320 000fa310
000000000001a908 0000000000000000 WA 0 0 32
[10] .noptrbss NOBITS 0000000000514c40 000fa310
00000000000046a0 0000000000000000 WA 0 0 32
[11] .note.go.buildid NOTE 0000000000400fc8 00000fc8
0000000000000038 0000000000000000 A 0 0 4
[12] .shstrtab STRTAB 0000000000000000 000fa310
0000000000000073 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
发现了.gosymtab等这种go语言特有的段名称,因此可以推断出这是一个用go语言写出的程序。
在输入错误的flag时会出现提示信息 Nope.。不知道为什么ida的strings找不到这个字符串。所以用linux下的xxd来找到这个字符串:
supergate@ubuntu:~/Desktop/task1$ xxd -g1 go | grep Nope.
000a5240: 3e 45 72 72 6e 6f 45 72 72 6f 72 4e 6f 70 65 2e >ErrnoErrorNope.
找到了ErrnoErrorNope.所在的起始位置为0xa5240,经过计算知道Nope.的真正位置是0xa524b。我们再查找一下程序的什么位置调用了这个位置:
supergate@ubuntu:~/Desktop/task1$ objdump -d go | grep a524b
47ba23: 48 8d 05 21 98 02 00 lea 0x29821(%rip),%rax # 0x4a524b
可知函数中0x47ba23处调用了这个字符串,因此我们在ida中定位这个位置,即可找到主要函数所在。
主要函数的C语言伪代码如下所示:
__int64 __fastcall sub_47B8A0(void **a1, __int64 a2, unsigned __int8 *a3, __int64 a4, __int64 a5, __int64 a6)
{
__int64 pswd; // rcx
signed __int64 cur; // rax
void **ptr; // rdx
char K; // bl
__int64 v10; // rdx
__int64 v11; // rdx
__int64 v12; // r8
__int64 v13; // rdx
__int64 v14; // r8
__int64 v16; // rdx
__int64 v17; // rdx
__int64 v18; // rdx
__int64 v19; // rcx
__int64 v20; // rdx
__int64 v21; // r8
__int64 v22; // [rsp+10h] [rbp-F0h]
char ENC; // [rsp+18h] [rbp-E8h]
signed __int64 v24; // [rsp+20h] [rbp-E0h]
__int64 v25; // [rsp+28h] [rbp-D8h]
void **v26; // [rsp+30h] [rbp-D0h]
char v27; // [rsp+46h] [rbp-BAh]
char v28; // [rsp+47h] [rbp-B9h]
void **v29; // [rsp+48h] [rbp-B8h]
signed __int64 LEN; // [rsp+58h] [rbp-A8h]
__int64 v31; // [rsp+60h] [rbp-A0h]
__int64 v32; // [rsp+68h] [rbp-98h]
__int64 v33; // [rsp+70h] [rbp-90h]
void *v34; // [rsp+78h] [rbp-88h]
__int64 v35; // [rsp+80h] [rbp-80h]
void *v36; // [rsp+88h] [rbp-78h]
__int64 v37; // [rsp+90h] [rbp-70h]
void *v38; // [rsp+98h] [rbp-68h]
__int64 v39; // [rsp+A0h] [rbp-60h]
__int64 v40; // [rsp+A8h] [rbp-58h]
__int64 v41; // [rsp+B0h] [rbp-50h]
void *v42; // [rsp+B8h] [rbp-48h]
__int64 v43; // [rsp+C0h] [rbp-40h]
__int64 v44; // [rsp+C8h] [rbp-38h]
__int64 v45; // [rsp+D0h] [rbp-30h]
__int64 v46; // [rsp+D8h] [rbp-28h]
__int64 v47; // [rsp+E0h] [rbp-20h]
__int64 v48; // [rsp+E8h] [rbp-18h]
__int64 v49; // [rsp+F0h] [rbp-10h]
while ( (unsigned __int64)&v37 <= *(_QWORD *)(__readfsqword(0xFFFFFFF8) + 16) )
sub_44AFC0((__int64)a1, a2);
if ( parameters_cnter != 2 )
{
v36 = &unk_4A5363;
v37 = 6LL;
v34 = &unk_4A5199;
v35 = 4LL;
a1 = &v42;
sub_44D5E0(&v42);
sub_40B8F0((__int64)&v42, a2, v16, (__int64)&v36);
v44 = v24;
v45 = v25;
if ( !parameters_cnter )
sub_4248C0(&v42, a2, v17, qword_4FA5C0);
sub_40B8F0((__int64)&v42, a2, v17, qword_4FA5C0);
v46 = v24;
v47 = v25;
sub_40B8F0((__int64)&v42, a2, v18, (__int64)&v34);
v19 = v24;
v48 = v24;
v49 = v25;
ENC = 3;
v24 = 3LL;
sub_474860((__int64)&v42, a2, v20, v19, v21);
v22 = 1LL;
sub_45D8B0((__int64)&v42);
}
if ( (unsigned __int64)parameters_cnter <= 1 )
sub_4248C0(a1, a2, a3, parameters_cnter);
pswd = *(_QWORD *)(qword_4FA5C0 + 16);
v31 = *(_QWORD *)(qword_4FA5C0 + 16);
cur = *(_QWORD *)(qword_4FA5C0 + 24);
LEN = cur;
if ( cur >= 42 )
{
ptr = 0LL;
K = 0;
v29 = 0LL;
v27 = 0;
if ( cur <= 0 )
{
LABEL_11:
v42 = &unk_4A8374;
v43 = 28LL;
sub_40B8F0((__int64)a1, a2, (__int64)ptr, (__int64)&v42);
v32 = v24;
v33 = v25;
v24 = 1LL;
sub_474860((__int64)a1, a2, v11, v25, v12);
sub_45D8B0((__int64)a1);
}
else
{
while ( 1 )
{
a2 = *((unsigned __int8 *)ptr + pswd);
if ( (unsigned __int8)a2 >= 0x80u )
{
v22 = pswd;
ENC = cur;
v24 = (signed __int64)ptr;
sub_447350((__int64)a1, a2, (__int64)ptr, pswd, a5, a6);
a2 = (unsigned int)v25;
a1 = v26;
pswd = v31;
ptr = v29;
K = v27;
}
else
{
a2 = (unsigned __int8)a2;
a1 = (void **)((char *)ptr + 1);
}
v28 = a2 + K;
LOBYTE(v22) = a2 + K;
sub_47B760((__int64)a1, a2, (__int64)ptr, pswd, a5, a6, v22, ENC);
if ( (unsigned __int64)v29 >= 0x2A )
sub_4248C0(a1, a2, v10, v29);
a3 = byte_4F6260;
pswd = byte_4F6260[(_QWORD)v29];
if ( ENC != (_BYTE)pswd )
break;
LOBYTE(cur) = LEN;
pswd = v31;
ptr = a1;
K += a2;
v29 = a1;
v27 = v28;
if ( (signed __int64)a1 >= LEN )
goto LABEL_11;
}
}
}
v38 = &Nope_str;
v39 = 5LL;
v40 = 0LL;
v41 = 0LL;
sub_40B8F0((__int64)a1, a2, (__int64)a3, pswd);
v40 = v24;
v41 = v25;
sub_474860((__int64)a1, a2, v13, v25, v14);
return sub_45D8B0((__int64)a1);
}
在命令行参数满足条件的情况下,主要流程是判断flag长度需要大于等于42,然后逐位进行加密,并且和常数数组进行比较。
知道流程后,可以想到逆向程序加密算法从而解决。也可以用pin打桩的方法来解决
打桩需要在linux下安装pin,这个教程比较多,就不再赘述。
对于本题的情况,首先我们知道对flag的检查是加密后和常数数组逐位比较的,并且比较的次数和当前比较的位数是有一定关系(i+2=cmp_cnt),特殊情况是flag正确时,比较全部完成(i+1=cmp_cnt=len)。因此我们只需将inscount0.cpp修改一下即可。
我们找到了“比较”这一操作位于0x047B96E处,因此只需在inscount0.cpp进行如下修改:
VOID docount() { icount++; }
改成:
VOID docount(void *ip)
{
if ((long long int)ip == 0x000000000047B96E)
icount++;
}
由于docount函数的参数改变,因此调用这个函数的函数内部也应该进行修改:
INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)docount, IARG_END);
改成:
INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)docount, IARG_INST_PTR, IARG_END);
需要将这个文件保存后编译为.so文件:
[MyPintools]$ make obj-intel64/MyPinTool.so TARGET=intel64
然后进行暴力打桩即可,脚本如下:(注意,以下脚本有个小问题导致最后一位跑不出来,不过比较容易发现)
import os
charset="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0 123456789_-+*{}'"
flag="hxp{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}"
File_loc="/home/supergate/Desktop/Pin/pin/pin -t /home/supergate/Desktop/Pin/pin/source/tools/MyPinTool/obj-intel64/MyPinTool.so -- /home/supergate/Desktop/task1/go \""
def check():
os.system(File_loc+flag+"\"")
return int(read("inscount.out").split(" ")[1])
def read(fname):
with open(fname) as f:return f.read()
while(True):
for i in range(4,40):
for ch in charset:
tempflag=list(flag)
tempflag[i]=ch
flag=''.join(tempflag)
res=check()
print flag
if res==i+2:break
if res==42:
print flag
exit(0)
最终正确答案为:hxp{k3eP_C4lM_AnD_D0n't_P4n1c__G0_i5_S4F3}