写在前面
在阶段4中的,我不得不承认,为了搞懂func4在搞什么,我还是花费了一些功夫的,主要原因是对其中几个汇编代码生疏,还特地上网查了一下。不过还好最后得出了答案!
本篇来看看phase_5,应该难度又有些上升了。
分析
同样的,先找到调用phase_5的汇编代码:
反汇编函数phase_5如下:
寄存器%rbx入栈,开辟0x20大小的栈空间。
mov %rdi,%rbx,使寄存器%rbx指向我们的输入。
接下来的关于%fs:0x28的操作我们这里忽略,这是gcc做的一个栈保护检测机制。
接着调用函数string_length返回我们输入字符的长度,判断长度是否为6个字节,如果不是则触发炸弹。就是说phase_5对输入字符串的长度是有严格限制的,不想phase_2那样只要前面字节是正确的,后面的有没有都无所谓。
输入6字节长度的字符串后,跳转到0x00000000004010d2处执行。设置寄存器%eax值为0,再跳转到0x000000000040108b指定。
movzbl (%rbx,%rax,1),%ecx,对于这样的指令格式我们遇到不止一次了,可能有的人遇到这样的汇编指令会晕头转向,这里我简单说明下:众所周知,x86/x64 寻址方式众多,什么直接寻址、间接寻址、基址寻址、基址变址寻址等等让人眼花缭乱,而 AT&T 语法对内存寻址方式做了一个很好的统一,其格式为section:displacement(base, index, scale),其中section 是段地址,displacement 是位移,base 是基址寄存器,index是索引,scale 是缩放因子,其计算方式为线性地址=section + displacement + base + index*scale,最重要的是,可以省略以上格式中的一个或多个部分,比如 movw 4, %ax 就是把内存地址 4 中的值移动到 ax 寄存器中,movw 4(%esp), %ax 就是把 esp+4 指向的地址中的值移动到 ax 寄存器中,依此类推。
再补充一点,一般遇到这样格式的指令,都是在循环访问数组。这里就是典型的访问数组的案列,即我们输入的6字节字符数组。
取出一个字符传入寄存器%ecx中。
mov %cl,(%rsp)
mov (%rsp),%rdx
and $0xf,%edx
这三行只需关注最后一行,即只取字符数值的低4位。例如字符'A',ascii码值为0x41,取低4位后得到0x01。
movzbl 0x4024b0(%rdx),%edx,又是访问数组,并且是用刚刚才算得的字符低4位数值作为索引来访问这个值。从movzbl得到这是个单字节数组,数组首地址是0x4024b0。我们使用x命令查看下这个数组的内容是什么。
观察,首先从array.3449命名上可以大胆测试,array是个局部静态字符数组。其次发现输出的内容的后半部分是在信号处理函数中使用过的,因此数组array的内容应该是前半部分。
接着往下看。
mov %dl,0x10(%rsp,%rax,1),哈哈,看来今天是跟数组杠上了,这句又是在访问数组,而且是个局部单字节数组,数组首地址在%rsp + 0x10处,目前猜测这个数组至少6个字节大小。
接下来三条汇编指令,我们知道这是个循环的过程。
循环结束后,movb $0x0,0x16(%rsp),将局部单字节数组第7个元素复制为0,所以我们可以得出这个局部数组至少7个字节大小。
最后又调用函数strings_not_equal来比较字符串,这个函数在phase_1中就已经分析过了。
待比较的两个字符串一个是刚刚的局部数组,一个是存储在地址0x40245e处的字符串,我们使用x/s查看是"flyers"。
如果不相等,则触发炸弹,否则OK。
好了,看来我们输入的6个字符是array数组的下标值。得到的6个字符必须是"flyers",得出这个结论就好办了,接下来就是数数游戏了。要得到字符"flyers",下标值必须依次为9、15、14、5、6和7。用16进制来表示的话,则为0x09、0x0f,0x0e,0x05,0x06和0x07。
也就是说我们输入的
第一个字符的低4位的值必须是0x09,查表得出字符')'、‘9’、‘I’、‘Y’、‘i’、‘y’符合条件。
第二字字符的低4位的值必须是0x0f,查表得出字符‘/’、‘?’、‘O’、‘_’、‘o’符合条件。
第三个字符的低4位的值必须是0x0e,查表得出字符‘.’、‘>’、‘N’、‘^’、n‘、‘~’符合条件。
第四个字符的低4位的值必须是0x05,查表得出字符'%'、‘5’、‘E’、‘U’、‘e’、‘u‘符合条件。
第五个字符的低4位的值必须是0x06,查表得出字符‘&’、‘6’、‘F’、‘V’、‘f’、‘v’符合条件。
第六个字符的低4位的值必须是0x07,查表得出字符‘''、‘7’、‘G’、‘W’、‘g’、‘w’符合条件。
因此phase_5的答案就多了,这6组字符随便按顺序组合即可。
C源码应该如下:
#include
#include
#include
#include
int strings_not_equal(const char *input, const char *dst)
{
int result;
size_t len1 = strlen(input);
size_t len2 = strlen(dst);
if (len1 != len2) {
result = 1;
} else if (input[0] == '\0') {
result = 0;
} else {
result = 1;
while (*input++ == *dst++) {
if (*input == '\0') {
result = 0;
break;
}
}
}
return (result);
}
static size_t string_length(const char *s)
{
if (s[0] == '\0') {
return (0);
}
const char *p = s + 1;
ptrdiff_t len = p - s;
while (*p++ != '\0') {
len = p - s;
}
return (size_t)len;
}
void phase_5(const char *input)
{
static int array[] = { 'm', 'a', 'd', 'u', 'i', 'e', 'r', 's', 'n', 'f', 'o', 't', 'v', 'b', 'y', 'l'};
char str[7];
size_t len = string_length(input);
if (len != 6) {
explode_bomb();
}
int i;
for (i = 0; i < 6; ++i) {
str[i] = array[input[i] & 0x0f];
}
str[6] = '\0';
int result = strings_not_equal(str, "flyers");
if (result != 0) {
explode_bomb();
}
}
#if 0
int main(int argc, const char *argv[])
{
phase_5(")/.%&'");
return 0;
}
#endif
其实本阶段对我来说没遇到什么难点,毕竟汇编指令都是常用的,只不过汇编指令比较多,需要更多的耐心才行。OK,进入下一阶段吧!