pwnable.kr: [mistake]
A walkthrough of the 'mistake' challenge
Introduction
As my first post, I thought I would do a quick writeup of the Mistake challenge found on pwnable.kr. This challenge took me a bit longer than I expected, but the mistake does turn out to have interesting side effects.
The challenge is as follows:
We all make mistakes, let’s move on.
(don’t take this too seriously, no fancy hacking skill is required at all)
This task is based on real event
Thanks to dhmonkey
hint : operator priority
We’re presented with the following C program:
#include <stdio.h>
#include <fcntl.h>
#define PW_LEN 10
#define XORKEY 1
void xor(char* s, int len){
int i;
for(i=0; i<len; i++){
s[i] ^= XORKEY;
}
}
int main(int argc, char* argv[]){
int fd;
if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0){
printf("can't open password %d\n", fd);
return 0;
}
printf("do not bruteforce...\n");
sleep(time(0)%20);
char pw_buf[PW_LEN+1];
int len;
if(!(len=read(fd,pw_buf,PW_LEN) > 0)){
printf("read error\n");
close(fd);
return 0;
}
char pw_buf2[PW_LEN+1];
printf("input password : ");
scanf("%10s", pw_buf2);
// xor your input
xor(pw_buf2, 10);
if(!strncmp(pw_buf, pw_buf2, PW_LEN)){
printf("Password OK\n");
system("/bin/cat flag\n");
}
else{
printf("Wrong Password\n");
}
close(fd);
return 0;
}
After looking over it a bit, we can break down what’s happening into a few steps:
- The program is getting the file descriptor for a file called
password
that we don’t have permission to read. - It’s preventing us from bruteforcing it by adding a sleep statement
- It then reads
PW_LEN
bytes (10) from the file descriptor and stores them in the character array - The program then initializes another character array and asks us to input a password.
- It stores 10 bytes from our input into pw_buf2 using
scanf()
and then callsxor()
on it. - We can see that the
xor()
function will xor each byte of our password with 1, as it’s defined in the macro. - It then compares our xor’ed password buffer with the original password buffer that contains the contents of
/home/mistake/password
. - If the contents match, we win!
Debugging
The way I got started was by creating a copy of this program and adding debug statements throughout it. It also helps to comment out the sleep statement and create a local password file that the program can read from. The password should be 10 characters.
There’s also a clue early on, but I didn’t notice it until later. When you run the original program, it sleeps somewhere between 0 and 20 seconds. After that, it should immediately prompt us for input and display input password:
. However, it doesn’t bring up that prompt until you hit enter. I totally overlooked this for awhile, but I did think it was kind of weird.
The two lines that stood out as weird to me were:
if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0){
if(!(len=read(fd,pw_buf,PW_LEN) > 0)){
There was a lot to unpack from these two lines (for someone not familiar with C), so I started on the first one. I got hung up on the fact that no matter what I gave the program as input (via stdin or stdout), somehow len
always ended up being 1 and fd
was always 0. I was so convinced that the unchanging len
was the problem that I was overlooking the root cause.
Instead of trying to figure out what exactly was wrong, I decided I would re-write those two concerning looking lines and figure out how the program would run if it didn’t contain a mistake! I rewrote those two lines to the following:
fd=open("./password",O_RDONLY,0400);
if(fd < 0){
printf("can't open password %d\n", fd);
return 0;
}
printf("fd value: %d\n", fd);
len=read(fd,pw_buf,PW_LEN);
if(!(len > 0)){
printf("read error\n");
close(fd);
return 0;
}
printf("len value: %d\n", len);
Fixing the Mistake
I noticed 3 changes after “fixing” the code:
fd
was no longer always 0len
was always 10 instead of always 1- The program didn’t hang before displaying the input prompt
I had been assuming that open()
returned 0 if the function was successful instead of realizing that it was returning a file descriptor. I believe the reason it’s incorrectly being set to 0 is because the open function open("/home/mistake/password",O_RDONLY,0400) < 0){
evaluates to 3, and 3 is not less than 0.
If you’re familiar with *NIX, you’ll recognize that 0 is the file descriptor for STDIN. The reason the program was hanging after the sleep statement was because it was trying to read from STDIN instead of reading from /home/mistake/password
!
What does this mean for us? We control the contents of pw_buf
(it’s what we pass via STDIN) and pw_buf2
(it’s what we pass at the input prompt).
We control both passwords now!
With that revelation under our belt, we just need to craft a 10 character password and figure out what that password becomes after each byte of it is xor’ed with 1. I decided to set a password of BBBBBBBBBB
. We know from ASCII that B == 42, so we can calculate that 42 XOR 1
is 43, which is the letter C. Now we know if we supply a second password of CCCCCCCCCC
, it will be XOR’ed with 1 again and the result will be BBBBBBBBBB
.
To clarify, here’s what happens step by step:
mistake@ubuntu:~$ ./mistake
do not bruteforce...
BBBBBBBBBB
<enter>
The 10 B’s entered via STDIN will be stored in pw_buf
because the mistake in the open()
call line causes fd
to be set to 0, causing the program to read 10 bytes from STDIN instead of the password file.
input password : CCCCCCCCCC
The input from this prompt will be stored in pw_buf2
and then the contents will be sent to the XOR function where each byte will be XOR’ed with 1. After the XOR function, pw_buf2
will contain the same contents as pw_buf
. The program will do a strncmp()
of pw_buf
and pw_buf2
, determine they are the same, and voila!
Password OK
Mommy, the operator priority always confuses me :(
Share this post
Twitter
Facebook
Reddit
LinkedIn