CS:APP二进制炸弹phase5

写在前面

在阶段4中的,我不得不承认,为了搞懂func4在搞什么,我还是花费了一些功夫的,主要原因是对其中几个汇编代码生疏,还特地上网查了一下。不过还好最后得出了答案!

本篇来看看phase_5,应该难度又有些上升了。


分析

同样的,先找到调用phase_5的汇编代码:

CS:APP二进制炸弹phase5_第1张图片

反汇编函数phase_5如下:

CS:APP二进制炸弹phase5_第2张图片

寄存器%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,进入下一阶段吧!


你可能感兴趣的:(汇编语言,工具学习)