// 과니입니다.
// 이번엔 PE (Portable Executable) 구조체를 이용하여 API 함수인 GetProcAddress함수를 직접 제작해보겠습니다.
// 참고서적은 한빛미디어의 Windows 시스템 실행파일의 구조와 원리입니다.
// 포팅할수있는 부분은 포팅하고 나머진 구현하여 제작하였습니다. (필요없는 부분은 제거도 ㅋㅋㅋ)
// 첨부파일은 PE Format Unit으로 추가해주신 후 사용하시면 됩니다.
// 별로 글제주는 없어서 팁는 주석으로 보충설명하는 식으로 하겠습니다.
// PE 구조를 활용할 수 있는 부분은 DLL내의 Export함수를 모두 뽑아올수있는 것 (물론 프로그램있지만)
// 더 나아가서는 OS Kernel 프로그래밍에도 도움이 됩니다. (아주 열심히 쭉 더 나가면 -_ -a)

uses
..., uPEFormat;

procedure
TForm1.Button1Click(Sender: TObject);
const
    SAMPLE_DLL = 'C:\Windows\System32\ntdll.dll';
    SAMPLE_NATIVE_API = 'ZwQueryInformationProcess';
var
    ProcAddr : Pointer;
    handleDll : THandle;
begin

    // LoadLibrary로 Dll을 로드하여 프로세스 주소에 매핑
    handleDll := LoadLibrary(SAMPLE_DLL);

    ProcAddr := MyGetProcAddress(handleDll, SAMPLE_NATIVE_API);

    if not Assigned(ProcAddr) then begin
        ShowMessage('함수가 매핑된 위치가 없습니다.')
    end else begin
        ShowMessage('함수 매핑된 위치 : ' + Format('%p', [ProcAddr]));
    end;

end;

function MyGetProcAddress(hModule : LongWord; pszSymbol : PChar) : Pointer;
type
    TWordArr = array [0..0] of Word;
    PWordArr = ^TWordArr;
    TLongWordArr = array [0..0] of LongWord;
    PLongWordArr = ^TLongWordArr;

var
   
// 각 파일 구조체 선언

    pImageBase : PUCHAR;
    pImageDosHeader : PIMAGE_DOS_HEADER;                       // IMAGE_DOS_HEADER
    pImageNtHeaders : PIMAGE_NT_HEADERS;                        // IMAGE_NT_HEADERS
    pImageDataDirectory : PIMAGE_DATA_DIRECTORY;             // IMAGE_DATA_DIRECTORY
    pImageExportDirectory : PIMAGE_EXPORT_DIRECTORY;      
// IMAGE_EXPORT_DIRECTORY

    dwFuncIndex : LongWord;
    pFuncName : PChar;

    FnIndex: LongWord;

begin
    Result := nil;

    pImageBase := PUCHAR(hModule);

    // 매핑되지 않았다면 종료
    // 매핑되지 않았다는 것은 DLL의 주소가 잘못되었다는 것.

    if not Assigned(pImageBase) then Exit;


    pImageDosHeader := PIMAGE_DOS_HEADER(pImageBase);

    // PE 포멧 파일인지 확인하여 PE파일이 아니라면 종료한다.
    // PE (Portable Executable) 포멧 파일은 Exe, Dll에 해당한다.
    // PE 구조는 IMAGE_DOS_HEADER, IMAGE_FILE_HEADERM, IMAGE_OPTIONAL_HEADER32 헤더로 구성된다.
    // IMAGE_DOS_SIGNATURE  0x5A4D (23117 : MZ)

    if pImageDosHeader.e_magic <> IMAGE_DOS_SIGNATURE then Exit;

    // e_lfanew : PE 헤더의 시작점을 가리키는 RVA 값이다.
    // 파일이 매핑된 주소의 시점부터 e_lfanew 값을 더하면 IMAGE_NT_HEADERS 가 나온다.

    pImageNtHeaders := PIMAGE_NT_HEADERS(LongInt(pImageBase) + pImageDosHeader.e_lfanew);

    // IMAGE_NT_SIGNATURE 를 확인한다.
    // IMAGE_NT_HEADERS의 시작지점이 $00004550 값인지 확인한다.
    // 0x00004550 (17744 : PE00)

    if pImageNtHeaders.Signature <> IMAGE_NT_SIGNATURE then Exit;

    pImageDataDirectory := @pImageNtHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];

    // IMAGE_EXPORT_DIRECTORY : export된 함수들을 담고 있는 구조체이다.
    pImageExportDirectory := PIMAGE_EXPORT_DIRECTORY(Cardinal(pImageBase) + pImageDataDirectory.VirtualAddress);

    dwFuncIndex := 0;

    // pImageExportDirectory.NumberOfFunctions : DLL 파일 내의 export된 함수의 갯수를 의미한다.
    // 처음부터 갯수만큼 돌면서 검색하려고 하는 함수가 존재하는 지 확인한다.

    while (dwFuncIndex < pImageExportDirectory.NumberOfFunctions - 1) do begin
        // 해당 주소의 함수명을 가져온다.
        pFuncName := PChar(PLongWordArr(Cardinal(pImageExportDirectory.AddressOfNames) +
                             Cardinal(pImageBase))^[dwFuncIndex] + Cardinal(pImageBase));

        // 검색하려고 하는 pszSymbol 함수와
        // DLL에서 검색된 pFuncName 함수를 비교한다.

        if StrLIComp(pszSymbol, pFuncName, Length(pszSymbol)) = 0 then begin
            // AddressOfNameOrdinals 순서수 테이블
            FnIndex := PWORDARR(Cardinal(pImageExportDirectory.AddressOfNameOrdinals) +
                            Cardinal(pImageBase))^[dwFuncIndex];

            // 검색하려는 함수명과 DLL에서 Export된 함수명이 같다면 해당 함수의 주소를 리턴한다.
            Result := Pointer(PLongWordArr(Cardinal(pImageExportDirectory.AddressOfFunctions) +
                          Cardinal(pImageBase))^[FnIndex] + Cardinal(pImageBase));
            Break;
        end;
        // 두 함수가 같지 않으면 Index를 증가해 다음 함수를 검색한다.
        inc(dwFuncIndex);
    end;
end;