Post

Pwnable.kr - input (풀이 봄)

input

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

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

Solution

코드가 너무 길어서 자름.
아래 5개의 스테이지를 통과해야함.

Stage 1

1
2
3
4
if(argc != 100) return 0;
if(strcmp(argv['A'],"\x00")) return 0;
if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
printf("Stage 1 clear!\n");
1
2
3
4
5
6
7
8
9
10
11
12
13
argv['A'], argv['B']는 A, B의 아스키값이 65, 66이므로 
argv[65], argv[66]과 같음.

argv[0]에는 파일경로가 들어있어서 argc는 기본 값이 1임. 

따라서 입력값은 총 99개가 되어야함. 즉, argv[1]부터 시작함.

로컬에서 테스트 해보았는데 널 값도 안들어가고, argv['B'] 값도 안들어감.

풀이를 보니 argv 인자는 공백을 기준으로 구분하는데, \x20이 공백이니까 
argv['B'] 값이 안들어간다고 함.

하지만 pwntools로 해주면 값을 넣을수 있다고 함.
1
2
3
4
5
6
7
8
9
from pwn import *

argvs = ["" for i in range(100)]
argvs[0] = "/home/input2/input"
argvs[65] = "\x00"
argvs[66] = "\x20\x0a\x0d"

p=process(argvs, executable='/home/input2/input')
## process함수의 argv인자를 통해 값을 전달해줄 수 있음.

Stage 2

1
2
3
4
5
6
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");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <string.h>

int memcmp(const void* ptr1, const void* ptr2, size_t num);

/*

두 개의 메모리 블록을 비교

ptr1 이 가리키는 처음 num 바이트의 데이터와 ptr2 가 가리키는 처음 num 바이트의 
데이터를 비교하여 이들이 같다면 0 을 리턴하고 다르다면 0 이 아닌 값을 리턴.

출처 : https://modoocode.com/84

*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <unistd.h>

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

/*

int fd	파일 디스크립터
void *buf	파일을 읽어 들일 버퍼
size_t nbytes	퍼버의 크기

Return : ssize_t	정상적으로 실행되었다면 읽어들인 바이트 수를, 실패했다면 -1을 반환

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

*/
1
2
3
4
5
6
fd가 0이면 입력, 2이면 에러임.

따라서 표준 입력은 문제 없지만 표준 에러 입력이 문제임. 

풀이를 보니 파일을 생성하여 파일에다 값을 써주고 에러 발생 시 
그 파일을 참조하도록 해주면 된다고 함.  
1
2
3
4
5
6
7
8
9
stderrfd = open('./stderr','w+')
stderrfd.write('\x00\x0a\x02\xff')
stderrfd.seek(0)        
## 파일에 쓰고 나면 포인터 위치가 4가 된다고 함.
## 파일을 읽기 위해선 포인터 위치를 0으로 해줘야 함.

p = process(executable='/home/input2/input',argv=argvs, stderr=stderrfd)
p.recvuntil('Stage 1 clear!\n')
p.send("\x00\x0a\x00\xff")

Stage 3

1
2
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");
1
2
3
4
5
6
strcmp 함수에 비교할 문자열을 넣어주면 결과를 정수로 반환함.
(문자열을 비교할 때 대소문자를 구분함).  

-1 : ASCII 코드 기준으로 문자열2(s2)가 클 때
 0 : ASCII 코드 기준으로 두 문자열이 같을 때
+1 : ASCII 코드 기준으로 문자열1(s1)이 클 때

pwntools의 process함수의 인자에 env값을 딕셔너리로 넣어줄 수 있음.

1
p=process(env={'\xde\xad\xbe\xef' : '\xca\xfe\xba\xbe'})

Stage 4

1
2
3
4
5
6
FILE* fp = fopen("\x0a", "r");
if(!fp) return 0;
if( fread(buf, 4, 1, fp)!=1 ) return 0;
if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
fclose(fp);
printf("Stage 4 clear!\n");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

size_t fread(void* ptr, size_t size, size_t count, FILE* stream);

/*

ptr : size * count 의 크기를 가지는 배열을 가리키는 포인터

size : 읽어들일 원소의 크기로 단위는 바이트. 
       예를 들어 size 가 4 이면 하나의 원소의 크기는 4 바이트 임을 일컫는다.

count : 읽어들일 원소들의 개수로 각 원소의 크기는 size 바이트.

stream : 데이터를 입력받을 스트림의 FILE 객체를 가리키는 포인터

*/
1
2
3
fread로 fp에서 4바이트짜리 1개를 읽어서 buf에 저장하겠다는 것.

따라서 \x0a라는 파일에 \x00\x00\x00\x00을 써주면 됨.

python 파일 입출력https://wikidocs.net/26

1
2
3
4
5
6
7
with open("foo.txt", "w") as f:
    f.write("Life is too short, you need python")

'''
with문을 사용하면 파일을 열고 닫는 걸 자동으로 처리해줌.
with문을 나가는 순간 파일이 닫힘.
'''
1
2
with open('./\0x0a', 'w+') as f :
    f.write("\x00\x00\x00\x00")

Stage 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
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, 4, 0) != 4 ) return 0;
if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
printf("Stage 5 clear!\n");
1
2
argv[67] 값을 포트로 하여 연결하겠다는 것.
연결한 후에는 buf에 \xde\xad\xbe\xef을 입력해줘 함.
1
2
3
4
argv[67]='12345'

r = remote('localhost', 12345)
r=sendline("\xde\xad\xbe\xef")

Payload

input2@pwnable:/tmp/fuckingshit$ ln -s /home/input2/flag flag
input2@pwnable:/tmp/fuckingshit$ python test.py
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 *

argvs = ["" for i in range(100)]
argvs[0] = "/home/input2/input"
argvs[65] = "\x00"
argvs[66] = "\x20\x0a\x0d"
argvs[67] = "12345"

stderrfd = open('./stderr','w+')
stderrfd.write('\x00\x0a\x02\xff')
stderrfd.seek(0)

with open('./\x0a', 'w') as fd:
    fd.write('\x00\x00\x00\x00')

p = process(executable='/home/input2/input',argv=argvs, stderr=stderrfd, env={'\xde\xad\xbe\xef':'\xca\xfe\xba\xbe'})
p.recvuntil('Stage 1 clear!\n')
p.send("\x00\x0a\x00\xff")
p.recvuntil('Stage 2 clear!\n')
p.recvuntil('Stage 3 clear!\n')
p.recvuntil('Stage 4 clear!\n')

r = remote('127.0.0.1', 12345)
r.send('\xde\xad\xbe\xef')
r.close()

p.recv()

p.interactive()
[+] Starting local process '/home/input2/input': pid 69231
[+] Opening connection to 127.0.0.1 on port 12345: Done
[*] Closed connection to 127.0.0.1 port 12345
[*] Switching to interactive mode
Stage 5 clear!
Mommy! I learned how to pass various input in Linux :)
[*] Got EOF while reading in interactive
$  

풀이 출처

https://chp747.tistory.com/268

This post is licensed under CC BY 4.0 by the author.