flat assembler
Message board for the users of flat assembler.

flat assembler > Windows > Working Win32 PE exe without an import table

Author
Thread Post new topic Reply to topic
Marut



Joined: 18 Jun 2017
Posts: 7
Location: Veneto, Italy
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: 1172
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: 15870
Location: 162173 Ryugu
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
Assembly Artist


Joined: 16 Jun 2003
Posts: 6871
Location: Kraków, Poland
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: 7
Location: Veneto, Italy
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: 15870
Location: 162173 Ryugu
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: 7
Location: Veneto, Italy
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
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: 15870
Location: 162173 Ryugu
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: 514
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: 7
Location: Veneto, Italy
@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: 514
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: 68
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 © 2004-2018, Tomasz Grysztar.

Powered by rwasa.