Write up (Wargame)/Pwnable

[pwnable.kr] input 풀이

그믐​ 2023. 9. 12. 02:43
반응형

 

Analysis


 

amd64 바이너리임.

 

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

 

이번에는 코드가 길다.

 

각각 파트를 나눠서 이해해본다.

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

main 함수에서 인자로 이것저것 사용하고, 프로그램에 어떻게 입력을 주는 지 알아본다고 한다

그저 우리는 올바른 입력을 주면 된다.

 

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

 

argv에 대한 입력인 것 같다. argc를 100으로 맞춰주어야 한다. 그럼 main에 인자를 100개 주어야 한다.

argv['A']니까 0x41번째 argv는 \x00이어야 한다.

0x42번째 argv는 \x20\x0a\x0d 이어야 한다.

 

그럼 1스테이지를 클리어한다.

 

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

stdio에서는 입력을 받아서 처음 buf가 \x00\x0a\x00\xff이어야 통과

다음 입력에서는 stderr로 받아서 buf에 \x00\x0a\x02\xff 가 있어야 통과이다.

 

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

 

3스테이지에서는 \xde\xad\xbe\xef라는 환경변수 값이 \xca\xfe\xba\xbe여야 한다. 이건 export나 env에 넣어두면 될 것 같다.

 

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

4 스테이지는 파일 입출력으로 \x0a라는 이름의 파일을 read한다.

해당 파일에서4바이트를 읽어들이고 그 값이 \x00\x00\x00\x00이어야 한다.

 

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

 

5 스테이지는 네트워크..

소켓을 생성해서 argv['C'] 포트에 바인드한다. 클라이언트로부터 4바이트를 받고 그 값이 \xde\xad\xbe\xef 인지 확인한다.

 

stage 5까지 클리어하면 플래그를 준다.

 

Solution


일단 여기까지 stage 1클리어

pwntools 에서 argv를 줄 수 있다.

 

stage 2에서 입력은 별 문제가 안 되지만 stderr을 주는건 문제가 있다.

pwntools 기능을 찾아본다.

https://docs.pwntools.com/en/stable/tubes/processes.html

 

pwnlib.tubes.process — Processes — pwntools 4.10.0 documentation

© Copyright 2016, Gallopsled et al. Revision 6ff74256.

docs.pwntools.com

 

pwntools에서는 stderr을 파일로 리다이렉트 가능하다.

따라서, 파일에 데이터를 써두고 해당 파일로 리다이렉트 시키면 된다.

이렇게 하면 stage2까지 클리어.

 

위에 보냈던 링크에서 보면 env 는 딕셔너리 형태로 입력한다.

딕셔너리만 지정해서 넣어주면 stage3도 금방 클리어한다.

 

파일 입출력은 파일을 미리 생성해둔다?

이게 바로 되네

 

마지막으로 소켓에 입력한다.

argv['C'] 에다가 대충 13337로 포트를 연다. 그리고 remote써서 보내면 되지 않을까..?

5 클리어는 했는데 왜 안뜨징

아. flag 경로가 ex.py 경로와 같아야 한다.

cp는 permission 문제로 사용할 수 없다. 그럼. 경로를 어떻게 맞출까 해서 떠오른게 심볼릭 링크였다.

 

ln -s ~/flag flag

 

해서 flag를 심볼릭 링크를 걸고 읽어들인다.

 

from pwn import *

argv = []
errfile = './errfile'

for i in range(100):
	argv.append('A')

stage5 = 1337
argv[0] = '/home/input2/input'
argv[ord('A')] = '\x00'
argv[ord('B')] = '\x20\x0a\x0d'
argv[ord('C')] = str(stage5)

with open(errfile, 'wb') as f:
	f.write('\x00\x0a\x02\xff')

envp = {'\xde\xad\xbe\xef':'\xca\xfe\xba\xbe'}

stage4 = '\x0a'
with open(stage4, 'wb') as f:
	f.write('\x00\x00\x00\x00')

p = process(argv, stderr=open(errfile), env=envp)

p.recvuntil('clear!\n')
p.send('\x00\x0a\x00\xff')

p.recvuntil('4 clear!\n')
r = remote('127.0.0.1', stage5)
r.send('\xde\xad\xbe\xef')
r.close()

p.interactive()

 

 

히히

반응형