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

Image

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:

Image

 

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

rabin2 -i ABO1_VS_2017.exe

Image

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

rabin2 -e ABO1_VS_2017.exe

Image

And the [Strings]:

rabin2 -zz ABO1_VS_2017.exe

Image

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

[Sections]

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


[Segments]


nth paddr  size vaddr  vsize perm name

--------------------------------------


[Entrypoints]

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


1 entrypoints

[Constructors]


0 entrypoints

[Main]

vaddr=0x00401060 paddr=0x00000460

[Imports]

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

[Symbols]


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:

IMAGE_NT_HEADERS

  Signature : 0x4550

IMAGE_FILE_HEADERS

  Machine : 0x14c

  NumberOfSections : 0x4

  TimeDateStamp : 0x596cdac2

  PointerToSymbolTable : 0x0

  NumberOfSymbols : 0x0

  SizeOfOptionalHeader : 0xe0

  Characteristics : 0x103

IMAGE_OPTIONAL_HEADERS

  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

 

RAHASH2

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:

Image

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

Image

Calculate the MD5 of a string:

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

Image

RAX2

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

Convert hexadecimal from Decimal or vice-versa:

Image

Convert to string from hex:

Image

From String to hex:

Image

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

Image

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

RASM2

This utility is Radare’s assembler and disassembler.

Disassemble 32 bits (default Intel architecture):

Image

 

You can assemble, for example, 32 bits:

 

Image

Or you can disassemble, for example, 64 bits:

Image
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:

Image

RADIFF2 

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:

Image
Image

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

Image

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.

Image

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

Image

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.

Image

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:

Image

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

Image

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:

Image

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.

Image

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

Image

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:

Image

In 0x7da we get the other file offset:

Image

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

Image

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

Image
Image

We can make it brighter using eco bright.

Then we can list the functions using afl:

Image

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
Image

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:

Image

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.

Image

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:

Image

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.

Image

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.

Image
Image

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.

Image

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:

Image
Reversing_and_exploiting_part_6_image_43_press_enter_zoom_mode_enter

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

Image

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.

Image

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

Image
Image
Image

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

Image

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

Image

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:

Image
Image

Here we see the function pdb._f.

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

Image

We can rename the function using:

afn new_name old_name

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

Image

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:

Image
Image

Let’s continue renaming:

Image

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:

Image

We rename all the gets:

Image

We’ll rename the variables using:

afvn new_name old_name
Image
ename_variables_using_afvn_new_name_old_name

We should then save once more:

Image
save_once_more_abo5

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: 

https://ci.appveyor.com/project/radareorg/radare2/history

Image

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

Image

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

Image

Next, go to ARTIFACTS.

Image

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

Image

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

STATIC MAP OF THE STACK

----------------------------------------------------

|           -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.

Image
buffer_is_ebp_0x400

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

Image

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.

Image
script_replace_return_address
Image

Debugging

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.

Image

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.

Image

If I usef8, it will step over gets.

Image

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

Image

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

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

Image

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.

Image

rasm2 -a x86 -b 32 "call eax"

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

Image

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

/x ffd0

Image

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

Image
Image

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

winexec_calc_shellcode 
='33d2526863616c6389e65256648b72308b760c8b760cad8b308b7e188b5f3c8b5c1f788b741f2001fe8b4c1f
2401f90fb72c5142ad813c0757696e4575f18b741f1c01fe033caeffd7'.decode("hex")
Image

*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
f788b741f2001fe8b4c1f2401f90fb72c5142ad813c0757696e4575f18b741f1c01fe033caeffd7')[0]

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

Image

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.

Image

Using f7, we’ll trace into the function.

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

Image

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

Image

We’ll trace until the RET.

Image

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

Image

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

Image

And there is the SHELLCODE in my buffer.

32 bit RESOLVER

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

Image

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).

Image

Next we’ll copy the shellcode from the script.

Image

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

Image

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

Image
Image

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.

Image

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.

Image
Image

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.

USING WINDBG

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.

Image

We’ll set a breakpoint at 0x401306.

Image

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

Image
press_g_stop_at_breakpoint_beginning_of_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):

Image

In WINDBG we’ll list its content.

Image

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

Image

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.

Image

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].

Image

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

Image

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:

Image
Image

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

Image

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

Image
peb_ldr_data_pointer

With this useful information, we can continue.

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

Image

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

Image

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

Image

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.

Image
Image
Image

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

Image

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. 

Image
Image

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:

Image
Image

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.

Image

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.

_IMAGE_DOS_HEADER

Image

First, the shellcode searches for the field 0x3c.

Image
Image

The value is 248 or 0xf8.

Image

The _IMAGE_NT_HEADERS is at 0xf8.

IMAGE_NT_HEADERS

Image
Image

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

Image

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.

IMAGE_OPTIONAL_HEADERS

Image

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

Image

This image above is what it will read.

 _IMAGE_DATA_DIRECTORY_ARRAY

Image
image_data_directory_array_export
Image

Each _IMAGE_DATA_DIRECTORY has two fields: VirtualAddress and Size.

Image

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.

Image

Search the header.

Image

Switch to header view.

Search for 0xf8.

Image

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

Image

We can now easily look for 0x78.

Image

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.

IMAGE_EXPORT_DIRECTORY

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;

}IMAGE_EXPORT_DIRECTORY,*PIMAGE_EXPORT_DIRECTORY;

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.

Image

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.

Image

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.

ESI=AddressOfNames

Image

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:

Image

ECX=AddressOfNameOrdinal

Image

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.

Image

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.

Image

This shows what it looks like after running the LODS.

Image

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

We see that the string is:

Image

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

Image

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.

Image

It will go through each string until it finds WinExec.

Image
Image

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

Image

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

ESI=AddressOfFunctions

Image
Image

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.

Image

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

Image

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

Image
Image

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

Let’s press G.

Image

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.

WATCH THE WEBINAR