Welcome back to another ARM buffer overflow challenge so in this post we are going to talk about how we can utilize multiple gadgets into the ROP chain to achieve our goal.
small recap:
As you guys know from the previous challenge we are able to take control of program by taking advantage of buffer overflow and using single gadget that contains instruction "pop {r0,pc}" by using this instruction we get the two top stack that we set to contain the address of "/bin/sh" and "system()" function, assign it to r0 (1st param) and pc (instruction pointer) to pop a shell
but using only a single gadget will not become effective in real-life scenario instead it will be mostly comprised of a huge and complex gadget.
to get the concept of this exploitation process, let's try to chain a two gadget together that able to overwrite a global variable
our target will be this following vulnerable code:
#import <stdio.h>
#import <unistd.h>
int changed_me = 0;
void gadget1(){
__asm__("pop {r0,r1,pc}");
}
void gadget2(){
__asm__("str r0,[r1]; pop {r4,pc}");
}
void validated(){
char input[8];
printf("You have to crash me ! \n");
gets(input);
if(changed_me != 0){
system("/bin/sh");
}else{
printf("who the hell are you \n");
}
}
int main(){
printf("Welcome to ROPLevel3 version 2 \n\n");
validated();
return 0;
}
~# gcc roplevel3.c -o roplevel3 --no-pie -fno-stack-protector
you can turn on the ASLR on
the following source code was inspired from roplevel3 Billie ellis exploit challenge link: https://github.com/Billy-Ellis/Exploit-Challenges/
finding the crash:



open the binary into gdb and let's input a long string to crash the application and from the figure, we can see that the offset is needed to be 13 but pay attention to the PC register at crash you can see that the character is kinda altered so you need to subtract the result of the pattern.py with one so it will be 12.
test it again with the following python script and we got the offset right


so we know how many offsets, we need to take control of the execution. It's time to construct the ROPGadget
our current objective is to overwrite the global variable changed_me by some random value (the point is we just need to insert some value onto the variable) if we able to do that we are able to grant access to execute a shell
there are two gadgets inside the source code:
void gadget1(){
__asm__("pop {r0,r1,pc}");
}
void gadget2(){
__asm__("str r0,[r1]; pop {r4,pc}");
}
the first gadget is set up the value of register r0,r1 and pc from the 3 top value in the stack and the second gadget will be used to store the value of r0 into the memory address that is stored in the r1 register and redirect the flow execution by popping the next two value of the stack
so here's how we construct the gadget :
first phase payload => padding + gadget1() address + 0x10101010 + <changed_me global variable address> + gadget2() address
the first phase we overwrite the PC register by pointing it into gadget1() function address now the gadget going to pop the 3 top value from the stack which is the value 0x10101010 m changed_me address and gadget2() address each of this value will be assigned to r0,r1,pc respectively after the first phase our current register will be like this
r0 = 0x10101010
r1 = <changed_me global variable address>
pc = gadget2() address
after this phase, the execution will continue to the gadget2() address
this will store r0 value that contains 0x010101010 to register r1 that right point to the memory address of changed_me global variable with this we are able to overwrite a value to a global variable
now all that's left is to redirect the execution again to the main function so we can get the shell
we need to update the previous payload to be like this: padding + gadget1() address + 0x10101010 + <changed_me global variable address> + gadget2() address + "AAAA" + main function
we append two value at the end of the payload since after storing the value to the global variable the gadget will assign the current stack value to register r4 and pc we need to provide two value to satisfy this execution, we don't care about the r4 value since we will not use it what we want is to assign the pc register with main function address so we can go back to that main function
Let's step through the payload manually so you get the sense of how the ROP works:

first, put a breakpoint at the end of the validated function and once we run it we are going to supply 12 characters so we know where we can edit the stack properly


this is how the stack looks like when we insert 12 characters notice that after the 12th character lies the return address.
we are going to overwrite this value with the payload that we have just construct previously
execute: ni so we can go to the next instruction


we need to gather this value in order to construct our payload

you can alter the stack value by using this command
0x00010484 => gadget1
0x01010101 => random value to get inserted into changed_me global variable
0x00020734 => location of changed_me global variable
0x0001049c => gadget2
of course, we need to supply additional value so we can get back to the main function.

let's step through the execution of our ROP chain one by one
the first is to execute the pop {r0,r1,pc}


once you arrive at the first gadget and type "ni" again now the register is set like the above figure


continue to the next instruction it will go to the second gadget that will store the value 0x01010101 to address global variable

if you proceed to the next instruction which is pop{r4,pc} it will update the register like the above figure. Continue the process we will end up with a shell

cool :) so we got a shell, now all it's left is to construct a python script with the following information we just got


COOL! WE GOT A SHELL!
So that's all folks hope you enjoy this post and see you again at the next arm buffer overflow series
Some note in this blog post in case you wondering why I am not using the original challenge
why don't you just use that challenge instead of creating your own?
yes I agree and I can do that but in the process of finishing the challenge, I encounter some unpleasant technical problems that make me nuts over 3 days but I'm grateful due to this struggle I get a chance to refine my understanding or buffer overflow.
1st technical problem, address, and bad character
as you guys know I compile the source code from the GitHub in azeria labs raspberry pi virtual machine but after I finish compiling the source code, the address of the global variable is using a bad character

in the original roplevel3 source code the user input is processed by scanf() function and scanf() will stop read your input if there are whitespace (0xb) and blank character (0x20) from the figure above we can see that the global variable stored in the address that contains blank character that will render our exploit useless. Well we can use ROP gadget to alter that condition but that will be a long and tedious process but I don't want to exhaust myself so I just changed the scanf to gets that are not sensitive to bad characters
2nd technical problem missing instruction
when I try to analyze the second gadget I noticed that after the second gadget "str r0, [r1]" there is no pop pc instruction that let us back to take control the execution flow so I altered the second gadget to have pop{r4,pc} :p
Comments
Post a Comment