1625-5 王子昂 总结《2017年9月21日》 【连续第354天总结】
A. Syclover成信CTF练习平台-RE
B.
打开是一个elf文件,运行报段错误
通过gdb调试发现错误出现在sub_400646的puts函数处
拖入IDA反编译:
__int64 __fastcall main(int a1, char **a2, char **a3)
{
__int64 result; // rax@3
__int64 v4; // rdx@7
char v5; // [sp+10h] [bp-10h]@4
__int64 v6; // [sp+18h] [bp-8h]@1
v6 = *MK_FP(__FS__, 40LL);
if ( a1 != 3 && (unsigned int)sub_400646((__int64)a2) )// 验证参数
{
puts("Keep thinking!");
result = 0LL;
}
else
{
printf("Please input your password(5 words):", a2, a2);// 检查password
__isoc99_scanf("%5s", &v5);
if ( (unsigned int)sub_400755((__int64)&v5) == 1 )
{
printf("Good Job!\nThe password:%s", &v5);
result = 0LL;
}
else
{
puts("Wrong!");
result = 0LL;
}
}
v4 = *MK_FP(__FS__, 40LL) ^ v6;
return result;
}
我们知道,main的三个参数分别为argc, argc, env
很明显a1即argc没有被反编译出来,但是我们知道数量要等于3,即需要附加两个参数(第一个参数为文件路径)
因此在未输入足够参数的时候puts的参数自动跳到了后边env造成溢出
sub_400646的内容很简单,直接将两个参数分别与字符串对比,写个脚本跑出来是“l1nux”和”crack“
验证完参数后对照password,查看sub_400755:
__int64 __usercall sub_400755@<rax>(__int64 a1@<rax>)
{
__int64 result; // rax@6
if ( *(_BYTE *)a1 + *(_BYTE *)(a1 + 4) != 106 || *(_BYTE *)a1 != 73 )// 1=73;5=33
{
result = 0LL;
}
else if ( *(_BYTE *)(a1 + 1) == 76 ) // 2=76
{
result = *(_BYTE *)(a1 + 2) + *(_BYTE *)(a1 + 3) == 137 && *(_BYTE *)(a1 + 3) == 70;// 4=70;3=67
}
else
{
result = 0LL;
}
return result;
}
稍微有点意思的验证,不过简单加减法就能得到5个ASCII:“ILCF!”
最后flag按提示拼接:flag{l1nux_crack_ILCF!}
down下来发现无壳,C++写的exe
直接运行提示输入password
题目提示是C++的类
IDA反编译的结构还是比较凌乱的,找不到main函数,直接搜索字符串”plz input your flag”找到了sub_43A1C0
int sub_43A1C0()
{
char v1; // [sp-4h] [bp-210h]@1
int v2; // [sp+Ch] [bp-200h]@1
LPVOID v3; // [sp+14h] [bp-1F8h]@1
int v4; // [sp+20h] [bp-1ECh]@4
int v5; // [sp+ECh] [bp-120h]@1
char input; // [sp+F8h] [bp-114h]@1
unsigned int v7; // [sp+1FCh] [bp-10h]@1
int v8; // [sp+208h] [bp-4h]@1
int savedregs; // [sp+20Ch] [bp+0h]@1
memset(&v2, 0xCCu, 0x1F4u);
v7 = (unsigned int)&savedregs ^ __security_cookie;
v5 = 0;
sub_43650C((int)"plz input your flag:", (unsigned int)&savedregs ^ __security_cookie);//printf
sub_4343D8("%s", &input, 256);//scanf
v3 = sub_4357C9(288);
v8 = 0;
if ( v3 )
v2 = sub_43618D(&input);
else
v2 = 0;
v4 = v2;
v8 = -1;
v5 = v2;
sub_433267(v2);//change_input
sub_43438D(v5);//strcmp
if ( sub_43418A(v5) )//get_result
sub_43650C((int)"flag is true!\n", v1);
sub_43350A();
sub_43410D("pause");
sub_43590E(&savedregs, &dword_43A31C);
sub_435A17();
return sub_434716();
}
虽然能够从字符串推测出来printf和scanf,但是大部分函数都经过多层包装,很难发现真正含义
这一题简单的做法是直接通过OD进行动态调试,从scanf开始跟踪input
发现在sub_433267后寄存器中出现了新的字符串
而在sub_43438D后改变了eax的值,最后跳转是test eax,eax,即比较过程应该在sub_43438D中完成
回到IDA中观察,sub_433267的过程很简单:
int __thiscall sub_43A670(int this)
{
unsigned int v1; // eax@3
char v3; // [sp+Ch] [bp-D8h]@1
unsigned int i; // [sp+D0h] [bp-14h]@2
int v5; // [sp+DCh] [bp-8h]@1
memset(&v3, 0xCCu, 0xD8u);
v5 = this;
if ( *(_DWORD *)(this + 280) )
{
for ( i = 0; ; ++i )
{
v1 = sub_4340BD(v5);
if ( i >= v1 )
break;
*(_BYTE *)(i + v5) ^= 0xCu;
}
}
return sub_434716();
}
sub_4340BD应该是get_value类的函数,取出数据成员
核心语句为最后的异或0xC
尝试输入123456789,手动逐字符与0xc异或后与sub_433267后的字符串对比发现一致
下一步则是找到cmp的字符串常量,IDA中发现地址为this + 256
看来是类常量
最简单的方法就是直接OD查看寄存器中的另一个字符串,发现为:
_UOwXN=`KH?d|G;Utz_vq
脚本异或处理后得到flag:SYC{TB1lGD3hpK7YxvSz}
这种是比较简单的类逆向,如果是很复杂的类,则需要理清类结构了
首先知道,this指针通常存放在ecx中
也就是说,如果很多函数调用之前mov ecx, xxx了,那么很可能它们就是函数成员
例如本题中,change_flag、strcmp和get_value都是函数成员
0111A296 . 8B8D E0FEFFFF mov ecx,dword ptr ss:[ebp-0x120]
0111A29C . E8 C68FFFFF call CTF_RE_B.01113267 ; change_input
0111A2A1 . 8B8D E0FEFFFF mov ecx,dword ptr ss:[ebp-0x120]
0111A2A7 . E8 E1A0FFFF call CTF_RE_B.0111438D ; strcmp
0111A2AC > 8B8D E0FEFFFF mov ecx,dword ptr ss:[ebp-0x120]
0111A2B2 . E8 D39EFFFF call CTF_RE_B.0111418A
0111A2B7 . 85C0 test eax,eax
0111A2B9 . 74 11 je short CTF_RE_B.0111A2CC
每次调用之前都将ebp-0x120送入ecx中,很可能就是在取this指针
因为函数成员的第一个参数必为this(隐性)
同时,如果函数体内ecx还未被初始化就被使用,那么基本上就可以确定它是函数成员了
注意IDA在这里反编译的时候给他们送入了错误的参数,甚至没有参数
说明IDA对类的函数成员识别地不太好
在内存中,类的成员是依次排列的,所以经常会使用this + xxx的方式来调用数据成员
虚函数表的指针存放在类中,通常call eax这一类就是在调用虚函数表
这种情况下首先要找到虚函数表,然后再逐个函数反编译
C. 明日计划
国赛RE