[pwnable.kr] input(4 pts) :: Write-Up

두비니

·

2020. 2. 25. 04:47

 

 

 

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

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

 

 

input2@pwnable:~$ cat input.c
#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 != 100) return 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, 4, 1, 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, 4, 0) != 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~4단계까지 나눠서 분석해볼게요. 

 

 

1) 1단계

 

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

 

우선 argc는 전달된 인자의 개수, 그리고 argv는 전달되는 인자의 내용이기 때문에 조건은

 

1. 인자 100개전달

2. argv['A'] = '\x00'

3. argv['B'] = '\x20\x0a\x0d'

 

이 3개네요.

 

 

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

 

이건 그냥 read함수가 뭔지만 알면되죠?

첫번째는 0, 즉 표준 입력(stdin)으로 \x00\x0a\x00\xff를 넘겨주면 되는데 

두번째는 2, 즉 표준 에러방식(stderr)으로 입력해줘야하는데...

이는 표준에러에 맞춰 파일 디스크립터를 생성한 뒤, process시에 stderr로 넘겨줄 수 있다고하네요.

 

문제느낌이 첫번째문제 fd랑 비슷하네요.

 

1단계보고는 그냥 길게 페이로드를 짤 생각이였는데, 이걸 보고 아예 폰툴을 써서 해야겠다 싶었습니당..

 

 

 

3) 3단계

 

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

 

일단 getenv함수는 환경변수를 불러오는 함수입니다. (환경변수가 뭔지 모르겠다면 구글링ㄱㄱ)

lob에서 환경변수를 꽤 다뤘었죠.

저 if문을 건너뛰려면 \xde\xad\xbe\xef라는 환경변수의 내용이 \xca\xfe\xbe\xbe가 되어야하네요.

 

 

4) 4단계

 

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

 

 

파일 여는 것에 대한 문제네요. \x0a라는 파일을 열고, 처음 4바이트가 \x00\x00\x00\x00이여야하네요!

 

 

 

5) 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, 4, 0) != 4 ) return 0;
	if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
	printf("Stage 5 clear!\n");

 

코드는 긴데 결국 요약하면 argv['C']에 해당하는 포트를 열고, 그 포트로 \xde\xad\xbe\xef가 전송되어야 한다는 내용이네요.

 

그래서 페이로드를 짜보면

 

from pwn import *

#stage 1
argvs = ["" for i in range(100)]
argvs[0] = "/home/input2/input"
argvs[ord('A')] = "\x00"
argvs[ord('B')] = "\x20\x0a\x0d"

#stage5 일부
argvs[ord('C')] = "66666"

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

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

#인자전달, stderr파일 열기, 황경변수 설정 등
r = process(executable='/home/input2/input',argv=argvs, stderr=stderrfd, env={'\xde\xad\xbe\xef':'\xca\xfe\xba\xbe'})
r.recvuntil('Stage 1 clear!\n')

#stage2의 stdin
r.send("\x00\x0a\x00\xff")
r.recvuntil('Stage 2 clear!\n')
r.recvuntil('Stage 3 clear!\n')
r.recvuntil('Stage 4 clear!\n')

p=remote('127.0.0.1', 66666)
p.send('\xde\xad\xbe\xef')
p.close()

r.interactive()

 이렇게 되겠네요. pwntools 이용했습니다.

 

 

 

ln -s /home/input2/flag flag

 

참 이렇게 심볼릭 링크를 걸어놓아야 내가 만들어놓은 tmp파일의 exploit파일이 실행될 수 있으니 꼭 하십셔.