2022. 3. 31. 12:42ㆍPwnable.kr
#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;
}
총 5개의 스테이지로 구성되어 있습니다.
각 스테이지별로 설명을 하겠습니다.
// 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");
첫 번째 스테이지는 인자가 100개 인지 65번째에 "\x00"이 들어있는지 66번째에 "\x20\x0a\x0d"가 들어있는지 확인하는 문제입니다.
pwntools를 사용하여 인자들을 전달하여 통과하였습니다.
from pwn import *
context.log_level = 'debug'
s = ssh(user='input2', host='pwnable.kr', port=2222, password='guest')
argv = ['A' for _ in range(100)]
argv[0] = '/home/input2/input'
argv[65] = b'\x00'
argv[66] = b'\x20\x0a\x0d'
p = s.process(executable=argv[0], argv=argv)
p.recv()
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 함수가 두 번 실행되는데 stdin, stderr로 부터 데이터를 4byte씩 읽어와 조건문과 비교하는 문제입니다.
stdin 경우 "\x00\x0a\x00\xff"를 입력하면 되지만 stderr 경우에는 "\x00\x0a\x02\xff" 데이터가 저장된 임의의 파일을 stderr로 연결해주어야 됩니다.
ssh.run, write 메서드를 사용하여 풀었습니다.
# stage 2
s.run("mkdir /tmp/pwnpwn")
s.write("/tmp/pwnpwn/stderr", "\x00\x0a\x02\xff")
# stage 1
argv = ['A' for _ in range(100)]
argv[0] = '/home/input2/input'
argv[65] = b'\x00'
argv[66] = b'\x20\x0a\x0d'
p = s.process(executable=argv[0], argv=argv, stderr='/tmp/pwnpwn/stderr')
p.recv()
# stage 2
p.sendline(b"\x00\x0a\x00\xff")
// env
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");
세 번째 스테이지입니다. 환경변수 "\xca\xfe\ba\be"의 값이 "\xde\xad\xbe\xef"인지 확인합니다.
process env 매개변수에 리스트 형식으로 전달하면 풀 수 있습니다.
# stage 3
env = {b'\xde\xad\xbe\xef' : b'\xca\xfe\xba\xbe'}
p = s.process(executable=argv[0], argv=argv, stderr='/tmp/pwnpwn/stderr', env=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");
파일명이 "\x0a"인 파일에서 4byte만큼 읽어온 데이터가 "\x00\x00\x00\x00"인지 확인하는 스테이지입니다.
/home/input2 폴더에서는 파일을 생성할 수 없습니다. 그래서 파일을 생성할 수 있는 /tmp 디렉터리에서 파일을 만든 뒤 process 매개변수 중 현재 작업 디렉터리를 설정할 수 있는 cwd를 /tmp/pwnpwn로 바꾸면 /tmp/pwnpwn/0xa 파일을 읽어와 조건문을 통과할 수 있습니다.
# stage 4
s.write(b"/tmp/pwnpwn/\x0a", b"\x00\x00\x00\x00")
p = s.process(executable=argv[0], argv=argv, stderr='/tmp/pwnpwn/stderr', env=env, cwd='/tmp/pwnpwn')
// 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'] 값을 포트번호를 하여 소켓 서버를 엽니다. 이후 입력받은 4byte의 데이터가 "\xde\xad\xbe\xef"인지 확인한 후 참이면 FLAG를 출력합니다.
# stage 5
argv[67] = '56248'
# stage 5
r = s.remote('localhost', 56248)
r.send(b"\xde\xad\xbe\xef")
Stage 5까지 통과했지만 FLAG를 출력하지 않습니다. 그 이유는 현재 디렉터리는 /tmp/pwnpwn인데 FLAG는 /home/input2에 존재합니다. 그래서 cat flag를 하면 파일을 읽을 수 없습니다.
그래서 심볼릭 링크를 이용해 input2 디렉토리에 있는 flag를 /tmp/pwnpwn/flag가 가리키도록 하면 FLAG를 읽을 수 있습니다.
최종 코드와 결과는 아래와 같습니다.
from pwn import *
context.log_level = 'debug'
s = ssh(user='input2', host='pwnable.kr', port=2222, password='guest')
# stage 2
s.run("mkdir /tmp/pwnpwn")
s.write("/tmp/pwnpwn/stderr", "\x00\x0a\x02\xff")
# final
s.run("ln -s /home/input2/flag /tmp/pwnpwn/flag")
# stage 4
s.write(b"/tmp/pwnpwn/\x0a", b"\x00\x00\x00\x00")
# stage 1
argv = ['A' for _ in range(100)]
argv[0] = '/home/input2/input'
argv[65] = b'\x00'
argv[66] = b'\x20\x0a\x0d'
# stage 5
argv[67] = '56248'
# stage 3
env = {b'\xde\xad\xbe\xef' : b'\xca\xfe\xba\xbe'}
p = s.process(executable=argv[0], argv=argv, stderr='/tmp/pwnpwn/stderr', env=env, cwd='/tmp/pwnpwn')
p.recv()
# stage 2
p.sendline(b"\x00\x0a\x00\xff")
# stage 5
r = s.remote('localhost', 56248)
r.send(b"\xde\xad\xbe\xef")
p.recv()
p.interactive()