前言
又是划水的最近,花了几天时间来入门逆向,二进制师傅是大爷,觉得自己会一点onlydbg
和ida
啥的,来做一做攻防世界的Re
题,发现很多都是要看题解才能完成(tcl没办法),这里碰到两个有意思的题来分析分析
正文
csaw2013reversing2
拖到Exeinfo
里看一看发现:
无壳,在拖到PEiD
中看看发现:
话不多说,直接ida pro32
打开看一看,找到main()
函数后一键F5看看伪码:
用我菜的不能再菜的C语言基础分析一下大致就是:
先创建了一个私有堆
,然后在这个私有堆
上分配了指定大小的内存,分配后的内存不可移动,接着
将后者内存拷贝到前者分配内存的私有堆
上,这里第一想法肯定是直接看后者的数据,但是结果很失望,转成字符后是这样的:
然后程序的逻辑接着就是进入IF
判断,但是不管如何只要进入判断都会执行
ExitProcess(0xFFFFFFFF)
,如果是这样的话,那后面的代码就没用了,所以这里是比较可疑的地方,继续向下分析,弹出对话框函数,将Flag
作为标题,窗体程序是lpMem + 1
这个位置(注意并不是IpMem),在后面就是释放私有堆的内存然后退出。
分析完之后我们可以运行一下程序,和之前的分析再来进行对比:
Flag
弹框中是乱码的,刚开始到这里就没辙了,想直接看字符串,发现并没有flag的字符串,因此肯定是存在变形函数变形后得到flag
注意:这个时候要注意到是存在弹框的,说明了什么?
说明if()
条件是不成立的,那么里面的代码根本没有执行,直接跳过了,而里面的代码是什么?我们跟进看一看:
__debugbreak()
这是反调试的,一调试就会退出,辛运的是它压根没执行
接下来这个函数,可以猜测到是对lpMen
的处理
还比较的复杂,这里这个函数是不是对之前拷贝后的内存的值进行某种变化,而这种变化最后呈现的就是flag的值?
很有可能是这样子,分析到这里开onlydbg
进行动态调试:
这里我们可以在sub_40102A() || IsDebuggerPresent()
任意一个函数设置断点来进行分析,这里我选择在后者进行断点
单步步入后我们发现
直接条件跳转到了MessageBoxA
中而并没有执行if
里面的函数,而我们的想法是在这里先条件跳转到sub_401000
这个函数,在将这个函数跳转到MessageBoxA
上,这样经过这个函数处理的私有堆能够呈现在弹出框中,也就是弹出真正的flag
,因此现在就是找到sub_401000
的地址
可以清楚看到在CALL
了一个函数后就直接跳转到了ExitProcess
中,那么我们就可以知道这个CALL
就是在调用sub_401000
,因此修改汇编,跳转到该函数的前一个位置
一直单步步入后发现最终会跳转到ExitPorocess
这时我们也需要修改汇编到第二个MessageBoxA
,为什么不是第一个呢?
原始的程序也是跳转到第二个MessageBoxA
,这里跟着原程序走就行
修改完成后可以单步步入继续看eax
的值,也可以直接F9运行修改后的程序
运行到这里的时候我们发现flag
已经写在了内存中,运行程序也得到了同样的结果
maze
这个题我确实是不会做,原谅一个菜鸡为什么如此辣鸡,看了题解之后想了很久才逐渐领悟,下面来分析一下:
先来看看文件类型:elf 64bit
放ida pro 64
直接看:
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
signed __int64 v3; // rbx
signed int v4; // eax
bool v5; // bp
bool v6; // al
const char *v7; // rdi
__int64 v9; // [rsp+0h] [rbp-28h]
v9 = 0LL;
puts("Input flag:");
scanf("%s", &byte_6010BF[1], 0LL);
if ( strlen(&byte_6010BF[1]) != 24 || strncmp(&byte_6010BF[1], "nctf{", 5uLL) || byte_6010BF[24] != '}' )
{
LABEL_22:
puts("Wrong flag!");
exit(-1);
}
v3 = 5LL;
if ( strlen(&byte_6010BF[1]) - 1 > 5 )
{
while ( 1 )
{
v4 = byte_6010BF[v3 + 1];
v5 = 0;
if ( v4 > 'N' )
{
v4 = (unsigned __int8)v4;
if ( (unsigned __int8)v4 == 'O' )
{
v6 = sub_400650((_DWORD *)&v9 + 1);
goto LABEL_14;
}
if ( v4 == 'o' )
{
v6 = sub_400660((int *)&v9 + 1);
goto LABEL_14;
}
}
else
{
v4 = (unsigned __int8)v4;
if ( (unsigned __int8)v4 == '.' )
{
v6 = sub_400670(&v9);
goto LABEL_14;
}
if ( v4 == '0' )
{
v6 = sub_400680((int *)&v9);
LABEL_14:
v5 = v6;
goto LABEL_15;
}
}
LABEL_15:
if ( !(unsigned __int8)sub_400690((__int64)asc_601060, SHIDWORD(v9), v9) )
goto LABEL_22;
if ( ++v3 >= strlen(&byte_6010BF[1]) - 1 )
{
if ( v5 )
break;
LABEL_20:
v7 = "Wrong flag!";
goto LABEL_21;
}
}
}
if ( asc_601060[8 * (signed int)v9 + SHIDWORD(v9)] != '#' )
goto LABEL_20;
v7 = "Congratulations!";
LABEL_21:
puts(v7);
return 0LL;
}
goto确实看的头晕,先从开始的if判断
分析,因为这个if判断
直接就exit()
里,所以怎么样都是不能让if
的条件满足
输入的长度是24位,并且前5位是
nctf{
,最后一位是}
,这个还是很容易看懂
接下来一定会进入while()
的循环之中,v4
就是nctf{}
里面的内容逐个取出
这里分了两种情况,一种情况是v4>'N'
,如果v4==O
,那么v9的高位(也就是(_DWORD *)&v9 + 1
)会进行sub_400650
,v4==o
则进行sub_400660
,同理另外一个情况也是类似,只是直接是v9
来作为这俩个函数的参数,分析这两个关键函数:
- sub_400650
bool __fastcall sub_400650(_DWORD *a1)
{
int v1; // eax
v1 = (*a1)--;
return v1 > 0;
}
- sub_400660
bool __fastcall sub_400660(int *a1)
{
int v1; // eax
v1 = *a1 + 1;
*a1 = v1;
return v1 < 8;
}
第一个是减一操作,第二个是加一操作,很奇怪,加一减一这意味着啥啊?
我们继续看这个:
里面那个函数的返回值一定要是1才行,我们看一下sub_400690
:
__int64 __fastcall sub_400690(__int64 a1, int a2, int a3)
{
__int64 result; // rax
result = *(unsigned __int8 *)(a1 + a2 + 8LL * a3);
LOBYTE(result) = (_DWORD)result == ' ' || (_DWORD)result == '#';
return result;
}
result =a1的基地址上 + a2 + 8 * a3
,如果result == ' ' || '#'
返回True
而a1就是asc_601060
,a2是SHIDWORD(v9)
,a3是v9
,这里我们不清楚a2到底是啥,我们看一下汇编图
当执行这个函数,也就是sub_400690
之前,先将[rsp+28h+var_28+4]
压入寄存器,再将[rsp+28h+var_28]
压入寄存器,这两个作为参数也就分别对应了SHIDWORD(v9)和v9
,因此SHIDWORD(v9)
就是v9的高位
因此在来看上文的result =a1的基地址上 + a2 + 8 * a3
a2就是v9
的高位,a3就是v9
的低位,那a1是啥?
其实可以大致猜测出,a1是一个8*8
的数组,并且值由这些组成,a2代表列,a3代表行,a3+1
也就是向下走了一个单位,对应了v4==0
,因此根据这样的判断我们得出'O','o'表示左右,'.','0'表示上下
而v5 == v6
则是判断是否越界,如果越界则直接break
,跳出循环后,
最终如果这个数组的值不是#
,则也直接over
因此符合了题目名字–迷宫,我们将那个数组显示出来
00******
*000*00*
***0*0**
**00*0**
*00*#00*
**0***0*
**00000*
********
因此我们的步骤就是右下右右下下左下下下右右右右上上左左
,再加上nctf{}
正好24个字符,也就组成了最后的flag
str = "右下右右下下左下下下右右右右上上左左"
str = str.replace('上', '.')
str = str.replace('下', '0')
str = str.replace('左', 'O')
str = str.replace('右', 'o')
str = 'nctf{' + str + '}'
print(str)