binee::
[sCTF 2016] pwn3 write up 본문
pwn3는 마지막 문제인 만큼 시간이 많이 걸린 문제였다.
보통 CTF에서 낮은 난이도나 중간 정도 난이도 문제로 이런 문제를 많이 내는데,
IDA pro Hax-Ray가 해석을 잘 못해서 좀 귀찮았던 케이스였다.
푸는 동안 2016 codegate watermalon 과 유사하다고 느꼈고
watermelon과 좀 다른 차이점은 문제가 좀 지저분하게 냈다.
강제로 취약점을 만들려고 지저분하게 만든 거 같았다.
덕분에 알고리즘 로직을 이해하는데 3시간은 쓴거 같다.
Binary 분석
pwn3 바이너리를 확인 해보면
pwn3: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=4838e54f549c6c8d9e49fc91153fe41060dbfbe5, not stripped
위와 같이 32bit elf 파일이고, 이번 문제는 Stack canary가 있다.
그리고 statically linked 이므로, ROP 가젯을 자유롭게 구할 수 있는 특징이 있다.
아마도 이 문제는 ROP를 이용 할 확률이 높은 문제일 것이다.
Program Logic 분석
.
IDA로 확인해보면 main함수에는 menu()함수를 호출하는 것 외에 별거 없다.
아래는 menu함수의 hex-xay 결과다.
소스가 너무 길게 나왔는데, IDA hex-ray가 재대로 해석을 못해서 그렇다.
가끔 IDA를 욕하지만, 그래도 좋은 툴인건 분명하다.
지금 해석한 결과를 보면 함수 인자가 길게 나오는데
실제로 print_thread 함수가 전달하는 인자는 1개다.
그 밖에 append_thread, scanf 함수등의 인자 표현을 잘 못 해석했다.
조금 더 보기 편하게 변수명도 수정해주고 함수의 전달 인자값도 수정했다.
그리고 더 분석하기 편하게 하기위해서 구조체도 설정하여,
변수들의 포인터 표현을 보기 편하게 변경했다.
위에 그림처럼 structures탭에서 구조체를 선언하여
해당 구조체의 자료형 타입을 쓰는 변수의 자료형을 설정하면,
구조체의 멤버변수를 접근하는 형식으로 표현하여 조금더 분석하기 편하게 된다.
구조체를 선언 할 때는 structures탭에서 insert 키를 누르면 되고
구조체 멤버는 d/a/*(data/ascii/array) 을 이용해서 추가하면 된다.
추가한 멤버의 자료형은 y를 눌러서 지정해주면 된다.
이렇게 구조체를 선언하고 hex-ray로 해석된 변수들에게
구조체 자료형을 선언해주면
구조체 멤버변수를 접근하기 위해서 사용한 포인터가 없어지고.
"->" or "."으로 표현되서 직관적으로 소스코드를 이해할 수 있다.
위에 hex-ray 결과를 비교해보면 차이가 큰 것을 느낄 수 있다.
IDA 사용법에 대한 내용이 찾아보기 힘들어서 여기에 간단하게만 적어서 소개한다.
다시 구조체로 돌아와서 구조체를 살펴보면
총 5개 멤버로 구성하고 나머지 하나는 메모리할당 규칙을 맞춰주기위해서 있는 dummy값인 것 같다.
struct Blog_List{
void *next_address;
char id;
char poster[32];
char title[64];
char contents[1024];
char dummy[3];
}
위에 그림 처럼 5 가지 항목으로 data를 저장한다.
각각의 사이즈는 위에 구조체와 같다.
구조체의 총 사이즈는 1128이다.
그럼 이제 각각의 기능에 대한 설명을 시작하겠다.
print_welcome, print_menu는 시작 메시지와, 메뉴 메시지를 출력하는 함수이다.
메뉴에서 지원하는 기능은 쓰기 수정, 모두 출력, 지정한 글 출력 총 4개 기능으로 나뉜다.
1번 쓰기 기능은 get_thread함수와 append_thread함수를 호출한다.
순서대로 설명하면, get_thread 함수는 이름과 다르게 menu함수에서 선언한 구조체 변수에 데이터를 넣는 것 외에 특별히 하는 것이 없다.
다음으로 append_thread함수는 get_thread함수에서 입력받은 정보를 구조체 배열에 추가한다.
mene에서 선언 지역 변수들이다.
user_info 변수는 get_tread 함수에서 입력받은 정보를 저장하는 곳이다.
입력받은 정보를 저장한 user_info변수는 append_tread함수로 blog_list에 저장된다.
여기서 특이한 점이 구조체 크기가1128인데,
list 넣는건 next_address[4byte], id[1byte], poster[32byte], title[64byte] ,dummy[3byte]와
strlen(contents) 값의 합이다. ( 4+1+32+64+3 = 104 + strlen(contents) )
+1은 주소값을 구분하려고 넣은 것 같다.
contents값이 없으면 1128-105= 1023라는 공간을 안 채우고 저장한다.
구조체 리스트에 104바이트만 채우고 다음 리스트는 105번째 주소에 넣게 된다는 말이다.
즉, 배열을 가지고 링크드 리스트를 구현 했다고 보면 된다. 매 객체마다 공간을 할당하는 것이 아니라 기존에 존재하는 배열 공간에 링크드 리스트 방식으로 저장한다고 보면 된다.
이 부분을 잘 이용하면 취약점을 발생 시킬 수 있다.
2번 수정 기능은 수정하고 싶은 게시물의 ID를 적어서 edit_thread 함수를 호출하여 contents의 내용을 1024바이트 수정 할 수 있게 해준다.
여기서부터 뭔가 이상한 느낌이 들기 시작한다.
3번 모든 게시물 출력은 print_all_threads함수를 호출해서 여태까지 작성한 모든 게시물을
리스트에서 찾아서 print_thread러 호출하는 작업을 한다.
이때 다음 next_address 주소에 값이 있으면 다음 구조체의 정보를 출려하고 값이 없으면 출력을 멈춘다.
위 그림처럼 print_thread는 출력만 하는 함수이다.
4번 기능도 게시물 ID를 입력해서 특정 게시물을 출력하는데, 이 때 print_thread를 호출한다.
프로그램 동작하는 로직이 이렇다.
여기서 주위있게 봐야 될 부분은 1번과 2번 기능에서 발생 할 수 있는 문제를
어떻게 취약점으로 사용 할 것인지를 생각하면 된다.
Exploit
말로 설명하는건 한계가 있을 것 같아서 그림으로 스택구조를 표현해봤다.
위에 그림 처럼 1번 쓰기 기능을 사용하면
get_thread() 호출하여 user_info변수에 입력 데이터를 저장한다.
그리고 append_thread 함수로 blog_list변수에 입력 데이터를 복사한다.
여기까지는 큰 문제가 없지만, 2번 수정 기능으로 contents 값을 수정하면
edit_thread함수는 기존 contents 영역을 넘어서
다음 구조체가 저장할 데이터 영역을 침범한다.
그리고 그 다음 구조체의 next_address를 원하는 값으로 수정할 수 있게 된다.
다음 구조체의 next_address를 ret_address-101값으로 하면
다음 구조체의 contents의 주소값이 ret_address와 동일해진다.
따라서 다음 구조체의 contents값을 수정하면 ret_address값을 변조 할 수 있게 된다.
이를 통해서 ROP를 하면 exploit을 할 수 있다.
물론 ret_address-101 값을 알아내기 위해서는 stack주소를 leak해야되는데,
친절하게도 3번이나 4번 기능을 이용하면 next_address 주소를 출력한다.
이를 통해서 stack의 주소를 추측 할 수 있다.
exploit 코드는 아래와 같다.
'CTF' 카테고리의 다른 글
[Plaid CTF 2016] unix_time_formatter write up (0) | 2016.04.23 |
---|---|
[Plaid CTF 2016] butterfly write up (1) | 2016.04.21 |
[sCTF 2016] pwn2 write up (0) | 2016.04.19 |
[sCTF 2016] pwn1 write up (0) | 2016.04.19 |
[Codegate 2016] oldschool write up (2) | 2016.04.19 |