반응형

 

analyzethis.dll
0.04MB

 

Import REConstructor 1.7c.zip
0.58MB
Import REConstructor 1.7e.zip
0.54MB

 

주제

- Imports rebuilding

- 임포트 리빌딩

 

진행 순서

- RegisterMe.exe
- UnPackMe_FSG2.0
- UnPackme_UPX
- PCGuard4.06C_UnpackmeAll
- UnPackMe_WinUpack0.39

 


 

이전 20번 포스팅에서 언패킹을 진행하며 Ollydump로 덤프를 뜰 때 Rebuild Import 옵션을 체크할 때도 있고, 체크 하지 않을 때도 있었다.

 

Rebuild Import 옵션은 조금 더 복잡한 패커들을 매뉴얼 언패킹 하기 위해 반드시 알아야 하는 기능이다.

 

Microsoft의 Windows 운영체제들은 여러가지 버전이 존재하고, 각 버전마다 DLL 구조체가 달라지기 때문에 당연히 API 함수들의 주소도 달라진다.

 

어떤 프로그램 실행됐을 때 해당 프로그램에서 사용하지 않는 즉, 예를 들어 시스템 DLL 파일에 존재하는 함수들의 리스트를 가지고 있는데 이 리스트를 Imports라고 한다.

 

하지만 이 Imports는 말 그대로 리스트일 뿐이므로 어떤 주소에서 해당 함수를 불러와야 하는지는 모른다.

 

이런 문제로 인해 모든 프로그램은 IAT(Import Address Table)라는 것을 가지고 있는데, IAT는 프로그램이 윈도우 API 함수를 호출할 때 참고하는 테이블로써 해당 함수의 주소를 가지고 있다.

 

그렇기에 프로그램이 시작 되기 전에 로더는 프로그램이 호출하려고 하는 모든 API 함수들의 주소를 찾고 이를 IAT 구조체로 만들어야 하는데 이렇게 만들어놓는 이유는 프로그램이 특정 API를 실행하고자 할 때 그냥 IAT를 통해서 불러오면 되기 때문이다.

 

문제는 패커나 프로텍터가 사용되면 대부분 원래의 IAT를 삭제하거나 변조하기 때문에 리버서들은 원본 IAT를 복구해야 하는데, 프로그램이 제대로 실행되기 위해서는 IAT를 리빌드하거나 수정 해야 한다.

 

조금 더 자세히 보자면, 프로그램이 처음 실행되면 윈도우 로더는 PE 구조를 해석하고, 메모리로 해당 프로그램을 매핑시켜야 한다.

 

또한 프로그램에서 사용하는 모든 DLL을 호출하고 프로세스 주소 공간에 해당 DLL들을 매핑시켜야 하는데, 프로그램들은 API 함수의 주소가 고정적이지 않기 때문에 함수 주소들은 모두 리스트화 시켜서 가지고 있게 된다.

(이때 사용되는 IAT는 컴파일된 코드 전체를 변경하지 않고 부분적으로 변경시키면서 주소를 저장한다.)

 

 

IAT는 윈도우 로더에 의해서 로딩된 DLL 내에 위치하는 함수 포인터들을 모아 놓은 일종의 테이블인데, 어떤 API 함수라고 할지라도 직접 하드코딩된 주소를 사용하지 않고 함수 포인터를 이용해 접근하게 하는 것이 주 목적이고, 이는 다양한 방법으로 호출할 수 있는데 CALL 함수를 이용해서 직접 포인터를 호출하거나 JMP 등을 이용할 수도 있다.

 

패킹된 바이너리는 바이너리 자체를 작고(사이즈 축소) 리버싱을 어렵게 하기 위해 Import 테이블을 변조시키게 되지만, 패킹된 바이너리라 할지라도 정상적인 컴파일러를 통해 생성되었기 때문에 어쨌든 바이너리는 실행이 되어야 한다.

(실행이 안되면 패커를 사용하는 이유가 없어진다.)

 

그렇기에 만약 패커가 임포트 테이블을 삭제했다면, 바이너리를 정상적으로 실행하기 위해 어떤 DLL과 어떤 함수를 로딩해야 하는지 찾아야 할 것이다.

 

삭제된 Import 테이블을 복구하는 방법을 이해하기 위해 우선 Import 테이블이 어떤 방식으로 존재하고 윈도우 로더가 어떻게 파싱하는지 알아야 한다.

 


RegisterMe.exe 분석 및 언패킹

- 이 파일은 패킹되지 않은 일반적인 바이너리 파일이다.

 

 

파일을 디버거에서 열면 위와 같이 401000 주소에 로드되는데 API 함수를 호출하고 있다.

 

kernel32.dll 파일 내에 포함된 GetModuleHandleA 라는 함수를 호출한다.

 

 

위의 사진에서는 Kernel32.dll 내에 포함된 ExitProcess 함수를 호출한다.

 

위 두 사진에서 호출하는 API 함수 모두 공통점이 있는데 "주소 | opcode | 어셈블리어 | 주석 또는 해석" 형태를 가지는 Ollydbg가  401214 주소와 40120E 주소에 어떤 함수가 있는지 알아내서 해석 부분에 해당 함수의 이름을 보여준다.

 

 

이전 ExitProcess 호출 주소에서 Enter를 누르면 위의 사진처럼 해당 함수를 호출하는 주소로 이동된다.

 

분석 대상 바이너리에서 ExitProcess 함수를 호출하려고 하면 위 사진에서의 명령어로 분석될 것이다.

 

위와 같은 구조는 정확한 함수의 주소로 점프 할 수 있도록 가리키기만 하면 되기 때문에 로더의 할 일이 쉬워지게 된다.

 

kernel32.dll 파일에서 호출하는 함수는 2개이고, user32.dll 파일에서 호출하는 함수가 대부분이며, ntdll.dll 파일에서 호출하는 함수는 하나이다.

 

 

ExitProcess 내부를 분석하기 위해 덤프 창에서 해당 주소로 이동한다.

 

ExitProcess 함수의 주소 765B4100

 

분기된 덤프창을 보면 각각의 API들이 참조하는 DLL 파일들에 맞추어 주소값이 세팅되어 있다.

 

이전에 kernel32.dll 파일에서 2개의 함수를 호출하는 것을 봤는데, 위의 덤프창을 보면 그 2개의 함수들의 주소인 "765B0A60", "765B4100"가 세팅되어 있고 그 다음에 00000000 값이 있다.

 

그 뒤로 나오는 74xxxxxx 주소값들은 user32.dll에서 참조되는 함수들의 주소이다.

 

위의 덤프창을 통해 알 수 있는 것은 IAT에서는 프로그램에서 사용하고자 하는 함수들의 주소가 있고, 어떤 DLL 으로부터 참조되는지에 따라 각 DLL 사이에 00000000 값이 존재하는 것을 알 수 있다.

 

IAT 내에서 user32.dll에서 참조되는 함수 주소 목록의 끝을 보면 00000000 값으로 채워져 있는것을 볼 수 있다.

 

즉, 정리하자면 IAT 내에서 각 DLL 사이에 00000000 값을 넣어줌으로써 dll들의 영역을 구분하고, 마지막 dll 파일에서 참조되는 함수 주소가 끝나면 이때는 "종료"를 의미하는 00000000 값이 들어가게 된다.

 

 

 

이제 헤더부터 살펴본다.

 

윈도우 로더는 처음에 바이너리의 헤더를 읽게 되는데, IAT 구조체를 위해 RVA 3C 주소에 위치하는 byte 값을 읽는다.

 

지금 분석하는 바이너리의 경우 ImageBase 값을 포함하면 40003C가 될 것이다.

 

이런 행위가 일어나는 이유는 Import 테이블의 RVA 값이 PE 헤더에서 RVA + 80h 부분에 저장되기 때문이다.

(Import 테이블과 IAT는 다른 것이다.)

 

Import 테이블은 특정 바이너리를 실행시키기 위해 Windows가 링크해야 하는 모든 API 함수에 대한 정보를 가지고 있다.

 

Import 테이블은 굉장히 간단한 구조인데, Import 되는 각 DLL 마다 하나의 헤더가 존재하고, 완전한 NULL 값을 가지는 잉여 헤더가 DLL의 끝을 알리기 위해 존재한다.

 

위의 덤프창의 경우 kernel32.dll, user32.dll에서 API 함수들을 Import 하고 있기 때문에 3개의 헤더가 존재할 것이다.

- DLL 헤더 2개 + Import 테이블 종료를 의미하는 잉여 헤더 1개

 

윈도우 로더는 각 헤더로부터 필요한 정보들을 가져와서 IAT를 생성하기 위해 사용한다.

 

IAT는 각 DLL 내에 존재하는 IAT들을 전부 모아서 만드는데, 각 DLL들의 IAT 헤더 이름은 IMPORT_IMAGE_DERECTORY 라고 한다.

 

해당 헤더는 아래의 구조체로 이루어져 있다.

 

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;            
        DWORD   OriginalFirstThunk;       // INT(Import Name Table) address (RVA)
    };
    DWORD   TimeDateStamp;
    DWORD   ForwarderChain;
    DWORD   Name;                         // library name string address (RVA)
    DWORD   FirstThunk;                   // IAT(Import Address Table) address (RVA)
} IMAGE_IMPORT_DESCRIPTOR;
typedef struct _IMAGE_THUNK_DATA64
{
	union
    {
        ULONGLONG ForwarderString;  // PBYTE
        ULONGLONG Function;         // PDWORD
        ULONGLONG Ordinal;
        ULONGLONG AddressOfData;    // PIMAGE_IMPORT_BY_NAME
    } u1;
} IMAGE_THUNK_DATA64;

typedef IMAGE_THUNK_DATA64 * PIMAGE_THUNK_DATA64;

/////////////////////////////////////////////////

typedef struct _IMAGE_THUNK_DATA32
{
    union
    {
        DWORD ForwarderString;      // PBYTE
        DWORD Function;             // PDWORD
        DWORD Ordinal;
        DWORD AddressOfData;        // PIMAGE_IMPORT_BY_NAME
    } u1;

} IMAGE_THUNK_DATA32;

typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;
typedef struct _IMAGE_IMPORT_BY_NAME {
    WORD    Hint;                         // ordinal
    BYTE    Name[1];                      // function name string
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

 

로더가 "IMPORT_IMAGE_DESCRIPTOR" 헤더를 읽어들이면, 참조하는 DLL을 체크한다.

 

그런 다음 로더는 해당 DLL들을 불러들이면서 IAT를 만들기 시작한다.

 

IAT를 만드는 것은 약간의 기술이 필요로 하는데, 먼저 OriginalFirstThunk 멤버를 조사한다.

(이 조사에서 얻은 정보는 어떤 문제가 발생되었을 때만 사용한다.)

 

그리고 FirstThunk 멤버를 가리키는 모든 포인터들이 해당 포인터가 가리키는 API 주소들과 치환된다.

 

만약 어떠한 이유로 인해 API 함수를 찾지 못한다면 이때 OriginalFirstThunk 멤버를 참조하여 해당 멤버로부터 주소를 가져오는데, 이렇게 했음에도 API를 찾지 못한다면 프로그램은 종료된다.

 

종료된 경우 FirstThunk 멤버에 존재하는 모든 메모리 주소와 포인터들은 현재 DLL 내에 존재하는 API 주소를 이름을 통해서 찾아내 가져온다.

(이러한 경우에는 RVA를 이용하지 않는다.)

 

그리고 IAT 생성은 바이너리가 메모리에 매핑된 이후에 실행된다는 것을 알아야 한다.

 

 

정리하자면 로더는 각각의 API 함수 이름들을 FirstThunk 멤버로부터 읽어들인 뒤 해당 함수의 주소를 찾게 되는데

 

만약 주소를 찾으면 함수의 이름을 주소로 대체시키지만, 찾지 못했을 경우에는 OriginalFirstThunk 멤버로 이동하여 주소를 찾게 된다.

 

즉, OriginalFirstThunk 멤버는 어떠한 문제가 발생했을 경우에 사용하는 일종의 FirstThunk 멤버의 백업이다.

 

FirstThunk 멤버는 바이너리가 Import 해야 하는 API 함수 이름들을 가지고 있는 배열의 포인터이다.

 

로딩 프로세스가 성공적으로 실행되면 FirstThunk 멤버 내의 모든 포인터들은 API 주소를 가지고 있게 된다.

 

그리고 이때 해당 주소들을 바로 IAT라고 부른다.

 

 

바이너리에서 발생되는 모든 호출(CALL)은 IAT로 이동된다.

로더에 의해 작성된 IAT는 아래와 같은 형태를 가진다.

- API 실제 주소

- JMP API

- PUSH RVA API

 

그렇기에 완벽하게 Import 테이블을 세팅하려면

- RVA / Import 테이블의 크기(사이즈)는 Import를 위해 data directory 내에 세팅되어야 한다.

- 각각의 DLL들을 IMAGE_IMPORT_DESCRIPTOR을 이용하여 선언하고, Import 테이블을 완전히 빈 헤더를 이용하여 닫는다.

- IMAGE_IMPORT_DESCRIPTOR 구조체는 OriginalFirstThunk, FirstThunk멤버와 API 이름을 가지고 있다.

- TIMEDateStamp, ForwarderChaing, OriginalFirstThunk 멤버는 0으로 세팅될 수 있다.

 

 

다시 디버거에서 Alt + M 키를 눌러 바이너리의 메모리 상태를 보면 위와 같이 PE Header, code, imports, data, resources 등을 볼 수 있다.

 

이전에 IAT 구조체를 위해 RVA 3C 값을 읽는다고 했었는데, 해당 값부터 분석을 시작하면 된다.

 

위의 사진 속 화면에서 PE Header를 더블 클릭하여 이동한다.

 

 

그러면 위와 같이 PE 헤더 구조에 맞는 정보들이 나오는데 스크롤을 내려 RVA 3C(40003C)값으로 이동한다.

 

 

Import 테이블의 주소를 찾는 법은 아래와 같다.

- VA 40003C 주소의 값 + 80h(RVA 3C + 80h)

- 위의 사진을 참고하면 C0h + 80h = 140h

 

 

Import 테이블 주소는 말 그대로 Import 테이블을 어디서 찾아야 하는지를 알려주기 위한 주소이다.

 

절대 IAT와 혼동해서는 안된다.

 

 

 

디스어셈블리 창에서 Ctrl + G 키를 누른 후 402050 주소로 이동하면 위와 같이 나온다.

 

 

Analyze This 플러그인을 사용하면 위와 같이 분석된다.

 

위 사진에서 회색으로 드래그 된 부분들이 Import 테이블의 첫 번째 파트인 IMAGE_IMPORT_DESCRIPTOR의 배열이다.

 

각각의 IMAGE_IMPORT_DESCRIPTOR 배열들은 총 5개의 dword 크기의 값을 가지고 있다.

 

위의 사진을 참고하면 총 3개의 IMAGE_IMPORT_DESCRIPTOR 구조체가 있는데, 각 구조체의 OriginalFirstThunk의 주소는 402098, 40208C, 400000이다.

 

OriginalFirstThunk 멤버
ImageBase + 2098 주소

 

첫 번째 dword 값이 OriginalFirstThunk 멤버이다.

 

이 멤버가 로더에게 현재 DLL로부터 Import 해야 하는 API 이름을 어디서 찾아야 하는지 알려준다.

 

ImageBase + 2098 주소로 이동하면 Import 해야 하는 API 이름을 찾을 수 있다.

 

 

두 번째, 세 번째 값은 분석하지 않아도 되는 부분이다.

- TimeDateStamp

- ForwarderChain

 

 

네 번째 값은 IMAGE_IMPORT_DESCRIPTOR 멤버가 대응하는 DLL 이름들의 RVA 값을 가지고 있다.

- 이번 분석의 경우 4021D8에서 user32.dll을 찾을 수 있다.

 

 

다섯 번째 값은 FirstThunk 멤버이다.

 

이 멤버는 Import된 모든 함수의 주소를 찾을 수 있는 IAT 구조체를 가리키는 포인터이다.

 

 

user32.dll에서 Import 되는 모든 API 함수의 주소를 가지고 있는 IAT 구조체가 40200C 주소에서 시작한다.

 

 

IMPORT 테이블의 두 번째 부분은 arrays of dwords(DWORD 배열) 이다.

 

IMAGE_IMPORT_DESCRIPTOR 헤더의 OriginalFirstThunk 멤버를 가리키는 포인터를 의미한다.

 

각각의 dword 값들은 IMPORT 되는 함수와 대응된다.

 

각각의 값들은 분리된 값이며 0으로 전부 채워진 dword 값으로 종료를 의미한다.

 

 

세 번째이자 마지막 부분에서 보여지는 문자열은 Import 되는 함수와 참조되는 DLL의 이름이다.

 

위 사진에서 보여지는 주소를 덤프창에서 보면 위와 같다.

 

 

디스어셈블 창에서 스크롤을 조금 위로 올리면 위와 같이 보이는데 위의 부분이 바로 코드로 작성된 IAT이다.

 

kernel32.dll에서 2개의 API가 사용되었고, 00000000으로 다른 DLL 파일과 분리시킨 뒤 user32.dll 에서 사용되는 API를 보여주고 있으며, 마지막으로 00000000 값으로 종료를 나타낸다.

 

 

이제 조금이나마 Import 테이블과 IAT에 대해서 이해했길 바라며 Rebuild Imports 기능이 페커에 따라 왜 필요한지 필요하지 않은지 알게 되었기 때문에 몇몇 패커가 적용된 바이너리를 더 분석할 것이다.

 


UnPackMe_FSG2.0 분석 및 언패킹

- Windows XP 환경에서 실습한다.

 

FSG 패커는 트레이싱(한줄 한줄 분석)을 통해 언패킹을 진행할 수 있는데, 스크롤을 좀 내리면 언패킹 루틴의 끝 부분을 볼 수 있다.

 

트레이싱을 하다보면 어떠한 루프를 만나게 되고 계속 거기서 무한 루프처럼 돌게 된다.

 

돌다 보면 코드는 메인 프로그램으로 분기되는데, 잘 분석해보면 딱 한개의 분기문만이 해당 루프를 벗어나게 해준다.

 

위는 EP 코드 부분이다.

 

트레이싱을 통해 매뉴얼 언패킹을 진행한다.

 

 

트레이싱을 하다 보면 4001D1 주소에서 [EBX+C] 값으로 분기된다.

 

해당 값이 바로 루프를 벗어나게 해주는 값 404000이다.

 

 

404000 주소로 분괴면 위와 같이 코드가 조금 이상하긴 하지만 어쨌든 OEP로 추정할 수 있으니 덤프를 뜬다.

 

 

Ollydump 플러그인을 이용해 덤프를 뜨는데 Rebuild Import 옵션은 체크 해제하고 덤프를 뜬다.

 

해제하는 이유는 FSG 패커가 Import 부분을 전부 망가뜨렸기 때문에 옵션을 체크하고 덤프를 떠버리면 ollydump 프러그인이 완전 이상한 값을 세팅하게 된다.

 


Lord PE 툴로 덤프 뜨기

 

 

ollydump 플러그인이 아니라 Lord PE라는 툴로 덤프를 뜰 수 있다.

 

이 툴로 덤프를 뜨려면, 먼저 덤프 대상 프로세스를 실행시킨 뒤 Lord PE 프로그램을 실행한다.

(대상 프로세스를 실행시킨 후 Lord PE 프로그램을 실행해야 목록에 뜬다.)

 

덤프 뜰려는 프로세스를 선택하고 마우스 우측 -> dump full 메뉴를 선택하면 덤프가 떠진다.

 


 

 

Import 테이블을 수정하기 위해 Import REConstructor 툴을 사용한다.

 

현재 ollydbg에 프로그램이 로드되어 있고 OEP 부분으로 이동되어 있다.

 

이 상태에서 Import REConstructor 툴을 실행하여 실행 중인 프로세스를 선택한다.

 

그리고 IAT Infoes Needed 부분에 있는 항목들의 값이 세팅되어 있을 것인데, 패커가 해당 부분의 값들을 망가뜨렸을 수도 있으니 직접 올바르게 수정해야 한다.

 

먼저 OEP부터 수정 또는 확인한다.

 

이전에 디버거에서 봤듯이 OEP 주소는 404000(ImageBase + 4000) 주소이다.

 

OEP 부분에 4000을 입력해주면 되고, AutoSearch 버튼을 누르면 자동으로 RVA와 Size 항목의 값이 수정된다.

 

 

 

AutoSearch 버튼을 누르면 위와 같이 창이 뜨는데 창을 닫으면 자동으로 수정 된 RVA, Size 값을 볼 수 있다.

 

하지만 이 상태에서 덤프된 바이너리를 수정하고 실행하면 동작이 되지 않는데, 그 이유는 RVA 값이 제대로 찾아지지 않은 것으로 이 역시 수동으로 올바른 값을 직접 세팅해줘야 한다.

 

 

디버거의 덤프창에서 4011e8 주소로 이동한다.

 

그러면 위와 같이 나오는데 무엇이 문제인지 분석한다.

 

이전에 IAT는 참조되는 DLL의 구분자로 00000000를 세팅하고, 종료를 나타내기 위해 또 다른 00000000을 사용한다고 했다.

 

그런데 위의 사진을 보면 4011E8 값이 00000000이다.

 

즉, 자동으로 찾아진 RVA 값은 해당 바이너리에서 참조되는 마지막 DLL의 시작 주소를 찾은 것이다.

 

 

스크롤을 조금 위로 올리면 401198 주소에 또 다른 DLL Import 부분을 볼 수 있는데, 프로그램이 1198 대신 11E8를 찾아서 오류가 발생한 것이다.

 

 

위와 같이 RVA 값을 1198로 수정해주고, Size 값은 100으로 수정해준 뒤 Get Imports를 클릭한다.

 

 

그러면 위와 같이 Import 되는 2개 함수가 모두 올바르지 않다고 나온다.

 

다시 수정을 해야 하는데, 툴에서 Show Invalid 버튼을 클릭해서 어디가 문제인지 확인한다.

 

 

그러면 FSG 패커가 이상한 값을 세팅했다는 것이 명확하게 보여지게 된다.

 

해당 주소값들은 존재하지 않는 값이기 때문에 잘라내야 한다.

 

 

잘라내야 하는 값을 선택 -> 마우스 오른쪽 -> Cut thunk 메뉴를 이용해 해당 값들을 삭제한다.

 

 

삭제한 후 다시 Get Imports 버튼을 클릭하면 위와 같이 Import 되는 함수 모두 valid: YES 상태가 된다.

 

 

모두 다 valid 상태이면 ImportREC 툴 하단에 있는 Fix Dump 버튼을 클릭하여 수정할 덤프 바이너리를 선택한다.

 

이전에 덤프한 바이너리를 선택하여 해당 바이너리의 Import 부분을 수정할 수 있다.

 

수정된 파일은 위에서 선택한 파일명 + "_"로 저장된다.

 

Exit 버튼을 눌러 창을 꺼준다.

 

 

덤프된 바이너리 헤더의 Import 부분을 수정했으니 PE 헤더도 다시 빌드해야 하는데

 

LordPE 툴에서 제공하는 Rebuild PE 기능을 이용한다.

 

 

위와 같이 Import 부분을 수정한 바이너리를 고르면 PE 헤더가 재빌드 되는데

 

 

재빌드가 성공적으로 끝나면 디버거에서 해당 바이너리를 열어서 어디가 변경되었는지 분석을 시작한다.

 

 

Ollydbg에 재빌드한 파일을 올린 후 메모리 상태를 보면 위와 같이 .mackt 라는 섹션이 추가되었는데

 

이 섹션이 바로 ImportREC 툴에서 추가한 올바른 Import가 포함된 섹션이다.

 


UnPackme_UPX 분석 및 언패킹

 

UPX는 트레이스 언패킹이 꽤 쉬운 패커에 속한다.

 

언패킹 루틴과 관련된 분기문을 쉽게 눈으로 확인할 수 있기 때문이다.

 

일반적으로 UPX 패커가 적용된 바이너리는 OEP를 알아낸 다음 Rebuild Imports 옵션을 체크해서 Ollydump 플러그인으로 덤프할 수 있다.

 

하지만 시간이 지남에 따라 UPX 패커 역시 약간의 변화를 가질 수 있게 되었는데, 지금 분석하는 바이너리는 일반적인 UPX 루틴과 약간 다른점이 존재한다.

 

일단 모든 언패킹 루틴을 트레이싱 하지 않고, 언패킹 루틴의 마지막에서 OEP로 분기되는 곳에 BP를 설치하는 것으로 분석할 것이다.

 

 

프로그램을 Ollydbg에 올리면 PUSHAD 명령을 통해 현재 레지스터들의 값들을 모두 스택에 저장하는 것을 볼 수 있다.

 

PUSHAD 명령이 쓰였기 때문에 스크롤을 내리다가 POPAD 명령이 보이면 해당 POPAD 명령에 BP를 건 뒤 F9를 눌러 실행하여 해당 BP로 이동 후 F8 키로 진행하여 OEP로 가도 되지만

 

ESP Trick을 이용해 언패킹한다.

 

 

패킹된 바이너리의 EP 부분에서 F8 키를 한 번 누르면 ESP 레지스터 값이 변경된다.

 

현재 변경된 ESP의 값은 12FFA4인데, 이 레지스터 값에 하드웨어 BP를 설치해서 OEP를 찾을 수 있다.

 

 

 

덤프 창에서 12FFA4 주소로 이동 후 4byte 값을 드래그 한 뒤 우클릭 -> Breakpoint -> Hardware, on access -> Dword를 선택해 하드웨어 BP를 설치한다.

 

 

하드웨어 BP를 설치 후 F9 키를 눌러 실행하면 위와 같이 49DC40 주소에서 멈추게 된다.

 

40DC40 주소에서는 401000 주소로 점프하는데 

 

 

401000 주소는 위와 같이 언패킹이 완료된 바이너리의 OEP이다.

 

이 상태에서 덤프를 뜬다.

 

 

보통은 UPX 패커가 적용된 바이너리를 덤프 뜰 때 Rebuild Import 옵션을 체크한 뒤 덤프를 떠 저장하지만

 

지금 분석하고 있는 바이너리는 그렇게 덤프를 뜨면 동작이 되지 않기 때문에 옵션을 체크 해제한 후 덤프를 더 저장한다.

 

그리고 Import REC 툴을 이용해 Import 부분을 복구한다.

 

Import REC 툴에서 대상 바이너리를 선택한다.

(현재 대상 바이너리는 Ollydbg에서 켜져 있기 때문에 프로세스 목록에서 보일 것이다.)

 

 

위와 같이 언패킹이 끝난 뒤 분기되었던 OEP 주소 1000을 입력하고

 

AutoSearch 버튼을 누르면 위의 메시지 창이 뜨는데, 확인 버튼을 눌러 창을 끄면 자동으로 RVA 값이 입력되어 있다.

 

자동으로 찾아진 RVA 값은 85278인데, 디버거의 덤프 창에서 485278(ImageBase + RVA) 주소로 이동하여 어떤 값이 있는지 확인한다.

 

 

위와 같이 485278 주소를 보면 7C809BD7 값이 세팅되어 있다.

 

값의 주소를 보니 7Cxxxxxx 형태이고 처음에 세팅된 값이 00000000인 것으로 보아 이전에 Import 하고 있는 또 다른 DLL이 존재하는 것을 추정할 수 있다.

 

 

스크롤을 조금 위로 올려보면 4850D8 주소에 또 다른 DLL 로부터 참조되는 주소(77xxxxxx)들을 볼 수 있다.

 

 

그리고 스크롤을 쭉 내려보면 485278 주소 이후에도 또 다른 DLL 로부터 참조되는 주소(77xxxxxx)들을 볼 수 있다.

 

 

위와 같이 수정 후 Get Imports 버튼을 누른다.

 

 

그러면 위와 같이 Import 되는 모든 함수들이 valid 한 상태인 것을 알 수 있다.

 

 

총 9개의 valid 모듈과 365개의 함수들이 추가된 것을 알 수 있는데, 이 상태에서 Fix dump 버튼을 눌러 이전에 덤프를 떴던 바이너리를 선택하면 Import 테이블 수정 작업이 완료된다.

 


PCGuard4.06C_UnpackmeAll 분석 및 언패킹

 

PC Guard 패커에는 바이너의 실행 회수를 제한할 수 있는 옵션이 있다.

 

분석하고자 하는 바이너리는 해당 옵션이 체크된 상태로 패킹된 것이기 때문에 완벽한 언패킹이 일어나기 전까지 자꾸 실행하면 안된다.

 

PC Guard 패커는 패킹/언패킹을 위해 DLL을 사용하므로 해당 바이너리와 같은 폴더 내에 DLL 파일이 없으면 실행되지 않고, PC Guard 패커는 OEP를 가지고 놈으로써 언패킹 루틴 중에 OEP를 숨기려고 한다.

 

분석을 하다보면 실제 프로그램이 실행되기 전에 약 20번 정도 break가 발생하는데, PC Guard 패커 역시 ESP Trick으로 언패킹이 가능하다.

 

 

패킹 된 바이너리의 EP 부분이다.

 

PUSH EBP 명령이 실행되는 순간 ESP 레지스터 값이 변경되는데, 이때 ESP 값을 이용해 ESP Trick을 이용해 언패킹 할 수 있다.

 

F8 키를 눌러 PUSH EBP 명령을 실행하고

 

ESP 레지스터 우클릭 -> Follow in Dump

 

Breakpoint -> Hardware, on access -> Byte

 

 

하드웨어 BP를 설치 후 F9 키를 눌러 실행하다 보면 위와 같이 메세지 창이 뜨는데 확인 창을 눌러주면 된다.

 

 

이어서 F9 키를 눌러 실행하다 보면 위와 같이 401001 주소 부분에서 멈추는데 

 

 

F9 키를 한 번 더 누르면 위와 같이 프로그램이 시작된다.

 

그렇다면 위의 401001 주소 부분이 OEP 부분인데 정확히는 함수 프롤로그 부분이므로 401000 주소부터가 OEP 부분이다.

 

Debug -> Hardware breakpoints -> Delete1

 

이 파일은 추가적인 분석이 필요로 하기 때문에 이전에 설치했던 ESP 레지스터 하드웨어 BP를 제거 후

 

위와 같이 401000 주소에 오른쪽 마우스 -> Breakpoint -> Hardware, on execution을 선택해 OEP 부분에 하드웨어 BP를 설치한다.

 

 

Ctrl + f2 키를 눌러 재실행 후 F9 키를 눌러 다시 401000 주소로 이동한다.

 

 

그리고 Ollydump를 이용해 덤프를 뜨는데 Rebuild Import 옵션을 해제하고 덤프를 뜬 뒤 Import REC 툴을 이용해 Import 부분을 수정해줘야 한다.

 

 

Import REC 툴에서 대상 바이너리를 선택하고

 

 

OEP 값을 이전에 알아낸 값으로 변경한 뒤 AutoSearch 버튼을 눌러 RVA 값을 자동으로 선택하게 한다.

 

그러면 위와 같이 RVA 값이 설정되는데 이 상태에서 Get Imports 버튼을 눌러 valid 상태를 알아본다.

 

 

추가된 6개의 모듈이 모두 valid 상태이고, 46개의 함수가 추가되었다.

 

이 상태에서 Fix Dump 버튼을 누른다.

 

 

이전에 덤프를 떴던 바이너리를 선택하여 해당 덤프 뜬 파일의 Import 부분을 변경하도록 한다.

 

 

Import 부분이 수정된 덤프 파일을 실행하면 위와 같이 잘 실행되고, 디버거에 올렸을 때 언패킹이 된 상태로 바로 로드된다.

 


UnPackMe_WinUpack0.39 분석 및 언패킹

 

WinUnpack 패커는 꽤 쓸만한 패커 중 하나였었다.

 

하지만 아쉽게도 제작자가 업데이트를 중단했기 때문에 지금 분석하고자 하는 바이너에 적용된 버전이 마지막 버전이다.

 

 

프로그램을 실행하면 위와 같이 뜬다.

 

 

하지만 ollydbg에 프로그램을 올리면 위와 같이 메세지 창이 뜬다.

 

잘못된 형식의 바이너리 라고 하는데, 일종의 안티 디버깅 루틴이다.

 

Olly Advanced 플러그인을 사용하면 해당 창이 발생하지 않는데, 어쨌든 이 상태에서 계속 바이너리 분석을 진행한다.

 

 

확인 버튼을 누르면 위와 같이 7C93120F 주소의 RETN 명령에서 시스템 브레이크 포인트가 걸려 프로그램이 멈추게 된다.

 

안티 디버깅 때문이므로 무시하고 일단 메모리 맵 상태를 보도록 한다.

 

 

메모리 맵 상태를 보면 UnPackMe 바이너리에 PE Header 하나만 존재하고 .data, .resource 등의 섹션이 보여지지 않는다.

 

그런데 자세히 보면 PE header의 사이즈가 A3000으로 엄청 크다.

 

즉, Winunpack 패커는 언패킹 루틴과 원래 코드를 헤더 섹션에 넣는다는 것을 알 수 있다.

 

PE Header를 더블 클릭해 이동한다.

 

 

그러면 위와 같이 나오는데

 

AddressOfEntryPoint 항목의 값을 보니 1018이다.

 

ImageBase 값 + 1018 = 401018이므로 디스어셈블리 창에서 401018 주소로 이동한다.

 

 

그러면 위와 같은데 패킹된 바이너리의 EP 부분이다.

 

401018 주소에 BP를 걸고 F9 키를 눌러 실행하면 401018 주소에서 멈추게 된다.

 

 

그리고 덤프 창에서 401018 주소로 이동하면 위와 같다.

 

WinUnpack 패커는일반적인 언패킹 방법을 사용할 수 있다.

 

이전에 여러번 사용했기 때문에 이번에는 간단한 트레이싱을 통해 언패킹을 진행한다.

 

트레이싱을 하는 도중에는 시야가 좀 넓어야 할 필요성이 있는데, 단순히 명령어만 보는 것이 아니라 레지스터/스택 상태, dump 창 등을 포함하여 봐야 한다.

 

예를 들어, 지금은 바이너리의 코드 섹션에 존재하는 40108 주소를 EP로 생각하고 시작했다.

 

이는 패커가 바이너리를 패킹 한 뒤 현재 보고 있는 401018 주소에 어떤 명령어를 작성할 수 있게 패킹된 코드를 다른 주소에다가 작성한 것으로 추정할 수 있다.

 

즉, 다시 말하자면 언패킹 루틴에서는 패커가 패커 자신의 코드(최소한 지금 분석 중인 401018 주소)를 덮어 씌울 것이다.

 

 

F8 키를 눌러 트레이싱 하다 보면 4010EB 주소에서 499EBF 주소로 점프하는데 499EBF 주소는 다른 섹션에 위치하고 있는 주소이다.

 

즉, OEP 주소인 것으로 추정할 수 있다.

 

 

F8 키를 눌러 499EBF 주소로 점프한 뒤 F8 키를 눌러 계속 트레이싱 하다보면 49A00D 주소에서 499EBF 주소로 분기하는데 이 분기가 반복된다.

 

49A00D 주소에서 스크롤을 조금 내려보면 위와 같이 49A05B 주소에서 RETN 명령이 있다.

 

OEP 부분인 줄 알았던 499EBF 주소 부분이 OEP 부분은 아니였지만, 진행하다보면 마지막 부분에 RETN 명령이 있고, 이 RETN 명령에 의해 어디로 리턴되는지 봐야 한다.

 

 

49A05B 주소의 RETN 명령에 BP를 걸고 F9 를 눌러 실행하면 위와 같이 49A05B 주소에서 멈추게 된다.

 

이 상태에서 F8 키를 한 번 누른다.

 

 

그러면 위와 같이 4271B0 주소에서 멈추게 되는데, 함수 프롤로그가 있는 것으로 보아 OEP 부분인 것 같다.

 

진짜 OEP 부분을 찾은 것 같으니 이 상태에서 덤프를 뜬다.

 

하지만 이번에는 Ollydump 플러그인을 사용하지 않고 Import REC 툴을 이용해 덤프를 뜬다.

 

 

위와 같이 덤프를 뜨고자 하는 바이너리를 선택한 후 -> 하얀색 창 부분에서 우클릭 -> Advanced Commands -> Select Code Section(s)를 선택한다.

 

 

덤프 떠서 저장하기
덤프 완료 메세지 창

 

그러면 위와 같이 코드 섹션 창이 나타나는데, 어떤 섹션이 코드를 포함하고 있는지 선택하라는 것이다.

 

기본적으로 가장 위쪽에 있는 섹션이 선택되어 있는데 이 상태에서 Full Dump를 선택하여 바이너리를 덤프한다.

 

덤프를 떠서 저장 하고나면 이제 IAT를 고쳐야 한다.

 

IAT를 고친다는 의미는 가끔 Import를 고친다는 의미와 같은데, 이번 분석에서는 IAT를 고치는 것이 아니라 Import 테이블을 고칠 것이다.

 

 

위와 같이 OEP 값을 이전에 디버거에서 알아냈던 271B0 값으로 수정하고 AutoSearch 버튼을 누르면

 

RVA와 Size 값들이 변경된다.

 

이 상태에서 Get Imports 버튼을 눌러서 Import 부분을 수정한다.

 

 

Get Imports 버튼을 누르면 위와 같이 dll 모듈들이 추가되고 모두 valid:YES 상태이다.

 

valid:YES 상태는 Import 수정 작업이 끝난 상태이니 Fix Dump 버튼을 누른다.

 

 

이전에 덤프 뜬 파일을 선택하면 

 

 

덤프 떠진 파일에 Import 수정 내용이 적용된다.

 

 

Import 부분이 수정된 파일을 실행하면 위와 같이 잘 실행된다.

 

디버거에 열면 여전히 오류 메세지 창이 뜨는데 정확한 이유는 모르겠지만 영상에서는 이대로 종료된다.

 

아무래도 IAT 복구에 초점이 맞춰져 있으니 덤프를 뜬 후 Import 부분이 수정되고 실행했을 때 잘 실행하면 성공적으로 끝낸 것이다.

 

 

반응형

+ Recent posts