DongDD's IT

[pwnable.kr] input 본문

Wargame/pwnable.kr

[pwnable.kr] input

DongDD 2018. 4. 19. 15:12

[pwnable.kr] input




Problem




내가 넣은 input으로 컴퓨터 프로그램을 어떻게 통과할 수 있는지 묻고 있었다.

먼저 ssh를 통해 문제에 접속해보았다.



이번 문제에서도 flag 파일과 input 실행 파일, input 소스 코드 파일이 있었다.

먼저 소스 코드 파일을 확인해보았다.


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
68
#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)!=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!= 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를 클리어할 수 있는 조건으로 코딩되어있었고 stage 5까지 클리어하게 되면 cat flag를 통해 flag를 알려주는 형태로 되어있었다.



Solution


각 stage 별로 나누어져있기 때문에 한번에 모든 값으 넣어주기 어려울 것 같아 pwntool을 사용하기 위해 로컬에서 ssh를 접속하는 방식으로 python을 작성해야겠다는 생각이 들었다.


Stage1


1
2
3
4
5
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");
 
cs

Stage1는 argv를 이용한 문제였다.

프로그램 실행 시 인자가 총 100개여야 하고 argv['A'], 65번째 인자가 "\x00"이여야 하고 argv['B'] , 66번째 이자가 "\x20\x0a\x0d"여야 stage1을 클리어할 수 있었다.

ssh 접속 후 pwntool에 있는 run을 이용해 인자를 string으로 저장해 넣었었는데 아스키코드로 인식해서 그런지 통과할 수가 없었다. 그래서 list형식으로 인자를 만들어주고 process를 이용해 실행하니 통과할 수 있었다.


1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
 
= ssh(user="input2", host="pwnable.kr", port=2222, password="guest")
 
arg = [str(i) for i in range(100)]
arg[65= "\x00"
arg[66= "\x20\x0a\x0d"
 
 
p2 = p.process(executable='/home/input2/input', argv=arg)
 
print p2.recvuntil("clear!\n")
 
cs

arg라는 100개의 list를 만들어주고 arg[65] = "\x00", arg[66] = "\x20\x0a\x0d"를 넣어주고 process를 통해 실행 파일과 인자를 넣어주니 첫 번째 stage를 통과할 수 있었다.




Stage2


1
2
3
4
5
6
7
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");
 
cs

두번째 stage는 stdin과 stderr를 이용하는 문제였다.

먼저 stdin의 4bytes를 읽어와서 이것이 "\x00\x0a\x00\xff"인지 확인하고 이 값과 일치해야 한다.

그 후에 stderr의 4bytes를 읽어와서 이것이 "\x00\x0a\x02\xff"인지 확인하고 이 값과 일치해야 Stage2를 clear할 수 있게 되어있었다.

stdin은 그냥 send를 통해 보낼 수 있지만 stderr를 따로 파일을 생성해 설정해주어야 했다.

err라는 파일을 생성해야하는데 input2에서는 파일 생성이 불가능했기 때문에 /tmp에 폴더를 만들어 그 폴더에 err라는 파일을 넣어주고 stderr를 그 파일로 대체했다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *
 
= ssh(user="input2", host="pwnable.kr", port=2222, password="guest")
 
arg = [str(i) for i in range(100)]
arg[65= "\x00"
arg[66= "\x20\x0a\x0d"
 
p.run("mkdir /tmp/dongdd")
p.write('/tmp/dongdd/err'"\x00\x0a\x02\xff")
 
 
p2 = p.process(executable='/home/input2/input', argv=arg,stderr="/tmp/dongdd/err")
 
print p2.recvuntil("clear!\n")
p2.send("\x00\x0a\x00\xff")
 
print p2.recvuntil("clear!\n")
 
cs


run을 통해 /tmp에 원하는 폴더를 생성해주고 write를 통해 그 폴더에 파일을 생성해 값을 넣어주었다.

그리고 process 인자에 stderr를 넣어줘 stderr를 생성한 파일로 대체하고 send를 통해 stdin으로 값을 넣어주었다.




Stage3


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

세번째 stage는 환경변수를 이용하는 문제였다. 

환경변수 "\xde\xad\xe\xef"의 값이 "\xa\xfe\xba\be"와 같으면 clear를 할 수 있게 되어있었다.

process에 환경 변수를 넣어줄 수 있었고 환경 변수를 넣을 때는 dictonary 자료형을 사용해야 한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *
 
= ssh(user="input2", host="pwnable.kr", port=2222, password="guest")
 
arg = [str(i) for i in range(100)]
arg[65= "\x00"
arg[66= "\x20\x0a\x0d"
 
p.run("mkdir /tmp/dongdd")
p.write('/tmp/dongdd/err'"\x00\x0a\x02\xff")
en = {'\xde\xad\xbe\xef' : "\xca\xfe\xba\xbe"}
 
p2 = p.process(executable='/home/input2/input', argv=arg,stderr="/tmp/dongdd/err",env=en)
 
print p2.recvuntil("clear!\n")
p2.send("\x00\x0a\x00\xff")
 
print p2.recvuntil("clear!\n")
print p2.recvuntil("clear!\n")
 
cs


먼저 dictionary 자료형으로 "\xde\xad\xbe\xef"라는 변수에 "\xca\xfe\xba\xbe"를 넣어주었다.

그리고 이 변수를 process의 환경 변수 인자로 env에 넣어주어 실행했다.



Stage4


1
2
3
4
5
6
7
8
 // file
FILE* fp = fopen("\x0a""r");
if(!fp) return 0;
if( fread(buf, 41, fp)!=return 0;
if( memcmp(buf, "\x00\x00\x00\x00"4) ) return 0;
fclose(fp);
printf("Stage 4 clear!\n");
 
cs

네번째 stage는 file descriptor를 이용한 문제였다.

"\x0a"라는 파일에서 4bytes를 읽어 이 값이 "\x00\x00\x00\x00"이면 통과할 수 있게 되어있었다.

아까 err파일을 생성했던 방식과 마찬가지로 "\x0a"라는 파일을 생성하고 "\x00\x00\x00\x00"을 넣어주면 클리어할 수 있다.

하지만 여기서는 절대 경로로 표시된 것이 아니기 때문에 파일을 생성할 수 없는 input2 폴더에서는 작업할 수 없다.

그렇기 때문에 현재 디렉토리를 /tmp/dongdd로 바꿔주었다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *
 
= ssh(user="input2", host="pwnable.kr", port=2222, password="guest")
 
arg = [str(i) for i in range(100)]
arg[65= "\x00"
arg[66= "\x20\x0a\x0d"
 
p.run("mkdir /tmp/dongdd")
p.write('/tmp/dongdd/err'"\x00\x0a\x02\xff")
p.write('/tmp/dongdd/\x0a'"\x00\x00\x00\x00")
en = {'\xde\xad\xbe\xef' : "\xca\xfe\xba\xbe"}
 
p2 = p.process(cwd="/tmp/dongdd/",executable='/home/input2/input',
                argv=arg,stderr="/tmp/dongdd/err",env=en)
 
print p2.recvuntil("clear!\n")
p2.send("\x00\x0a\x00\xff")
 
print p2.recvuntil("clear!\n")
print p2.recvuntil("clear!\n")
print p2.recvuntil("clear!\n")
 
cs


process 인자에 현재 디렉토리(cwd)를 "/tmp"의 생성한 폴더로 바꿔주고 write를 통해 "\x0a"파일을 생성해 "\x00\x00\x00\x00"을 넣어주었다.




Stage5



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

마지막 stage에서는 일반적인 소켓 프로그래밍을 볼 수 있엇다.
port 번호 설정 부분을 보면 argv['C'], 즉 처음 넣었던 인자들에서 67번째 인자 값을 포트 번호로 설정하는 것을 볼 수 있다.
그 후에 이 소켓에서 4bytes를 받고 이 값이 "\xde\xad\xbe\xef"라면 stage5를 clear하는 형태로 되어있었다.

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
from pwn import *
 
= ssh(user="input2", host="pwnable.kr", port=2222, password="guest")
 
arg = [str(i) for i in range(100)]
arg[65= "\x00"
arg[66= "\x20\x0a\x0d"
arg[67= "12345"
 
p.run("mkdir /tmp/dongdd")
p.write('/tmp/dongdd/err'"\x00\x0a\x02\xff")
p.write('/tmp/dongdd/\x0a'"\x00\x00\x00\x00")
en = {'\xde\xad\xbe\xef' : "\xca\xfe\xba\xbe"}
 
p2 = p.process(cwd="/tmp/dongdd/",executable='/home/input2/input',
                argv=arg,stderr="/tmp/dongdd/err",env=en)
 
print p2.recvuntil("clear!\n")
p2.send("\x00\x0a\x00\xff")
 
print p2.recvuntil("clear!\n")
print p2.recvuntil("clear!\n")
print p2.recvuntil("clear!\n")
 
p1 = p.remote('localhost',12345)
p1.send("\xde\xad\xbe\xef")
 
print p2.recv()
 
cs


먼저 arg[67]에 사용할 포트 번호(12345)를 넣어주었다. 그 후에 process를 통해 실행되면서 accept 부분에 정지해있을 것이기 때문에 remote(nc)를 이용해 그 포트로 접속해준 후 "\xde\xad\xbe\xef"를 보내주게 되면 clear할 수 있다.




Stage5를 클리어했지만 flag가 출력되지 않았다. stage3을 클리어하기 위해 cwd를 바꿔줬기 때문에 flag가 없어서 인 것 같았다.

flag를 input2 폴더에 있지만 현재 디렉토리는 /tmp/dongdd이기 때문에 이 폴더에 심볼릭 링크를 이용해 flag파일을 생성해주면 문제를 통과할 수 있을 것 같았다.

코드에 심볼릭 링크를 실행하는 한 줄을 추가했고 flag를 획득할 수 있었다.






**************     Answer & Flag     **************




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
from pwn import *
 
= ssh(user="input2", host="pwnable.kr", port=2222, password="guest")
 
arg = [str(i) for i in range(100)]
arg[65= "\x00"
arg[66= "\x20\x0a\x0d"
arg[67= "12345"

p.run("mkdir /tmp/dongdd")
 
 
p.write('/tmp/dongdd/err'"\x00\x0a\x02\xff")
p.write('/tmp/dongdd/\x0a'"\x00\x00\x00\x00")
en = {'\xde\xad\xbe\xef' : "\xca\xfe\xba\xbe"}
 
ar = [str(i) for i in range(4)]
ar[1= "-s"
ar[2= "/home/input2/flag"
ar[3= "flag"
p.process(cwd='/tmp/dongdd/', executable='/bin/ln',argv=ar)
 
p2 = p.process(cwd="/tmp/dongdd/",executable='/home/input2/input',
                    argv=arg,stderr="/tmp/dongdd/err",env=en)
 
print p2.recvuntil("clear!\n")
p2.send("\x00\x0a\x00\xff")
 
print p2.recvuntil("clear!\n")
print p2.recvuntil("clear!\n")
print p2.recvuntil("clear!\n")
 
p1 = p.remote('localhost',12345)
p1.send("\xde\xad\xbe\xef")
 
print p2.recv()
 
cs

ln을 실행하기 위해 ar list 변수를 생성해 인자를 넣어주었고 process를 통해 실행히켜 flag를 "/tmp/dongdd"에 생성했다.

계속 실행하게 되면 생성되는 파일들이 엉킬 수가 있기 때문에 실행하기전에 "/tmp"에 들어가 생성했던 파일들을 삭제해주어야 한다.

코드에 넣을 수 있었지만 그냥 수동으로 지우는 방법을 택했다.




Flag : Mommy! I learned how to pass various input in Linux :)



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

[pwnable.kr] mistake  (0) 2018.06.14
[pwnable.kr] leg  (0) 2018.06.10
[pwnable.kr] random  (0) 2018.04.02
[pwnable.kr]passcode  (0) 2018.03.28
[pwnable.kr] flag  (0) 2018.03.09
Comments