이번 포스트에서는 Pwnable의 input문제에 대해 다루겠다.
문제 코드는 다음과 같다.
#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개의 스테이지가 있고 스테이지를 다 클리어하면 flag를 얻을 수 있는 것으로 보인다.
## 문제 풀이 :
먼저 우리의 공격 코드를 만들어야 하는데, /tmp 폴더에서만 실행 권한이 있으므로 /tmp 폴더에 들어가서 우리만의 폴더를 하나 만들어 보겠다.
코드 폴더를 만든 다음에 각 스테이지에 대해 풀이해보겠다.
# Stage 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 (인자)의 수가 100개이면서 argv['A'] = "\x00", argv['B'] = "\x20\x0a\x0d"이면 해당 스테이지를 클리어할 수 있는 것으로 보인다.
## 코드 :
# stage 1
argvs = [str(i) for i in range(100)]
argvs[ord('A')] = '\x00'
argvs[ord('B')] = '\x20\x0a\x0d'
# Stage 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");
파일 스크립트 0 = stdin, 2 = stderr이므로 stdin으로 "\x00\x0a\x00\xff" 입력받고, stderr로 "\x00\x0a\x02\xff" 입력받으면 된다.
## 코드 :
# stage 2
with open('./stderr', 'a') as f:
f.write('\x00\x0a\x02\xff')
target = process(executable = '/home/input2/input', argv=argvs, stderr=open('./stderr'), env=envVal)
target.sendline('\x00\x0a\x00\xff')
# Stage 3
// env
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");
getenv() 함수는 환경 변수 검색 함수이다. 따라서 환경변수 "\xde\xad\xbe\xef"에 "\xca\xfe\xba\xbe"를 넣으면 된다.
## 코드 :
# stage 3
env_val = {'\xde\xad\xbe\xef':'\xca\xfe\xba\xbe'}
# Stage 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"이면 된다.
## 코드 :
# stage 4
with open('./\x0a', 'a') as f:
f.write('\x00\x00\x00\x00')
# Stage 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");
스테이지 5는 소켓에 관한 코드이다. "saddr.sin_port = htons( atoi(argv['C']) );"를 확인해보면 포트번호를 'C'로 해서 서버를 열고, "\xde\xad\xbe\xef"를 보내주면 된다.
## 코드 :
# stage 5
argvs[ord('C')] = '40000'
conn = remote('localhost','40000')
conn.send('\xde\xad\xbe\xef')
그러나 현재 우리의 코드 폴더에 flag가 없으므로 flag에 대한 심볼릭을 생성해보겠다.
## 최종 결과 :
### 공격코드 :
from pwn import *
# stage 1
argvs = [str(i) for i in range(100)]
argvs[ord('A')] = '\x00'
argvs[ord('B')] = '\x20\x0a\x0d'
# stage 2
with open('./stderr', 'a') as f:
f.write('\x00\x0a\x02\xff')
# stage 3
env_val = {'\xde\xad\xbe\xef':'\xca\xfe\xba\xbe'}
# stage 4
with open('./\x0a', 'a') as f:
f.write('\x00\x00\x00\x00')
# stage 5
argvs[ord('C')] = '40000'
target = process(executable = '/home/input2/input', argv=argvs, stderr=open('./stderr'), env=env_val)
target.sendline('\x00\x0a\x00\xff')
conn = remote('localhost','40000')
conn.send('\xde\xad\xbe\xef')
target.interactive()
### 실행 결과 :
Flag = Mommy! I learned how to pass various input in Linux :)
'Wargame > Pwnable' 카테고리의 다른 글
Pwnable (9. mistake) (0) | 2022.07.07 |
---|---|
Pwnable (8. leg) (0) | 2022.07.06 |
Pwnable (6. random) (0) | 2022.07.04 |
Pwnable (5. passcode) (0) | 2022.07.03 |
Pwnable (4. flag) (0) | 2022.07.02 |