Frida는 Dynamic Binary Instrumentation(DBI) 프레임워크이며, Windows, mac OS, iOS, Android 애플리케이션을 동적으로 분석할 수 있게 해주는 도구이다. Frida는 함수를 후킹하여 해당 함수의 동작을 분석하거나, 다른 동작을 수행하도록 코드를 덮는 등의 작업을 할 수 있게 해준다. Frida는 크게 두 가지 구성 요소로 이루어진다. 안드로이드 기기 내에 설치 후 열어두어야하는 Frida Server와, Local host에 설치하여 후킹 코드를 전달하고 후킹 결과를 확인할 수 있도록 하기 위한 Frida 클라이언트로 구성된다.

Frida의 상세 사용법 등에 대해서는 아래 링크에 정리해두었다.

 

Tools: Android Frida

Frida Overview

yutaa.notion.site

 

이를 이용하여 분석하고자 했던 악성 애플리케이션 내에 존재하는 환경 탐지 메커니즘을 찾고 우회할 수 있는 스크립트를 작성해보았다. 이 스크립트를 사용하면 에뮬레이터에서의 실행을 방해하는 코드를 우회하여 정상적으로 애플리케이션을 실행할 수 있게 된다.

다음은 이러한 과정을 별도로 정리해두었던 보고서의 내용을 가져온 것이다.


1. Sample APK

  • Sample Hash (SHA256): 82D644A1F3BBA120327E7EB6029F6B986C95C35F0C40CD43001F2DBEDEE2EE6F

 

이번 샘플로 사용한 악성 앱의 경우 MobSF와 같은 악성 애플리케이션 분석 프로그램으로 분석할 경우 악성 행위가 제대로 탐지되지 않는 특징을 가진다. multidex 애플리케이션의 특징을 이용하여 악성 코드를 별도의 파일로 분리하고 암호화 시켜두었기 때문이다. 

그 후 암호화되지 않은 classes.dex 파일을 이용하여 애플리케이션을 실행하고, 실행된 dex를 이용하여 암호화된 dex 파일의 내용을 복호화해 실행한다. 복호화된 dex 파일을 분석할 경우 곧바로 확인할 수 있겠지만 이러한 특징을 가지고 있기 때문에 복호화된 dex 파일의 내용을 획득하지 못하면 실제로 어떤 악성 행위를 행하는지 파악하기 어렵다.

그렇다면 이를 해결하기 위해 실제 모바일 기기에 설치 후 행위 기반의 동적 분석을 진행하면 되지 않느냐라고 말할 수 있다. 그러나 이 샘플을 기기에서 실행할 경우 Root 탐지, Emulator 탐지, ADB 탐지 등 다양한 탐지 메커니즘에 의해 곧바로 프로그램이 종료된다. 그렇기에 동적 분석을 위해서는 이러한 탐지 메커니즘을 우회하는 작업을 선행해야만 한다.

 

2. Frida

탐지 메커니즘을 우회하기 위해 유명한 동적 분석 도구인 Frida를 사용할 것이다. 이를 사용하면 코드 내 특정 함수를 원하는 대로 변경하여 실행 흐름을 조작할 수 있다. 도구의 이러한 특징을 이용하여, 프로그램 내에서 동적 분석 탐지를 위해 사용되는 코드를 확인하고 실행 흐름에 영향을 미치는 핵심 코드를 조작한다면 프로그램이 종료되지 않도록 만들 수 있다.

샘플 APK는 안드로이드 환경에서 실행되는 애플리케이션이기 때문에 Frida에서 제공하는 Java 관련 API을 주로 사용할 것이다. 그 중 Java.use()를 사용하면 현재 로딩된 클래스의 wrapper를 얻을 수 있어 매우 손쉬운 후킹이 가능하다. 여기서 로딩된 클래스에만 접근할 수 있다는 부분이 핵심인데, 이번 샘플의 경우 앱이 실행된 후 복호화 작업이 끝나야 앱의 구성 요소가 로드되기 때문에 처음부터 앱의 구성 요소에 접근할 수 없다. 현재 초점을 두고 있는 탐지 메커니즘 역시 이 암호화된 dex 파일 내에 위치해있다. 따라서 어느 시점에 복호화 작업이 끝나는 지를 파악하여 이 시점을 먼저 가로챈 다음, 후킹하고자 하는 탐지 메커니즘에 접근하는 방식을 사용할 것이다. -> 보고서 작성 당시 이렇게 생각하고 3번의 과정을 분석하였는데, 막상 분석하고 난 후 frida 스크립트를 작성하려고 하니 jadx 상에서 보이는 클래스나 메서드의 이름이 jadx에서 임의로 붙여둔 이름이라 이를 명확히 가리켜 해당되는 wrapper를 가져올 수 없었다. 따라서 범용적으로 사용되는 Java나 android 라이브러리가 detection code에서 사용되는 시점을 후킹하여 원하는 작업을 하도록 하였다. (이 부분에 대한 분석은 이어지는 4번에서 확인할 수 있다.)

 

3. Detailed Analysis: Decryption Process

프로그램이 시작되는 파일을 찾기 위해 AndroidManifest.xml 파일을 확인하면, application 태그의 name 속성으로 com.lzsEsq.dykSgp.jhvqZx.pupsPVlBod라는 이름의 클래스가 적혀있는 것을 확인할 수 있다.

이를 통해 com.lzsEsq.dykSgp.jhvqZx.pupsPVlBod 클래스부터 분석을 시작하면 될 것이라는 것을 알 수 있다. 이 파일에서 한 가지 더 확인할 수 있는 부분은 entry 클래스가 속한 패키지명은 com.lzsEsq.dykSgp.jhvqZx 인데, AndroidManifest.xml 파일에 기록된 패키지명은 com.ldjSxw.heBbQd 로 차이를 보인다는 것이다.

암호화되어있지 않은 classes.dex의 소스 코드는 jadx를 이용하여 곧바로 확인할 수 있다. 파일 내용을 확인하면 앞서 AndroidManifest.xml 파일에서 보았던 entry 클래스 pupsPVlBod를 찾을 수 있다.

이 클래스는 메세지 수신을 위한 handler를 가진다.

본격적인 분석 시작을 위해 이 클래스의 onCreate 메서드를 확인하였다.

onCreate 메서드에서는 3개의 서로 다른 함수를 호출한다. 분석 결과, 이 중 첫 번째로 호출되는 함수 m19a가 주된 실행을 담당하였다.

m19a 함수는 base context와 com.ldjSxw.heBbQd.MainApplication 클래스에 대한 인스턴스를 인자로 하는 attach 메서드를 호출한다. 이로 인해 본 클래스 내에 정의된 attachBaseContext 메서드가 호출된다. 그리고 이 attachBaseContext 메서드에서 대부분의 작업이 진행된다.

attachBaseContext 메서드는 크게 다음과 같은 작업을 수행한다.

  • 복호화된 파일이 저장될 폴더 초기화
  • 암호화된 파일 복호화
  • 신규 폴더로 apk 파일 복사

이 과정에서 신규로 저장될 파일들은 com.ldjSxw.heBbQd.MainApplication_{appVersion}/app 폴더에 위치하게 될 것임을 알 수 있으며, 실제 디바이스 내에서 확인했을 때에도 /data/data/com.ldjSxw.heBbQd.MainApplication_null/app 폴더 내에 위치하였다.

위 과정에서 dexDir 경로가 존재하지 않을 경우 m9k 함수를 실행시키는데, 이 과정에서 암호화된 dex 파일에 대한 복호화 과정을 확인할 수 있다.

m9k 함수에서 주로 수행하는 작업은 다음과 같다.

  1. 새롭게 애플리케이션 파일이 위치할 경로 내에 있는 폴더 및 파일들을 모두 삭제한다.
    프로그램이 실행될 때 실제로 사용될 경로이기 때문에 다른 파일의 간섭을 최소화하기 위한 조치로 보인다.
  2. 인증서 관련 파일을 제외한 나머지 파일들 중, classes.dex 파일이 아닌 dex 파일을 복호화하여 신규 위치에 저장한다. (m10j method)
  3. classes.dex 파일과 인증서 관련 파일이 아닌 나머지 파일을 신규 위치에 저장한다.

여기서 복호화에 사용되는 m10j 메서드를 따라가면, pupsPVlBod 클래스와 함께 존재하던 ymcBssEDD 클래스의 decrypt 메서드가 사용되는 것을 확인할 수 있다.

decrypt 함수의 세부 구조는 libset.so 파일에 native 라이브러리로 구현되어 있어 별도의 분석이 필요하다. 이 라이브러리를 분석할 경우 복호화 키를 얻을 수 있는데, 이 부분은 2.3.1 파트에서 다뤘기 때문에 넘어간다.

이러한 일련의 과정으로 암호화된 dex 파일을 복호화하고 apk 파일 내 나머지 파일들을 신규 위치로 이동하여 실행할 준비를 마치게 된다.

다시 m19a 메서드로 돌아오면, 앞서 정리한 과정을 수행하고 기타 Field의 값을 획득한 후 마지막에 f1302m으로 빈 메시지를 전송한다. f1302m은 분석 초기에 확인하고 넘어갔던 메세지 수신 handler이다.

f1302m으로 메시지를 전송하면 정의해둔 handleMessage 메서드가 실행된다. 이 코드에서는 handleMessage 메서드를 다음과 같이 정의하여 메세지를 수신할 경우 f1304oonCreate 메서드가 호출되도록 하였다. f1304o은 m19a 메서드의 초반에서 정의한 대로 com.ldjSxw.heBbQd.MainApplication 의 클래스 인스턴스를 가진다. 결과적으로 m19a 메서드에서 메세지를 보내는 행위는 com.ldjSxw.heBbQd.MainApplication 클래스가 실행되게 만드는 결과를 낳는다.

앞서 AndroidManifest.xml 파일을 확인하며 entry 클래스의 패키지명과 본 apk 파일의 패키지명이 달랐던 것을 기억하는가? 그 때 확인하였던 apk 파일의 패키지명은 메세지를 보내 실행되도록 만드는 클래스의 패키지명과 일치하며, 이러한 점들을 미루어 생각했을때 본격적으로 앱의 구성 요소가 로드될 것임을 짐작할 수 있다. 따라서 MainApplication으로 메세지를 보내는 sendEmptyMessage 함수가 호출되는 시점이 dex 파일의 복호화가 완료된 지점일 것이다.

 

4. Detailed Analysis: Detection Process

AndroidManifest.xml 파일 내 기재된 바에 의하면 샘플 APK는 IntroActivity에서 시작한다.

이 IntroActivity는 BaseActivity를 상속받는데, 이러한 이유로 프로그램이 시작하면 BaseActivity의 onCreate 메서드가 실행되고, 그 후에 IntroActivity의 onCreate 메서드가 실행된다.

아래는 BaseActivity의 onCreate 메서드이다. 이 메서드는 실행되자마자 m47k 메서드를 실행하고 그 결과값에 따라 프로그램을 종료시킨다. 종료 시 반환하는 문자열로 Rooted가 사용되는 것을 통해 루트 탐지 부분일 것으로 생각할 수 있다. 실제로 앱을 에뮬레이터 상에서 실행하였을 때 루트 상태의 에뮬레이터를 사용할 경우 곧바로 종료된다.

이에 따라 탐지 우회 방안을 찾기 위해 m47k 메서드에서 사용되는 각 함수를 하나씩 분석하였다.

Locale Detection: 시스템 언어 설정을 확인하여 한국어를 사용하는 기기에서만 앱이 실행되도록 하는 코드이다. 이를 통해 한국어 사용자를 대상으로 악성 행위를 수행하도록 하고 있음을 알 수 있다.

 

Root Detection: 다음의 두 함수를 이용하여 루팅된 기기에서 실행되고 있는지 확인한다. 이를 위해 루팅된 기기에서 주로 발견되는 Build Tag 내 test-keys 키워드가 포함되어있거나, Superuser.apk 패키지가 설치되었는지 여부를 확인하고 있다.

 

Emulator Detection: 에뮬레이터 상에서 앱이 실행되고 있는지를 확인하기 위해 앱이 실행중인 기기 내 /proc/tty/drivers 파일 내용을 확인한다. 그 중에서도 QEMU 기반의 Android 가상 환경에서 사용되는 goldfish kernel을 사용하는 에뮬레이터를 대상으로 탐지하고 있다.

 

ADB Detection: Android의 Secure system setting 내 속성 중 adb_enabled 속성값을 확인하여 adb 사용중인지에 대해 확인한다.

 

VPN Detection: 에뮬레이터에서 사용 중인 네트워크 인터페이스 중 tun0이나 ppp0과 같은 VPN에서 주로 사용되는 인터페이스명이 존재하는지 확인한다.

 

HTTP Proxy Detection: Proxy를 사용할 경우 http.proxyHost와 http.proxyPort 속성에 각각 proxy 설정값이 부여된다. 그렇기 때문에 이 값이 설정되었는지 여부를 이용하여 proxy 사용 여부를 확인한다.

 

5. Detection Bypass

앞서 확인한 각 탐지 메커니즘에 대해 다음과 같은 Frida 스크립트를 작성하였다. Frida에서 API로 지원하는 언어 중 Javascript를 사용하였으며, 후킹하고자 하는 대상인 Android 앱이 Java를 기반으로 작성된 프로그램이기 때문에 Frida API 중 Java Instrumentation을 주로 사용하였다.

스크립트 작성 시 decompile 특성 상, 프로그램 작성자에 의해 명명된 함수의 원본 함수명을 찾는데 어려움이 있다. 따라서 주로 android나 java 라이브러리에 정의된 함수의 이름을 이용하여 이를 후킹하는 방식을 사용하였다.

 

Locale Detection: getLanguage 함수는 String 타입으로 현재 시스템에 설정된 언어를 반환한다. 그렇기 때문에 이를 hooking하여 어떤 시스템에서도 한글 설정이 되어 있는 것과 같이 인식하도록 반환값을 “ko”로 고정되게 만들었다.

 

Root Detection 1: java 라이브러리 method인 contains의 반환값을 hooking하여 탐지하고자 하는 값이 포함되지 않은 것으로 처리되도록 하였다. 이 때 루트 탐지에 사용된 값이 test-keys이므로, 이 값으로 비교할 경우에만 우회 코드가 동작하도록 하기 위한 조건을 함께 설정하였다.

 

Root Detection 2: Superuser.apk 가 존재하는지를 검사하기 위해 File 클래스의 생성자를 이용하여 검사할 경로의 파일을 불러온다. 이를 특징으로 삼아, /system/app/Superuser.apk 경로를 File 생성자의 인자로 넘기며 호출하는 순간을 hooking하였다. 여기서는 탐지할 경로를 없을 만한 파일의 경로로 설정하여, File 클래스가 존재하지 않는 파일의 경로를 조사하도록 하였다.

 

Emulator Detection: indexOf method 사용 중 비교 대상 문자열로 goldfish를 사용하는 경우에만 해당 문자열이 포함되지 않았음을 나타내는 결과인 -1을 반환하도록 하였다.

 

ADB Detection: Settings.Secure 클래스 내 getInt 메서드를 사용하여 adb_enabled 속성값을 조회하는 경우는 탐지를 위한 과정 중 사용하는 경우가 유일할 것으로 생각되어, adb_enabled 속성값 조회 시 항상 0을 반환값으로 가지도록 하였다.

 

VPN Detection: tun0ppp0 문자열과 비교하는 경우가 흔히 존재하지 않을 것으로 생각되어, 이를 다른 문자열과 비교하기 위해 equals 메서드의 인자로 사용하는 경우를 후킹하여 일치하지 않음을 나타내는 false를 반환값으로 가지도록 하였다.

 

Proxy Detection: System.getProperty 메서드를 이용해 http.proxyHosthttp.proxyPort 속성값을 조회하는 경우를 후킹하여 두 경우에 대해 null을 반환값으로 가지도록 하였다. 이를 통해 실제 proxy를 사용중이더라도 정상적인 속성값을 조회하지 못해 proxy가 사용중인지 알 수 없도록 하였다.

 

'Reversing' 카테고리의 다른 글

Packer 개념 및 UPX unpacking 실습  (0) 2023.08.06
PE File Format, IAT&EAT 정리  (0) 2023.08.04
 

Inject ME!!!

Description 드림이가 수상한 DLL 파일을 획득하였습니다. DLL 파일과 함께 있던 TXT 파일에는 조건을 맞춰서 DLL을 로드시키면 플래그를 얻을 수 있다고만 쓰여 있었습니다. 어떻게 해야 DLL 파일을 로

dreamhack.io

 

문제 파일로 dll 파일 하나만 달랑 주어진다.

문제 설명을 읽어보면 DLL 파일을 조건에 맞춰서 로드시켜야한다고 하는데… 조건이 무엇인지는 안알려준다.

조건을 어떻게 찾아야할지 감을 못잡고 있다가 무작정 hex editor를 켜서 flag 문자열에 대해 검색해보았다.

그랬더니 아래와 같이 flag 문자열 주변에 dreamhack.exe라는 문자열이 있는 것을 발견하였다.

 

DLL은 자체적으로 로드되기보단 실행 파일에 기생(?)하여 실행되어야 할 것인데, Windows에서 사용되는 대표적인 실행 파일은 exe 확장자를 가진 파일이다.

그런데 마침 dreamhack.exe 라는 .exe 확장자를 가진 문자열을 발견하였고, dreamhack.exe 파일이 뭔지는 모르겠지만 일단 주어진 DLL을 로드하는 코드를 다음과 같이 만들었다.

#include "stdio.h"
#include "windows.h"
#include "conio.h"

void main() {
	HMODULE			hDll = NULL;

	hDll = LoadLibraryA("prob_rev.dll");
	if (hDll == NULL) {
		printf("Failed to load dll");
		return;
	}

	printf("Success to load library");
	while (_getch() != 'q');

	FreeLibrary(hDll);
}

 

여기서 dreamhack.exe 라는 문자열을 활용할 수 있는 방안은 실행할 exe 파일의 이름을 동일하게 맞춰주는 것이라고 생각되었기에, 이 코드를 빌드한 결과로 얻은 exe 파일의 이름을 dreamhack으로 변경한 후 실행하였다.

이걸 운이 좋았다고 표현하는게 맞는건지… 바로 flag를 얻을 수 있었다.

 


이렇게 풀어놓고 안찝찝할 수가 없다. 그래서 다른 풀이자분들의 풀이를 보았고, x64dbg를 이용해서 직접 dll의 행위를 리버싱할 수 있는 것을 알았다. 그렇기에 나도 직접 디버깅을 해보았다.

먼저 확인 가능한 문자열을 확인해보았고 다음과 같은 결과를 얻었다.

여기서도 dreamhack.exe는 매우 수상한 문자열로 느껴지므로 이것이 사용되는 위치로 이동해서 분석을 시작하였다.

 

이 부근을 디버깅해보면 dreamhack.exe 문자열이 strncmp 함수에 의해 rcx에 저장된 어떤 문자열과 비교되는 것을 알 수 있다.
그리고 문자열 비교 전 File명을 가져오는 함수가 사용되는데, 이 때 사용되는 파일의 경로가...

주어진 dll을 로드하고 있는 파일의 경로이다.

 

이를 계속 실행시켜보면 결국 현재 dll을 로드하고 있는 파일의 이름과 dreamhack.exe 문자열을 비교하고 있다.

 

따라서 실제로 파일명을 가져오는 함수의 결과를 담는 rax의 값을 dreamhack.exe로 바꿔주면 이 부분을 정상적으로 통과해 flag를 얻을 수 있다.

 

 

rev-basic-8

Reversing Basic Challenge #8 이 문제는 사용자에게 문자열 입력을 받아 정해진 방법으로 입력값을 검증하여 correct 또는 wrong을 출력하는 프로그램이 주어집니다. 해당 바이너리를 분석하여 correct를 출

dreamhack.io

 

이 문제도 여타 다른 문제와 마찬가지로 입력값을 넣고 일련의 연산을 취한다음 이것이 특정한 값과 일치하는지를 비교하여 Correct / Wrong을 출력하는 문제이다. 역시나 Correct가 나올 수 있는 입력값을 찾아야 한다.

아래는 프로그램의 main 코드이다. 이중 일부 부분의 직접 변수명이나 타입을 바꾸어준 상태이다.

보면 uVar1의 값을 가지고 Correct / Wrong 결과를 가르는데 이 값을 결정짓는게 FUN_140001000 함수이다. 이 함수의 인자로 입력값이 들어가는걸 보면 이 함수가 핵심인게 분명하다.

 

아래 코드는 FUN_140001000 함수의 decompile 결과이며, 미리 각 변수의 이름을 해석하기 편리하도록 고쳐둔 상태이다.

코드를 보면 20개의 문자를 하나씩 비교하는데, 이 때 입력값에 대해 일련의 연산을 취한 후 이 값이 문제 속 데이터와 일치하는지 검사한다.

 

DAT_140003000 위치를 가보면 정확히 20개의 16진수 데이터가 위치해있음을 알 수 있다.

그러나 위 코드에서 입력값에 -5가 곱해지는 것도 그렇고 좀 더 자세한 분석이 필요할 것으로 보여 어셈 코드로 다시 살펴보았다.

 

입력값에 대한 연산은 140001031과 140001037 이렇게 두 부분이 핵심이다.

  • 14000102d: 입력값에 대해 byte 단위로 가져온 후
  • 140001031: 이에 대해 FB를 곱하고
  • 140001037: 그 결과에서 하위 1바이트만 남기고 지운다.

여기서 연산 결과에 대해 하위 1바이트만 남기고 지우기 때문에, 곱셈 결과가 어떻게 나오든 문제 속 하드코딩되어 있는 각각의 바이트와 하나씩 비교가 가능한 것으로 보인다.

 

이 문제를 풀기 위해 처음에는 곱셈의 반대인 나눗셈으로 시도해보려고 했지만, 아무래도 AND 연산으로 하위 바이트를 자르고 비교하는 형태이기 때문에 역연산으로 정답인 입력값을 찾기 어려울 것 같았다. 그래서 결국 가능한 입력 범위 안에서 브루트포스 코드를 작성하였다.

answer = [0xAC, 0xF3, 0x0C, 0x25, 0xA3, 0x10, 0xB7, 0x25, 0x16, 0xC6, 0xB7, 0xBC, 0x07, 0x25, 0x02, 0xD5, 0xC6, 0x11, 0x07, 0xC5]
input_data = []

for i in range(20):
	for num in range(0x20, 0x7E):
		if (num * -5) & 0xFF == answer[i]:
			print(chr(num), end='')
			break

 

이를 실행하면 플래그를 얻을 수 있으며, 뜨끔하는 문장을 얻을 수 있다.ㅋㅋ

 

 

patch

flag를 그리는 루틴을 분석하고 가려진 flag를 보이게 해주세요. Reference GDI+ - Win32 apps | Microsoft Docs Graphics Functions - Win32 apps | Microsoft Docs File — x64dbg documentation

dreamhack.io

 

이 문제는 실행하면 아래와 같이 딱 중요한 부분만을 가려놓은 윈도우 GUI 프로그램을 제공한다.

 

문제 설명을 토대로 유추하면.. Graphic 함수를 이용하여 flag를 그리는데, 이 루틴 중 flag를 가리는 부분을 패치하여 플래그를 얻어내는 문제인 것으로 추정된다.

IDA를 통해 문제 파일을 열어보면 Windows GUI 프로그램 국룰 시작 지점인 WinMain 함수를 확인할 수 있다. 그런데 내 IDA는 서버와의 오류 때문에 hex-ray가 되지 않으니 IDA가 찾아준 WinMain의 위치를 이용하여 Ghidra에서 WinMain의 hex-ray 결과를 볼 것이다.

 

아래는 Ghidra에서 분석해준 WinMain 함수의 hex-ray 결과이다.

 

MSDN의 API 문서에서 검색하면 알 수 있겠지만..

32줄의 CreateWindowExW 함수를 이용하여 윈도우 창을 실행하고,

이에 앞서 30줄의 RegisterClassExW 함수를 이용하여 CreateWindowEx 함수에서 사용할 창 클래스를 등록한다. 그렇기에 RegisterClassExW의 매개인자로 들어가는 WNDCLASSEXW 타입의 local_a8 변수의 속성을 18~29줄에서 정의하고 있는 것을 알 수 있다.

처음에는 이렇게 창 클래스 정의하는 부분은 단순 속성이라 생각하고 CreateWindowExW 함수를 이용하여 창을 생성하고 난 이후 if 문 안에서 이루어지는 작업이 그래픽 작업을 하는 부분인줄 알고 헤맸던 것 같다. 그런데 원하는 그래픽 작업 흔적이 명확하게 발견되지 않아 찾는 방식을 바꿨다.

Ghidra의 Symbol Tree 부분을 보면 GDIPLUS.DLL 이라고 그래픽 작업 관련 함수를 포함하는 DLL를 발견할 수 있다. 본 프로그램에서 사용되는 dll과 함수라고 생각하고 이 부분이 참조되고 있는 부분을 찾아나갔다.

 

다 사용되는 것 같긴 했지만, 이 중 GdipDrawLineI 함수에 대한 Reference 내역을 먼저 조회했다.

많은 결과가 나오지만 이 중 가장 첫 번째 항목을 따라갔다.

 

추적 결과 FUN_140001240 함수에서 사용되고 있었으며,

 

이 부분의 Call Tree를 따라가보면

 

다음과 같이 윈도우 창 클래스를 정의하던 부분의 lpfnWndProc 속성에 정의되는 FUN_1400032f0 함수를 통해 Pen 작업이 이루어지고 있음을 알 수 있다.

WNDCLASSEXW클래스의 lpfnWndProc 속성의 경우 창 클래스에 대한 프로시저 포인터를 가진다. 여기에 들어가는 함수는 콜백 함수 형태를 가지며, 윈도우 창에서 사용자에 의한 상호작용이 발생할 경우 이를 어떻게 처리할 것인지를 정의한다. 그렇기 때문에 이 부분에서 창에 보여질 행위 중 하나인 draw 작업이 이루어진 것으로 생각한다.

 

FUN_1400032f0 함수 내부를 살펴보면

BeginPaint 함수와 EndPaint 함수가 있으며, 이 사이에서 GdipAlloc 후 어떤 작업을 수행하고 GdipFree까지 하고 있다. 그 과정에서 FUN_140002c40 함수가 호출되는데, 이 함수의 내부를 살펴보면

 

다음과 같이 반복적으로 특정 함수가 호출하는 작업이 이루어짐을 확인할 수 있다.

아래 코드를 보면 초반에는 FUN_140002b80 함수가 반복적으로 사용되고 있고, 그 이후에 서로 다른 함수가 호출되고 있다.

 

우선 FUN_140002b80 함수의 역할에 대해 보기 위해 내부를 분석했다.

이 함수에서는 GdipCreatePen1 함수 호출 이후 GdipDrawLineI 함수를 이용하여 한번의 직선을 그리고 GdipDeletePen 함수를 이용하여 Pen 작업을 끝낸다.

즉, FUN_140002b80 함수가 한번 실행될 때마다 이 param으로 들어온 값에 따라 한 개의 직선을 그리고 있는 것으로 생각된다.

 

FUN_140002b80 함수 이후에 차례로 나타나는 여러 함수들의 경우는 조금 다른 코드 패턴을 가진다.

그 중 FUN_140002b80 함수 이후로 가장 처음 나타나는 FUN_1400017a0 함수는 다음과 같이 여러 차례의 GdipCreatePen1 - GdipDrawLineI - GdipDeletePen 과정을 가지며, 여러 선을 그리고 있는 것으로 추정된다.

FUN_1400017a0 함수 이후에 나타나는 여러 함수들도 FUN_1400017a0 함수와 같이 여러 개의 선을 그리는 코드를 가진다.

 

따라서 FUN_140002b80 함수와 그 이후에 위치하는 FUN_1400017a0 함수와 같은 함수들이 각각 어떤 선을 그리는지 보기 위해 다시 IDA를 이용하여 해당 위치를 동적 분석하였다.

FUN_140002b80 함수와 FUN_1400017a0 함수에 각각 bp를 걸고 실행시켰다.

 

먼저 처음 호출된 FUN_140002b80 함수의 경우 아래와 같은 선을 그린다.

그리고 그 다음에 호출된 FUN_140002b80 함수 역시 다음과 같은 선을 그린다.

여러 차례 진행되는 FUN_140002b80 함수를 모두 호출하고 나면 다음과 같은 선들이 그려진다.

이 선들이 그려진 곳은 분명 플래그를 가리는 위치이다. 따라서 FUN_140002b80 함수는 플래그를 가릴 때 사용하는 함수로 여길 수 있다.

그리고 그 이후에 bp를 걸었던 FUN_1400017a0 함수를 실행하면 다음과 같이 플래그의 첫글자가 그려진 것을 확인할 수 있다.

이후의 함수들을 모두 호출하면 다음과 같이 모든 플래그가 그려지게 되는 것을 알 수 있다. (비록 가려져 있긴 하지만…)

이를 통해 플래그를 가리는 함수인 FUN_140002b80 함수가 실행되지 않도록 막으면 정상적으로 플래그를 확인할 수 있을 것임을 알았다.

 

패치를 하기 위해 다음과 같은 방안을 생각해보았다.

  1. FUN_140002b80 함수 시작 위치 어셈을 ret로 패치
  2. FUN_140002b80 함수를 call 하는 위치 어셈을 nop으로 패치

IDA로 패치하려니 계속 권한 오류가 떠서 x64dbg로 패치했다.
x64dbg에서 패치하려고 하는 주소로 이동한 후 고안한 방법대로 패치를 진행한다.

 

1. FUN_140002b80 함수 시작 위치 어셈을 ret로 패치

→ 잘 된다.

 

2. FUN_140002b80 함수를 call 하는 위치 어셈을 nop으로 패치

→ 잘된다.

 

사실 두 방법 모두 FUN_140002b80 함수의 실행을 방해한다는 측면에서 같은 방식이긴 하다.

그러나 두번째 방법을 사용할 경우 FUN_140002b80 함수가 실행되는 모든 곳에 대해 패치를 해야하니 더 번거롭기 때문에 굳이 이렇게 할거면 첫 번째 방법이 좀 더 나은 선택지인 것 같다는 생각을 했다.

이렇게 패치를 해도 괜찮은 이유는 다음과 같다.

  1. 본 프로그램이 레지스터를 이용하여 인자를 전달하는 x64 프로그램이고, FUN_140002b80 함수 역시 함수 내에서 스택을 정리하는 fastcall 호출 규약을 사용한다. 그렇기 때문에 함수 실행과 관련하여 스택을 직접 손봐줘야할 필요가 없다.
    다만 함수 내에서 ret를 이용하여 함수를 종료할 경우 함수 내에서 스택을 사용하기 전에 ret를 넣어줘야 가장 최근에 스택에 넣었던 ret 값을 그대로 다시 가져와 원래 위치로 돌아갈 수 있을 것이다.
  2. FUN_140002b80 함수의 리턴값이 코드의 다른 부분에 사용되지 않기 때문에, 별도로 리턴값을 관리해줄 필요 없이 함수 실행만 막으면 된다.

 

1번과 관련하여 잘못 패치를 하는 상황을 재현해보았다.

여기에서는 FUN_140002b80 함수의 프롤로그 이후 사용할 스택 메모리를 확보한 상황에서 스택 정리 없이 곧바로 ret를 넣은 경우이다.

FUN_140002b80 함수의 프롤로그 실행 시 ret 값에 의하면 본 함수 종료 후 rip는 7FF63BC22C71 값을 가지며 함수를 call 했던 코드의 다음 코드로 돌아가야 한다.

그러나 지금과 같이 스택 프레임 확보를 위해 rsp값을 조정해주고 난 상황에서 ret를 이용하여 곧바로 pop rip를 수행하게 될 경우 dummy 값이 rip로 들어가 이후 정상적인 프로그램 실행에 문제를 일으키게 된다.

 

패커에 대해 공부하는 과정에서 정리한 자료입니다.

자료에는 아래 내용이 기록되어 있습니다.

 

  • 데이터 압축
    • 압축 기법의 유형
    • 패커의 유형

 

  • UPX packer 실습
    • Packing
    • Unpacking
      • OEP vs. EP
      • 일반적인 실행 압축 해제 매커니즘
      • Tracing
      • UPX 패커의 특징

 

 

 

실행 압축(Packer)

데이터 압축

yutaa.notion.site

 

 

리버싱을 공부하는 과정에서 개념 정리하고, 실습한 내용 공유합니다.

아래 내용이 기록되어 있습니다.

  • PE File format
  • RVA to RAW
  • IAT, EAT

 

 

PE File Format(+ IAT, EAT)

기본 개념

yutaa.notion.site

 

+ Recent posts