# 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')
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://jesuscries.gitbook.io/home/ctf-writeups/reverse-engineering/acs-2023-expr.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
