woonadz :)

[CodeEngn Advance] RCE L01 문제 풀이 및 분석_nabi 본문

IT/codeengn 문제 풀이

[CodeEngn Advance] RCE L01 문제 풀이 및 분석_nabi

C_scorch 2022. 11. 2. 23:17
반응형

CodeEngn Advance RCE L01 문제

 

 

CodeEngn Advance RCE L01 문제풀이

 

문제 속 실행파일 실행
exeinfo에 exe 파일을 올렸을 때

UPX로 패킹되었다는 것을 알 수 있다.

언패킹 후 Ollydbg에 올려 분석해보겠다.

 

L01 파일을 UPX로 언패킹
Ollydbg에 언패킹 된 파일 업로드
00417700 주소 실행 시 뜨는 경고창

이러한 경고창은 안티디버깅 기술이 exe 파일에 포함되어있을 경우 실행된다고 한다.

안티디버깅 기술을 우회하기 위해 해당 주소에서 부르는 함수 안으로 들어가보겠다.

 

IsDebuggerPresent 함수 발견

IsDebuggerPresent() 함수는 안티디버깅 함수이다. 

 


https://learn.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-isdebuggerpresent 

 

IsDebuggerPresent function (debugapi.h) - Win32 apps

Determines whether the calling process is being debugged by a user-mode debugger.

learn.microsoft.com

위 문서를 요약하자면,

BOOL IsDebuggerPresent();

반환값

1 : 현재 프로세스가 디버거 컨텍스트에서 실행 중인 경우

0 : 현재 프로세스가 디버거 컨텍스트에서 실행되고 있지 않은 경우


해당 함수를 실행시키면 1의 값을 반환할 것이라 예상할 수 있고, 이를 우회하기 위해서는 반환값을 0으로 임의조작해주면 될 것이라고 추측할 수 있다.

이제 이를 증명해보겠다.

 

EAX 레지스터의 값이 1로 변환

EAX 레지스터의 값이 1로 변환된 것을 확인할 수 있다.

또한 0040E969 주소의 어셈블리 명령어를 확인하면, TEST EAX,EAX이다.

이는 EAX 레지스터의 값이 0인지 확인하기 위해 사용되므로 안티디버깅 함수의 반환값을 판단하는 역할을 한다.


TEST 명령어

두 피연산자들에 대한 비트 연산(AND)을 수행한다.

AND 연산을 수행하기에 반환값이 항상 참이 된다고 생각할 수 있으나,

피연산자들의 값 중 하나라도 0(값이 들어있지 않다면)이 있다면 거짓을 반환하게 된다.

즉, EAX 레지스터에 0이 있다면 거짓을 반환(ZF를 1로 설정)하고 값이 있다면 참(ZF를 0으로 설정)을 반환한다.


TEST EAX, EAX 연산의 결과(ZF 값)가 0이다

EAX 레지스터에 1이라는 값이 있었기에 ZF 값이 0이 되었다.

이번에 실행할 명령어 JNZ는 ZF 값이 0이라면 점프한다.

계속 실행시켜보겠다.

 

004338DE 주소로 점프된 상태

004338DE 주소로 점프하자 처음에 발견한 경고창을 띄우는 MessageBoxA 함수를 발견할 수 있었다.

전체 흐름을 파악했으므로, 다시 디버깅 함수를 탐지하는 주소로 돌아가 안티디버깅을 우회해보겠다.

 

IsDebuggerPresent 함수의 반환값을 0으로 변경

안티디버깅 기술 자체를 우회한 것은 아니지만, 반환값을 0으로 변경함으로써 전체 프로그램이 디버깅 당하고 있다는 사실을 모르게 변경한다.

 

ZF 값이 1로 변경
jump 하지 않고 실행
실행파일을 실행시켰을 때 메시지를 호출하는 함수

0040EA13 주소에서 실행파일을 실행시켰을 때 메시지를 호출하는 함수를 찾을 수 있었다.

 

timeGetTime 함수는 메시지를 일정시간 띄울 때 사용되기도 한다

문제에서 주어진 밀리세컨드 단위를 통해 timeGetTime 함수가 해당 exe 파일에서 메시지 박스를 일정시간 고정시키는 함수라는 것이라고 추측할 수 있다.

 


https://learn.microsoft.com/en-us/windows/win32/api/timeapi/nf-timeapi-timegettime

 

timeGetTime function (timeapi.h) - Win32 apps

The timeGetTime function retrieves the system time, in milliseconds. The system time is the time elapsed since Windows was started.

learn.microsoft.com

DWORD timeGetTime();

timeGetTime 함수는 시스템 시간(Windows가 시작된 이후 경과된 시간)을 밀리초 단위로 반환함.

(추가적으로, 반환된 값은 DWORD 값이다.)


EAX 레지스터의 timeGetTime 함수의 반환값이 담김
sleep 함수를 호출
timeGetTime 함수를 다시 호출한다.
ESI 레지스터와 EAX 레지스터를 비교

00444C4D 주소의 명령어로 인해 현재 ESI 레지스터에는 처음에 호출한 timeGetTime 함수의 반환값이 담겨있다. 또한 현재 EAX 레지스터에는 두번째로 호출한 timeGetTime 함수의 반환값이 담겨있다. 즉, 첫번째 timeGetTime 함수가 실행되었을 때까지의 windows가 시작된 이후의 경과 시간과 두번째 timeGetTime 함수가 실행되었을 때까지의 windows가 시작된 이후의 경과 시간을 비교하는 것이다.

 

EAX 레지스터의 값이 더 크기 때문에 JNB 명령어 실행
EAX 레지스터의 값이 ESI 레지스터의 값보다 크다

EAX 레지스터의 값에서 ESI 레지스터의 값을 뺀다는 것은 sleep 함수로 인해 스레드가 멈춰있었던 시간을 포함하는 첫번째 timeGetTime 함수 호출과 첫번째 timeGetTime 함수 호출 사이의 시간 간격을 얻을 수 있다.

 

두 인자값을 비교하였을 때, EAX 레지스터의 값이 더 작다
EAX 레지스터의 값이 더 작았기 때문에 점프하지 않고 계속 실행
EBP 레지스터를 호출함으로써 그 값인 sleep 함수가 호출되었다.

 

두 인자를 비교한다.
제로플래그가 0이기에 점프한다.
EDI 레지스터를 호출함으로써 그 값인 timeGetTime 함수가 호출되었다.
EAX와 ESI 레지스터 값을 비교한다.

현재 ESI 레지스터에는 첫번째로 호출한 timeGetTime 함수의 반환값이 들어있다. 또한 현재 EAX 레지스터에는 세번째로 호출한 timeGetTime 함수의 반환값이 들어있다. 

 

EAX 레지스터의 값이 더 크기 때문에 점프한다.

점프 후(앞의 과정과 같은 주소) EAX 레지스터의 값에서 ESI 레지스터의 값을 뺀 EAX 레지스터와 DS:[EBX+4]의 값인 0000337B 값을 비교한다.

EAX 레지스터 값이 더 크기 때문에 점프한다
exe 파일을 윈도우에 띄우는 코드를 확인할 수 있다.
thread 종료 함수

계속 실행시키다 예기치 못하게 프로그램이 종료되는 함수를 부르는 주소로 들어가면 thread 종료 함수를 확인할 수 있다.

 

 

 

이로써 분석을 완료했고, 답을 도출하겠다.


EAX 레지스터의 값에서 ESI 레지스터의 값을 뺀 EAX 레지스터와 DS:[EBX+4]의 값인 0000337B 값을 비교하는 분기문을 통해 window에 실행되고 있는 thread의 실행을 지속적으로 확인하고 특정값(0000337B)을 초과할 때까지 반복해서 timeGetTime() 함수와 sleep 함수를 호출하는 것을 알 수 있었다.

따라서 thread가 0000337B 만큼 수행한 후에 종료된다는 것을 증명할 수 있다.

 

답은 0000337B를 10진수로 변환 후(13179) MD5 해시값(대문자)로 변환해주면 된다.

필자는 파이썬 코드를 통해 해시값 변환 및 대문자 변환을 해주었다.

 

정답이다.

반응형