본문 바로가기

Rerversing/Exercise reversing

[Reversing.kr] - Replace 문제 풀이

5번째 문제 풀이 시작... (블로그를 너무 간만에 하게 됐다. 흠!~~)






[그림. 1] Replace 실행 모습



예제 파일의 실행 모습은 뭐 기존의 것과 별 차이는 보이지 않는다.







[그림. 2] Access violation error


빈 칸에 숫자 밖에 입력이 안 되는 형식이므로 난 숫자 "12345"를 입력하고 Check를 눌렀는데, 누르는 순간에

Access violation error 가 똿~~ 


windbg를 기본 디버거로 잡아놨었기 때문에 애러 화면에 대한 정보를 볼 수 있었다.


0x40466F 주소의 명령문에서 애러가 났는데 확인해 보면 eax의 주소에 byte 단위의 0x90 값을 MOV 시키려고 했다가 뻗어버린 것을 알 수 있다.

해당 명령이 실행될 때 EAX에는 0x60163604라는 값이 지정되어 있는데, 존재하지도 않는 주소에 특정 값을 넣으려 하니

Access violation error가 발생했다고 볼 수 있겠다.


참고로 해당 예제는 PE파일의 Relocation이 일어나지 않기 때문에 windbg에서 나온 주소를 다른 디버거로 열어본다 해도 주소는 변하지 않고 고정이니 저 주소를 그대로 찾아가면 된다.






[그림. 3] 0x40466F 주소의 명령 코드 - 01


파일을 실행하지 않고 바로 해당 주소로 가보면 windbg에서 보여주었던 명령어 따위는 안 보이고 call 40467A 명령이 보이게 된다. 그 밑에는 "81" 이라는 값만이 덩그러니 보이는데, 흠! 0x81 값은 

뭐, 아무튼 애러가 난 뒤의 해당 주소의 명령과 실행 전의 해당 명령 코드는 서로 다른 것을 알 수 있는데, 그렇다면 어떤 단계를 거쳐서 해당 주소의 해석이 달라진다고 볼 수 있지 않을까?

그 것을 염두하고 분석을 진행하자.






[그림. 4] GetDigitemInt 함수


본 예제는 DialogBoxParamA 함수를 이용하여 다이얼로그 박스를 생성하고 거기에다가 특정 키 값(숫자만 가능)을 요구하는 구조였다. 사용자가 입력한 키 값을 어떤 식으로든 활용한다고 볼 수 있는데, 그 값을 이용하기 위해서는 GetDigitemInt 함수를 이용하게 될 것이며 역시나 함수가 존재하고 있다. 예제 파일을 실행한 후에 저 함수 주소에 BP를 걸고 진행을 해보자.


본 함수는 다이얼로그 박스에 입력된 사용자 값을 EAX에 16진수 값으로 리턴해준다.

나는 12345를 입력했으므로 EAX에는 0x3039 값이 리턴이 되었다.


함수 호출이 끝나면 EAX값을 4084D0 주소에 저장하는데, 저장하는 이유가 있을테니 저 주소를 주시하도록 하자.

그 다음에 CALL 40466F 라는 명령이 나오는데, 저 주소는 [그림. 2]에서 언급한 Access violation error 가 발생한 주소가 아닌가?

그러나 아직은 해당 주소로 진입을 하면 [그림. 3]에 보이는 명령어 코드들이 기다리고 있다.

[그림. 3]을 보면 CALL 40467A명령어가 보이는데, 그 함수를 실행한 뒤 빠져나오면 나면 다음 실행될 코드는?

바로 위에서 언급된 "81" 코드인데, 이건 뭔가 좀 이상하지 않나? 뭐! 분명 뭔가 일어나겠지? 아무튼 40467A로 우선 들어가보자.






[그림. 5] 0x40467A 주소 코드


16진수 0x619060EB 값을 406016 주소에 넣고, 그 후에 CALL 404689 라는 주소를 호출하는데 그 주소에는 4084D0 주소의 DWORD 값을 1만큼 증가시키는 명령이 보인다.

(참고로 4084D0은 아까 사용자가 입력한 12345(16진수로 0x3039) 값이 저장되어 있었다.)


아무튼 1 증가 시켰으므로 4084D0 주소의 현재 값은 0x303A 되시겠다.


그 후, 0x40468F 주소의 RETN 명령이 기다리는데, 아까 404684에서 CALL이 한 번 일어났으므로 리턴될 곳은

0X404689 주소이다. (어라? 또 INC ~~~~ 명령이 수행되겠군 -_-)


한 번 더 수행되었으니 4084D0 주소의 현재 값은 0x303B 되시겠고, 또 다시 RETN을 수행하게 된다. (뺑뺑이 돌리넵...)


자 두 번째로 리턴될 주소는 0x404674이다.

0x404674의 주소는 아까 [그림. 3]에서 봤던 코드 "81"이 존재하는 영역이었다.

F7을 눌러서 트레이싱을 한 번 해보자.







[그림. 6] 0x40467E 주소


F7을 눌러서 트레이싱을 해보니 바로 0x40467E 주소로 넘어와 버렸다.

그렇다면 실행된 코드는 404674에서 40467D 의 값들이 하나의 코드로서 실행 되었다는 것인데, 그 사이의 Hex Dump를

코드로 확인해보면 다음과 같이 나온다.


"ADD DWORD PTR DS:[4084D0], 601605C7"


디스어셈블리로 화면에 해석이 제대로 나오지 못했을 뿐, 정작 실행된 코드는 위와 같다.

그러므로 사용자 값이 저장된 위치의 값은 현재 0x60163602 가 되는 샘이다.


근데 여기서 [그림. 6]에 또 다시 수행될 코드를 보면 404684 ~ 40468F 코드가 기다리고 있다.

이 코드 부분은 [그림. 5]에서 봤었던 코드가 아닌가?


즉, 4084D0 주소의 값을 두 번이나 다시 INC 시킬 것이기 때문에 4084D0의 값은 0x60163604가 되겠다.

하지만 아까와 달리 이번에 RETN이 되는 위치는 CALL STACK 상 0x40106A 되시겠다.

(직접 해보시면 앎)


리턴된 주소 40106A에서는 EAX 값을 초기화 시킨 후 404690으로 점프를 시킨다.







[그림. 7] 0x404690 주소


jmp 타고 넘어온 주소의 코드이다. 이 화면은 [그림. 5]에서도 볼 수 있는 화면이다.

0484D0 주소(이 주소는 위에서 계속 언급했던 사용자 값이 저장된 주소)의 값을 DWORD 크기 만큼 EAX에 넣고,

다음 코드에서는 40469F 주소를 PUSH 시킨다.

(EAX에 값을 왜 넣을까? 그 부분은 뒤에 나오게 된다.)


그 후에 CALL 404689 가 나오는데 이렇게 되면 STACK 에는 RETN 시 돌아갈 주소 2개가 연속으로 쌓이게 되는 것을 알아두자.


위 그림에서 트레이싱을 계속 하면 40469A 주소의 CALL 명령에 따라서 또 다시 0x404689의 INC 코드가 실행된 후에

다시 RETN 명령을 만나게 된다.

(이 단계로 인해 4084D0의 값은 0x60163605가 된다.)


그리고 현재 스택 상에는 RETN으로 돌아갈 주소 2개가 연속으로 쌓여있다. (Return to 0x40469F가 2개 연속 쌓여있음)


40469F 주소로 리턴하면  0xC39000C6 값을 40466F 주소에 넣는 명령을 실행하고 그 뒤에 CALL 40466F가 수행이된다.







[그림. 8] 0x40466F 주소의 명령 코드 - 02


40466F는 windbg에서 access violation error가 발생된 위치였는데, 첫 번째 디버깅에서는 분명 문제가 없을 코드였지만

지금은 windbg에서 봤던 그 애러를 발생시키는 코드가 존재하고 있다.

[그림. 7] 에서의 40469F 주소의 코드가 40466F의 코드를 변경시킨 것이고 EAX에 4084D0 주소의 값을 넣는 코드도 있었는데,

그 코드를 주소로 삼아서 0x90 값을 넣으려고 하나 접근할 수 없는 메모리 주소이기에 애러가 발생하게 되는 것이다.


자~ 그렇다면 이 부분을 해결해야 하지 않을까? 제대로 된 주소 참조여야 애러가 터지지 않겠고....

그리고 0x90 값을 왜 넣으려 했을까? 해당 값은 어셈 명령어로는 NOP 명령을 의미한다.

즉 어느 주소의 코드를 NOP로 만들어서 우회시키기 위한 행위일 것 같은데....


만약 EAX에 제대로 된 주소가 지정되어 있다고 가정을 하고 계속 트레이싱을 해보면??

그 주소에는 NOP가 하나 생성될 것이다.


그 후에 40467의 RETN 명령어가 실행이 될텐데, 그림에서 스텍 상태를 보면 리턴할 주소는 4046AE이다.

4046AE의 주소에는 INC EAX 명령이 기다리는데, EAX에 제대로 된 주소가 지정이 되었다고 가정을 한 상태니 1 증가한 다음 주소가 EAX에 지정이 될 것이고, 그 다음 명령어인 CALL 40466F 로 인해서 또 다시 NOP를 입력하는 코드로 넘어간 후에

NOP를 입력시킨 후에 RETN을 또 하게 된다.


CALL 40466F가 방금 4046AF 주소에서 수행되었으니 다음의 RETN 주소는 4046B4가 될 것이다.

(Access violation error가 날 때 마다 그 명령어를 수행하지 않기 위해 New Origin here 기능으로 EIP를 RETN 쪽으로 변경해보자)


아무튼 4046B4 까지 도달하면 40466F의 주소에 0x6E8 값을 넣게 되는데, 그 명령이 수행되면 40466F의 주소는

[그림. 7]에서 봤던 그 주소의 명령어 코드로 다시 바뀌게 된다.


4046BE 코드에서 POP EAX가 실행되어 남아있던 RETURN 주소인 40469F 주소가 스택에서 정리되고

JMP 401071코드로서 해당 영역을 빠져나온다.






[그림. 10] SetDigitemTextA 함수 구간


JMP로 빠져나온 401071 주소를 보면 401084 주소로 점프를 시킨다.

그런데, 점프되는 영역 사이를 보면 SetDigitemTextA 함수가 사용이 되는데, 이 함수는 다이얼로그 박스의 문자열을 세팅해줄 때

사용되는 함수이다.

401073 주소의 명령을 보면 이상하게 적혀있지만 저 주소의 hex dump를 해석하면 PUSH 00406034가 되며, 406034 주소에는

Correct 라는 문자열이 존재한다.

그렇다면 현재 다이얼로그 박스에 표시된 Wrong을 Correct로 변경시켜주는 역할의 함수인 것인데...

저 함수가 실행되기 위해서는 401071 주소의 코드가 실행이 되면 안 되는 것 아닌가?


저 코드를 우회시키면 게임을 끝날 것 같은데, 마침 401071의 코드는 "EB 11" 이다.

2btye 크기의 코드인데, 아까 0x90 값을 두 번 입력하는 행위를 하지 않았던가?


그 두 번의 NOP 값이 401071과 401072 주소에 각각 입력이 되었다면 JMP 코드는 없어지며 SetDigitemTextA가 실행이 될 수 있다는 결론에 도달하게 된다.


정리해보면 access violation error 가 발생했던 40466F 주소의 코드인 "MOV BYTE PTR DS: [EAX], 90" 에서 EAX 레지스터에

401071 주소가 세팅이 되었다면 그 주소의 2byte 코드는 모두 NOP가 되는 것인데, EAX는 사용자가 입력했던 값이 저장되었던

4084D0 주소의 값에서 몇몇 연산을 거친 후에 EAX에 복사되는 것을 확인 했었다.


즉, 사용자 입력 값이 저장된 4084D0 주소에서 처음으로 값을 늘리고 지지고 볶고 건드렸던 몇 개의 연산 과정과

NOP가 실제로 셋팅되어야 할 401071 주소와의 관계를 역으로 계산해보면 사용자가 처음에 입력했어야 할 값이 계산이 될 것이다.


계산은 16진수로 하되 최종 정답은 다시 10진수로 변환해야 한다는 것을 잊지 말자.






[그림. 11] Correct!


성공~~