[Reversing.kr] Twist1

#REVERSING

Reversing.kr의 Twist1 문제풀이


Reversing.krTwist1 문제인데 정말 시간을 많이 썼다. 지금까지 푼 문제 중에서 난이도는 제일 높았다. 난이도가 높은 이유를 나름 분석해보면 우선 자체 패킹이 되어서 OEP를 찾기 까다롭고 안티 디버깅 기법이 여러번 등장하기 때문인 거 같다.

reversing.kr

난이도가 높다는 것은 위 사진처럼 정답자 숫자만 봐도 체감이 된다.


우선 동봉된 메모장을 열어보면 아래와 같은데

Twist1.exe is run in x86 windows.

문제 프로그램은 32비트 환경에서 정상 작동되는 것 같다.

내 메인 컴퓨터는 64비트 환경이라서 32비트를 위해서는 가상머신에 32비트 윈도우를 설치하고 해야되는데 너무 귀찮아서 처음에는 그냥 64비트로 진행했다.

Packing

우선 해당 프로그램은 패킹이 된 상태이다. ExeinfoPE를 열어본 결과 어떤 방식으로 패킹이 되었는지 나오지 않는다. 아마 자체 방식으로 패킹이 되었을 것이다.

패킹이 되었어도 프로그램을 실행하면서 패킹이 풀리고 어느순간 OEP에 진입하는 곳이 분명 나올 것이다. 그러므로 처음엔 디버거를 붙이고 트레이싱을 했다.

Anti-Debugging (pop ss)

우선 디비거를 붙이고 실행하면 아래 pop ss 부분에서 디버거가 멈춘다.

pop_ss

처음에는 뭔가 싶었는데 구글링을 통해서 알아본 결과 안티 디버깅 기법이다.

내가 이해한 것을 정리하면 pop ss스택 세그먼트(ss)pop 하는건데 sspop 하는 도중에 인터럽트가 발생하면 스택에 잘못된 값이 들어갈 수 있어서 다음 명령어가 수행될 때까지 모든 인터럽트의 실행을 막는다.

디버거를 통한 실행은 인터럽트를 동반하므로 디버거를 통한 실행은 pop ss에서 실행이 막히는 것이다.

Bypass of pop ss

분석결과 pop ss는 다음과 같은 순서로 실행된다.

call 407300 -> call 402050 -> 402052 (pop ss)

즉, call 402050 명령어를 ret으로 패치하면 402050 함수가 실행이 안되면서 pop ss 실행을 막을 수 있었다.

Unpacking & OEP

pop ss을 우회하고 트레이싱 하다보면 패킹이 풀리면서 그토록 찾던 OEP로 점프하는 부분이 나온다.

OEP에서 main 함수에 진입하면 아래와 같은 분기문이 보인다. 우리의 목표는 Correct로 분기하는 Flag를 찾으면 되는 것이다. branch

Dump

OEP를 찾았으니 여기서 덤프를 따면 언패킹된 프로그램이 생기면서 이후 분석이 편하다. 이 블로그x32dbg 기본 플로그인 Scylla로 덤프를 뜨는 방법이 잘 설명되어 있다.


Virtual Box Install

여기까지는 64비트 환경에서 무리없이 진행이 되었다. 그런데 64비트에서는 40129B 명령어에서 디버거가 멈춘다. 처음에는 안티 디버깅 기법인줄 알고 어떻게든 해결하려고 했지만 결국 해결하지 못하고 가상머신(virtual box)windows7-32bit를 설치하기로 했다.

virtualbox_win7

windows7에서 실행하니 40129B 명령어에서 디버거가 멈추지 않고 앞으로 쭉쭉 진행이 된다.

Dump Problem

그렇게 모든게 잘 해결된 줄 알고 앞으로 진행하다가 40720D에서 디버거가 또 멈췄다. 덤프 과정에서 문제가 있었다는 것을 알 수 있었다. Scylla로 덤프를 땄을 때 OEPmain 함수 부분은 잘 덤프가 되었지만 40720D 명령어 부분이 덤프가 잘 안됐다.

이 부분을 해결하려고 시간을 정말 많이 쓴 거 같다. 결국 Scylla를 이용한 해결법은 찾지 못했다.

정말 포기할까 생각하다가 문득 생각이 든게 그냥 HxD를 이용해서 HEX값을 모조리 복붙해서 언패킹된 프로그램을 만드는 방법이었다.

그리고 Address of Entry Point 값을 HxDOEP에 맞게 바꿨더니 너무 잘된다.

ZwQueryInformationProcess

트레이싱하다 보면 두 번째 안티디버깅 기법이 등장한다. 아래와 같이 총 세번 호출된다. (함수의 두 번째 인자만 바꿔서 3번 호출된다.) zwQuery_1 첫번째 호출 zwQuery_2 두번째 호출 zwQuery_3 세번째 호출

우회법은 그냥 레지스터 건들면서 분기문 잘 조절하면 쉽게 우회가 된다. 나는 이왕하는거 코드 패치해서 디버거를 붙이고 다시 실행해도 안 멈추도록 만들었다.

여기까지 오면 정말 끝난 것이다. 이 다음부터 그토록 찾던 문자열 비교가 이루어진다.

문자열 비교 & END

문자열 비교는 ROR, ROL, XOR 명령어를 이용한다. JMP가 많이 있어서 한눈에 파악은 힘들지만 그냥 트레이싱 하다보면 쉽게 구해진다.

input[0] ROR 0x6 == 0x49
> input[0] == 0x52 (R)

input[2] XOR 0x77 == 0x35 
> input[2] == 0x42 (B)

input[1] XOR 0x20 == 69
> input[1] == 0x49 (I)

input[3] XOR 21 == 0x64
> input[3] == 0x45 (E)

input[4] XOR 46 == 0x8
> input[4] == 0x4E (N)

input[5] ROL 0x4 == 0x14
> input[5] == 0x41 (A)

위와 같은 순서로 비교를 하는데 그냥 트레이싱 잘 따라가면 무리없이 구해진다. 중간중간 낚시 코드도 있는 거 같았다.

결론적으로 FlagRIBENA이었다. 너무 힘들었다... correct