/* CAT(1) */

By Jeff White (karttoon)

Meanwhile, in the land of x86 assembly...

Whether you play in the malware, exploit, research, hobbyist, or programmer space - at some point you've encountered the age-old problem of locating a function address. I can't count how many times I've stepped through code that does exactly this, but I've always taken it for granted. To truly understand how it works, I wanted to get my hands dirty and re-create the wheel so-to-speak. Throughout this blog, I'll piece together shellcode I write and explain why, or what, is happening at each phase while identifying a specific DLL (kernel32) and finding the address of a specific function (LoadLibraryA) inside of it.

Before starting on this, I read a lot of articles on the subject and, while there is a ton of information out there, I kept finding myself scratching my head at one piece or another. How did they jump from A to B? Their debugger (nicely) tells them the values here but where is it getting them from? How the hell did this guy get the address of the function before even finding it in the first place?!

I was left with incomplete pictures of the process and this basically summed up my experience after a few hours.

This won't be new information for people but maybe if someone finds him or herself in the same boat as me, they'll get something out of having it all together.

Alright, to reiterate the goals of this post - identify the address of the LoadLibraryA function inside of kernel32.dll regardless of the OS we're running on. I say that last part only because for earlier versions of Windows OS the kernel32 DLL is located at known locations, so you didn't really need to "hunt" for it. I will keep the shellcode free of null-bytes and I'll refrain from placing the strings "LoadLibraryA" or "kernel32" in the shellcode directly because...why not?

*EDIT - 15NOV2016*
User @AsselmanT pointed out that on Windows 10 the kernel32 dll name is in all uppercase and thus this shellcode won't work on that version.

Thread Information Block (TIB)

Our first step is to find the address of the Process Environment Block (PEB) by using the Thread Information Block (TIB), which is a structure that stores information about the currently running thread. The PEB will contain additional structures, which will allow us to locate the address of kernel32.dll. The structure for the TIB is below, up to the item we're interested in.

Position Length Description FS:[0x00] 4 Current Structured Exception Handling (SEH) frame FS:[0x04] 4 Stack Base / Bottom of stack (high address) FS:[0x08] 4 Stack Limit / Ceiling of stack (low address) FS:[0x0C] 4 SubSystemTib FS:[0x10] 4 Fiber data FS:[0x14] 4 Arbitrary data slot FS:[0x18] 4 Linear address of TEB FS:[0x1C] 4 Environment Pointer FS:[0x20] 4 Process ID (in some windows distributions this field is used as 'DebugContext') FS:[0x24] 4 Current thread ID FS:[0x28] 4 Active RPC Handle FS:[0x2C] 4 Linear address of the thread-local storage array FS:[0x30] 4 Linear address of Process Environment Block (PEB) ...

At FS:[0x30] we can see there will be a 4 byte pointer to the address of the PEB. You can find the address of the TIB by looking in a debugger for the base address stored in the FS register, then look at offset 0x30 for the address of the PEB structure.

Based on the screenshot above, we can see that the TIB is located at 0x7FFDF000 and at 0x7FFDF030 is the address of the PEB - 0x7FFD3000. I'll write the shellcode in sections as each piece is covered and then combine it together at the end.

; TIB 33DB XOR EBX, EBX ; Zero EDI 6A30 PUSH 0x30 ; Setup RVA for PEB 5B POP EBX ; Set EBX to 0x30 (avoids null-bytes) 648B1B MOV EBX, DWORD PTR FS:[EBX] ; Set EBX to address of PEB

Now, you could just reference this offset by placing 0x30 into the MOV command instead of the push/pop into EBX, but this would also introduce 2 null-bytes, which we want to avoid.

Process Environment Block (PEB)

Within the PEB structure, I'm after the PEB_LDR_DATA address, which is the 7th element in the structure and contains data about the loaded modules for the process. Another useful piece of data is the base image address but I won't be using it here (think quick way of getting base address for offsets during exploitation) that is located at 0x08 from the PEB base address.

typedef struct _PEB { BOOLEAN InheritedAddressSpace; BOOLEAN ReadImageFileExecOptions; BOOLEAN BeingDebugged; BOOLEAN Spare; HANDLE Mutant; PVOID ImageBaseAddress; PPEB_LDR_DATA LoaderData; PRTL_USER_PROCESS_PARAMETERS ProcessParameters; PVOID SubSystemData; PVOID ProcessHeap; ...

The pointer to the PEB_LDR_DATA structure is located at 0x0C offset from the PEB base address.

At 0x0C is our pointer to the PEB_LDR_DATA structure so we can add that value to the current base address of the PEB to get the address of the next structure - 0x778D7880.

; PEB 8B580C MOV EBX, DWORD PTR DS:[EBX+0x0C] ; Set EBX to address of PEB_LDR_DATA

PEB_LDR_DATA

The PEB_LDR_DATA structure most importantly contains the addresses for various lists of the loaded modules.

typedef struct _PEB_LDR_DATA { ULONG Length; BOOLEAN Initialized; PVOID SsHandle; LIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; } PEB_LDR_DATA, *PPEB_LDR_DATA;

The lists contain the same information but are sorted in different orders (eg sorted according to memory order or the order in which they were loaded). Since we're interested in kernel32.dll, we can iterate over one of the lists until we find the entry for the DLL.

I picked the InLoadOrderModuleList list, which is doubly-linked, to find the module. Again, we'll add 0x0C to the base address of the PEB_LDR_DATA structure to get the list RVA.

; PEB_LDR_DATA 8B580C MOV EBX, DWORD PTR DS:[EBX+0x0C] ; Set EBX to address of InLoadOrderModuleList

At 0x778D788C we find the pointer to the first entry of InLoadOrderModuleList at 0x3C1BB0 and store that in EBX.

LDR_MODULE

As already mentioned, the LDR_MODULE structure contains a doubly-linked list. Effectively, you'll have a "forward" and "backward" link to each item in the list.

typedef struct _LDR_MODULE { LIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; PVOID BaseAddress; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; ULONG Flags; SHORT LoadCount; SHORT TlsIndex; LIST_ENTRY HashTableEntry; ULONG TimeDateStamp; } LDR_MODULE, *PLDR_MODULE;

In the EBX register is the RVA to the first entry in the list, which holds a RVA/pointer to the address of the next module in the list. The main items of interest here are the BaseAddress and BaseDllName.

It's probably worth mentioning that you can use a number of these entries for fingerprinting the DLL you're after, so if you didn't want to include the Unicode string "kernel32.dll" in your shellcode, you could compare image size, hash, time stamp, file path, etc. In the end, stepping through will make it obvious so the effort isn't always worth the reward but it depends on the intent and use case.

The below is what we find at the first entry in the linked list - 0x3C1BB0. Using the structure above, the first red highlighted box is the address for the next entry in the list (also highlighted) than the following 4 bytes is the pointer for the previous entry. In this case, the previous entry is the address we just had - 0x778D788C.

If you look at the second set of red boxes, you will see that the forward link points to the next item on the list at RVA 0x3C1F68 and the back link points to the first entry at RVA 0x3C1BB0.

The green boxes show the base address for the modules and the blue box shows the pointer to the address of the module name, which is stored in Unicode. A quick look at RVA 0x3C1A4C shows "calc.exe" and at 0x77868328 we see "ntdll.dll". Looking at the Executable Modules also confirms the base address of 0x77800000 for this module.

To tie all of this together now with some shellcode we can iterate over each item in the list and compare the Unicode name found at RVA 0x30 from the beginning of the entry to "kernel32.dll".

Normally you could just include a null terminated Unicode string of the module name to compare against, but since one of the goals is to avoid that, we can mix things up a bit. Instead, lets make three comparisons at various offsets in the string to check against the module name. If they don't match at any of the three checks, the code will reset and move onto the next module.

The three offsets we want to compare against are 0x00, 0x08, and 0x10. Below is the shellcode to tackle this, along with some comments to help explain each piece.

; LDR_MODULE - InLoadOrderModuleList 33FF XOR EDI, EDI ; Zero EDI 4F DEC EDI ; Set EDI to 0xFFFFFFFF (2 bytes smaller than MOV with 0xFFFFFFFF) 81EF94FF9AFF SUB EDI, 0xFF9AFF94 ; Set EDI to UNICODE "k.e." (0x0065006B ".e.k") 8B7330 MOV ESI, DWORD PTR DS:[EBX+0x30] ; Set ESI to address of pointer to module name 8B1B MOV EBX, DWORD PTR DS:[EBX] ; Advance EBX to next module 3B3E CMP EDI, DWORD PTR DS:[ESI] ; Compare first four bytes 75EE JNZ SHORT REV 0x11 ; Jump back to check next module 8B5B04 MOV EBX, DWORD PTR DS:[EBX+0x04] ; Reset EBX to previous entry 33FF XOR EDI, EDI ; Zero EDI 4F DEC EDI ; Sed EDI to 0xFFFFFFFF 81EF9AFF93FF SUB EDI, 0xFF93FF9A ; Set EDI to UNICODE "e.l." (0x006C0065 ".l.e") 8B7330 MOV ESI, DWORD PTR DS:[EBX+0x30] ; Set ESI to address of pointer to the module name 83C608 ADD ESI, 0x08 ; Advance ESI to offset 0x08 for second compare 8B1B MOV EBX, DWORD PTR DS:[EBX] ; Advance EBX to next module 3B3E CMP EDI, DWORD PTR DS:[ESI] ; Compare second four-bytes 75D6 JNZ SHORT REV 0x29 ; Jump back to check next module 8B5B04 MOV EBX, DWORD PTR DS:[EBX+0x04] ; Reset EBX to previous entry 33FF XOR EDI, EDI ; Zero EDI 4F DEC EDI ; Set EDI to 0xFFFFFFFF 81EFD1FF9BFF SUB EDI, 0xFF9BFFD1 ; Set EDI to UNICODE "..d." (0x0064002E ".d..") 8B7330 MOV ESI, DWORD PTR DS:[EBX+0x30] ; Set ESI to address of pointer to the module name 83C608 ADD ESI, 0x08 ; Advance ESI to offset 0x10 for third compare 8B1B MOV EBX, DWORD PTR DS:[EBX] ; Advance EBX to next module 3B3E CMP EDI, DWORD PTR DS:[ESI] ; Compare third four-bytes 75C1 JNZ SHORT REV 0x3E ; Jump back to check next module 8B5B04 MOV EBX, DWORD PTR DS:[EBX+0x04] ; Set EBX to VA of list entry for kernel32.dll

When we run this, it iterates over module "calc.exe", then "ntdll.dll", and finally "kernel32.dll". At the end, the EBX register points to 0x3C1F68, which was the starting value for the third entry in the linked list shown previously. We can verify this by double-checking the entry.

a

Now that EBX holds the address for the list entry for kernel32.dll, we can grab the base address at offset 0x18.

; Store base address of kernel32.dll 8B5818 MOV EBX, DWORD PTR DS:[EBX+0x18] ; Set EBX to base address of kernel32.dll

Export Address Table (EAT)

With the base address in hand, the next step is to parse the kernel32.dll PE header structure for the Export Address Table (EAT) structure location. First things first, we need to determine the RVA to the PE structure by looking at the offset held in the DOS header structure.

We can see at 0x3C the offset to the PE header is stored in the DOS header.

So at the base address for kernel32.dll, plus the value found at 0x7614003C (0xF0), is the start of the PE header.

At RVA 0x78 from the PE header we can see the RVA for the EAT.

; EAT RVA 8B533C MOV EDX, DWORD PTR DS:[EBX+0x3C] ; Set EDX to PE signature address 8B541A78 MOV EDX, DWORD PTR DS:[EBX+EDX+0x78] ; Set EDX to EAT RVA

The EDX register now holds our RVA for EAT, 0xBF4C4.

Function Resolution - Arrays of days

Now that we have the location for the export table, we need to iterate through the three contained arrays - specifically, the AddressOfFunctions, AddressOfNames, and AddressOfNameOrdinals arrays. This part jumps around a bit but stick with me here.

The AddressOfNames (32-bit) and AddressOfNameOrdinals (16-bit) arrays are effectively linked. Each index in both array correspond to the other; AddressOfNames[0] is directly tied to AddressOfNameOrdinals[0]. AddressOfNames will contain offsets to the ASCII string name of the function, whereas AddressOfNameOrdinals will contain the corresponding functions index number in the AddressOfFunctions array. The entries in AddressOfFunctions contain RVA's for the functions starting address. To throw one more wrench into it, we have to take into account the nBase value found in the EAT, as it needs to be added to the ordinal value; this is done to align the index in the array, as the programmer doesn't have to start it at the first ordinal.

To show how this works, I'll step through resolving some names to function addresses. In the below image, the Base value is in red, the offset for the AddressOfFunctions table is in green, the offset for the AddressOfNames is in blue, and the offset for the AddressOfNameOrdinals is in purple.

The first 64 bytes of each of the arrays follow.

[+]AddrFunc array RVA 0xB4FEC 761F4FEC 33 3C 05 00 11 F3 0B 00 761F4FF4 C7 ED 0B 00 E8 ED 0B 00 761F4FFC 11 59 04 00 DF 70 03 00 761F5004 F5 25 04 00 CA B2 0A 00 [+] AddrName array RVA 0xB6528 761F6528 0F 85 0B 00 27 85 0B 00 761F6530 3C 85 0B 00 4B 85 0B 00 761F6538 54 85 0B 00 5D 85 0B 00 761F6540 6E 85 0B 00 7F 85 0B 00 [+] AddrOrd array RVA 0xB7A64 761F7A64 02 00 03 00 04 00 05 00 761F7A6C 06 00 07 00 08 00 09 00 761F7A74 0A 00 0B 00 0C 00 0D 00 761F7A7C 0E 00 0F 00 10 00 11 00

If we look at the first entry in the AddrName array, 0xB850F, and add it to the base address for kernel32.dll, we see the name of the function at 0x761F850F - AccquireSRWLockExclusive.

Looking at the corresponding first entry in the AddrOrd array we see the ordinal value 0x02. Adding the nBase of 0x01 gives us the value 0x03. We can see that AddrName[0] == AccquireSRWLockExclusive and AddrOrd[0] == 0x03, meaning that in AddrFunc[2] we have RVA 0xBEDC7, which gives us address 0x761FEDC7.

This one is probably a bad example because instead of finding the function RVA, we find another ASCII name that tells us this function was imported from NTDLL. This won't be accounted for this in the shellcode but if we did, we'd need to follow the same process of finding this new name in AddrName and the accompanying ordinal to get the function address.

Picking a different one, at AddrName[2] we have RVA 0xB853C which gives the address 0x761F853C and a function name of ActivateActCtx.

At AddrOrd[2] is 0x04, then adding the nBase value, we're left with 0x05. The 5th element in the above 64 bytes of AddrFunc is RVA 0x45911, which gives the address 0x76185911. A quick double-check of the Names in kernel32 confirms this is accurate.

Sweet! Almost there...

This is the breakdown of tasks that we now need to accomplish with the shellcode.

The shellcode will be split into three checks, similar to the module name check. Luckily, the ASCII string "LoadLibraryA" lends itself to us nicely since it's exactly 12 bytes long.

; EAT 33C9 XOR ECX, ECX ; Initialize counter for index 8B441A20 MOV EAX, DWORD PTR DS:[EDX+EBX+0x20] ; Set EAX to AddrName array RVA 33FF XOR EDI, EDI ; Zero EDI 4F DEC EDI ; Set EDI to 0xFFFFFFFF 81EFB3909E9B SUB EDI, 0x9B9E90B3 ; Set EDI to ASCII "Load" (0x64616F4C "daoL") 8BF3 MOV ESI, EBX ; Set ESI to base address 03F0 ADD ESI, EAX ; Set ESI to AddrName VA 8B36 MOV ESI, DWORD PTR DS:[ESI] ; Set ESI to RVA of first element in AddrName 03F3 ADD ESI, EBX ; Set ESI to address of first element in AddrName 83C004 ADD EAX, 0x04 ; Advance EAX to next element in AddrName 41 INC ECX ; Advance index counter 3B3E CMP EDI, DWORD PTR DS:[ESI] ; Compare first four-bytes 75E7 JNZ SHORT REV 0x18 ; Jump back to check next element 83C604 ADD ESI, 0x04 ; Advance ESI four-bytes for second compare 33FF XOR EDI, EDI ; Zero EDI 4F DEC EDI ; Set EDI to 0xFFFFFFFF 81EFB3969D8D SUB EDI, 0x8D9D96B3 ; Set EDI to ASCII "Libr" (0x7262694C "rbiL") 3B3E CMP EDI, DWORD PTR DS:[ESI] ; Compare second four-bytes 75D7 JNZ SHORT REV 0x28 ; Jump back to check next element 83C604 ADD ESI, 0x04 ; Advance ESI four-bytes for third compare 33FF XOR EDI, EDI ; Zero EDI 4F DEC EDI ; Set EDI to 0xFFFFFFFF 81EF9E8D86BE SUB EDI, 0xBE868D9E ; Set EDI to ASCII "aryA" (0x41797261 "Ayra") 3B3E CMP EDI, DWORD PTR DS:[ESI] ; Compare third four-bytes 75C7 JNZ SHORT REV 0x38 ; Jump back to check next element 49 DEC ECX ; Remove additional index since all compares succeeded 8B441A24 MOV EAX, DWORD PTR DS:[EDX+EBX+0x24] ; Set EAX to AddrOrd array RVA 6BC902 IMUL ECX, 0x02 ; Multiply index by 2 (AddrOrd 16-bit boundaries) 03C1 ADD EAX, ECX ; Add offset for index to AddrOrd array RVA 33C9 XOR ECX, ECX ; Clear ECX 66:8B0C18 MOV CX, WORD PTR DS:[EAX+EBX] ; Set CX to ordinal value 034C1A10 ADD ECX, DWORD PTR DS:[EDX+EBX+0x10] ; Add nBase to ordinal value 49 DEC ECX ; Align ECX to AddrFunc indexes 6BC904 IMUL ECX, 0x04 ; Multiply index by 4 (AddrFunc 32-bit boundaries) 8B441A1C MOV EAX, DWORD PTR DS:[EDX+EBX+0x1C] ; Set EAX to AddrFunc array RVA 03C3 ADD EAX, EBX ; Set EAX to AddrFunc array address 03C1 ADD EAX, ECX ; Set EAX to address of function RVA 8B08 MOV ECX, DWORD PTR DS:[EAX] ; Set ECX to function RVA (avoid null-byte of EAX register) 03CB ADD ECX, EBX ; Set ECX to function address

Letting this run, ECX will be the address of our sought after function.

The last part of that shellcode might be a little confusing and, admittedly, took me a little bit to wrap my head around so I'll use a quick example with the previous ActivateActCtx function to demonstrate what's happening.

When we find ActivateActCtx, which is the 3rd element in the list, ECX will equal 0x03 due to us advancing ECX for the next element since we assume the comparison will fail. As our counter for ECX needs to start at 0 so we can get the correct offset during the multiplication of the index, it means that 0x03 will be the 4th element in the array starting at 0, thus we simply move back one index with the first DEC ECX instruction.

Index 1 2 3 4 5 6 Count 0 1 2 3 4 5 Offset 0 2 4 6 8 10 ActivateActCtx | ECX = 0x03 (4th element, off by 1) DEC ECX | ECX = 0x02 (3rd element, correct) 0x2*2 = 0x04 | Offset in AddOrd array - 16-bit boundary)

Looking at the first 64-bytes of AddrOrd we can see that at +0x04 into the array, we have the ordinal 0x04. To this, we add the nBase value of 0x01 so we end up with 0x05. To get the 5th element in the AddrFunc array, we need to decrease ECX again to account for the array starting at 0.

Index 1 2 3 4 5 6 Count 0 1 2 3 4 5 Offset 0 4 8 12 16 20 Ordinal+nBase | ECX = 0x05 (6th element, off by 1) DEC ECX | ECX = 0x04 (5th element, correct) 0x4*4 = 0x10 | Offset in AddrFunc array - 32-bit boundary)

Then simply applying that offset to AddrFunc address will give you the RVA to the ActivateActCtx function.

That's a wrap

With that, we've got our address and can go on doing whatever we want. While not the most efficient way, especially given the way I decided to do the 3 checks for the module and function names, it works as expected, contains no null-bytes, and clocks in at 189 bytes.

Final shellcode:

; TIB 33DB XOR EBX, EBX ; Zero EDI 6A30 PUSH 0x30 ; Setup RVA for PEB 5B POP EBX ; Set EBX to 0x30 (avoids null-bytes) 648B1B MOV EBX, DWORD PTR FS:[EBX] ; Set EBX to address of PEB ; PEB 8B580C MOV EBX, DWORD PTR DS:[EBX+0x0C] ; Set EBX to address of PEB_LDR_DATA ; PEB_LDR_DATA 8B580C MOV EBX, DWORD PTR DS:[EBX+0x0C] ; Set EBX to address of InLoadOrderModuleList ; LDR_MODULE - InLoadOrderModuleList 33FF XOR EDI, EDI ; Zero EDI 4F DEC EDI ; Set EDI to 0xFFFFFFFF (2 bytes smaller than MOV with 0xFFFFFFFF) 81EF94FF9AFF SUB EDI, 0xFF9AFF94 ; Set EDI to UNICODE "k.e." (0x0065006B ".e.k") 8B7330 MOV ESI, DWORD PTR DS:[EBX+0x30] ; Set ESI to address of pointer to module name 8B1B MOV EBX, DWORD PTR DS:[EBX] ; Advance EBX to next module 3B3E CMP EDI, DWORD PTR DS:[ESI] ; Compare first four bytes 75EE JNZ SHORT REV 0x11 ; Jump back to check next module 8B5B04 MOV EBX, DWORD PTR DS:[EBX+0x04] ; Reset EBX to previous entry 33FF XOR EDI, EDI ; Zero EDI 4F DEC EDI ; Sed EDI to 0xFFFFFFFF 81EF9AFF93FF SUB EDI, 0xFF93FF9A ; Set EDI to UNICODE "e.l." (0x006C0065 ".l.e") 8B7330 MOV ESI, DWORD PTR DS:[EBX+0x30] ; Set ESI to address of pointer to the module name 83C608 ADD ESI, 0x08 ; Advance ESI to offset 0x08 for second compare 8B1B MOV EBX, DWORD PTR DS:[EBX] ; Advance EBX to next module 3B3E CMP EDI, DWORD PTR DS:[ESI] ; Compare second four-bytes 75D6 JNZ SHORT REV 0x29 ; Jump back to check next module 8B5B04 MOV EBX, DWORD PTR DS:[EBX+0x04] ; Reset EBX to previous entry 33FF XOR EDI, EDI ; Zero EDI 4F DEC EDI ; Set EDI to 0xFFFFFFFF 81EFD1FF9BFF SUB EDI, 0xFF9BFFD1 ; Set EDI to UNICODE "..d." (0x0064002E ".d..") 8B7330 MOV ESI, DWORD PTR DS:[EBX+0x30] ; Set ESI to address of pointer to the module name 83C608 ADD ESI, 0x08 ; Advance ESI to offset 0x10 for third compare 8B1B MOV EBX, DWORD PTR DS:[EBX] ; Advance EBX to next module 3B3E CMP EDI, DWORD PTR DS:[ESI] ; Compare third four-bytes 75C1 JNZ SHORT REV 0x3E ; Jump back to check next module 8B5B04 MOV EBX, DWORD PTR DS:[EBX+0x04] ; Set EBX to VA of list entry for kernel32.dll ; Store base address of kernel32.dll 8B5818 MOV EBX, DWORD PTR DS:[EBX+0x18] ; Set EBX to base address of kernel32.dll ; EAT RVA 8B533C MOV EDX, DWORD PTR DS:[EBX+0x3C] ; Set EDX to PE signature address 8B541A78 MOV EDX, DWORD PTR DS:[EBX+EDX+0x78] ; Set EDX to EAT RVA ; EAT 33C9 XOR ECX, ECX ; Initialize counter for index 8B441A20 MOV EAX, DWORD PTR DS:[EDX+EBX+0x20] ; Set EAX to AddrName array RVA 33FF XOR EDI, EDI ; Zero EDI 4F DEC EDI ; Set EDI to 0xFFFFFFFF 81EFB3909E9B SUB EDI, 0x9B9E90B3 ; Set EDI to ASCII "Load" (0x64616F4C "daoL") 8BF3 MOV ESI, EBX ; Set ESI to base address 03F0 ADD ESI, EAX ; Set ESI to AddrName VA 8B36 MOV ESI, DWORD PTR DS:[ESI] ; Set ESI to RVA of first element in AddrName 03F3 ADD ESI, EBX ; Set ESI to address of first element in AddrName 83C004 ADD EAX, 0x04 ; Advance EAX to next element in AddrName 41 INC ECX ; Advance index counter 3B3E CMP EDI, DWORD PTR DS:[ESI] ; Compare first four-bytes 75E7 JNZ SHORT REV 0x18 ; Jump back to check next element 83C604 ADD ESI, 0x04 ; Advance ESI four-bytes for second compare 33FF XOR EDI, EDI ; Zero EDI 4F DEC EDI ; Set EDI to 0xFFFFFFFF 81EFB3969D8D SUB EDI, 0x8D9D96B3 ; Set EDI to ASCII "Libr" (0x7262694C "rbiL") 3B3E CMP EDI, DWORD PTR DS:[ESI] ; Compare second four-bytes 75D7 JNZ SHORT REV 0x28 ; Jump back to check next element 83C604 ADD ESI, 0x04 ; Advance ESI four-bytes for third compare 33FF XOR EDI, EDI ; Zero EDI 4F DEC EDI ; Set EDI to 0xFFFFFFFF 81EF9E8D86BE SUB EDI, 0xBE868D9E ; Set EDI to ASCII "aryA" (0x41797261 "Ayra") 3B3E CMP EDI, DWORD PTR DS:[ESI] ; Compare third four-bytes 75C7 JNZ SHORT REV 0x38 ; Jump back to check next element 49 DEC ECX ; Remove additional index since all compares succeeded 8B441A24 MOV EAX, DWORD PTR DS:[EDX+EBX+0x24] ; Set EAX to AddrOrd array RVA 6BC902 IMUL ECX, 0x02 ; Multiply index by 2 (AddrOrd 16-bit boundaries) 03C1 ADD EAX, ECX ; Add offset for index to AddrOrd array RVA 33C9 XOR ECX, ECX ; Clear ECX 66:8B0C18 MOV CX, WORD PTR DS:[EAX+EBX] ; Set CX to ordinal value 034C1A10 ADD ECX, DWORD PTR DS:[EDX+EBX+0x10] ; Add nBase to ordinal value 49 DEC ECX ; Align ECX to AddrFunc indexes 6BC904 IMUL ECX, 0x04 ; Multiply index by 4 (AddrFunc 32-bit boundaries) 8B441A1C MOV EAX, DWORD PTR DS:[EDX+EBX+0x1C] ; Set EAX to AddrFunc array RVA 03C3 ADD EAX, EBX ; Set EAX to AddrFunc array address 03C1 ADD EAX, ECX ; Set EAX to address of function RVA 8B08 MOV ECX, DWORD PTR DS:[EAX] ; Set ECX to function RVA (avoid null-byte of EAX register) 03CB ADD ECX, EBX ; Set ECX to function address

Shellcode bytes:

\x33\xDB\x6A\x30\x5B\x64\x8B\x1B\x8B\x5B\x0C\x8B\x5B\x0C\x33\xFF\x4F\x81\xEF\x94\xFF\x9A\xFF\x8B\x73\x30\x8B\x1B\x3B\x3E\x75\xEE\x8B\x5B\x04\x33\xFF\x4F\x81\xEF\x9A\xFF\x93\xFF\x8B\x73\x30\x83\xC6\x08\x8B\x1B\x3B\x3E\x75\xD6\x8B\x5B\x04\x33\xFF\x4F\x81\xEF\xD1\xFF\x9B\xFF\x83\xC6\x08\x8B\x1B\x3B\x3E\x75\xC1\x8B\x5B\x04\x8B\x5B\x18\x8B\x53\x3C\x8B\x54\x1A\x78\x33\xC9\x8B\x44\x1A\x20\x33\xFF\x4F\x81\xEF\xB3\x90\x9E\x9B\x8B\xF3\x03\xF0\x8B\x36\x03\xF3\x83\xC0\x04\x41\x3B\x3E\x75\xE7\x83\xC6\x04\x33\xFF\x4F\x81\xEF\xB3\x96\x9D\x8D\x3B\x3E\x75\xD7\x83\xC6\x04\x33\xFF\x4F\x81\xEF\x9E\x8D\x86\xBE\x3B\x3E\x75\xC7\x49\x8B\x44\x1A\x24\x6B\xC9\x02\x03\xC1\x33\xC9\x66\x8B\x0C\x18\x03\x4C\x1A\x10\x49\x6B\xC9\x04\x8B\x44\x1A\x1C\x03\xC3\x03\xC1\x8B\x08\x03\xCB



Older posts...