ACS 2023: expr
Expression
Last updated
Expression
Last updated
Multi-threaded flag checker designed to thwart Angr's symbolic execution. Subroutine functions can be exported via Headless Ghidra and flattened by converting it to sequential execution.
Expression (expr) is a simple flag checker that verifies if our user input matches the flag.
Taking a look at FUN_00104f60
, the function takes in our user input as its 1st argument and spawns 101 separate threads to execute different subroutine functions.
We will now call this function asThread Pool Manager
from this point onwards.
Within each subroutine function are specific conditions that we have to pass in order to release/unlock the mutex. If these conditions are not met, the program will hang indefinitely due to the mutex being locked.
Decoding all the values manually 101 times will be a pain in the ass, so it's pretty obvious that we had to rely on some kind of automated solver like Angr or z3. This challenge is a textbook scenario for applying Angr's Find Condition feature to search for a state that meets our arbitrary condition - In this case, we want the string "pass" to be printed out on stdout.
However, Angr in its current state does not support multi-threading program. Therefore, our plan is to recompile the program to force it to check our input sequentially.
Firstly, we can select File -> Export Program
from Ghidra to export the decompiled C code.
Or using Ghidra's headlessAnalyzer
:
Within the decompiled code, most of the exported definitions such as eh_frame_hdr
, fde_table_entry
and more can be removed as they are not needed. The only components we need are:
Type Definition: Define data type aliases that are Ghidra-compatible, such as typedef unsigned int undefined4
, etc.
Subroutine Functions: Logical operations that verify our input, such as FUN_0010XXXX
, etc.
Main Boilerplate: Program Entry Point/Main Function that handles user input before passing execution control to Thread Pool Manager.
Thread Pool Manager: The long list of if statements that invoke each subroutine function in a separate thread via pthread_create
.
There are 2 ways to convert these subroutine functions from their multi-threaded form to sequential execution.
Redefine the signature for pthread_create
. This only needs to be done once.
Uncomment the mutex lock & unlock procedures for all 101 subroutine functions.
We can keep the decode function intact, because pthread_create
from now on will just act as a mask for all the subroutine functions.
Finally, compile the code with GNU Make
:
Once again, uncomment the mutex lock & unlock procedures for all 101 subroutine functions.
Eliminate all occurrences of pthread_create
by invoking the subroutine functions directly.
Recompile the C code with GNU Make
:
With Angr's symbolic execution, we managed to get the flag after approximately 10 minutes.
Flag: ACS{y0u50lv3DtH33xpr35510N5!}