반응형
#include "header.h"
#include <conio.h>

int print_image_export_directory(FILE* fp, u_char* buf, IMAGE_EXPORT_DIRECTORY* ied)
{
    int raw = 0, num_of_names = 0, num_of_functions = 0;
    IMAGE_EXPORT_DIRECTORY* pied = (IMAGE_EXPORT_DIRECTORY*)ied;

    // EXPORT dll 출력
    printf("[%08X] - Characteristics[%zdbyte]\t\t: %08X\n", offset, sizeof(pied->Characteristics), pied->Characteristics);
    offset = get_file_offset(fp, sizeof(pied->Characteristics));

    printf("[%08X] - TimeDateStamp[%zdbyte]\t\t: %08X\n", offset, sizeof(pied->TimeDateStamp), pied->TimeDateStamp);
    offset = get_file_offset(fp, sizeof(pied->TimeDateStamp));

    printf("[%08X] - MajorVersion[%zdbyte]\t\t: %04X\n", offset, sizeof(pied->MajorVersion), pied->MajorVersion);
    offset = get_file_offset(fp, sizeof(pied->MajorVersion));

    printf("[%08X] - MinorVersion[%zdbyte]\t\t: %04X\n", offset, sizeof(pied->MinorVersion), pied->MinorVersion);
    offset = get_file_offset(fp, sizeof(pied->MinorVersion));

    raw = (int)convert_rva_to_raw(buf, &(ied->Name), OPERAND_DWORD);
    printf("[%08X] - Name[%zdbyte]\t\t\t: %08X(RVA), %08X(RAW), %s\n", offset, sizeof(pied->Name), ied->Name, raw, buf + raw);
    offset = get_file_offset(fp, sizeof(pied->Name));

    printf("[%08X] - Base[%zdbyte]\t\t\t: %08X\n", offset, sizeof(pied->Base), pied->Base);
    offset = get_file_offset(fp, sizeof(pied->Base));

    printf("[%08X] - NumberOfFunctions[%zdbyte]\t\t: %08X\n", offset, sizeof(pied->NumberOfFunctions), pied->NumberOfFunctions);
    offset = get_file_offset(fp, sizeof(pied->NumberOfFunctions));

    printf("[%08X] - NumberOfNames[%zdbyte]\t\t: %08X\n", offset, sizeof(pied->NumberOfNames), pied->NumberOfNames);
    offset = get_file_offset(fp, sizeof(pied->NumberOfNames));

    printf("[%08X] - AddressOfFunctions[%zdbyte]\t\t: %08X(RVA), %08X(RAW)\n", offset, sizeof(pied->AddressOfFunctions), pied->AddressOfFunctions, (int)convert_rva_to_raw(buf, &(pied->AddressOfFunctions), OPERAND_DWORD));
    offset = get_file_offset(fp, sizeof(pied->AddressOfFunctions));

    printf("[%08X] - AddressOfNames[%zdbyte]\t\t: %08X(RVA), %08X(RAW)\n", offset, sizeof(pied->AddressOfNames), pied->AddressOfNames, (int)convert_rva_to_raw(buf, &(pied->AddressOfNames), OPERAND_DWORD));
    offset = get_file_offset(fp, sizeof(pied->AddressOfNames));

    printf("[%08X] - AddressOfNameOrdinals[%zdbyte]\t: %08X(RVA), %08X(RAW)\n", offset, sizeof(pied->AddressOfNameOrdinals), pied->AddressOfNameOrdinals, (int)convert_rva_to_raw(buf, &(pied->AddressOfNameOrdinals), OPERAND_DWORD));
    offset = get_file_offset(fp, sizeof(pied->AddressOfNameOrdinals));

    printf("\n----------------------------------------\n\n");
    printf("-------------------- ( EAT ) --------------------\n\n");

    // 이름 배열의 개수, 함수 배열의 개수
    num_of_names = pied->NumberOfNames;
    num_of_functions = pied->NumberOfFunctions;
    printf("number of names : %d\n", num_of_names);
    printf("number of functions : %d\n\n", num_of_functions);
    printf("-------------------------------------------------\n\n");

    // EXPORT 함수 이름들 실제 RAW 위치
    raw = (int)convert_rva_to_raw(buf, &(pied->Name), OPERAND_DWORD);
    char* offset_export_func_names = (char*)(buf + raw + (strlen(buf + raw) + 1));

    // 이름 배열의 주소(이름 배열에 있는 각 4byte RVA 값들을 가리키기 위해)
    raw = (int)convert_rva_to_raw(buf, &(pied->AddressOfNames), OPERAND_DWORD);
    DWORD *pnames =(DWORD*)(buf + raw);

    // ordinals
    raw = (int)convert_rva_to_raw(buf, &(pied->AddressOfNameOrdinals), OPERAND_DWORD);
    WORD* pordinals = (WORD*)(buf + raw);

    // function
    raw = (int)convert_rva_to_raw(buf, &(pied->AddressOfFunctions), OPERAND_DWORD);
    DWORD* pfunctions = (DWORD*)(buf + raw);

    // Export 함수 이름 배열(RAW)의 주소를 백업하여 사용
    char* p_offset_export_func_names = offset_export_func_names;

    for (int i = 0; i < num_of_names; i++, p_offset_export_func_names += (strlen(p_offset_export_func_names) + 1))
    {
        short ordinal = -1;
        DWORD eat_rva = 0, n_index = -1, f_index = -1;
        DWORD* ppnames = pnames; // addfess of names
        DWORD* ppfunctions = pfunctions; // address of functions

        for (int j = 0; j < num_of_names; j++, ppnames++)
        {
            /*
            * IMAGE_EXPORT_DIRECTORY 구조체의 name 멤버의 값을 RAW로 변경한 뒤 해당 위치로 가면
            * export 하는 dll 파일의 이름이 있는데, 해당 위치에서 dll 파일의 이름 길이 + 1 한 위치부터는
            * 해당 dll파일에서 export 하는 함수들의 이름들이 있고, 해당 함수들 중 정보를 찾으려는 함수 이름과
            * IMAGE_EXPORT_DIRECTORY 구조체의 AddressOfNames 멤버의 값을 RAW로 변환한 위치에 있는 RVA 값들을
            * RAW로 변환하여 해당 위치에 있는 문자열들과 비교하여 몇 번째에 있는지 찾는다.
            */
            raw = (int)convert_rva_to_raw(buf, ppnames, OPERAND_DWORD);
            if (!strcmp(p_offset_export_func_names, buf + raw))
            {
                n_index = j;
                break;
            }
        }		

        // EXPORT 함수 이름에 해당하는 ordinal
        // n_index = name_index, 
        if (n_index != -1)
        {
            ordinal = *(pordinals + n_index);
        }
        else
        {
            i -= 1;
            continue;
        }

        // EXPORT 함수 주소 배열에서 + ordinal한 위치에 해당하는 값
        if (ordinal != -1)
            eat_rva = *(ppfunctions + ordinal);


        // function ordinal 구하기
        if (eat_rva != 0)
        {
            for (int k = 0; k < num_of_functions; k++, ppfunctions++)
            {
                if (eat_rva == *ppfunctions)
                {
                    f_index = (k + 1);
                    break;
                }
            }
        }

        if (eat_rva != 0)
        {
            // NO : 함수의 개수만큼 출력됐는지 확인하기 위함
            printf("No.%d [%s]\n- %ld(Name Index)\n- %04X(Name Ordinal)\n- %08X(Name RVA), %08X(Name RAW)\n- %08X(Function Ordinal)\n- %08X(Function RVA)\n\n", i+1, p_offset_export_func_names, n_index, ordinal, *ppnames, raw, f_index, eat_rva);
            printf("-------------------------------------------------\n\n");
        }

        // 사용자가 입력한 값이 q이면 프로그램 종료, 그게 아니라면 계속 반복
        int ch = _getch();
        if (ch == 'q')
        {
            printf("프로그램 종료\n");
            return 0;
        }

    }

    //함수 이름 없이 Ordinal로만 Export된 함수의 주소를 찾을 수도 있다.

    return 0;
}


매개변수

int print_image_export_directory(FILE* fp, u_char* buf, IMAGE_EXPORT_DIRECTORY* ied)

 

인자로 파일 포인터와 파일의 내용이 담긴 동적 메모리 주소, ied 구조체 포인터를 받는다.

 

실질적으로는 ied 구조체만 받아도 되지만, buf를 받는 이유는 RVA 값을 RAW 값으로 변환하는 함수 convert_rva_to_raw() 함수를 호출할 때 인자로 넘겨줘야 convert_rva_to_raw() 함수 내에서 섹션을 찾아 변환해준다.


IMAGE_EXPORT_DIRECTORY 구조체 정의

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;
    DWORD   TimeDateStamp;
    WORD    MajorVersion;
    WORD    MinorVersion;
    DWORD   Name;
    DWORD   Base;
    DWORD   NumberOfFunctions;
    DWORD   NumberOfNames;
    DWORD   AddressOfFunctions;     // RVA from base of image
    DWORD   AddressOfNames;         // RVA from base of image
    DWORD   AddressOfNameOrdinals;  // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

 

winnt.h 헤더파일에 위와 같이 되어 있다.

 


주요 구조체 멤버

  • NumberOfFunctions : 실제 Export 함수 개수
  • NumberOfNames : Export 함수 중에서 이름을 가지는 함수 개수(≤ NumberOfFunctions)
  • AddressOfFunctions : Export 함수 주소 배열(배열의 원소 개수 = NumberOfFunctions)
  • AddressOfNames : 함수 이름 주소 배열 (배열의 원소 개수 = NumberOfNames)
  • AddressOfNameOrdinals : Ordinal 주소 배열(배열의 원소 개수 = NumberOfNames)

GetProcAddress() 동작 원리

 

GetProcAddress() 함수는 라이브러리에서 함수 주소를 얻는 API이다.

 

  1. AddressOfNames 멤버를 이용해 “함수 이름 배열”로 간다.
  2. “함수 이름 배열”은 문자열 주소가 저장되어 있다. 문자열 비교(strcmp)를 통해 원하는 함수 이름을 찾는다. (이때 배열의 인덱스를 name_index라고 임시로 정한다)
  3. AddressOfNameOrdinals 멤버를 이용해 “ordinal 배열”로 간다.
  4. “Ordinal 배열”에서 name_index로 해당 ordinal 값을 찾는다.
  5. AddressOfFunctions 멤버를 이용해 “함수 주소 배열(EAT)”로 간다.
  6. “함수 주소 배열(EAT)”에서 이전에 구한 ordinal을 배열 인덱스로 하여 원하는 함수의 시작 주소를 얻는다.

EAT 영역에서 라이브러리 내 함수들의 정보 가져오는 알고리즘

1. EXPORT 함수 이름들이 적힌 RAW 주소 부분을 구한다.
(EXPORT하는 라이브러리 이름 뒤에부터가 시작 부분이다.)

2. IMAGE_EXPORT_DIRECTORY 구조체에서 AddressOfNames의 값(RVA)을 RAW로 바꾸고 해당 RAW 위치로 가보면
4byte RVA 값들이 배열되어 있는데 해당 RVA 값들을 RAW로 바꾼 값에 위치한 문자열과
1번에서 구한 RAW 주소에 위치한 문자열을 strcmp로 비교하여 맞을 때까지 반복문을 돌려 name_index를 가져온다.

3-1. name_index를 성공적으로 가져왔다면 IMAGE_EXPORT_DIRECTORY 구조체에서 AddressOfNameOrdinals의 값(RVA)을
RAW로 바꾸고 해당 위치로부터 name_index * 2 만큼 떨어진 위치에 있는 값인 ordinal을 구한다.
(즉, AddfressOfNameOrdinals[name_index])

	3-2. name_index를 가져오는 데 실패했다면, i의 값에 1을 빼 반복문이 함수 이름의 개수만큼 반복할 수 있도록 증가시키지 않고
    	continue로 이후 구문들을 실행하지 않고 다시 i를 이용하는 반복문이 반복하도록 한다.

4. ordinal을 성공적으로 가져왔다면 IMAGE_EXPORT_DIRECTORY 구조체에서 AddressOfFunctions의 값(RVA)을
RAW로 바꾸고 해당 위치로부터 ordinal * 4 만큼 떨어진 위치에 있는 값인 EXPORT 함수의 주소(RVA)를 구한다.

5. EXPORT 함수의 주소(RVA)를 구했다면 반복문으로 AddressOfFunctions의 주소(RAW)부터 4byte씩 이동하며 값을 읽어 EXPORT 함수의 주소(RVA)와 일치한지 검사하는 방법으로 Function index를 구한다.

6. 구한 값들 name_index, name_ordinal, name RVA, name RAW, Function_index, Function_RVA을 출력한다.

 

반응형

+ Recent posts