1. 문제확인



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

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


엄마~ 프로그램에 어떻게 내 입력이 통과 할 수 있어여? 하고 물어보고있어여

엄마는 아니지만 문제를 한번 봅시댱



2. 문제 코드 분석


2-1) main()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.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 != 100return 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, 41, 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, 40!= 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;
}



이번 문제는 메인함수밖에 없는데 지금까지 본 것들이랑 비교해서 더럽게 길었어여 ㅠㅠ

구성을 보니까 이번 문제를 풀려면 다섯가지 스테이지를 풀어야하는거 같네여


각 스테이지 별로 물어보는 항목을 정리하면!


1번 Stage는 프로그램 실행할 때 인자가 어떻게 들어가는지를 알고있냐? 

2번 Stage는 pwnable.kr fd문제처럼 file descriptor번호 알아?

3번 Stage는 프로그램 실행할 때 환경변수 줄 수 있냐?

4번 Stage는 프로그래밍으로 파일 핸들링할 수 있음?

5번 Stage는 소켓 통신 할 줄 암?


소스가 길어서 한번에 보기 힘드니까 스테이지 별로 짤라서 볼께여 ㄱㄱ



2-1) Stage 1

1
2
3
4
5
6
7
8
9
int main(int argc, char* argv[], char* envp[]){
    // argv
    if(argc != 100return 0;
    if(strcmp(argv['A'],"\x00")) return 0;
    if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
    printf("Stage 1 clear!\n");
 
    return 0;
}



argc에는 ./input 과 같은 실행명령어를 포함해서 인자의 갯수가 들어있어여

argv는 char *형 배열인데, 인자가 각각 들어있고여, 가장 첫 번째에는 실행명령어가 들어있어여

예를 들어서 ./input aaa bbb 라고 실행을 하면


argc == 3

argv[0] == "./input"

argv[1] == "aaa"

argv[2] == "bbb" 가 되겠져?!


envp에는 프로그램을 실행할 때 로드하는 환경변수들이 들어있는데 얘는 카운트를 가지고있지를 않아여ㅠ

대신 배열요소의 끝검사를 NULL로하면 하나씩 다 뿌려볼 수 있답니당


여하튼! 

스테이지 1번을 풀려면 argc의 갯수를 100개로 맞추면서 argv['A'] 그러니까 argv[65]번째 인자엔 \x00이 있어야하고

argv[66]에는 \x20\x0a\x0d가 있으면 클리어 할 수 있을꺼에여 어렵지 않져?



2-1) Stage 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main(int argc, char* argv[], char* envp[]){
 
    // 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");
 
    return 0;
}



스테이지 2를 풀려면 read함수랑 파일디스크립터 번호를 알아야되여 ㅠㅠ


read함수 원형은


1
ssize_t read(int fd, void *buf, size_t nbytes);                            



요렇게 생긴놈입니당! 리턴타입 ssize_t는 size_t는 우선은 그냥 int형으로 생각하고 푸셔도

문제푸는데에는 지장이 없어영 하지만! 자세히 알고싶으시다면 아래 링크를 한번 살펴보세욥


당연하지만 간과하는 size_t ssize_t

- http://lacti.me/2011/01/08/different-between-size-t-ssize-t/ 


read는 파일디스크립터 번호를 받아서 지정한 바이트만큼 버퍼에 읽어오는 함수에여

여기서 예약되어있는 파일디스크립터 넘버를 몇 개 살펴보쟈규여


0 : stdin

1 : stdout

2 : stderr


얘네들을 이용하면 콘솔 I/O랑 에러났을 때 발생 한 값들을 read 할 수 있을거에여

문제에서는 0번이랑 2번 썼으니까 입력 / 에러발생에서 4바이트씩 읽어오겠져!



2-4) Stage 3

1
2
3
4
5
6
7
8
int main(int argc, char* argv[], char* envp[]){
        
    // env
    if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
    printf("Stage 3 clear!\n");
 
    return 0;
}



스테이지3은 환경변수에 \xde\xad\xbe\xef항목에 \xca\xfe\xba\xbe값만 셋팅해주면 문제가 없어보여여 


2-5) Stage 4

1
2
3
4
5
6
7
8
9
10
11
12
13
int main(int argc, char* argv[], char* envp[]){
                
    // file
    FILE* fp = fopen("\x0a""r");    
    if(!fp) return 0;
    if( fread(buf, 41, fp)!=1 ) return 0;
    if( memcmp(buf, "\x00\x00\x00\x00"4) ) return 0;
    fclose(fp);
        
    printf("Stage 4 clear!\n");
 
    return 0;
}



스테이지 4는 파일을 열어서 맨앞에서 네바이트를 읽고 \x00\x00\x00\x00값인지 체크하고있는데

파일이름이 \x0a에여 ㅠㅠ 콘솔에서 만들순 엄꾸.. fopen같은 함수를 써서 만들어줘야겠어여


2-6) Stage 5

드뎌 마지막 스테이지 5를 봅시당


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
int main(int argc, char* argv[], char* envp[]){
                        
    // 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, 40!= 4 ) return 0;        
    if(memcmp(buf, "\xde\xad\xbe\xef"4)) return 0;        
    printf("Stage 5 clear!\n");
 
    return 0;
}




어휴; 뭐 땀시 이렇게 소스가 길었나 했더니 스테이지 5때문이네여;;

하나하나 다 뜯어보고 설명하면 좋지만 양이 넘나 많네여 ㅠ 잘 모르시거나 생소하신 분들은 링크 걸어드릴게여


[소켓]3. 함수와 구조체 설명!

http://rotapple.tistory.com/8 


흐름만 간략히 파악하고 가면 실행할 때 던져주는 인자 중에 argv['C'] 요놈 그러니까 argv[67] 에 있는 값을 포트로해서

소켓을 열꺼에여 그리구 그 소켓에서 네 바이트를 받아서 \xde\xad\xbe\xef값이 들어오면 클리어!


3. 익스플로잇 코드


이번 문제를 풀려면 간단한 입력만으로는 안끝나여 ㅠㅠ 

파일을 핸들링하거나 소켓으로 데이터를 쏘거나 해야해서 짧게나마 코드를 작성해서 풀어야할꺼에여

이 문제 푸신 분들이 거의 c로 작성해서 푸셨더라고여? 그래서 저는 파이썬으로 도전해보려고 쌩파이썬으로 도전했는데

생각보다 너무 안풀리고 의도랑 다르게 돌아가는 바람에 ㅠ

파이썬으로 짠 코드를 컨닝하려고 하는 도중!! pwntools라는 걸 알게됐어여


기능이 무궁무진한거 같긴한데 이번 포스팅에서 한번에 다룰순 없을거 같고 요번에 쓴거 위주로만 썰풀어볼게여

일단 결과코드부터 ㄱㄱ


아 코드는 이 분 블로그를 참조했어여!  

http://gmltnscv.tistory.com/27


그리구 문제풀려고 서버에 접속하면 홈디렉터리에서는 코드를 작성할 수가 없어여 ㅠㅠ

/tmp 아래에 자기 폴더를 하나 뙇 만들어주고 작업을 하시면 됩니당


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *
 
argvs = [str(i) for i in range(100)]
argvs[ord('A')] = '\x00'
argvs[ord('B')] = '\x20\x0a\x0d'
 
with open('./stderr_file''a') as f:
    f.write('\x00\x0a\x02\xff')
 
envVal = {'\xde\xad\xbe\xef':'\xca\xfe\xba\xbe'}
 
with open('./\x0a''a') as f:
    f.write('\x00\x00\x00\x00')
 
argvs[ord('C')] = '99999'
 
target = process(executable='/home/input2/input', argv=argvs, stderr=open('./stderr_file'), env=envVal)
target.sendline('\x00\x0a\x00\xff')
 
conn = remote('localhost'99999)
conn.send('\xde\xad\xbe\xef')
target.interactive()                                                 



첫 번째 줄 from pwn import * 에서 pwntools를 임포트하고이써여 물론! 임포트하려면 설치를 해야겠져?


설치 방법은

apt-get install python2.7 python2.7-dev python-pip

pip install pwntools

apt-get install libcapstone-dev


뙇 치면 설치가 뙇!! 후하


별 이상 없이 설치가 되셨다면 뒷부분 이어서 ㄱㄱ할게여


3 ~ 5라인은 스테이지 1번을 위한 부분이에여

3라인에서 argvs를 선언해서 0부터 99까지 문자열로 만들었어여

4, 5라인에서는 ord()함수를 통해서 문자를 정수형으로 바꿔준담에 스테이지1에서 원하는 값들로 값들을 바꿔줬지영


7~8라인은 스테이지 2를 풀기위한 부분이에여 stderr_file이라는 파일을 만들어서 문제에서 요하는(stderr일 때)값을 썼어여

with 문을 써서 열면 따로 닫아줄 필요가 없다는 사실!

18라인에서 stdin을 해주려고 sendline을 써서 값을 넣어줬어여



10라인에 envVal은 환경 변수를 선언했어여 이따가 실행할 때 넘겨주면 뙇! 하고 스테이지 3을 풀 수 있어여 


12 ~ 13라인은 스테이지 4에서 파일 열어서 값읽어보는거 있었짜나여? 그거 때매 같은이름으로 파일 열어서 값써준검미다.


15 라인에서 어디 포트로 쏠껀지 인자에 포트번호 써준 담에

20라인에서 연결하고!

21라인에서 값을 뙇 하고 쏴줌으로써 스테이지 5도 해결이되지욥


17라인이 이 모든 셋팅을 마치고 실행하는 부분이에여 executable에서 실행할 바이너리 주소, argv에 전달할 인자,

stderr에서 stderr일 때 할 행위가 들어가있고, 환경변수까지 전달을 해줬어여



다 작성을 마치고 기쁜마음으로 뙇 돌리면!!!!



안나와여 플래그가 ㅠㅠㅠㅠㅠ


아 읽어줄 플래그가 엄꾸나! 근데.... 퍼미션때매 카피도안되고ㅠㅠ..


그래서 심볼릭 링크를 걸어줍니다 헤헷


그리고 다시 한번 돌리면!!


뙇 하고 성공했어여ㅋㅋ


이번 문제는 요구하는 것들이 어렵지는 않았는데 익스플로잇코드를 처음 짜본..터라 쉽지는 않았던거 같아여 ㅠㅠ

그래도 덕분에 pwntools라는 툴도 알게됐으니~~ 다음부턴 아주 요긴하게 써먹어야겠쯥니당 ! 이상!


'Study > Pwnable' 카테고리의 다른 글

[HITCON Training] lab4 / return to library  (0) 2017.11.29
[HITCON Training] lab2 / shellcraft  (0) 2017.11.28
NX(No eXcutable) / ROP  (1) 2017.09.29
pwnable.kr passcode  (5) 2017.07.31
PEDA / pwnable.kr bof문제  (0) 2017.07.12

+ Recent posts