> For the complete documentation index, see [llms.txt](https://jesuscries.gitbook.io/home/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://jesuscries.gitbook.io/home/ctf-writeups/reverse-engineering/acs-2023-expr.md).

# ACS 2023: expr

## TL;DR

> 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.&#x20;

## Challenge Overview

Expression (expr) is a simple flag checker that verifies if our user input matches the flag.

<figure><img src="/files/oWvJSSa1REObrg0HfupN" alt=""><figcaption></figcaption></figure>

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.&#x20;

{% hint style="info" %}
We will now call this function as`Thread Pool Manager` from this point onwards.
{% endhint %}

<figure><img src="/files/4LzPqgn2StGhgf3kLahX" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/q7c6LzPOISifsPTSWaLw" alt=""><figcaption></figcaption></figure>

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.&#x20;

## Export Program

Firstly, we can select `File -> Export Program` from Ghidra to export the decompiled C code.

<figure><img src="/files/o9mTuSHiyHvcCyF57568" alt=""><figcaption></figcaption></figure>

Or using Ghidra's `headlessAnalyzer`:

<figure><img src="/files/inKBbosYGqvVphuXKYbU" alt=""><figcaption></figcaption></figure>

## Cleaning Up

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:

1. Type Definition: Define data type aliases that are Ghidra-compatible, such as `typedef unsigned int undefined4` , etc.
2. Subroutine Functions: Logical operations that verify our input, such as `FUN_0010XXXX`, etc.
3. Main Boilerplate: Program Entry Point/Main Function that handles user input before passing execution control to Thread Pool Manager.
4. Thread Pool Manager: The long list of if statements that invoke each subroutine function in a separate thread via `pthread_create`.

<figure><img src="/files/UFFSUUwADfRuTRJksL9W" alt=""><figcaption></figcaption></figure>

There are 2 ways to convert these subroutine functions from their multi-threaded form to sequential execution.

## Recompilation

### Method 1: Overriding Definitions

Redefine the signature for `pthread_create`. This only needs to be done once.

```c
int pthread_create(pthread_t *thread, ulong *attr, long (*func_ptr)(long), char *arg) {
    return (*func_ptr)((long) arg);
}
```

Uncomment the mutex lock & unlock procedures for all 101 subroutine functions.

```c
long FUN_001011d0(long param_1)

{
  uint uVar1;
  
  //pthread_mutex_lock((pthread_mutex_t *)&DAT_0010c070);
  uVar1 = *(uint *)(param_1 + 0x1b) >> 2 & 0x7ff;
  if (((uVar1 >> 2 ^ 0x557) + (uVar1 * 0x40 + 0x10c ^ (*(uint *)(param_1 + 0x14) & 0x7ff) >> 6) &
      0x7ff) == 0x79d) {
    //pthread_mutex_unlock((pthread_mutex_t *)&DAT_0010c070);
    return 0;
  }
  return 1;
}
```

We can keep the decode function intact, because `pthread_create` from now on will just act as a mask for all the subroutine functions.

<figure><img src="/files/Sdg9wXmErGRQDkT74ztn" alt=""><figcaption></figcaption></figure>

Finally, compile the code with `GNU Make`:

<figure><img src="/files/UYlxI9CIKwcAGXOzZljD" alt=""><figcaption></figcaption></figure>

{% file src="/files/SCfajC73GVEPFD3264tO" %}

### Method 2: Replacing Functions

Once again, uncomment the mutex lock & unlock procedures for all 101 subroutine functions.

```c
long FUN_001011d0(long param_1)

{
  uint uVar1;
  
  //pthread_mutex_lock((pthread_mutex_t *)&DAT_0010c070);
  uVar1 = *(uint *)(param_1 + 0x1b) >> 2 & 0x7ff;
  if (((uVar1 >> 2 ^ 0x557) + (uVar1 * 0x40 + 0x10c ^ (*(uint *)(param_1 + 0x14) & 0x7ff) >> 6) &
      0x7ff) == 0x79d) {
    //pthread_mutex_unlock((pthread_mutex_t *)&DAT_0010c070);
    return 0;
  }
  return 1;
}
```

Eliminate all occurrences of `pthread_create` by invoking the subroutine functions directly.

<figure><img src="/files/bRRyz3tZObjctuBOgMMK" alt=""><figcaption></figcaption></figure>

Recompile the C code with `GNU Make`:&#x20;

<figure><img src="/files/KXoIVVcWl3OGBoFFSjUI" alt=""><figcaption></figcaption></figure>

{% file src="/files/tfPJu0wZdsFM3aN7t5CP" %}

## Running Angr

With Angr's symbolic execution, we managed to get the flag after approximately 10 minutes.

<figure><img src="/files/YQTBtCUzFJ0wxOAw8V5F" alt=""><figcaption></figcaption></figure>

**Flag:** ACS{y0u50lv3DtH33xpr35510N5!}

## Final Script

{% hint style="warning" %}
For some reason, using this scaffold [template](https://github.com/jakespringer/angr_ctf/blob/master/02_angr_find_condition/scaffold02.py) from Angr CTF would always terminate the script halfway. As a workaround, I used this [template](https://github.com/angr/angr-examples/blob/master/examples/csaw_wyvern/solve.py) instead which appears to be more optimized.
{% endhint %}

```python
#!/usr/bin/env python3

import angr
import claripy
import logging

project = angr.Project("override_definition",load_options={"auto_load_libs": False}, main_opts={"base_addr": 0})
flag_chars = [claripy.BVS(f"c_{i}", 8) for i in range(29)]  # Flag length = 29
flag = claripy.Concat(*flag_chars)

logging.getLogger('angr').setLevel('INFO')
initial_state = project.factory.entry_state(stdin=flag)

# Printable characters only (including SPACE)
for f in flag_chars:
	initial_state.solver.add(f >= 0x20)
	initial_state.solver.add(f < 0x7f)

sm = project.factory.simulation_manager(initial_state)
sm.explore(find=lambda s: b"pass" in s.posix.dumps(1))

if sm.found:
	solution_state = sm.found[0]
	solution = solution_state.solver.eval(flag, cast_to=bytes)
	print(solution)
else:
    raise Exception('Could not find the solution')
```
