리버싱

Simcurity·2023년 6월 6일
0

리버싱

목록 보기
9/9
post-thumbnail

오늘은 지뢰찾기 프로그램을 분석해보겠습니다.

1. 환경 구축

우선 윈도우 7 32bit 환경을 워크스테이션으로 구축했습니다.

그리고 올리디버거 32bit로 다운받았습니다.
지뢰 찾기 게임은 ASLR 기술이 적용되어 있습니다.
ASLR이란 프로그램의 분석이나 버퍼오버플로우 등을 방지하기 위해 주소 값을 프로그램 실행 시마다 난수 값으로 랜덤화하여 할당하는 기술입니다.
다른 기술로는 스택 가드, 스택 쉴드가 있습니다.

2. 디버깅 시작

2-1) 시간 설정 변경

일단 가장 쉬운 시간을 변경해보겠습니다.

하단에 왼쪽은 시간, 오른쪽은 지뢰의 갯수를 나타냅니다.

올리디버거에서 상단의 File -> Attach를 누르면 다음과 같이 실행 중인 분석 가능한 파일들이 나타납니다. 지뢰 찾기를 Attach 하겠습니다.

윈도우 7에서 올리디버거를 사용하니 신기하고 새롭네요 ㅋㅋ..
가장 만만한 시간을 건드려보기 위해 해당 함수에 브레이크 포인트를 설정해야합니다.
시간에 관련된 함수로 SetTimer()함수가 있습니다. 이 함수로 가정하고 내부 모듈 호출을 모두 찾아보겠습니다.

SetTimer()함수에 대한 정의
여기서 uElapse는 밀리세컨즈의 단위로 1000이면 1초에 한 번씩 타이머가 동작한다는 의미입니다.

SetTimer()함수 호출이 있네요.
이 곳에 브레이크 포인트를 설정해줍니다.

예상대로 프로그램을 재시작하자 타이머가 올라가지 않습니다.
그럼 이제 코드를 보며 SetTimer()함수에 어떤 인수가 들어가는지 확인해보겠습니다.

여기서 Timeout를 가리키는 PUSH 3E8은 PUSH 1000을 의미합니다. -> 3E8(16) = 1000(10)
그럼 저 인수인 3E8값을 5배로 늘린다면 시간은 5초에 1초씩 타이머가 동작하여 시간이 5배 느리게 흘러갈 것입니다.
타이머를 조작하기 위해 5000을 헥사 값으로 나타내면 1388입니다.

다음과 같이 수정을 하고 별도의 파일로 저장하겠습니다.

프로그램 실행 시 참조 DLL파일이 위치한 원본 실행 파일이 있는 곳에 이름만 바꾼 후 저장합니다.

5초에 1초씩 타이머가 동작합니다.

3. 맵핵 만들기

지뢰 찾기에서는 지뢰의 위치를 알아내는 것이 맵핵입니다.
가장 먼저 프로그램을 리버싱하여 지뢰의 위치를 알아내고 그것을 화면에 구현해줄 프로그램을 만들어야 합니다.
일단 가정을 해보겠습니다.
게임을 진행하며 3가지의 함수 호출을 예상할 수 있습니다.

1. 타일을 누름과 동시에 타이머가 동작될 때 SetTimer() 함수 호출
2. 지뢰를 눌러 게임이 종료 되었을 때 게임을 다시 할지 물어보는 창을 호출하는 GetDlgItem() 함수 호출
3. 타일을 눌렀을 때 호출되는 함수를 사용하는 것으로 GetKeyState() 함수 호출

이 세 가지 중에 하나를 정하여 맵핵을 만들 수 있습니다. 그러나 어떤 방법이 가장 좋은지 정답은 없습니다. 리버싱의 오랜 경험과 직관적인 능력을 활용해야 합니다.

세 번째 방법으로 시도를 해보겠습니다.

GetKeyState() 함수는 가상 키 값을 인자로 받아 가상 키의 상태를 숫자로 반환하는 함수입니다. 지뢰 찾기 게임의 경우 타일들이 가상 키로 인자가 되고 상태를 함수를 통해 체크합니다.
우클릭 -> search for -> search name in current module 로 이동을 하면 현재 모듈에서 사용하는 모든 함수와 문자열을 보여줍니다.
여기서 GetKeyState() 함수를 찾아보겠습니다.

그리고 names 메뉴에서 해당 함수를 우클릭 -> Find References를 누르면 함수가 어디서 호출되는지 목록이 나옵니다.

명령어 형식을 보면 CALL로 시작하는 함수 직접 호출이 있고, MOV로 명령어의 주소를 EDI에 저장해 놓고 나중에 CALL EDI와 같은 방식으로 함수를 호출합니다.
search for -> all intermodular calls 로는 직접 호출만 볼 수 있지만,
search for -> names 로는 간접적인 호출도 모두 볼 수 있어 함수가 사용되는 모든 영역을 확인할 수 있습니다.
이제 정확히 어디서 함수가 호출되는지 알아보기 위해 모두 브레이크 포인트를 설정해주고 프로그램을 다시 시작해보겠습니다.


프로그램이 실행되기 전에 한번 걸립니다.
GetKeyState() 함수는 가상 키가 눌려지는 것을 지속적으로 모니터링하고 있기 때문에 프로그램 로딩 시 호출될 수 있다.
타일이 눌려질 때 호출되는 부분을 찾기 위해 브레이크 포인트를 해제하고 건너 뛰겠습니다.
다시 F9을 눌러 실행하자 프로그램이 실행되었습니다.

타일을 하나 눌러보자

008FC7FA에서 BP가 걸립니다. 다시 F9으로 재게 시 008FC826에서도 BP가 걸립니다.
다시 F9으로 재게 시 타일은 뒤집히지 않았습니다. GetKeyState() 함수가 가상 키가 눌려지는 이벤트를 지속적으로 모니터링하고 있어 다음 상태로 넘어가지 않는 것입니다. 따라서 이 두 BP를 해제하겠습니다.
이 후 다시 타일을 클릭하자 008FC950에서 BP가 잡혔습니다.


게임을 보니 제가 클릭한 타일이 눌려져 있었습니다. 그럼 아마 제가 누른 부분을 지뢰인지 아닌지 비교하는 비교문이 따라올 것이고 그 정보가 맵핵을 만들 실마리가 될 수 있습니다.
그러므로, 008FC950을 제외한 모든 BP를 제거하겠습니다.

이제 부터 BP아래에 오는 비교문들을 분석해보겠습니다.

1. 첫번째 비교문

일단 가장 먼저 오는 TEST AL, AL을 보시면 EAX의 하위 1바이트가 NULL인지 확인하는 함수입니다. 만약 NULL인 경우 제로 플래그가 1이 설정됩니다.
현재의 경우 제로 플래그가 1이되어 JE의 주소로 점프를 할 것입니다.
분기문의 역할을 알 수 있는 가장 좋은 방법은 제로 플래그를 바꿔 무슨 변화가 일어나는지 확인하는 방법입니다. 제로 플래그를 더블 클릭해서 점프를 시키지 않고 실행해보겠습니다.

제가 눌렀던 타일이 다시 원상복귀 되었습니다. 이렇게 분기문의 역할을 알아볼 수 있습니다.

2. 두번째 비교문


JNZ에서 제로 플래그가 1로 점프하지 않지만 제로 플래그 값을 변화시켜 점프를 하고 실행하자

제가 클릭한 타일에 깃발이 생겼습니다. 즉, 사용자가 마우스 오른쪽 클릭으로 지뢰를 표시했는지 체크하는 부분입니다.
이 비교문도 맵핵을 위해서는 그다지 필요한 정보는 아닌것 같습니다.

3. 세번째 비교문


마찬가지로 점프 부분에서 제로 플래그를 변경해보았습니다.

그런데 딱히 별 다른 동작은 없고 클릭한 타일의 주위에 지뢰가 없는 타일을 모두 뒤집습니다.

이제 다음으로는 스텝 인투를 하여 호출되는 함수 안으로 들어가 보겠습니다.

조금 내려오다 보면 CALL 명령이 있습니다. 여기서 스텝 인투 해보겠습니다.

점프 후 전형적인 스택 프레임 생성과정인 명령이 나옵니다.

PUSH EBP
MOV EBP, ESP


스탭 오버로 진행 중 또 다른 CALL을 만나 일단 BP를 설정하고 해당 CALL로 스텝 인투 해보겠습니다.

리턴이 아닌 점프로 함수를 끝내고 있습니다. 그러므로 서브루틴을 종료하는 것이 아니고 나중에 서브루틴이 종료되면 00BA6FE3 주소로 돌아갑니다.

마지막 점프 전에 스택 상태는 다음과 같았습니다.
중간에 인자 1, 1을 두개를 넣어주었습니다. 이 인자가 무엇을 의미하는지는 아직 정확히 모르지만 다음 점프할 주소의 서브루틴(00BA0C50)에서 명확해질것 같습니다.

이제 세번째 서브루틴인 00BA0C50으로 점프를 했습니다.
여기까지 왔으므로 BP를 설정하고 분석을 하겠습니다.
우선 새로운 스택 프레임 생성까지(00BA0C53) 실행을 했습니다.
스택 프레임 구조에서는 스택에 있는 값을 참고할 때 EBP를 기준으로 사용하므로 서브루틴을 분석할 때는 EBP 레지스터 값을 설정한 후에 분석을 시작하는 것이 좋습니다.
00BA0C57 주소에 있는

MOV EBX, DWORD PTR SS:[EBP+8]

명령은 X좌표를 설정하는 명령어입니다.
EBP 레지스터로부터 8바이트 떨어진 부분에서 4바이트를 읽어 EBX레지스터에 넣습니다.
00BA0C6A 주소에 있는

MOV EDI, DWORD PTR SS:[EBP+C]

명령은 Y좌표를 설정하는 명령어 입니다.
이전 서브루틴에서 인자인 1두개는 X좌표와 Y좌표를 뜻합니다.
다시 확인을 해보기 위해 다시 실행해 다른 곳을 눌러보았습니다.


X좌표 4와 Y좌표 1 인자가 들어가는 것을 볼 수 있습니다.
이어서 분석하는 중 새로운 비교문을 만났습니다.

제로 플래그를 변경하고 실행하자

지뢰가 나오더니 패배 메세지가 떴습니다.
즉, 해석을 해보면 CMP EAX, 9 에서 만약 EAX가 9가 아니라면 제로플래그는 0이고 분기에서 점프를 하지 때문에 지뢰를 클릭한 동작으로 진행됩니다.

다음 비교문으로 넘어가겠습니다.

해당 명령어의 메모리 덤프를 확인하며 여러번 실행을 해보니 첫 메모리 숫자가 제가 클릭한 타일의 갯수만큼 늘어나는 것을 확인했습니다.

처음 클릭 후 1 증가된 모습

즉, 두번째 클릭부터는 00BA0C95의 비교문에서 결과가 달라져 제로 플래그가 0이므로 JNZ 분기문에 의해 지정 주소로 점프하게 됩니다.

점프 후 여러번 실행을 해보며 00BA0CC6 주소에서 비교하는 CMP BYTE PTR DS:[EDI+EAX], CL에서 제로 플래그가 1인 경우 지뢰가 아니고 0인 경우 지뢰인 로직을 알아 냈습니다.

코드를 보면 X좌표를 이용해 뭔가를 계산합니다. 이어서 EDI(Y좌표)를 이용해 EAX와 더한 메모리 위치의 값을 CL과 비교하여 제로 플래그가 0인 경우 클릭한 타일의 좌표가 최종적으로 지뢰인지 아닌지를 알 수 있습니다.
이제 x좌표와 y좌표만 있으면 지뢰인지 아닌지를 찾을 수 있습니다.

4. 코드 인젝션

일단 코드를 삽입할 빈 공간을 찾아야 하므로 프로그램 밑 부분으로 이동했습니다.

코드 인젝션을 하기 위해

임의의 빈 공간으로 점프를 시켰습니다.
이제 0010DF75에 코드를 작성합니다.

CMP DWORD PTR DS:[ESI+18],ECX 
JE 000B0C9A
------------------------------> 원본 코드에서 점프해온 곳으로 원본 코드의 대체 명령

PUSH EAX
PUSH EDX
PUSH ECX
PUSH EBX
PUSH EDI
------------------------------> 맵핵 프로그램에서 사용하는 레지스터의 값을 스택에 넣어 놓음으로서 맵핵 기능이
끝나면 다시 꺼내 다음 명령어에서 문제없이 사용하기 위해 백업

MOV EDX, 0
MOV ECX, 0
MOV EDI, 0
MOV EBX, 0
------------------------------> 사용할 레지스터를 초기화

MOV EAX, DWORD PTR DS:[ESI+44]
MOV EAX, DWORD PTR DS:[EAX+0C]
MOV EAX, DWORD PTR DS:[EBX*4+EAX]
MOV EAX, DWORD PTR DS:[EAX+0C]
MOV CL, BYTE PTR DS:[EAX+EDI]
------------------------------> EBX, EDI에 0부터 80까지 차례대로 값을 넣어 81번 반복하면서 지뢰가 저장돼있는지 
확인

MOV BYTE PTR DS:[EDX+4B5850], CL
------------------------------> 지뢰 확인 결과 빈 메모리에 저장 

INC EBX
------------------------------> X좌표인 EBX 값을 0부터 1씩 증가

INC EDX
------------------------------> 지뢰 확인 결과를 특정 메모리에 저장시키는데 메모리 위치 0부터 80까지 차례대로 
저장

CMP EBX, 9
JL SHORT 0010DF97
------------------------------> EBX가 9보다 작을 경우 0010DF97 주소로 점프 반복문

INC EDI
------------------------------> Y좌표인 EDI 값을 0부터 1씩 증가

CMP EDI, 9
JNE SHORT 0010DF92
------------------------------> EDI가 9보다 작을 경우 0010DF92 주소로 점프 반복문

POP EDI
POP EBX
POP ECX
POP EDX
POP EAX
------------------------------> 레지스터 값 복구

JMP 00B00CBA
------------------------------> 원래 로직으로 복귀

프로그램 실행하자 지정한 메모리 주소에 지뢰의 위치가 01로 표기됩니다.

4B5850부터 81개 까지 표기됩니다.

메모장에 표기해봤습니다. 그럼 01위치를 한번 눌러보겠습니다.

지뢰 위치가 정확히 일치하네요.

5. 마무리

책 내용을 이해하는데 정말 오래걸렸다..
이해가 너무 안돼서 따라하면서 설명 부분을 적어도 20번씩 읽었던 것 같다..
이해하려고 노력하다가 중간부턴 이해한다기보단 그냥 외워버린다는 마인드로 임한것 같다.
이게 기초적인 실용 프로그램 해킹이라니 정말 놀랍다 ㅋㅋㅋ
그러면 악성코드나 요즘 사용되는 상용 프로그램들은 어느정도일까..
어셈블러 프로그래밍도 이해해보면서 여러가지를 배웠다.
예를 들어, 사용할 레지스터를 먼저 백업시키고 초기화한다던가 반복문을 쓰는 형식에 대해서도 어느정도 윤곽이 잡혔다. 또한, 리버싱을 할 땐 어느 부분이 나에게 필요한 부분인지 아는 능력이 정말 중요한것 같다. 이러한 경험들을 해보며 배웠던 스택 프레임, 메모리, 레지스터, 주소 등 기초지식들이 점점 머리에 스며드는 것 같다. 아직 많이 부족하지만 열심히 배우며 성장하고 싶다.

0개의 댓글