[pwnable.kr] input 풀이
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
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()
히히