flat assembler
Message board for the users of flat assembler.

Index > Windows > Working Win32 PE exe without an import table

Author
Thread Post new topic Reply to topic
Marut



Joined: 18 Jun 2017
Posts: 12
Location: Veneto, Italy
Marut 30 Aug 2017, 00:59
DISCLAIMER: this is just for fun. Do not use this code in your programs, it's not reliable enough.

So, just for the sake of it I created a little proggy that gets to the point of being able to import any Win32 API function, but starts out without a PE import table. Thought I could share it here.
Although it does its job, the code is a bit raw:
  • This program scans through the export directory of KERNEL32.DLL sequentially; the PE format enforces alphabetical ordering on the exported functions, so the most efficient approach would be a binary search, but that means more code and more hassle, and I felt lazy;
  • To find the base of the KERNEL32.DLL (or KERNELBASE.DLL) image, this program combs the memory looking for a MZ signature, starting from the return address on the stack and working its way backwards, so it assumes that the call to the entry point of the function is done from one of the above, and that the DLL code segment is AFTER the image base (not sure if it's 100% guaranteed); this part could use a few more safety checks, but heh, it's alright. An alternative approach would be to look through the Process Environment Block Loader Data Structure for the image base, but according to Microsoft this approach is not future-proof, and also that would mean searching for the library in the list of loaded modules by name, which means implementing case-insensitive string comparisons in the code and, again, I felt lazy;
  • Here I assume that GetProcAddress and LoadLibraryA are present in the module I find; if not, the program could theoretically snap onto different functions thinking they're the right ones; and yes, the implementation is definitely wacky.
  • Instead of looking for ExitProcess this way, you could look for it manually like with GetProcAddress and LoadLibraryA, but I wanted to keep the code small;
  • If you increase STEP from 1000h (= page granularity) to 10000h (= allocation granularity) the code still seems to work and with a lot fewer access violations, but then you rely on the fact that the image base is copied on a newly allocated memory chunk, which might not necessarily be true on all versions of Windows (past/future)

Code:
; working EXE file without an import table

format PE GUI NX
entry start

STEP                    = 1000h
MB_ICONINFORMATION      = 64

include 'macro\proc32.inc'

section '.text' code readable executable

  start:
        mov     eax,[esp]               ; return address is (supposedly) inside kernel32.dll

        push    ExceptionHandler        ; install exception handler
        push    dword [fs:0]
        mov     [fs:0],esp

        and     eax,-STEP               ; scan for MZ header
        lea     ecx,[eax-400000h]
        mov     [infLimit],ecx
 searchLoop:
        mov     ecx,[eax]               ; might cause access violations
        sub     eax,STEP
        cmp     cx,'MZ'
        jne     searchLoop

        mov     esi,[esp]               ; uninstall exception handler
        mov     [fs:0],esi
        add     esp,$8

        add     eax,STEP+3ch
        lea     esi,[eax-3ch]           ; esi = base of the executable
        mov     ax,[eax]                ; offset of PE header
        cmp     dword [eax],'PE'
        jne     crash
        mov     eax,[eax+78h]           ; export directory
        lea     edx,[eax+esi]           ; edx = actual address of export directory
        mov     ecx,[edx+18h]           ; NumberOfNames
        mov     ebp,ecx                 ; ebp = NumberOfNames
        mov     eax,[edx+20h]           ; AddressOfNames
        add     eax,esi

        mov     ebx,'GetP'              ; manually scan export table for GetProcAddress
        call    subFunctionScan
        add     esi,4
        mov     ebx,'rocA'
        call    subFunctionScan
        add     esi,4
        mov     ebx,'ddre'
        call    subFunctionScan
        add     esi,3
        mov     ebx,'ess'
        call    subFunctionScan
        sub     esi,11
        mov     ebx,ecx
        sub     ecx,ebp
        neg     ecx
        mov     edi,[edx+24h]           ; AddressOfNameOrdinals
        add     edi,esi
        movzx   ecx,word [edi+2*ecx]
        mov     edi,[edx+1ch]           ; AddressOfFunctions
        add     edi,esi
        mov     edi,[edi+4*ecx]
        add     edi,esi
        mov     [GetProcAddress],edi

        mov     ecx,ebx                 ; resume scan, looking for LoadLibraryA
        mov     ebx,'Load'
        call    subFunctionScan
        add     esi,4
        mov     ebx,'Libr'
        call    subFunctionScan
        add     esi,4
        mov     ebx,'aryA'
        call    subFunctionScan
        add     esi,1
        mov     ebx,'ryA'
        call    subFunctionScan
        sub     esi,9
        sub     ecx,ebp
        neg     ecx
        mov     edi,[edx+24h]           ; AddressOfNameOrdinals
        add     edi,esi
        movzx   ecx,word [edi+2*ecx]
        mov     edi,[edx+1ch]           ; AddressOfFunctions
        add     edi,esi
        mov     edi,[edi+4*ecx]
        add     edi,esi
        mov     [LoadLibrary],edi

        stdcall edi,_kernel32           ; get handle and increment the process's reference count for
        test    eax,eax                 ; kernel32, just for good measure
        jz      crash
        mov     [hKernel32],eax

        invoke  GetProcAddress,eax,_ExitProcess ; get address for ExitProcess (could have been done like the above)
        test    eax,eax
        jz      crash
        mov     [ExitProcess],eax

        invoke  LoadLibrary,_user32     ; load user32.dll
        test    eax,eax
        jz      crash
        mov     [hUser32],eax

        invoke  GetProcAddress,eax,_MessageBox  ; get address for MessageBoxA
        test    eax,eax
        jz      crash
        mov     [MessageBox],eax

        invoke  MessageBox,0,_msgGotIt,_msgGotItTitle,MB_ICONINFORMATION
        invoke  ExitProcess,0


subFunctionScan:
        sub     ecx,1
        jb      crash
        mov     edi,[eax]
        add     eax,4
        cmp     [esi+edi],ebx
        jne     subFunctionScan
        sub     eax,4
        add     ecx,1
        ret

crash:
        hlt

ExceptionHandler:
        mov     eax,[esp+0ch]                   ; get CONTEXT
        mov     ecx,[eax+0b0h]                  ; get eax
        cmp     dword [eax+0b8h],searchLoop     ; verify that eip = searchLoop
        jne     @f
        cmp     ecx,[infLimit]                  ; if the scan crossed the lower boundary, just give up
        jb      @f
        sub     ecx,STEP                        ; update eax
        mov     [eax+0b0h],ecx
        xor     eax,eax
        ret
     @@:
        mov     eax,1
        ret


section '.data' data readable writable

_kernel32               db 'KERNEL32.DLL',0
_user32                 db 'USER32.DLL',0
_ExitProcess            db 'ExitProcess',0
_MessageBox             db 'MessageBoxA',0
_msgGotIt               db 'Have a nice day!',0
_msgGotItTitle          db 'Got it!',0

align 4
infLimit                dd ?
hKernel32               dd ?
hUser32                 dd ?
GetProcAddress          dd ?
LoadLibrary             dd ?
ExitProcess             dd ?
MessageBox              dd ?
                                
Post 30 Aug 2017, 00:59
View user's profile Send private message Reply with quote
Furs



Joined: 04 Mar 2016
Posts: 2565
Furs 30 Aug 2017, 10:27
Epic hack Wink

At first I thought it used hardcoded system calls for specific Windows versions.
Post 30 Aug 2017, 10:27
View user's profile Send private message Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20451
Location: In your JS exploiting you and your system
revolution 30 Aug 2017, 10:38
These no-imports files have been around for a while now, there are some old examples already on this board. IIRC they only work on some versions of Windows. Win2000 and WinXP did things differently and one would run them while the other wouldn't. It's been a while since I tested any of these, so perhaps Win7, 8, 8.1 and 10 have changed things in the loader?
Post 30 Aug 2017, 10:38
View user's profile Send private message Visit poster's website Reply with quote
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8359
Location: Kraków, Poland
Tomasz Grysztar 30 Aug 2017, 13:09
It might be worth it to add a reference to the old (12 years old!) discussion: https://board.flatassembler.net/topic.php?p=27846
Post 30 Aug 2017, 13:09
View user's profile Send private message Visit poster's website Reply with quote
Marut



Joined: 18 Jun 2017
Posts: 12
Location: Veneto, Italy
Marut 30 Aug 2017, 23:40
Oh cool, I see you used more or less the same approach revolution, with some differences here and there. So I guess mine wouldn't work on Win2K as well.
Vasilev instead walked through the PEB, it seems.

I don't know what changes were made to the loader, but from a quick test it seems that in Windows 10 ntdll.dll, KERNEL32.DLL and KERNELBASE.DLL are always mapped inside a process's virtual space; if the process is 32-bit and the OS is 64-bit, wow64.dll, wow64win.dll and wow64cpu.dll are loaded too.
Post 30 Aug 2017, 23:40
View user's profile Send private message Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20451
Location: In your JS exploiting you and your system
revolution 01 Sep 2017, 16:02
Also, some AVs panic when they see non-standard exe file like these. So even if the OS itself successfully runs it, other users may find their AV blocks it. And then you get an angry messages accusing your software of being malware. Sad
Post 01 Sep 2017, 16:02
View user's profile Send private message Visit poster's website Reply with quote
Marut



Joined: 18 Jun 2017
Posts: 12
Location: Veneto, Italy
Marut 01 Sep 2017, 20:32
Yeah, tell me about it. I have Avast, whenever I compile anything my AV delays the execution of the program to scan it first, I have to interrupt the scan manually every time. Not only with FASM, even with MSVC++.
And to add insult to injury, small executables like those produced with FASM have a higher chance to trigger the heuristic algorythms inside Avast; when I downloaded the FASM64 pack it was a parade of false positives. If I want to program without losing my mind, I just have to turn the AV off temporarily.
Post 01 Sep 2017, 20:32
View user's profile Send private message Reply with quote
emily



Joined: 02 Sep 2017
Posts: 1
emily 02 Sep 2017, 00:33
Very cool! A tip for getting the base of kernel32.dll from the loader return address on the stack is to align the return address to 64k and then go back one 64k increment. this works because all modules are located on a 64k boundary.
Code:
IMG_BASE  = 10000h
entry:
  mov     eax, [esp]
  xor     ax, ax               ; align to 64k
  lea     ebx, [eax - IMAGE_BASE]  ; go back 64k    

You can also get tricky and jump directly to the PE header (+100h) or optional header by doing something like
Code:
lea     ebx, [eax - IMAGE_BASE + 100h]    
Post 02 Sep 2017, 00:33
View user's profile Send private message Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20451
Location: In your JS exploiting you and your system
revolution 02 Sep 2017, 09:16
emily wrote:
Very cool! A tip for getting the base of kernel32.dll from the loader return address on the stack is to align the return address to 64k and then go back one 64k increment. this works because all modules are located on a 64k boundary.
Code:
IMG_BASE  = 10000h
entry:
  mov     eax, [esp]
  xor     ax, ax               ; align to 64k
  lea     ebx, [eax - IMAGE_BASE]  ; go back 64k    

You can also get tricky and jump directly to the PE header (+100h) or optional header by doing something like
Code:
lea     ebx, [eax - IMAGE_BASE + 100h]    
Yes, this works. But unfortunately this is not guaranteed to work, it just happens to be the behaviour of the current version(s) of Windows. Sometime in the future this could change.
Post 02 Sep 2017, 09:16
View user's profile Send private message Visit poster's website Reply with quote
fasmnewbie



Joined: 01 Mar 2011
Posts: 555
fasmnewbie 04 Sep 2017, 18:38
Some code you have back there, Marut. Refreshing.

To save you from risk of violations, you could change your STEP to 0x40000. This works for both 64-bit and 32-bit versions of Windows. kernel32.dll is apparently considered an application and not a DLL if you know what I mean. ANDing it with -0x40000 will take you straight to the base address. Just my 2 cents worth.
Post 04 Sep 2017, 18:38
View user's profile Send private message Visit poster's website Reply with quote
Marut



Joined: 18 Jun 2017
Posts: 12
Location: Veneto, Italy
Marut 08 Sep 2017, 20:57
@emily: thank you. Yes, that's what I'm saying in the last bullet of my first post; in fact, the original draft of the program had to work that way, because I didn't have an exception handler set up yet. In the end I opted to use STEP = 4k for the reason explained by revolution.

@fasmnewbie: Thanks, I didn't know that. Anyway, access violations don't worry me (SEH is there for a reason) as much as the risk of missing the MZ header. I kinda chose the "paranoid" route here.
Post 08 Sep 2017, 20:57
View user's profile Send private message Reply with quote
fasmnewbie



Joined: 01 Mar 2011
Posts: 555
fasmnewbie 09 Sep 2017, 19:33
Marut, I think your approach is more towards completeness rather than "paranoid". I developed about the same (incomplete) code in 64-bit mode some time back and never hit any incompatibility wall on 64-bit win with STEP = 0x400000. But only tested it on 2 machines. Could behave differently on others though. Gave up early because the binary output really bleeds my eyes.

Added some more descriptive comments... not sure if it offers anything new compared to yours. Attempted to access msvcrt.dll from it. No SEH. No Ordinals.

p/s I think Tomasz know more about this when creating that FASM internal PE linker. Should ask him.

Code:
;-----------------------------------
;Creating .exe by calling
;the 64-bit DLLs directly without header
;Lazy string compare. Just for fun.
;-----------------------------------
format PE64 console
entry main

KernelBase      dq 0
PEHeader        dq 0
PEOptHeader     dq 0
Directory       dq 0
IMG_EXPORT      dq 0
AddrOfNames     dq 0
AddrOfFunctions dq 0
NumOfFunctions  dq 0
LoadLibAddr     dq 0
ExitProcessAddr dq 0
GetProcAddr     dq 0
FreeLibAddr     dq 0
s               db 'ExitProcess',0
t               db 'LoadLibraryA',0
m               db 'msvcrt.dll',0
x               db 'printf',0
g               db 'GetProcAddress',0
c               db 'getchar',0
l               db 'FreeLibrary',0
h               db 'Hello World, no header',0ah,0
BASE            = 0x40000


main:
        mov     rsi,[rsp]         ;Somewhere in kernel32.dll
        and     rsi,-BASE         ;Point to KernelBase Address
        mov     [KernelBase],rsi

        movzx   rbx,byte[rsi+3ch]
        add     rsi,rbx           ;Points to PEHeader
        mov     [PEHeader],rsi
        add     rsi,4*6           ;Points to PEOptHeader
        mov     [PEOptHeader],rsi

        add     rsi,112           ;Offsets from PEOptHeader
        mov     [Directory],rsi   ;96 for win32

        mov     ebx,[rsi]         ;RVA to Export
        mov     rax,[KernelBase]
        add     rax,rbx           ;Point to IMAGE_EXPORT_DIRECTORY
        mov     [IMG_EXPORT],rax  ;save it
        mov     ebx,[rax+4*6]
        mov     [NumOfFunctions],rbx

        add     rax,4*8           ;point to "AddrOfNames" field
        mov     [AddrOfNames],rax
        mov     ebx,[rax]         ;get RVA to Name

        mov     rax,[KernelBase]
        mov     ebx,[rsi]
        add     rax,rbx
        add     rax,4*7
        mov     [AddrOfFunctions],rax


;Get the kernel32.dll function addresses
;to be used in the subsequent code
get_all_address:
        mov     rbx,g
        call    Probe_Function_Address
        mov     [GetProcAddr],rax

        mov     rbx,t
        call    Probe_Function_Address
        mov     [LoadLibAddr],rax

        mov     rbx,s
        call    Probe_Function_Address
        mov     [ExitProcessAddr],rax

        mov     rbx,l
        call    Probe_Function_Address
        mov     [FreeLibAddr],rax


;Test and run all functions with the
;loaded addresses. Observe calling conventions
run_all:
        sub     rsp,40

        ;Load "msvcrt.dll" library
        ;Base Address in RAX
        mov     rcx,m
        call    [LoadLibAddr]
        mov     r15,rax

        ;Get "printf" address from "msvcrt.dll"
        ;Address in RAX
        mov     rdx,x    ;Function string to find
        mov     rcx,rax  ;Base Address of msvcrt.dll
        call    [GetProcAddr]

        ;Call "printf" from "msvcrt.dll"
        mov     rcx,h
        call    rax      ;call printf

        ;Get "getchar" address from "msvcrt.dll"
        ;Address in RAX
        mov     rcx,r15  ;Address of msvcrt.dll
        mov     rdx,c    ;Function string to find
        call    [GetProcAddr]
        call    rax      ;call getchar

        ;Call "FreeLibrary" from "kernel32.dll"
        ;Requires input from LoadLibrary's return
        mov     rcx,r15
        call    [FreeLibAddr]
        add     rsp,40


done:   mov     rcx,0
        call    [ExitProcessAddr]


;-----------------------------------
;Probe_Function_Address RBX/1
;Get a kernel32.dll's function address
;-----------------------------------
;RBX    : pointer to function string to probe
;-----------------------------------
;Return : RAX - Address of function
;NOTE   : This doesn't load a library
;         Load only once the address is known
;       : Probe kernel32.dll APIs only
;-----------------------------------
align 16
Probe_Function_Address:
        mov     rax,[AddrOfNames] ;address of RVA from IMPORT
        mov     edx,[rax]         ;Get the RVA field
        mov     rax,[KernelBase]  ;Start from BASE
        add     rax,rdx           ;Go Straight to Array of AddrOfNames
        mov     rcx,[NumOfFunctions]
        mov     rbx,qword[rbx]
        mov     rsi,0
.fString:
        mov     edx,dword[rax]
        add     rdx,[KernelBase]
        mov     rcx,[rdx]
        cmp     rbx,rcx
        je      .fAddress
        add     rax,4
        add     rsi,1
        sub     rcx,1
        jnz     .fString
.fAddress:
        mov     rax,[IMG_EXPORT]
        add     rax,4*7           ;point to "AddrOfFunctions" field
        mov     ebx,[rax]         ;get the field value (RVA)
        mov     rax,[KernelBase]  ;start from BASE
        add     rax,rbx           ;BASE + RVA of AddrOfFunctions
        mov     ecx,[rax+rsi*4]   ;Get RVA
        mov     rax,[KernelBase]  ;Start from BASE
        add     rax,rcx           ;BASE + RVA
        ret    
Post 09 Sep 2017, 19:33
View user's profile Send private message Visit poster's website Reply with quote
jochenvnltn



Joined: 15 Jul 2011
Posts: 96
jochenvnltn 16 Oct 2017, 08:56
Cool Smile But there is no need to use LoadLibrary on user32.dll

Code:
proc GetUser32

        mov  esi, [fs:30h] ; get a pointer to the PEB
        mov  esi, [esi+0Ch]; PEB.lpLoaderData
        mov  esi, [esi+1Ch]; PEB_LDR_DATA.InInitializationOrderModuleList
@@:     mov  ecx, [esi+8h] ; LDR_MODULE.BaseAddress
        mov  edi, [esi+20h]; LDR_MODULE.BaseDllName+UNICODE_STRING.Buffer
        mov  esi, [esi]
        cmp  dword [edi+08h],0320033h
        jne  @b
        mov  eax,ecx
        ret
endp
    
Post 16 Oct 2017, 08:56
View user's profile Send private message MSN Messenger Reply with quote
Display posts from previous:
Post new topic Reply to topic

Jump to:  


< Last Thread | Next Thread >
Forum Rules:
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


Copyright © 1999-2025, Tomasz Grysztar. Also on GitHub, YouTube.

Website powered by rwasa.