---
# System prepended metadata

title: CTF GNU/Weeb Writeup

---

## Intro
This is a challenge from CTF GNU/Weeb 2023 that was made to celebrate indonesia's independence day.

## Recon
In this challenge, we are given an ELF binary named `cgw` , you can find the binary [here](https://drive.google.com/file/d/1jfV0AISwdE9eJQbP_ceAT8Pz3adbCaGY/view?usp=sharing).

![](https://hackmd.io/_uploads/BJ36ZEThn.png)


In this writeup I will explain how the program works using the help of decompiled code from IDA free 8.2.
![](https://hackmd.io/_uploads/HyOfcE633.png)

## Start
![](https://hackmd.io/_uploads/SJ5RW4T3h.png)

The program starts with main function:
- There is a check of the argument length (argc).
	- if argc is 2, then run the program as child process. 
	- if argc is 1, then try to fork and start the child process by running `/proc/self/exe 1` . if the process is successful, then execute `parent_orchestrate_esyscall`.
![](https://hackmd.io/_uploads/rJEgMNah3.png)


So we can see that we are working with a binary that creates 2 processes , a parent and a child process. Lets see what each of the processes are doing.

## Parent Process

### parent_orchestrate_esyscall
![](https://hackmd.io/_uploads/SJ7WfV6n2.png)

Not much to see here, basically it executes `parent_exec_kernel_trampoline` until the child process is killed

### parent_exec_kernel_trampoline
![](https://hackmd.io/_uploads/SkrVMEa2n.png)

In here, we can see that ptrace is executed. This ptrace function is used to observe and/or control the child process
![](https://hackmd.io/_uploads/SkbrzNp3h.png)

After the ptrace syscall is successful, `parent_validate_pt_regs_anti_cheat` is executed


### parent_validate_pt_regs_anti_cheat
![](https://hackmd.io/_uploads/HJ1Uf4a3n.png)

This is where the password check is done in parent process' side. this function seems to copy some value from child process into the parent's memory using `kernel_copy_from_process`.
After the copy function is done, the data is processed and then compared. We will come back to this function later.

### kernel_copy_from_process
![](https://hackmd.io/_uploads/rJqLzN6h2.png)

This function is basically copying `copy_amount` bytes inside child_memory_address into destination_address.
To achieve this, it uses `PTRACE_PEEKDATA` request (2 in integer, based on [ptrace.h](https://github.com/torvalds/linux/blob/master/include/uapi/linux/ptrace.h)) to view the child process' memory.

## Child Process
In child function, there is only one important function, named `run_child`.

### run_child
![](https://hackmd.io/_uploads/SJXwMET22.png)

in this function, there are several syscalls that IDA cannot decompile, so we will observe the assembly instead.
![](https://hackmd.io/_uploads/BJy_fNa3n.png)

I still do not understand the eax value in this syscall , but based on the `/dev/urandom` value in rdi im guessing that it is reading 32 (0x20) bytes of data from `/dev/urandom`.

After that, decode_cookie function is called , after the decode_cookie loop is done, the data is printed using syscall.
The last 2 syscalls is used to print `Enter the password: ` and read user input.
![](https://hackmd.io/_uploads/S1K_z46hh.png)

After that, `check_password` is called. Basically it will return the flag or correct response if the password is correct. 

### decode_cookie
![](https://hackmd.io/_uploads/SJhtzVTn3.png)

The function looks complicated, but after observing the code, I assume that this function converts 8 bytes of char into integer.

## Finding the password

So far, we know that the password check is done in parent process by copying the data from child process , and our input is initially stored inside the child process' memory.

Now the last part of the puzzle is finding out what value is taken from the child process' memory using `kernel_copy_from_process`.
![](https://hackmd.io/_uploads/Sy59zE6nn.png)

Unfortunately, debugging this program using gdb is troublesome, because this program needs `ptrace` to work, and gdb is also using `ptrace` to trace the process execution. This can lead to unexpected behavior.

So instead of using gdb, we will use `strace` command that traces syscalls.
![](https://hackmd.io/_uploads/HkHjBEp33.png)

![](https://hackmd.io/_uploads/SyBozVa32.png)

because we know that `kernel_copy_from_kernel` is using PTRACE_PEEKDATA request, we will search for that specific request in the trace logs.

Here im sending `AAAAAAA` as input.
![](https://hackmd.io/_uploads/HJ0jM4phn.png)

from here we found 4 interesting values.

After running it multiple times, the value always changed, so we can safely assume that this value is random. And one of the ways to produce a random value is by reading `/dev/urandom`. And our assumption was correct.
![](https://hackmd.io/_uploads/HkInMVp32.png)

![](https://hackmd.io/_uploads/SJ0hGNann.png)

Now we know that the value of `comparer` are the 32 bytes of data the program reads from `/dev/urandom`, and the value `password_and_flag` is our input with a requirement of 28 bytes in length.
![](https://hackmd.io/_uploads/SyKTf4ah3.png)


Our input (password) is processed by this code.
```c
for ( i = 0LL; i != 27; ++i )
      correct_password_and_flag[i] = (password_and_flag[i] + comparer[i])&0xff;
```

To find the correct password, we can simply subtract the correct value with the random value.
```c
for ( i = 0LL; i != 27; ++i )
      password_and_flag[i] = (correct_password_and_flag[i]-comparer[i])&0xff;
```

We can get the correct password value by parsing the if statements

```python
correct_password_and_flag=bytearray(28)
correct_password_and_flag[10] = 65 
correct_password_and_flag[3] = 99 
correct_password_and_flag[5] = 99 
correct_password_and_flag[17] = 41 
correct_password_and_flag[23] = 126 
correct_password_and_flag[7] = 100 
correct_password_and_flag[25] = 126 
correct_password_and_flag[0] = 98 
correct_password_and_flag[15] = 39 
correct_password_and_flag[13] = 38 
correct_password_and_flag[20] = 44 
correct_password_and_flag[22] = 126 
correct_password_and_flag[16] = 43 
correct_password_and_flag[9] = 34 
correct_password_and_flag[12] = 37 
correct_password_and_flag[24] = 126 
correct_password_and_flag[4] = 99 
correct_password_and_flag[11] = 36 
correct_password_and_flag[21] = 124 
correct_password_and_flag[26] = 126 
correct_password_and_flag[14] = 95 
correct_password_and_flag[8] = 100 
correct_password_and_flag[18] = 42 
correct_password_and_flag[2] = 98 
correct_password_and_flag[1] = 98 
correct_password_and_flag[6] = 100 
correct_password_and_flag[19] = 96 
print(correct_password_and_flag)
```
```python
└─$ python3 lel.py    
bytearray(b'bbbcccddd"A$%&_\'+)*`,|~~~~~\x00')
```

Now that we get all we need to find the correct password, its time to make the solver script

## Solver
Because the correct password depends on the value of the random generated data, the key is not static. We need to use the cookie provided by the program at runtime. I am using `subprocess` to retrieve the data and process it

```python
import subprocess,re,struct
def recvuntil(process, delimiter):
    output = b""
    while True:
        chunk = process.stdout.read(1)
        if not chunk:
            break
        output += chunk
        if re.search(delimiter, output.decode()):
            break
    return output

def sendafter(process, prompt, data):
    output = b""
    while True:
        chunk = process.stdout.read(1)
        if not chunk:
            break
        output += chunk
        if prompt.encode() in output:
            break
    
    process.stdin.write(data)
    process.stdin.flush()

def generate_password_from_cookie(w):
    comparer=list("bbbcccddd\"A$%&_'+)*`,|~~~~~\x00")
    l=b""
    for i in w:
        l+=struct.pack('<Q', i)
    l=bytearray(l)

    for i in range(28):
        l[i]=(ord(comparer[i])-l[i])&0xff
    return l

process = subprocess.Popen(["./cgw"], stdout=subprocess.PIPE, stdin=subprocess.PIPE)
recvuntil(process,"Your cookies are:\n")
cookie=[
    int(recvuntil(process,"\n").strip()),
int(recvuntil(process,"\n").strip()),
int(recvuntil(process,"\n").strip()),
int(recvuntil(process,"\n").strip())
]

res=generate_password_from_cookie(cookie)[:28]
sendafter(process,"Enter the password: ",res)
print(recvuntil(process,"\n").decode().strip())
```

```bash
└─$ python3 w.py   
__flag{17_agustus_2023_aaa_bbb_ccc_52f39840f025693339c42}
```

Flag = **__flag{17_agustus_2023_aaa_bbb_ccc_52f39840f025693339c42}**

## Bonus
We can also get the flag faster by patching the anti cheats. Patching if statement `jne`/`jnz` instructions into `nop`s will always give the flag when we are sending 28 bytes of password.

Before:
![](https://hackmd.io/_uploads/SJ_AfE6nh.png)

After:
![](https://hackmd.io/_uploads/H1c1mVphn.png)

After patch decompile result:
![](https://hackmd.io/_uploads/ByDlmVT23.png)

Running patched binary:
![](https://hackmd.io/_uploads/ByxZQN6n3.png)


Thank you GNU/Weeb for the great challenge, any feedbacks for the writeup are appreciated!
