Reversing & Exploiting With Free Tools: Part 6

In part 5, we completed our analysis of Stack4 using IDA Free. In this next part, we’ll be solving ABO1, using RADARE.

The first thing we need to do is to find the binary information located in ABO1_VS_2017.exe. Go to the folder where the executable is and extract it using rabin2.

Using RABIN2

rabin2 -l ABO1_VS_2017.exe


There is a lot of information to be learned here. For example, we see that it is compiled in x86. Since our operating system is a 64-bit Windows 10, the 32-bit executables run in what is called WOW64 (Windows on Windows 64-bit).

This is like a microworld that emulates a 32-bit system inside of our main 64-bit system. A precise definition can be found on Wikipedia and reads:

In computing on Microsoft platforms, WoW64 (Windows 32-bit on Windows 64-bit) is a subsystem of the Windows operating system capable of running 32-bit applications on 64-bit Windows. It is included in all 64-bit versions of Windows—including Windows XP Professional x64 Edition, IA-64 and x64 versions of Windows Server 2003, as well as 64-bit versions of Windows Vista, Windows Server 2008, Windows 7, Windows 8, Windows Server 2012, Windows 8.1 and Windows 10. In Windows Server 2008 R2 Server Core, it is an optional component, but not in Nano Server. WoW64 aims to take care of many of the differences between 32-bit Windows and 64-bit Windows, particularly involving structural changes to Windows itself.

It is completely transparent to the user that if you run a 32-bit compiled file, you will be unable to see any difference, as it will run without warnings, pop-ups, or anything else that would make that user will aware of any difference from the 64-bit file.

We also see information about CANARY or STACK PROTECTION COOKIE. We won’t go into detail right now, but note that it says “FALSE”, just as the NX Protection (or DEP on Windows) does. We’ll return to this later on in the  exercise.

We will now try to make the program open and run a calculator.

First, let’s gather any information that rabin2 can give us with the argument -h:



For example, we can bring up  the [Imports] using -i:

rabin2 -i ABO1_VS_2017.exe


We can also bring up the [entrypoints] from the optional header:

rabin2 -e ABO1_VS_2017.exe


And the [Strings]:

rabin2 -zz ABO1_VS_2017.exe


Some of the most important output includes the [Sections], [Segments], [Entrypoints],[Constructors],[Main],[Imports],[Symbols],[Strings], Header structures, and [Relocations], listed below:

rabin2 -g ABO1_VS_2017.exe


nth paddr          size vaddr         vsize perm name


0   0x00000400  0x10c00 0x00401000  0x11000 -r-x .text

1   0x00011000   0x6400 0x00412000   0x7000 -r-- .rdata

2   0x00017400    0xa00 0x00419000   0x2000 -rw- .data

3   0x00017e00    0x200 0x0041b000   0x1000 -r-- .rsrc


nth paddr  size vaddr  vsize perm name



vaddr=0x00401306 paddr=0x00000706 haddr=0x00000130 type=program

1 entrypoints


0 entrypoints


vaddr=0x00401060 paddr=0x00000460


nth vaddr      bind type name


1   0x00412110 NONE FUNC USER32.dll_MessageBoxA

1   0x00412000 NONE FUNC KERNEL32.dll_HeapAlloc

2   0x00412004 NONE FUNC KERNEL32.dll_RaiseException

3   0x00412008 NONE FUNC KERNEL32.dll_QueryPerformanceCounter

4   0x0041200c NONE FUNC KERNEL32.dll_GetCurrentProcessId

5   0x00412010 NONE FUNC KERNEL32.dll_GetCurrentThreadId

6   0x00412014 NONE FUNC KERNEL32.dll_GetSystemTimeAsFileTime

7   0x00412018 NONE FUNC KERNEL32.dll_InitializeSListHead

8   0x0041201c NONE FUNC KERNEL32.dll_IsDebuggerPresent

9   0x00412020 NONE FUNC KERNEL32.dll_UnhandledExceptionFilter

10  0x00412024 NONE FUNC KERNEL32.dll_SetUnhandledExceptionFilter

11  0x00412028 NONE FUNC KERNEL32.dll_GetStartupInfoW

12  0x0041202c NONE FUNC KERNEL32.dll_IsProcessorFeaturePresent

13  0x00412030 NONE FUNC KERNEL32.dll_GetModuleHandleW

14  0x00412034 NONE FUNC KERNEL32.dll_GetCurrentProcess

15  0x00412038 NONE FUNC KERNEL32.dll_TerminateProcess

16  0x0041203c NONE FUNC KERNEL32.dll_RtlUnwind

17  0x00412040 NONE FUNC KERNEL32.dll_GetLastError

18  0x00412044 NONE FUNC KERNEL32.dll_SetLastError

19  0x00412048 NONE FUNC KERNEL32.dll_EnterCriticalSection

20  0x0041204c NONE FUNC KERNEL32.dll_LeaveCriticalSection

21  0x00412050 NONE FUNC KERNEL32.dll_DeleteCriticalSection

22  0x00412054 NONE FUNC KERNEL32.dll_InitializeCriticalSectionAndSpinCount

23  0x00412058 NONE FUNC KERNEL32.dll_TlsAlloc


nth paddr       vaddr      bind type size name


1    0x00011110 0x00412110 NONE FUNC 0    imp.USER32.dll_MessageBoxA

1    0x00011000 0x00412000 NONE FUNC 0    imp.KERNEL32.dll_HeapAlloc

2    0x00011004 0x00412004 NONE FUNC 0    imp.KERNEL32.dll_RaiseException

3    0x00011008 0x00412008 NONE FUNC 0    imp.KERNEL32.dll_QueryPerformanceCounter

4    0x0001100c 0x0041200c NONE FUNC 0    imp.KERNEL32.dll_GetCurrentProcessId

5    0x00011010 0x00412010 NONE FUNC 0    imp.KERNEL32.dll_GetCurrentThreadId

6    0x00011014 0x00412014 NONE FUNC 0    imp.KERNEL32.dll_GetSystemTimeAsFileTime

7    0x00011018 0x00412018 NONE FUNC 0    imp.KERNEL32.dll_InitializeSListHead

8    0x0001101c 0x0041201c NONE FUNC 0    imp.KERNEL32.dll_IsDebuggerPresent

9    0x00011020 0x00412020 NONE FUNC 0    imp.KERNEL32.dll_UnhandledExceptionFilter

10   0x00011024 0x00412024 NONE FUNC 0    imp.KERNEL32.dll_SetUnhandledExceptionFilter

11   0x00011028 0x00412028 NONE FUNC 0    imp.KERNEL32.dll_GetStartupInfoW

12   0x0001102c 0x0041202c NONE FUNC 0    imp.KERNEL32.dll_IsProcessorFeaturePresent

13   0x00011030 0x00412030 NONE FUNC 0    imp.KERNEL32.dll_GetModuleHandleW

14   0x00011034 0x00412034 NONE FUNC 0    imp.KERNEL32.dll_GetCurrentProcess

15   0x00011038 0x00412038 NONE FUNC 0    imp.KERNEL32.dll_TerminateProcess

16   0x0001103c 0x0041203c NONE FUNC 0    imp.KERNEL32.dll_RtlUnwind

17   0x00011040 0x00412040 NONE FUNC 0    imp.KERNEL32.dll_GetLastError

nth paddr      vaddr      len size section type    string


0   0x00011172 0x00412172 30  62   .rdata  utf16le Aapi-ms-win-core-fibers-l1-1-1

1   0x000111b0 0x004121b0 28  58   .rdata  utf16le api-ms-win-core-synch-l1-2-0

2   0x000111ec 0x004121ec 8   18   .rdata  utf16le kernel32

3   0x00011200 0x00412200 7   16   .rdata  utf16le api-ms-

4   0x00011210 0x00412210 7   16   .rdata  utf16le ext-ms-

5   0x00011228 0x00412228 8   9    .rdata  ascii   FlsAlloc

6   0x0001123c 0x0041223c 7   8    .rdata  ascii   FlsFree

7   0x00011244 0x00412244 11  12   .rdata  ascii   FlsGetValue

8   0x00011258 0x00412258 11  12   .rdata  ascii   FlsSetValue

9   0x0001126c 0x0041226c 27  28   .rdata  ascii   InitializeCriticalSectionEx

10  0x0001141c 0x0041241c 8   9    .rdata  ascii   __based(

11  0x00011428 0x00412428 7   8    .rdata  ascii   __cdecl


670 0x00017264 0x00418264 11  12   .rdata  ascii   CreateFileW

671 0x00017272 0x00418272 13  14   .rdata  ascii   WriteConsoleW

672 0x00017282 0x00418282 13  14   .rdata  ascii   DecodePointer

673 0x00017292 0x00418292 14  15   .rdata  ascii   RaiseException

674 0x000172a2 0x004182a2 12  13   .rdata  ascii   KERNEL32.dll

0   0x00017400 0x00419000 7   8    .data   ascii   Vamosss

1   0x00017408 0x00419008 28  29   .data   ascii   A ejecutar la calculadora..\n

2   0x00017428 0x00419028 5   6    .data   ascii   hola\n

3   0x000176a2 0x004192a2 26  27   .data   ascii                             

4   0x00017782 0x00419382 26  27   .data   ascii   abcdefghijklmnopqrstuvwxyz

5   0x000177a2 0x004193a2 26  27   .data   ascii   ABCDEFGHIJKLMNOPQRSTUVWXYZ

6   0x000178aa 0x004194aa 26  27   .data   ascii                             

7   0x00017991 0x00419591 26  27   .data   ascii   abcdefghijklmnopqrstuvwxyz

8   0x000179b1 0x004195b1 26  27   .data   ascii   ABCDEFGHIJKLMNOPQRSTUVWXYZ

0   0x00017e60 0x0041b060 381 382  .rsrc   ascii   <?xml version='1.0' encoding='UTF-8' 
standalone='yes'?>\r\n<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>\r\n  
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">\r\n    <security>\r\n      <requestedPrivileges>\r\n        
<requestedExecutionLevel lpic      false

relocs   true

signed   false

sanitiz  false

static   false

stripped false

subsys   Windows CUI

va       true

PE file header:


  Signature : 0x4550


  Machine : 0x14c

  NumberOfSections : 0x4

  TimeDateStamp : 0x596cdac2

  PointerToSymbolTable : 0x0

  NumberOfSymbols : 0x0

  SizeOfOptionalHeader : 0xe0

  Characteristics : 0x103


  Magic : 0x10b

  MajorLinkerVersion : 0xe

  MinorLinkerVersion : 0xa

  SizeOfCode : 0x10c00

  SizeOfInitializedData : 0x7a00

  SizeOfUninitializedData : 0x0

  AddressOfEntryPoint : 0x1306

  BaseOfCode : 0x1000

  BaseOfData : 0x12000

  ImageBase : 0x400000

  SectionAlignment : 0x1000

  FileAlignment : 0x200

  MajorOperatingSystemVersion : 0x6

  MinorOperatingSystemVersion : 0x0

  MajorImageVersion : 0x0

  MinorImageVersion : 0x0

Useful Utilities Included in Radare

  • rax2 
  • rabin2 
  • rasm2 
  • radiff2 
  • rafind2 
  • rahash2
  • radare2 
  • rarun2 
  • ragg2/ragg2-cc



This is a utility that can find different hash values:

rahash2 -a all ABO1_VS_2017.exe

This option shows us hashes with all the algorithms:


We can compute the MD5 hash value for each 512 byte block:

rahash2 -B -b 512 -a md5 ABO1_VS_2017.exe

Then we can calculate the entropy for each 512 byte block:

rahash2 -B -b 512 -a entropy ABO1_VS_2017.exe


Calculate the MD5 of a string:

echo -n ”admin” | rahash2 -a md5 -s”



This utility is an expression evaluator and can be used for numerical conversion.

Convert hexadecimal from Decimal or vice-versa:


Convert to string from hex:


From String to hex:


Complete mathematical operations (don’t leave spaces between numbers and operators):


If you use the -h flag in any of these executables, it will list all the possibilities.


This utility is Radare’s assembler and disassembler.

Disassemble 32 bits (default Intel architecture):



You can assemble, for example, 32 bits:



Or you can disassemble, for example, 64 bits:

disassemble 64 bits radare

You can use the flag -a to specify the architecture (in our case Intel x86), and the flag -b lists the bit number of the architecture (32 or 64).

To see the list of supported architectures and bits, use the -L flag:



This utility is used to do diffing between binaries, which means it finds changes between different versions of a binary.

To test this, we made a series of changes in ABO1_VS_2017.exe and stored it as ABO1_VS_2017changed.exe. This second executable doesn’t work, but it’s useful for diffing and seeing the differences between both files:


We can see that radiff2 found differences between the files both in the offset 0x742 and in 0x7da. To see these differences in more detail, we could use a hex editor and go to the file offsets.

It’s helpful to know the differences between a virtual address and a file offset. The next section will go into these distinctions.

Differences Between Virtual Addresses and File Offsets

When we start using addresses, we can see that there are references to both virtual addresses and file offsets. Virtual addresses are addresses in the virtual memory that can be seen in a debugger. Addresses in the executable or file offsets (also called raw offsets or paddr in Radare) are those in the stored binary on disk and can be seen in a hexadecimal editor.

In other words, a virtual address refers to the program running in memory, and file offsets refer to the corresponding address on the hard disk where the binary file is stored and not running.

Why is it necessary to make some calculations to get one from the other?

If it were copied as is from the hard disk to memory before it’s executed, it should be very easy to do the conversion. For example, if we wanted the offset 0x742, we should just able to get the virtual address and add 0x742 to the image base (the initial address in memory). So why doesn’t it work like that?

Let’s look at the sections using rabin2:

rabin2 -S ABO1_VS_2017.exe


If there’s no randomization in the imagebase, as is the case in this example, it can be read in the header. When we use rabin -g we see all the information and the header structures. Amongst all that info is the optional header, which shows image base at 0x400000.


In the unmodified version, the offset file 0x742 i contains the byte 0x7c:


If we add 0x742 to the beginning of the file in memory (the image base 0x400000), in a perfect world we would get the same 0x7c byte:

0x400000 + 0x742 = 0x400742

Unfortunately, we can see that this address isn’t even in the code segment in memory, since it starts at 0x401000. Why is this happening?

This is because sections are not copied with same size from disk to memory. Usually more space is allocated in memory, and then it is copied. So the header in disk has a size of 0x400, while the header in memory has a size of 0x1000.

So the 0x400 from the header in disk is copied into 0x1000 of memory, which will be at the beginning of the virtual address, which OS will then pad with zeroes.


So this means when we look at the first section, if it starts on disk at  0x400, then 0x401000 in memory should be the equivalent.

First we see the binary on disk:


Now we print the address 0x401000 on memory with Radare:


It coincides, making the calculation simple. If I have 0x742, then we automatically know from its length alone that it is part of the first section, since the first part on the disk starts at 0x400 and ends at  0x11000:


We need to get the same distance on disk as there is on the beginning of the first section Once we have that distance, we can add it to 0x401000 without differences between the disk and memory.


So the difference from the beginning of the section on disk of 0x400 to the file offset of 0x742 is 0x342. Now we add that 0x342 to 0x401000.

0x401000 + 0x342 = 0x401342


There it is the same 0x72, so the offset of 0x742 corresponds to the virtual address 0x401342.

If the above explanation was confusing, just remember that the most important aspect is the mathematical formula we have derived from it:

(target virtual address) = (target raw offset) - (raw offset of section) + (virtual address of section) + (image base)
  • target virtual address =  the address we want to get in memory
  • target raw offset = file offset (0x742 in our case)
  • raw offset of section = beginning of the section where the file offset is on disk (0x400 in our case)
  • virtual address of section + image base = in our case, it would be the beginning of that same section in memory so 0x400000 + 0x1000 = 0x401000

So our formula would be:

(target virtual address) = 0x742 - 0x400  + 0x401000

This is how we get the value 0x401342.

We can do this in rax2 with the flag -k to use the same base in output:


In 0x7da we get the other file offset:


So we know that the changed address is the same in both executables--0x401342. Now we open ABO1_VS_2017.exe in radare.


Next, we’ll load symbols with idp and then  analyze the program, with aaa:


We can make it brighter using eco bright.

Then we can list the functions using afl:


The columns show the following information:

  • 1st column: virtual address
  • 2nd column: number of basic blocks in the function
  • 3rd column: size of function in bytes
  • 4th column: name of the function

In the image above, we see the line with the original byte 0x72. If we open another cmd, we can do the same with the patched ABO1:


We can display graphs comparing what it looks like if we did not do all of this work. However, we will see this later. For the time being, we can get a glimpse of all the code changes.

Static Reversing

First, we need to return to the main function.


If the symbols load correctly, we will be able to see the call to printf by its name. Otherwise, we will see a call to a numerical address.

Before continuing our analysis, we need to create a project, so we don’t lose everything we have done so far. This will allow us to recover  our work if an error occurs:


Using the Ps command will save everything that has been completed to this point. We’ll name this session abo4.

We can save periodically using different names so we can have the option to return to different points in the process. Alternately, we can always save using the same name, which will overwrite the previous version.

Since we’re in the main menu, we next use the command V! (If for some reason you are not in the main menu, first type s main.)

The difference between the command V and V! is the PANELS MODE. So using V!, we go to the visual PANELS MODE.


To go to the menu, we press the key m, navigate to the option we want using the arrow keys, then press enter. In this case, we’ll go to HEX DUMP, which was where we saw the memory previously.


Now we can see the HEX DUMP, and we can using the tab key to bring visible elements into focus.

When using tab to switch between views, the current view is surrounded with green.


Let’s return to the HEX DUMP.

If we press ENTER, we switch into ZOOM MODE. If we press ENTER again, we return to the previous state:


If we’re in ZOOM mode, we can tab to switch between the different panels in ZOOM MODE:


When we return to HEX DUMP, we can use the arrow keys to move up and down and sideways.

If we quit using Q, this will put us in visual mode with V. We’ll have to press p to change and get the HEX VIEW.


In this case, we can use the tab key to change visualizations the same way that we could tab between panels in PANELS MODE.


If we press the space bar, we can see in blocks mode:


If we want to execute any command we type in a colon(:)


After the colon key has been entered, we can type in any command. At any time, we can write q to quit.

Let’s look at the following example:


Here we see the function pdb._f.

If we press the space bar, we return to panels mode or the listing we were in:


We can rename the function using:

afn new_name old_name

Now that it looks better, let’s save it:


If we open the project in another console using the Po command, we can see we successfully saved our progress, as the new names are still in place:


Let’s continue renaming:


We can see that in the main function there’s no vulnerability that allows us to execute the calculator. We can also see that there’s a call to MessageBoxA and a call to a function1. At the end of the function, it calls to printf and exits, so if there’s any vulnerability, it would be in function1.

So let’s check function1:


We rename all the gets:


We’ll rename the variables using:

afvn new_name old_name

We should then save once more:


From here on out, if something it doesn’t work in the remainder of this exercise, you’ll need to search for the latest builds of radare. The next section will guide you through this process.

Get the latest builds of Radare

First, go to this url:


Look for the build at the top of the list that is in green (the compiled one):


Below are the JOBS. Click on any of the green ones:


Next, go to ARTIFACTS.


Note: Whenever you need to search for something recently added, it’s best to search in the latest compiled versions.


AFVF tells us that buffer variable is 0x400 above of the horizon.



|           -0x400 BUFFER                       |

|                                                            |

|                                                            |

| ------------------------------- HORIZON ---|

| 0  - STORED EBP                              |

| 4 - RETURN ADDRESS                     |


This is the static map of the stack.

We can see that buffer is EBP-0x400.


If something is wrong with a variable type, it can be modified with AFVT.


As has been stated earlier, the goal of this ABO is to run the calculator.

Here, we can see that  the address of buffer is given directly as an argument to gets. It can be overflowed since there’s no limitation to the bytes we can write:

----------------------------------------------------  < ----------

|           -0x400 BUFFER                       |                |

|                                                             |                |

|                                                             |                |

| ------------------------------ HORIZON ---|                |

| 0  - STORED EBP                              | < -----------

| 4 - RETURN ADDRESS                     |


We need to fill the selected zone that is the 0x400 of buffer and 4 of STORED EBP. Using a script, we should be able to replace the return address with 0x41424344.



Now radare’s debugger has a bug to attach, we can  debug it with x64dbg (version of 32-bits). Note: It works fine if we start the binary from the beginning.


Let’s put a breakpoint after MessageBoxA. If we accept the message, it will stop there.

If we  use the f7 key, it will trace into the function. After completing the LEA instruction in EAX, we will have the address of buffer.


If I usef8, it will step over gets.


We can see that the buffer is filled with ‘A’s, so we can continue until the RET (return).


It will jump to 0x41424344, which means we’ve controlled the execution.

We can also see that EAX still points to the buffer.


This means that if we find a CALL EAX or JMP EAX in the code section, we can jump to execute the code in the buffer because there’s no DEP or ASLR.


rasm2 -a x86 -b 32 "call eax"

Rasm2 will tell us the byte sequence in the CALL EAX.


The byte sequence is ffd0. We canL look for the ffd0 byte string using the search in radare:

/x ffd0


We can see that there is a CALL EAX in 0x4024b4, so let’s jump there:


Since we are already executing code, we can add a shellcode to execute a calculator.  We’ll analyze how this works later on:


*The above image has a misprint. The decode shown above only works that way in python 2. The shellcode is decoded as follows in python 3:

import codecs

decode_hex = codecs.getdecoder("hex_codec")

winexec_calc_shellcode = decode_hex('33d2526863616c6389e65256648b72308b760c8b760cad8b308b7e188b5f3c8b5c1

So if we execute the scripts, we’ll see:


If we run it again and attach it to the x64dbg in the MessageBoxA that has a breakpoint set, as we did earlier in the exercise.

We’ll stop there.


Using f7, we’ll trace into the function.

After executing the LEA instruction in EAX, the buffer pointer is still there.


Within the buffer, there is the shellcode, and then there are all of the ‘A’s.


We’ll trace until the RET.


It will then jump to 0x4025b4. Let’s continue tracing using f7.


Here we can see the CALL EAX that we found earlier.


And there is the SHELLCODE in my buffer.


Let’s see how  the 32-bit resolver works.  First, we’ll find out if continuing the execution will allow the calculator to run:


Standalone executable with Shellcode

For this task, there’s more than one way to skin a cat. There are some quick and dirty ways, and some careful and elaborate ways. It depends on the situation.

The following method is quick and dirty, but in love, war, and reversing, anything goes.

The task at hand here is to analyze the shellcode. The best way is to run it in an independent environment. If it is included in an executable, we could analyze it manually at the same time we’re running it in ABO01. This method is slightly trickier and time consuming if we have to re-run it multiple times.

However, if you want to do it in the ABO1, feel free to do so.

In this method, we’re going to create an executable with the shellcode to run it in a standalone environment. This will open the calculator., We can do that by running OLLYDBG.exe with, for example, the stack4 (any executable will work).


Next we’ll copy the shellcode from the script.


Make sure it’s stopped at the entry point and not in a dll.


We’ll mark big grey zone with the mouse and right click in BINARY PASTE.


We’ll wait until the last byte has been copied. In this case, the last byte is d7. It will be in red, so it will be easy to spot.

Now in the same area that’s been greyed out, let’s right click, and select copy to executable.


In the new window that opens, we’ll again right click, and this time select save file. We’ll choose a name for the new executable and  save it in an easily accessible place.


If we did everything correctly, running SHELLCODE_NEW.exe will open the calculator. We’ll now have the shellcode in a separate executable, ready to parse.

Next we will use WINDBG for the first time.


We saw in OLLYDBG that the beginning of the shellcode copies to the entry point 0x401306.

We’ll begin by launching the executable SHELLCODE_NEW.exe in WINDBG, and wait until it breaks.


We’ll set a breakpoint at 0x401306.


If we press G, it will stop at the breakpoint at the beginning of the shellcode.


We can see that EDX is set to zero, and then moves the value of FS:[EDX+0x30] to ESI. Since EDX is zero, the value of FS:[0x30] corresponds to the table called Thread Environment Block (TEB).

Threat Environment Block (TEB)

If we search for TEB on google, we’ll find the Wikipedia page, which has relevant information we can use.  

If you scroll down the page, we can find and copy the import table we need, which is the Process Environment Block (PEB):


In WINDBG we’ll list its content.


Certain nt structures can be accessed with the command dt, then nt!. Then we add the name with an underscore in front of it:


While this doesn’t always work, we were successful here, and can see that the 0x30 field is the pointer to the PEB.

Process Environment Block (PEB)

Let’s look at the base of the fs register using the command dg fs.


While your machine may show something different, in this example, the fs registry has a base of 0x3bc000. This means it is fs:[0].  If we add 0x30 we will have the PEB that it is in—fs:[0x30].


We can now list the fields again, this time with the base address of fs.


Having found the pointer to the PEB, let’s see if it can also be listed with WINDBG.

Since there is a link, we can simply click it:


We can see the listed PEB. Next, let’s see what field the shellcode reads.


In the offset 0xc, the field  shows LDR, which is a  type of _PEB_LDR_DATA.


With this useful information, we can continue.

Let’s click on the link that WINDBG gives us in LDR:


First, let’s click the InLoadOrderModuleList link in WINDBG.


We can see the value that the shellcode reads on my machine. This is an LDR_DATA_TABLE_ENTRY or _LDR_MODULE structure.


This double linked list structure has two LIST_ENTRY fields, FLINK and BLINK. The first structure of the linked list corresponds to the executable. To access the next structure we use FLINK.


Using the address in ESI, we can list the first structure in Windbg.


We see there’s a structure of type LDR_DATA_TABLE_ENTRY for each module. In this case, the base is 0x400000 and the name is shellcode_NEW.exe. So, the first entry is the executable.

This is going to read the content of ESI. The FLINK will point to the next structure, in this case, 0x714848. 


This corresponds to the structure _LDR_TABLE_ENTRY of ntdll.dll.

We see that it goes again to another FLINK, reading the content of EAX. This reads the structure of next module:


We see that it found the structure of kernel32.dll, and in the offset 0x18 we have the DllBase or Image base of this module.


The structure where it starts the header that is  in imagebase is called _IMAGE_DOS_HEADER. We see the MZ of two bytes at the beginning of the DOS executable.



First, the shellcode searches for the field 0x3c.


The value is 248 or 0xf8.


The _IMAGE_NT_HEADERS is at 0xf8.



To get the address and list the contents in the Windbg fields, we’ll add IMAGE_NT_HEADERS to the base of kernel32.dll.


Remember that the shellcode searches the field 0x78. The OptionalHeader is in 0x18 Let’s click there next.

This goes to 0x60, where the DataDirectory is located.  If we add 0x60 to 0x18, this makes 0x78.  This means the shellcode is searching the data directories.



0x18 + 0x60 of the DataDirectory field = 0x78


This image above is what it will read.



Each _IMAGE_DATA_DIRECTORY has two fields: VirtualAddress and Size.


In EBX, we have the offset in the header of_IMAGE_DATA_DIRECTORY_ARRAY.

If it’s simpler, you can also open kernel32 in C:\Windows\SysWow64.


Search the header.


Switch to header view.

Search for 0xf8.


We can double click on the first column to sort from 0 to 0xf8.


We can now easily look for 0x78.


We get the same value reading 0x972c0 which is _IMAGE_DATA_DIRECTORY_ARRAY. In this case this  is the VirtualAddress of the EXPORT TABLE.


Now we can add the value 0x972c0 to the base.


Now, we are in this structure:

typedef struct _IMAGE_EXPORT_DIRECTORY {

  uint32_t Characteristics;

  uint32_t TimeDateStamp;

  uint16_t MajorVersion;

  uint16_t MinorVersion;

  uint32_t Name;

  uint32_t Base;

  uint32_t NumberOfFunctions;

  uint32_t NumberOfNames;

  uint32_t** AddressOfFunctions;

  uint32_t** AddressOfNames;

  uint16_t** AddressOfNameOrdinal;


AddressOfFunctions is an RVA that points to an array of function addresses. The function addresses, however, are also RVAs. AddressOfNames is a pointer to a list of function names. All of these addresses are RVAs and must be added to the image base in order to properly obtain the function name and address.

AddressOfNameOrdinal is an RVA to a list of ordinals. The ordinals, being just numbers representing the exported functions and not addresses, are not RVAs.


These are pointers to three important tables which are the offsets 0x1c, 0x20 and 0x24.

The first two are tables of pointers (4 bytes) and third one is a table of words (2 bytes).

The AddressOfNames is at the offset 0x20 of IMAGE_EXPORT_DIRECTORY.


By adding the base to the AddressOfNames, which was in EDI, we now have the address of that array of pointers with the name of exported functions in ESI.



In the offset 0x24 of IMAGE_EXPORT_DIRECTORY, we’ll find the offset of the list AddressOfNameOrdinal. This will be added the base in EDI and saved in ECX:




We see that the index to go through the table is in EDX, and is incremented by 1. This is multiplied by 2 because each ordinal is a word.


Read the first ordinal to EBP.

We’ll use LODS in EAX to load the first pointer to names, because ESI pointed to those names and increments ESI by 4.


This shows what it looks like after running the LODS.


The ESI increases by four.  We’ll adds the base from EDI to EAX and compare it.

We see that the string is:


Compare the first 4 bytes with 456E6957h (WinE).


If we change the constant for the first 4 characters of the name by any exported function of kernel32.dll we can get its address.

Since it is different, it looks for the next ordinal.


It will go through each string until it finds WinExec.


Now search the third table AddressOfFunctions that was in 0x1c,  because it used AddressOfNames and AddressOfOrdinal already.


This adds the imagebase that was in EDI, and is now in the ESI AddressOfFunctions.



We see the offsets to the functions.

The ordinal of WinExec in EBP must be a multiple of 4, because pointers go by 4. It then adds it to ESI.


The imagebase is EDI adds the offset to WinExec. This leaves  address of WinExec in EDI.


Using CALL EDI, it jumps to WinExec.  Before the jump, we pushed a pointer to a string calc in the stack.


The result executes the calculator without hardcoding and resolving. This is why it is called a RESOLVER.

Let’s press G.


Most resolvers work in the same way. They look for the export table of a DLL, and then loop until they find the name and address of the table.

I hope this will encourage you to solve ABO2, which we will do with GHIDRA in the next lesson.  See you in part 7!


Want to learn more pen testing techniques?

CTA Text

Watch our webinar, Getting Inside the Mind of an Attacker: Going Beyond the Exploitation of Software Vulnerabilities and get expert insight into how threat actors take advantage of weaknesses found in software, configuration, and identity.