tl;dr
- Challenge is a VM implemented over signals and ptrace
- Reverse Instruction types and implementation
- Use gdb scripting to find the executed code and get the pseudo VM code
- Reverse the VM functionality (Hill cipher) for flag and profit
Challenge Points: 500
Challenge Solves: 21
Solved by: R3x, silverf3lix, Ayushi
Initial Analysis
Challenge takes an input - and running strace we see that it forks a child and then does some ptrace calls.
1 | ~> strace ./signal_vm |
Taking a look into the binary for a better understanding we come across the main function.
1 | signed __int64 sub_40172D() |
This leads us to understand that the code is basically forking and trying to establish some communication between the child and parent using ptrace.
Analysis of the Child
Run the binary on gdb with set follow-fork-mode child
to observe the behaviour of the child. We get SIGILL
.
Let take a close look at the disassembly of the child.
1 | push rbp |
This is strange - looks like the child is made to trigger the signal. This leads us to the conclusion that the parent is responsible for handling the signal and continuing the execution of the child somehow.
Initial analysis of the Parent
Now lets take a look at what is happening in the parent. On reversing the function handler
we come to the following conclusions.
- Parent is the VM handler and the child is basically the VM code.
- Every time the child sends a signal the parent basically handles it like a opcode and performs actions. This is done with the help of ptrace.
- The VM has a set of registers in the parent which are modified based on the opcode and one of these have to be set to 0 for us to get the flag.
Digging deeper into the parent VM
First thing to understand the role ptrace actually plays. Strace gives us -
1 | ptrace(PTRACE_GETREGS, 1763, NULL, 0x7ffc4cb9c0e0) = 0 |
Having not seen anything other than PTRACE_TRACEME - we start digging into the man page.
The
ptrace()
system call provides a means by which one process (the “tracer”) may observe and control the execution of another process (the “tracee”), and examine and change the tracee’s memory and registers.
PTRACE_PEEKTEXT/POKETEXT - Read/Write a word at the address addr in the tracee’s memory.
PTRACE_GETREGS/SETREGS - Read/Write into the registers of the tracee.
The parent has handlers for the following signals and each of them define a certain class of instructions:
- SIGILL (signal no 4) -
move
class - SIGTRAP (signal no 5) -
logical
class - SIGFPE (signal no 8) -
compare
class - SIGSEGV (signal no 11) -
jump
class
Now the following script skims through the signals triggered and parses them to give a set of readable instructions which decreased our work.
1 | import gdb |
Final Steps
From the instructions given out by the above script we were able to deduce that it is basically Hill cipher.
The key Matrix is a 7x7 one generated from the string below
1 | .data:00000000006D5100 aAlmostHeavenWe db 'Almost heaven west virginia, blue ridge mountains',0 |
The ciphertext matrix can be found from the instructions generated by the above script.Then we used sagemath to do the math for us.
1 | from sage.all import * |
Running the above script gave us the flag =>
de1ctf{7h3n_f4r3_u_w3ll_5w337_cr4g13_HILL_wh3r3_0f3n_71m35_1_v3_r0v3d}