3 minute read

Okay, I lied 😄 I thought I’d use part 2 to discuss removing NULL bytes, and that’s going to happen I promise! But I had some good feedback from the first part of the x64 shellcode and assembly post and some questions regarding calculating PE offsets. So, I wanted to use Part 2 to explain how I arrived at the specific offsets I used in my code. Here goes!

First let’s get a decent PE viewer. I’m using Pepper as my PE viewer of choice for viewing x64 binaries: Pepper x64 PE Viewer

I’ll walk through each part of the PE header an exports section. Let’s use the assembly code from the blog as our guide:

mov r8, rbx         ; mov kernel32.dll base addr into r8

Kernel32 base address in my case is: 00007FFA63570000

So, 00007FFA63570000 is now in r8 and rbx

mov ebx, [rbx+0x3C] (move into lower 32 bits of the rbx register, hence why we use ebx)

Our x64dbg debugger would show: dword ptr ds:[rbx+3C]=[kernel32.00007FFA6357003C]=F8

This is our PE signature offset, as seen in the image below. We first get the value pointed to by kernel32.00007FFA6357003C, which is F8, and then we add that to kernel32 image

add rbx, r8 = 00007FFA635700F8 (PE header/DOS header)

Now, we’re in [IMAGE_OPTIONAL_HEADER64] [IMAGE_DATA_DIRECTORY]

mov edx, [rbx+0x88]

This is to get the offset to our export table image

F8 + 0x88 = 180 Which equals 00000000000A3D80 for me, which is the RVA for the EXPORT TABLE

add rdx, r8

00007FFA63613D80 → address of RVA EXPORT TABLE

mov r10d, [rdx+0x14]          ; r10d (the lower 32 bits of r10) now holds the function count.

image

xor r11, r11                  ; Zero R11 before use
mov r11d, [rdx+0x20]          ; r11d (the lower 32 bits of r11) now holds the AddressOfNames RVA

00000000000A5814 in x64dbg image

add r11, r8                   ; AddressOfNames VMA

00007FFA63615814

mov rcx, r10                      ; r10 has our total function count.  Set RCX loop counter

; Loop over Export Address of Names Table to find WinApi names

kernel32findfunction: 
               
    mov ebx, [r11+rcx*4]                 ; EBX = RVA for first AddressOfName

For the instruction above, we’re using:

  • r11 = RVA of function names

  • + rcx = the place in the function list

  • * 4 = rcx * 4: Since each RVA in AddressOfNames is a 4-byte entry, multiplying rcx by 4 gives the correct offset to retrieve the RVA of a specific function name.

Once the loop finishes, our function name will have been found and the location in the function names will be stored in rcx

We push rcx then pop it into r15, which leads us here:

OrdinalLookupSetup:  ;We found our target WinApi position in the functions lookup
   pop r15           ;Winexec position
   js OrdinalLookup
   
OrdinalLookup:   
mov rcx, r15                        ;Winexec location in function names
xor r11, r11                        ;clear r11
mov r11d, [rdx+0x24]                ; AddressOfNameOrdinals RVA

X64dbg output = dword ptr ds:[rdx+24]=[kernel32.00007FFA63613DA4]=A7280 image

add r11, r8                   ; AddressOfNameOrdinals VMA

Add to kernel32 = 00007FFA63617280

; Get the function ordinal from AddressOfNameOrdinals
inc rcx
mov r13w, [r11+rcx*2]         ; AddressOfNameOrdinals + Counter. RCX = counter

Virtual memory address + rcx (1612) * 2 (bytes) = ordinal value for WinExec!!! image

;With the function ordinal value, we can finally lookup the WinExec address from AddressOfFunctions.
; Get function address from AddressOfFunctions
xor r11, r11                           ; clear r11
mov r11d, [rdx+0x1c]          ; AddressOfFunctions RVA

image

add r11, r8                   ; AddressOfFunctions VMA in R11. Kernel32+RVA for addressoffunctions
mov eax, [r11+r13*4]        ; Get the function RVA.

R11 = Relative Virtual memory address

R13 = Winexec ordinal (0x64C = 1612 decimal)

* 4 (bytes) = 1612 = Winexec

X64dbg = dword ptr ds:[r11+r13*4]=[kernel32.00007FFA636156D8]=608B0 image

add rax, r8                   ; Found the GetProcAddress WinApi!!!

Add kernel32 to our RVA of WinExec image

RAX now hold the actual address for WinExec!!!

That’s it! Now you can use Winexec to your liking and you should also have a better idea how to walk the PE and parse PE headers, explore the export directory info, etc. Thanks!

Leave a comment