본문 바로가기

Wargame/pwnable.kr

[Grotesque] Starcraft


마지막으로 글 쓴 날짜가 언젠가 봤더니 작년이다. 1년 전에 쓴걸보니.. 그동안 정말 매우 많이 바빴나보다. 일하는 부서를 옮겼더니, 나랑 평생 연이 없을 줄 알았던 개발. 특히 안드로이드 플랫폼 보안 쪽을 다루며 매일 야근중이다. (살...살려주....)
현재는 잠깐 여유가 나니 문제를 풀 틈이 생겼다. 주말에 2~3시간 정도지만; 이 문제는 취약점이 금방 보였지만 exploit할 때 어떤 부분을 이용해야 할지 많이 고민했다. (아무래도 가끔씩 해킹공부 하다보니 내 기술력이 점차 무뎌져가서 그런게 아닐까 ㅠㅠ)

이 문제는 내 느낌엔
1. C++ 디버깅을 체험해봐라
2. C++ 코딩 시 vtable을 잘못 사용할 경우 어떤 취약점이 발생하는가?

이 두가지를 공부시킬 목적인 듯 하다. 주말마다 짬짬히 하느라 3주쯤 걸렸다. 책 작성하기로 1년 전에 맘 먹었는데 체력이 안따라준다 ㅜ 올해는 일이 안 바쁘길 기도해야지.

- 400명 이상 푼 문제는 PoC 코드를 공개하겠다.
- 이 글에는 문제 풀이, 힌트 등이 있으니 실력 증진을 원하는 분들은 충분히 삽질한 후 읽어주시길 바란다.

1. 코드 흐름

====================================================

gdb로 디버깅할 수도 있겠지만, 너무 지옥일 거라는 생각이 든다. 내가 C++ 디버깅을 많이 안해본 것도 있지만, 요 언어가 함수를 객체로 다루다보니까 어디가 어딜 참조하는지 눈에 확 안들어온다.

그래서 우리는 신의 도구, "IDA Pro"를 이용해야만 한다. (포렌식 짱짱 이*호 협찬. 땡스)

starcraft@pwnable.kr -p2222로 접속해서 바이너리를 다운받은 후, IDA로 열어보면 위 화면과 같다.
이 문제는 코드 공개가 되어있지 않으므로 IDA로 빠르게 분석해보자.
call 부분을 보면 알겠지만, C++은 함수명이 정직하지 않다. 게다가 저렇게만 보면 뭔 함수가 어떻게 불리는지 전혀 모른다.

그래서 sub_269A, sub_26E2등 하나하나 눌러보면서 함수명을 자기 느낌대로 추측해서 샥 빠르게 정리해보자.
이 작업은 얼마 안걸린다.


이런 식으로 말이다. 함수 이름은 그냥 왼쪽에 함수리스트 하나씩 눌러보면서 느낌가는대로 정리했다.
main에서 중요한 일이 있을 것 같으니 주석으로 뭐하는 부분인지 간략하게 주석도 달고.. 파라미터는 무슨 역할을 하는지 간략하게 정리했다. 이렇게 적으면 코드가 한눈에 보기 좋아진다.

C++은 위 그림의 파란색 네모에 표시된 것처럼 함수를 호출하는데, 저 부분은 대략 user의 unit StatInfo를 보여주는 부분일 것이다.
user_select는 맨 처음 unit 선택하는 부분에서 특정 유닛의 객체 정보[유닛 이름, 스탯, 공격 옵션, isDead? wanna Cheat 시 분기점 등]를 담고 있다.

이게 끝나면 .data.rel.ro 부분을 또 정리해준다.

대충 이런 느낌이다.
이러면 클래스 정보, 클래스마다 생성된 객체 정보 등을 쉽게 확인할 수 있다.

이렇게 정리하고나서 하나씩 함수 기능을 살펴보면서 "사용자에게 입력받는" 부분을 찾아보자. 함수 이름 중에 ostream, istream이 있는데, o는 output stream, i는 input stream이다. 즉, ostream은 "출력", istream은 "입력"이다.

istream이 있는 곳을 빠르게 훑어보자.

1. main의 wanna cheat? (yes/no) :

wanna cheat? 가 출력되려면 g_level++이 10보다 커야한다는데.. 일단 최소 컴퓨터를 11번 이상 이겨야된다는 뜻인듯 하다.
wanna cheat? 프로세스를 좀 상세하게 보자면

이런 부분이 보이는데, 눈여겨볼 부분은 show me the money, there is no pwnable.kr level 같다.
there is no pwnable.kr level을 command로 입력할 경우, 유저의 gold 정보를 return 하는데.. 이걸로 뭘 할 수 있는지 빠르게 파악해보는게 좋아보인다.

2. unitAscii_artwork 에서 input unit ascii artwork 부분

뭐하는 부분인지는 모르겠는데 g_level과 m_level을 비교한 뒤에 사용자의 input을 받는다.
여기서 m_level은 main에서 이미 선언되었던 변수로, 값은 10이다. g_level은 사용자의 현재 stage 레벨을 의미한다. 즉 사용자가 최소 10번 이상은 이겨야 저 input process가 동작할 준비가 된다는 뜻이다.
수상하니 여기도 눈여겨봐두자.

3. wannaCheatForProtoss, Terran, Zerg 등


4. Terran, Zerg, Protoss 등 각 종족의 공격 옵션(atkOpt)


5. Ghost의 Nuclear 공격 옵션 중 "nuclear launch detected!" 다음 입력받음. 입력 버퍼의 크기는 104


6. Ghost, Templar 의 공격 옵션. 다른 유닛에 비해 유난히도 뭔가가 많다. 그러니 일단 이것도 유심히 보는 걸로.



1~6번까지 훑어봤다면 이제 바이너리를 실행해보면서 어떤 식으로 starcraft 바이너리가 동작하는지 확인하자.


2. 고려 사항

====================================================

1. Partial RELRO, Canary, NX, PIE 다 걸려있다 : 스택의 버퍼가 넘치는지 체크함. 스택엔 실행권한이 없음. 스택, 라이브러리 뿐만 아니라 코드영역도 주소가 randomize 된다는 뜻. 즉 코드영역의 주소는 memory leak 되지 않는 이상 공격에 활용 불가

2. rdi: "/bin/sh", call system_addr 실행 시, glibc 2.26 이상 버전에서는 오류가 발생한다. 버그인지 뭔지는 모르겠지만, __libc_system 실행 중, __do_system으로 분기해서 코드를 실행하는데 여기서 "/bin/sh"를 찾을 수 없다고 나온다.
그러니 딱 system 주소를 호출할 때에만 로컬에서 실행하지말고 바로 pwnable.kr 문제 서버로 공격하던가, 아니면 ubuntu 16.06 설치하고 그 환경에서 테스트하는게 좋다.
* glibc 버전 확인 방법: ldd --version

3. unit asciiWork는 왜 stage 11 이상에서만 사용자의 입력을 받는걸까?

4. 일단 stage 11 이상 넘기려면 유닛이 매우 강해야 가능할텐데, 강한 유닛들을 위주로 starcraft 바이너리의 기능을 확인해보는건 어떨까

3. 필요 지식
====================================================
1. C++ 리버싱하는 법을 공부해야 한다.

관련 주소: https://tribal1012.tistory.com/179
C언어와는 다르게 C++은 절차지향이 아닌 객체지향이다. 객체 단위로 메모리를 관리하기 때문에 디컴파일 해보면 C랑은 좀 구조가 달라서 분석하기 어렵다.

2. pie 보호기법이 뭔지 알아야 한다.

관련 주소: bpsecblog.wordpress.com/2016/06/10/memory_protect_linux_4/

3. partial RELRO 보호기법이 뭔지 알아야 한다.
관련 주소: bpsecblog.wordpress.com/2016/05/18/memory_protect_linux_2/

 

4. Type Confusion을 이해하면 좋다. 말 그대로 타입을 혼동하는 취약점이다. 예를 들어 "A"라는 OBJ의 타입유형이 "read"인데, 특정 오류로 인해 "A" OBJ의 타입유형을 "write"나 "null" 등 다른 타입으로 혼동하여 "write", "null"에 대한 동작을 수행하게 되는 버그이다.

 

5. RTL(Return to Library) 기법(64bit)을 알아두면 좋다. 말그대로 라이브러리에서 특정 주소를 리턴하는 공격 기법이다.
관련 주소: https://shayete.tistory.com/entry/4-Return-to-Library-RTL?category=857069

6. x64 환경의 함수가 어떻게 동작하는지 정확하게 알아야 한다.
관련 주소: https://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64
예를 들면, read(fd, buf, sizeof(buf))라는 함수가 있을 때, 첫번째 인자는 fd, 두번째 인자는 buf, 세번째 인자는 sizeof(buf)이다.
첫번째 인자값은 rdi 레지스터에 들어가고, 두번째는 rsi, 세번째는 rdx에 들어간다. 함수의 반환값은 rax에 들어간다. 64bit 환경의 Return 형식은 32bit와는 다르게 rdi, rsi, rdx 등 레지스터들이 모두 세팅된 상태로 특정 함수 주소를 call해야 해당 함수가 실행된다.


4. 문제 풀이
====================================================


위의 코드흐름을 봤다면 exploit을 위한 선행조건을 바로 알 수 있다. "stage 11 이상 돌파할 것"

main에서는 stage 11이상(실제로 해보면 12 이상부터다) 돌파할 경우, command를 입력하는 프로세스로 넘어간다.
unitAscii_artwork는 사실 코드만 봤을 때는 뭔지 모르겠지만 stage 11이상 돌파할 경우 사용자의 입력을 받는 프로세스를 이용할 수 있다. 단순히 정상적으로 starcraft 바이너리를 이용할 땐 unitAscii_artwork를 쓸 수 없지만 혹시 모르니 기억해두자.

stage11 이상으로 넘어가려면 어떻게 해야하나 유닛을 하나하나 보니 Ghost, Templar, Ultralisk가 있다.
Ghost는 nuclear나 clock이 있으니 뭔가 킹능성이 보이고. Ultralisk는 단순 무식하게 체력, 공격력이 높고. Templar는 짱센 Arcon으로 변신할 수 있다. 기능을 각각 이용해보니 Ghost는 그냥 약골 유닛으로 판정. 얘는 나가리다. 그러나 Nuclear 입력받는 부분은 혹시 사용할지도 모르니 따로 메모해두자.

아콘, 울트라리스크 중 단순히 체력, 공격력만 놓고 보면 아콘이 월등히 Stage 11 이상 도달할 확률이 높다. 아콘을 선택해보자.
아콘 공격 선택 옵션을 IDA로 확인해보면, Templar와 공유하는 걸 알 수 있다.
근데.. 얼라리?



원래라면 Templar는 unit_templar_atkOpt 테이블을 쓰도록, Arcon은 default_AtkOpt만 쓰도록 구성되어 있다. (만약 정상적인 경우라면 말이다.)
그러나 Templar 클래스에서 Templar 유닛을 생성(Operate)하고 Arcon 변신을 하는 도중, Arcon 테이블이 아닌 Templar 테이블을 그대로 참조하는 상황이 발생한다. 여기서 Type Confusion 취약점이 발생하는 것이다. Starcraft 바이너리는 아콘이 템플러인줄 아는것이다.

뭐 여튼 Arcon은 Templar의 AtkOpt를 참조하고 case 1, 2, 3을 그대로 써버릴 수 있게 된다. 아콘이 싸이오닉 스톰이라도 갈기는 상황이 오나 테스트를 해보니

아콘의 AtkOpt는 메모리 주소를 잘못 참조하는 바람에 Ultralisk의 unitAscii_artwork, statInfoForZerg, atkOptBurrow를 호출해버린다.
statInfoForZerg를 호출하면 치명적인 leak이 발생하는데 바로 libc 라이브러리의 함수인 __GI_exit의 주소가 노출되는 것이다.
이 주소만 가지고 있다면 starcraft와 링크된 라이브러리 함수 주소들로 다 return(RTL)할 수 있다.

그리고 처음에 의문을 가졌던 부분인 unitAscii_artwork 함수도 사용할 수 있게 되었다.
이제 이것들을 이용해서 문제를 잘 풀어보자.

이 문제의 핵심은 exploit을 위한 RTL Gadget을 얼마나 잘 찾느냐인데, 이를 찾는 힌트를 주자면

위 그림은 libc.so.6 라이브러리의 예시인데, starcraft에 링크된 라이브러리를 확인하고 그 라이브러리에 포함된 string 혹은 gadget을 이렇게 찾을 수 있다. 꼭 libc.so.6만 쓰려는 생각은 버리자.

그리고 공격 데이터를 starcraft 바이너리에 때려 넣을 때, 다들 아시겠지만

starcraft 바이너리에 값을 잘 넣나 테스트하실 분들은 pwntool 기능 중, gdb.attach(p)를 이용해보자.
귀찮다고 이거 안쓰다간, 장담하는데 시간 꽤 많이 버린다. gdb.attach는 검색 ㄱㄱ

최근 안 사실인데 텍스트 모자이크 쯤은 다 복원 가능하다고 하더라. 그래서 내가 작성한 글의 cat flag부분은 재빨리 다른 글씨로 덮어버렸다.

'Wargame > pwnable.kr' 카테고리의 다른 글

[Grotesque] lokihardt  (2) 2021.04.01
[Grotesque] maze  (2) 2020.10.12
[Grotesque] aeg  (2) 2020.10.03
[Rookies] crypto1  (0) 2020.09.14
[Rookies] note  (0) 2020.09.10