#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가지의 조건을 통과하면 플래그 값을 출력한다고 한다. 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");
1단계 조건은 argc가 100개, argv['A']와 argv['B]가 요구하는 값을 충족하면 된다. 익스플로잇 코드는 아래와 같다. 한가지 헷갈리는 점은 쉘에서 실행파일에 인자를 주고 실행하면 argv의 첫번째 값은 실행파일 경로가 된다. 예를 들어 "/home/kali/input 인자1 인자2 인자3" 과 같이 3개의 인자를 주면 argv는 /home/kali/input 경로도 인자에 포함된다. 그래서 argc는 3이 아닌 4가 된다. 하지만 이번 익스플로잇 코드에서는 쉘에서 실행하는 것이 아니라 파이썬 코드 내에서 argc, argv 자체를 미리 만들어서 제공하므로 인자가 3개면 argc는 4가 아닌 3이된다.
#stage1
argv_list = [str(i) for i in range(100)]
argv_list[ord('A')] = '\x00'
argv_list[ord('B')] = '\x20\x0a\x0d'
// 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");
2단계 조건은 buf에 \x00\x0a\x00\xff 값을 입력받아야하고 2번은 표준에러 값을 buf로 가져오므로 표준에러에 \x00\x0a\x02\xff 값이 있어야한다.
#stage2
with open('./stderr', 'a') as e: // 표준에러에 값 추가
e.write('\x00\x0a\x02\xff')
#stage2
r.sendline('\x00\x0a\x00\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이면 된다.
#stage3
env_list = {'\xde\xad\xbe\xef':'\xca\xfe\xba\xbe'}
// 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라는 파일에서 4바이트 만큼 읽어온 값이 \x00\x00\x00\x00이면 된다.
#stage4
with open('./\x0a', 'a') as f:
f.write('\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단계 조건은 port 값을 argv['C']에서 가져와서 서버를 연다. 그리고 4바이트 만큼 들어오는 데이터를 buf에 저장하고 \xde\xad\xbe\xef와 같으면 통과한다.
#stage5
argv_list[ord('C')] = '7777'
r_local = remote('localhost', 7777)
r_local.send('\xde\xad\xbe\xef')
r.interactive()
마지막으로 익스플로잇 코드를 종합하면 아래와 같다.
from pwn import *
#stage1
argv_list = [str(i) for i in range(100)]
argv_list[ord('A')] = '\x00'
argv_list[ord('B')] = '\x20\x0a\x0d'
#stage2
with open('./stderr', 'a') as e: # 표준에러에 값 추가
e.write('\x00\x0a\x02\xff')
#stage3
env_list = {'\xde\xad\xbe\xef':'\xca\xfe\xba\xbe'}
#stage4
with open('./\x0a', 'a') as f:
f.write('\x00\x00\x00\x00')
#stage5
argv_list[ord('C')] = '7777'
p = process(executable='/home/input2/input', argv=argv_list, stderr=open('./stderr'), env=env_list)
#stage2
p.sendline('\x00\x0a\x00\xff') # 입력값 입력
r_local = remote('localhost', 7777)
r_local.send('\xde\xad\xbe\xef')
p.interactive()
하지만 문제점이 아직 있다. 문제 디렉토리에서는 쓰기 권한이 없으므로 파일을 생성할 수 없다. 따라서 /tmp라는 폴더에 가서 임의의 폴더를 하나 만들고 해당 폴더안에서 작업하면 파일을 생성할 수 있다. 그리고 flag 파일이 없으므로 아래 명령어로 원래 디렉토리의 플래그 파일에 대해서 심볼링 링크 파일을 하나 생성합니다. 그리고 exploit 코드를 실행하면 플래그를 획득할 수 있습니다.
ln -s /home/input2/flag flag
'시스템 해킹 > pwnable.kr' 카테고리의 다른 글
[Pwnable.kr] coin1 (0) | 2021.07.28 |
---|---|
[Pwnable.kr] shellshock (0) | 2021.07.27 |
[Pwnable.kr] random (0) | 2021.07.27 |
[Pwnable.kr] passcode (0) | 2021.07.27 |
[Pwnable.kr] cmd1 (0) | 2021.04.21 |
댓글