본문 바로가기

Reversing/리버싱 핵심원리

[리버싱 핵심원리] 2장 Hello World! 리버싱

스터디 자료 모음: 리버싱 핵심원리 스터디 자료 목록

 

 

티스토리로 글을 올리니 깨지네요.. 제가 쓴 원본은 다음과 같습니다: 

[리버싱 핵심원리] 2장. Hello World! 리버싱.pdf
0.30MB


1. Hello World 리버싱

HelloWorld.cpp

 
 
 
 

간단한 코드이지만 윈도우 프로그래밍을 해 본 적이 없어서 낯선 부분들이 많았다. 하나씩 살펴보겠다.

_tmain, TCHAR

1바이트 문자(ASCII 매핑) 형식인 char, 멀티바이트 문자(Unicode 매핑) 형식인 wchar_t는 들어봤는데, TCHAR는 처음 봤다. 처음 실행될 서브루틴으로 main 대신 _tmain이 있는 부분도 매우 낯설었다.

 

이것들은 tchar.h에 정의된 매크로다. MSDN 문서에 따르면, 이들은 같은 코드가 빌드 환경에 따라 단일 바이트 char 또는 wchar_t를 사용하도록 만든다. tchar.h에 어떤 매크로가 정의되어 있는지, 또 그 매크로들이 어떻게 전환되는지는 이 문서에서 확인할 수 있다.

 

위 코드는 _UNICODE 또는 _MBCS 매크로가 정의되지 않았으므로, int main(int argc, char *argv[])라는 익숙한 형태로 컴파일 될 것이다.

 

MessageBox

Win32 API이다. MessageBox는 말 그대로 모달 대화 상자를 보여 주는 함수이다.

 
 
 
 

 

위 프로그램의 호출은,

  • hWnd: NULL이라 다행히 핸들 지옥에 빠지지 않았다.
  • lpText: 모달 본문에 Hello World를 출력한다.
  • lpCaption: 모달 제목에 저자분 블로그 주소를 넣는다.
  • uType: 확인 버튼만 사용한다.

는 동작이다.

 

x32dbg 설정

책에서 사용하는 ollydbg 대신 x64dbg(32비트 바이너리이므로 x32dbg)를 이용해 실습을 진행했다.

실습예제를 받아 x32dbg로 열어 보면, EP인 0x4011A0이 아니라 다른 주소에서 처음 멈춘다. ntdll 영역이라고 나와 있는데, 메인 스레드의 TLS Callback이다.

 


 

설정으로 들어가 Entry Breakpoint 외에 다른 옵션들을 꺼 두면 책과 같이 바이너리의 Entry Point에서 디버깅을 시작할 수 있다.

 

디버거 실습

이제 바이너리의 EP인 0x4011A0부터 디버깅을 진행할 수 있다.

 
 
 
 

x86dbg의 단축키는 ollydbg와 비슷하다.

  • F7: step in
  • F8: step over
  • Ctrl + F2: restart
  • Ctrl + F9: execute till return
  • Ctrl + F8: animate
  • F12: pause
  • F2: set breakpoint
  • *: IP로 커서 이동
  • -: 이전 커서 위치로 이동

 

책을 따라 디버깅을 진행해보자. 원본 소스코드를 알고 있으므로, 각 CALL 인스트럭션을 따라가 보다가 MessageBox를 실행시키지 않고 있으면 빠져나오면 된다. Excute till Return(Ctrl + F9) 단축키로 쉽게 프로시저에서 빠져나올 수 있는데, 0x402524 위치의 프로시져에서만 Execute till Return을 수행하면 프로그램 종료까지 실행된다. RET 인스트럭션을 만나기 전 까지 단순 메모리 연산만 수행하는데 왜 못 찾는건지 모르겠다. 버그인가? (다른 프로시져에서는 정상 작동한다)

 

0x401C5A 프로시저의 어셈블리 코드에서, 반복문에 해당하는 분기 흐름을 직관적으로 확인할 수 있다. 디버거 최고. 이중 반복문으로 포인터를 훑으며 arr[i] = arr[i + 1] = $cx를 찾는 듯 하다.

 


 

아무튼, 여러 프로시저를 거쳐 0x401000에서 main함수를 찾을 수 있다.

 
 
 
 

주석은 내가 추가한 부분이다. 스택 프롤로그와 에필로그가 보이지 않는다는 점이 특이하다. 32비트 특성인가? 공부해야 할 내용이 참 많다.

 

Stub 건너뛰고 바로 코드 찾기

1. Step Over 하면서 이상 동작 찾기

앞서 해 본 방법은 Step Into를 이용해 각 프로시저를 확인하면서, MessageBox를 호출하는지 확인하는 방식이었다. 그것보다는, Step Over를 하면서 모달이 뜨는 순간을 확인하는 것이 간단하다.

 

EP에서 시작해 F8을 연타하다 보면, 0x401144(CALL 0x401000) 이후에 모달이 뜨는 것을 확인할 수 있다. 호출하는 위치에서 main 함수를 찾을 수 있다.

 

2. 문자열 검색하기

Shift + D 단축키로 현재 모듈의 모든 문자열을 검색할 수 있다. Find Strings 아이콘을 눌러도 된다.

 


 

하드코딩된 문자열을 참조하는 위치를 확인해보면 main 함수를 찾을 수 있다. 메모리 덤프에서 해당 문자열을 확인해 볼 수도 있는데, char가 아니라 wchar_t라 각 글자마다 널바이트 패딩이 있는게 인상적이다.

 

3. API 호출 확인

Find Strings 아이콘 옆에 Find Intermodular Calls 아이콘이 있다. 이걸 눌러보면 외부 API 호출을 모두 뽑아준다. 딸깍.

 

4. API에 BP 걸기

Symbols 탭을 보면 현재 메모리에 로딩된 모든 모듈과 심볼이 나온다. user32.dllMessageBoxW 함수를 찾아 BP를 걸 수 있다.

 


 

그리고 프로그램을 재시작하면, MessageBox가 호출되는 순간에 BP에 걸린다. 여기서 Ctrl + F9로 Execute till Return을 하면, 호출자인 main함수로 갈 수 있다.

 

2. 코드 패치 실습

메모리 수정

다른 메시지를 출력하기 위해서, 모달의 본문으로 사용되는 0x4092a0 영역의 메모리 값을 바꿀 수 있다.

  1. 메모리 덤프 윈도우에서 Ctrl + G로 이동
  2. 문자열을 선택한 후 Ctrl + E로 편집 (유니코드 문자열 수정)

 

코드 패치

그러나 이 방법은 .rdata 영역 앞뒤에 다른 용도로 사용중인 변수들이 있기 때문에, 길이 제한이 있다. 책에서는 NULL padding을 이용한다. 바이너리의 각 세그먼트에는 정렬 조건을 맞추기 위해 NULL padding이 있고, 이 부분은 다른 코드에서 사용할 수 있기 때문에 마음껏 수정해도 된다는 것을 이용한 것이다.

x32dbg의 Memory Map 메뉴에서 확인해 보면, .rdata 영역은 0x408000부터 0x409fff까지이다. NULL padding 중 적당한 위치에 원하는 문자열을 삽입하면 중요한 데이터를 손상시키지 않고도 데이터를 삽입할 수 있다. 그러나, 이 주소는 임의로 삽입한 것이므로 코드를 수정해야 한다.

 

다시 코드 윈도우로 돌아가, lpText를 삽입하는 인스트럭션인 0x401007을 선택하고, Space를 눌러 코드를 패치하자.

 


 

그리고 코드를 수정하면, 바뀐 결과를 볼 수 있다.

 

덤프 뜨기

이렇게 디버거에서 프로세스를 수정해도, 파일을 재시작하면 사라진다. 프로세스를 수정했지, 프로그램을 수정하지 않았기 때문이다. 프로세스를 파일로 다시 저장해야 수정 결과가 남아있게 된다.

 

x32dbg는 Scylla라는 플러그인으로 덤프를 지원한다. Scylla 메뉴를 클릭하고, Dump 버튼을 눌러 프로세스 덤프를 뜰 수 있다.

와! 이제 다시 실행해도 수정된 결과가 나온다.

 

3. 참고자료