본문 바로가기
CS & Reversing

[book] 리버스 엔지니어링 기드라 실전 가이드: Chapter 3

by m_.9m 2023. 7. 24.

3. downloader.exe


downloader.exe는 윈도우 실행 형식인 PE 포맷의 32비트 프로그램이다. 외부에서 다른 프로그램을 다운로드해 실행하는 간단한 다운로더이다.

3.1 분석 접근법


3.1.1 main 함수 분석

main 함수 분석의 장점은 대상을 포괄적으로 분석하고 이해할 수 있다는 점이다. 그러나 특정 처리만을 분석하고 싶거나 용량이 큰 프로그램에는 적합하지 않다. 프로그램 작성 시는 main 함수부터 시작하지만 프로그램 실행 시는 main 함수 앞에 초기화 처리 등이 진행된다 보통 PE 포맷 파일의 실행 파일은 PE 헤더 내의 AddressOfEntryPoint의 RVA에 ImageBase를 가산한 주소에서 실행된다. 기드라에서 엔트리 포인트의 심벌 명은 Entry이다.


AddressOfEntryPoint
파일이 메모리에 매핑된 후 코드 시작 주소이다. ImageBase값에 이 값을 더해 코드 시작 지점을 설정한다.
ImageBase
 PE 파일이 로딩되는 시작 주소이며 EXE, DLL 파일은 user memory 영역에 위치하며 SYS 파일은 Kernel memory 영역에 위치한다. 일반적으로 EXE 파일은 0x400000 DLL 파일은 0x1000000 값을 가진다.

PE 파일은 Offset으로 파일의 첫바이트로부터 얼마나 떨어져있는지 표시되고 메모리에 적재될 때는 VA로 위치를 표현한다. RVA(Relative Vitual Address)는 기준(ImageBase)로부터의 상대주소를 뜻한다. VA는 RVA+ImageBase로 나타낼 수 있다.


main 함수를 찾기 위해 첫번째 인수에 ImageBase를 기반으로 함수를 찾아본다. x86의 PUSH 명령 오퍼 코드는 0x68이며 EXE 파일이기에 ImageBase는 기본적으로 0x4000000이다. x86의 CALL 명령은 0xe8부터 시작되기 때문에 주소는 68 00 00 40 00 e8이다. [Search] → [Memory]에서 패턴을 검색한다.

wikipedia opcode 정리된 표: x86 instruction listings

 

Opcode가 여러 개인 이유
Opcode는 1개만 존재할 수도 있고 여러 개가 존재할 수도 있는데 ISA 마다 다르다. ISA(Instruction Set Architecture)는 일종의 CPU 설계도로 하드웨어와 소프트웨어 사이의 Interface를 정의하는 역할을 한다. 명령을 어디에 저장할지, 몇 개, 어떻게 사용할지, 어떤 메모리와 호환이 되는지 등 CPU마다 하나의 명령어 집합체를 만든다.

결과 값 중 Function name이 entry인 것이 main 함수이다. main 참수의 호출에는 몇 가지 패턴이 있어 이를 기억하면 쉽게 찾을 수 있다. main 함수를 찾으려면 윈도우 초기화 처리를 이해하고 윈도우 프로그램에 대한 이해가 필요하다.

→ 여러가지 까보는 경험이 필요함, 패턴을 익히는 것이 중요 / 심볼이 메인인지 등 살피는 연습이 필요

 

해당 함수가 있는 부분을 들어간다. 이후 Decompile 창 Edit Function Signature에서 함수 이름을 main으로 수정한다.

 

3.1.2 import 함수 분석

import 함수의 장점은 특정 처리에 초점을 두고 분석할 수 있다는 점이다. 하지만 라이브러리 함수를 사용하거나 멀웨어가 패킹되어 있고 윈도우 API를 호출하기도 하는 등 임포트 함수만이 전부는 아니다. 예시 파일에 있는 윈도우 API URLDownloadToFileA은 지정한 URL에서 파일을 다운로드해 지정한 파일 명으로 보존하는 함수이다. API의 동작 및 인수는 마이크로소프트 독스에서 확인 가능하며 URLDownloadToFileA에서 중요 인자는 다운로드 대상 URL 포인터를 지정하는 두 번째 인수인 szURL과 저장할 파일 명이나 경로에 포인터를 지정하는 세 번째 인수인 szFileName이다.


Function URLDownloadToFile
HRESULT URLDownloadToFile(
LPUNKNOWN pCaller, LPCTSTR szURL, LPCTSTR szFileName, Reserved DWORD dwReserved, LPBINDSTATUSCALLBACK lpfnCB
);

 


참조원을 확인하고 싶은 경우 심벌 명을 클릭하거나 아래와 같이 조회한다.

 

참조로 이동하면 다음과 같이 함수 이름이 URLDownloadToFileA이고 임포트 된 URL URLDownloadToFileA 함수의 주소를 호출하고 반환하는 함수임을 알 수 있다.

해당 함수는 URLDownloadToFileA 함수의 래퍼 함수이기 때문에 함수를 타고 올라가 FUN_004010e0 함수를 분석한다. FUN_004010e0로 호출된 윈도우 API에 URLDownloadToFileA가 있다.

PathFileExistsA 함수를 살펴보면 지정한 경로에 파일이 존재하는지 확인하는 함수로 존재하는 경우

TRUE로 반환되는 것을 확인한다. 함수의 주소를 확인한 파일의 경로 param_2는 URLDownloadToFileA 의 제 3인수 szFileName으로 지정되며 다운로드 받은 파일의 저장소가 되어있다.

FUN_004010e0는 지정된 파일이 있는지 확인하고 없을 경우 파일을 다운로드하는 로직이다. FUN_004010e0의 이름을 download_file로 변경한다.

3.1.3 문자열 분석

문자열 분석의 장점은 컴파일로 인해 손실되지 않는 정보를 활용하여 프로그램 기능을 추정할 수 있다는 점이다. 예로 작성하는 레지스트리 Key나 통신처의 URL, 실행하는 명령어 등이 하드 코딩 되어 있으면 분석이 용이하다. 그러나 자동 분석이 안되는 경우 일반적 문자열로 정의되지 않을 수 있다. Defined String 창에서 문자열을 확인 가능하다.

3.1.4 함수 인수 분석

주소 0x00418000에는 hello.exe 포인터 4바이트 후에 URL과 같은 문자열의 포인터, 또 4바이트 후에 수치(0x41)가 정의되었다 그 후 주소에서도 같은 데이터 구조가 존재하는 걸로 보아선 C 언어 구조체로 생각되어질 수 있다.

디스어셈블한 코드나 디컴파일한 코드는 기계어에 비해 읽기 쉽지만 값이 무엇을 가르키고 있는지 알기 어려운 경우가 많다. 구조체라고 생각되는 영역을 발견하면 기드라에서 구조체 정의를 수행한다.

⇒ addr 주소 확인. 반복적으로 나오기때문에 구조체로 의심해봐야한다.

3.1.5 구조체 자동 정의

기드라는 디컴파일한 코드 내의 변수를 분석해 자동으로 구조체를 정의하는 Auto Create Structure 기능이 있다. 이는 기드라가 변수를 분석하고 구조체를 자동으로 정의한다. 정의된 구조체는 Data Type Manager 창 내의 [auto_structs]에 [astruct]로 정의된다.

이 기능은 주로 단순한 정의만 가능해 수동으로 구조체를 정의하는 방법을 사용하게 된다.

local_8은 download_file 함수의 첫 번째 인수로 전달되기 떄문에 download_file 함수 분석과 구조체 정의를 편집한다. Data Type Manager 창에서 구조체 이름을 클릭하면 구조체 이름을 편집할 수 있는 Structure Editor 창이 열린다.

 

각 행이 구조체의 구성원이며 데이터 형, 구성원 변수 명, 코멘트 편집이 가능하다. 오프셋, 형태 사이즈, 니모닉은 데이터 형을 정의하면 자동으로 반영돼 편집할 수는 없다. 더블클릭을 통해 변경이 가능하며 자주 사용되는 데이터형은 콘텍스트 메뉴의 [Favorite]에 등록 사용이 가능하다. 혹은 함수 명 수정에서 구조체를 더블클릭해 수정이 가능. download_file param_1을 astruct*로 변경한다. 

 

해당 함수를 참조하는 것은 아래의 두 함수이다. URLDownloadToFileA 함수의 제 2인수는 앞과 같이 다운로드 대상 URL에 포인터를 지정한다. 그래서 구조체의 두 번째 멤버 변수(field_0x4) 이름을 URL로 변경한다.

주소 0x0041800을 선두로 하는 구조체의 경우 0x1이 제 2인수가 된다. SHGetSpecialFolderPathA 함수의 제 3인수로 사용된다. SHGetSpecialFolderPathA는 CSIDL에 정의된 폴더 경로를 취득하는 함수에서 정의된다.

SHGetSpecialFolderPathA

BOOL SHGetSpecialFolderPathA(

HWND hwnd, [out] LPSTR pszPath, [in] int csidl, [in] BOOL fCreate

);

중요 인수는 인수로 지정된 폴더 경로 포인터를 지정하는 제 2인수 pszPath와 대상 폴더를 식별하는 ID를 지정하는 제 3인수 CSIDL이다.

 

3.1.6 Equate 적용

기드라는 특정 수치를 정의에 따라 문자열로 바꾸는 Equate(이퀘이트) 기능이 있다. 코드를 분석할 때 수치의 의미를 이해하기 쉽다. SHGetSpecialFolderPathA의 3인수의 구조체를 int형 csidl으로 변환한다.

변환 후에 메뉴에서 [Set Equate]을 선택하고 열린창의 [Equate Strings]에 csidl이라고 입력한다. 0x1A에 대응하는 Equate는 여러가지가 있다. 필터로 데이터베이스 내에 정의된 Equate에서 대상을 좁힐 수 있다. 또한 0x1A에 대응하는 CSIDLsms CSIDL_APPDATA를 눌러 수치를 적용하면 분석에 더 용이하게 사용할 수 있다. 

 

3.1.7 디컴파일 결과 수정

FUN_004010a0 함수로 호출된 FUN_00401140의 세번째 인수에는 포맷 문자열과 같은 문자열이 전달돼 있다.

내부에서는 FID_conflict:vswprintf 함수를 호출함에 따라 snprintf 함수라는 가변 길이 인수를 갖는 함수라고 추측할 수 있다.

snprintf 함수는 대상 문자열에 대한 포인터. 기입 크기. 포맷 문자열, 포맷 문자열에 대입할 변수가 필요하다. %s가 2개 존재하기 때문에 이번 경우 인수는 총 5개이다.

→ snprintf 함수 명이 최신화가 되었을 것; 이런 것일거다 하는 추측. → 경험의 영역

디컴파일 결과에 인수가 세개만 나타나는 것으로. 기드라에서 가변 길이 인수의 함수는 디컴파일 할 수 없는 경우가 많다는 것을 알 수 있다. 그런 경우엔 디스어셈블 결과를 분석해 디컴파일 결과를 수정해야한다. FUN_004010a0 함수 디스어셈블 결과를 본다

FUN_00401140 함수 호출 이전에 PUSH가 5번 일어난다. 주소 0x004010bc와 0x004010bg에서 PUSH 되는 값은 디컴파일 결과에는 반영되지 않는 것을 확인한다.

FUN_00401140 함수의 인수를 편집한다. void *인수를 2개 추가해 5개로 편집한다. 인수를 편집하면 FUN_004010a0 함수의 디컴파일 결과가 갱신된다.

 

FUN_004010a0 함수 호출원을 디스어셈블한 결과를 Listing 창에서 확인한다. 선두에 있는 EBP 대피 처리를 제외하고 3개의 PUSH 명령이 있다. 주소 0x004010e8에서 PUSH되는 값은 디컴파일 결과 값에 반영되지 않았다.

 

ECX에는 astruct 구조체의 선두 주소가 저장되어 있기 때문에 void*형의 인수를 추가한다.

astruct 구조체의 첫 번째 멤버 변수(field_0x0)에는 파일명으로 보이는 문자열 hello.exe가 저장되어 있음을 확인한다. 그리고 FUN_004010a0 함수에서 SHGetSpecialFolderPathA 함수를 호출하여 APPDATA의 경로를 취득한 후 경로와 astruct 구조체의 첫 번째 멤버 변수를 포멧 문자열로 저장했습니다.

구조체의 첫 번째 멤버 변수 이름을 filename으로 하고 FUN_004010a0 함수의 이름을 make_path로 변경한다.

또한 astruct 구조체는 download.exe가 사용하는 설정 정보를 저장하고 있기 때문에 이름을 CONFIG 구조체라고 했다. CONFIG 구조체의 구조체명은 conf이다.(원래 __cdecl download_file(astruct *cond,char *buf)

 

3.2 독자적인 구조체 수동 정의


Data Type Manager에서 New > Structure 에서 독자적인 구조체를 만들 수 있다.

 

3.2.1 자체 구조체 임포트/익스포트


기드라에서 C 언어의 헤더 파일을 파싱하여 구조체를 임포트할 수 있다. 앞의 CONFIG를 임포트하는 예시이다.

만든 구조체를 Export C Header로 파일 저장한 후 File > Parse C Source로 + 버튼을 통해 방금 만든 헤더 파일을 선택한다. 정상적으로 화면에 표시되는 경우 Parse ro Programe을 눌러 정상적으로 가져와진 경우 Data Type Manager 확인할 수 있다.

원래는 CONFIG 파일이 있어야 하지만 나의 경우 에러와 함께 다음과 같이 임포트되었다. 이와 같은 방법으로 Export를 진행할 수도 있다.

3.3 downloader.exe를 마치며


main 함수에서는 config를 읽고 download_file 함수에서 다운로드에 성공하거나 config 끝까지 루프한다. download_file 함수 내에서는 config의 CSIDL 과 파일명을 연결하여 다운로드 경로를 작성하고 URLDownloadToFile 함수의 반환 값을 함수의 반환 값으로 반환한다. 다운로드가 끝나면 ParthFileExistsA로 파일 존재를 확인한다. 존재할 경우 CreateProcessA 함수에서 다운로드한 파일을 실행한다. CreateProcessA 함수를 위해서는 GetStartupInfo 함수로 호출 프로세스 작성 시 지정된 STARTUPINFO 구조체의 내용을 획득한다.

 

📝 스터디 노트

main함수 분석/import함수 분석 확인

→ 엔트리부터 전체 다 보는 Top

→ 밑에서부터 dll 찾아보며 올라오는 bottom

남아있는 심볼에 의존할 수밖에 없는데 레지스터에 들어있는건 어떤 배열이고 Microsoft docx 검색 법 기르기 / 문자열 분석