###### tags: `TSG CTF 2021`
# TSG CTF 2021 Beginner's Rev Author's Writeup
author: [@m1kit](https://twitter.com/m1kit)
## Problem Overview
You are given a binary that tells you if a flag is correct or not.
## Hints
- Hint 1: Try to run the given program. You should get somehow helpful information about the flag.
- Hint 2: Do you know [ghidra](https://ghidra-sre.org/)? To understand the behavior of the program, you need to do some static analysis.
- Hint 3: Don't spend too much on reading the code. Once you get an idea of the behavior, I recommend you to try some dynamic analysis with various tools.
- <details>
<summary>Hint 4:</summary>
There are some interesting system calls.
</details>
- <details>
<summary>Hint 5:</summary>
32 characters, 32 processes
</details>
- <details>
<summary>Hint 6:</summary>
You can use strace to observe system calls.
</details>
🙈 Solution below
👇
👇
👇
👇
👇
👇
## Solution
### Run
As mentioned in the hint, we can run the program first.
```
❯ ./beginners_rev
give me exactly one argument
❯ ./beginners_rev aaa
give me an argument with 32 chars
❯ ./beginners_rev aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
wrong
```
Now we know this programs determine correctness of something with 32 chars length.
### Read
Next, let's read the program with ghidra.
We can see the entrypoint like this.
It calls `check` only if it has 32 chars.
```c=
undefined8 main(int param_1,long param_2) {
char *__s;
size_t sVar1;
undefined8 uVar2;
if (param_1 == 2) {
__s = *(char **)(param_2 + 8);
sVar1 = strlen(__s);
if (sVar1 == 0x20) {
uVar2 = check(__s);
return uVar2;
}
puts("give me an argument with 32 chars");
}
else {
puts("give me exactly one argument");
}
return 1;
}
```
Let's see `check`.
There are some interesting system calls!
```c=
byte check(long param_1) {
__pid_t _Var1;
int iVar2;
int iVar3;
int iVar4;
uint uVar5;
long in_FS_OFFSET;
byte bVar6;
undefined local_34;
byte local_33;
long local_30;
uVar5 = 0;
iVar4 = 0;
iVar3 = 0;
local_30 = *(long *)(in_FS_OFFSET + 0x28);
do {
_Var1 = fork();
iVar4 = iVar4 + 1;
if (_Var1 == 0) {
iVar4 = 0;
uVar5 = uVar5 | 1 << ((byte)iVar3 & 0x1f);
iVar2 = open("/dev/null",1);
dup2(iVar2,1);
}
iVar3 = iVar3 + 1;
} while (iVar3 != 5);
iVar3 = iVar4 + -1;
iVar2 = is_correct((int)*(char *)(param_1 + (int)uVar5),uVar5);
bVar6 = iVar2 == 0;
if (iVar4 != 0) {
do {
iVar3 = iVar3 + -1;
wait(&local_34);
bVar6 = bVar6 | local_33;
} while (iVar3 != -1);
}
if (bVar6 == 0) {
puts("correct");
}
else {
puts("wrong");
}
if (local_30 == *(long *)(in_FS_OFFSET + 0x28)) {
return bVar6;
}
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
```
The key point is the program calls `fork()` five times in `for`-loop.
Therefore we have 32 processes, and each of them checks one character in the flag around line number 29.
Hope this rough illust of 4 processes example will help you.
![](https://i.imgur.com/yigfqUA.png =400x400)
After that results from processes are aggregates around line number 35.
![](https://i.imgur.com/4diKGEL.png =400x400)
Each process prints the result to stdout at line number 38-43, however, only the message from the root process is visible since anything else is redirected to `/dev/null` at line number 23.
## Solve
With [strace](https://man7.org/linux/man-pages/man1/strace.1.html) command, you can observe system calls executed.
We can use this to check the number of times ` puts("correct");` has been called.
```shell
strace -f ./dist/beginners_rev "$1" 2>&1 | grep correct | wc -l
```
We can create a simple brute force script to determine the flag.
Note that you need to check from the back to the front, due to the structure of the process tree.
```python
import subprocess
import string
def oracle(v):
p = subprocess.Popen(["./the_magic_oracle_above.sh", v], stdout=subprocess.PIPE)
output, _ = p.communicate()
return int(str(output, 'ascii'))
ans = ['*'] * 32
for i in range(31,-1,-1):
for c in string.printable:
ans[i] = c
flag = ''.join(ans)
if oracle(flag) == 32 - i:
break
print(flag)
```
## Alt 1
[@moratorium08](https://twitter.com/moratorium08)'s solution
Alternatively, you can patch the binary to remove `dup2(open("/dev/null", 1),1);`.
Now you can see the number of "correct"s in your stdout.
## Alt 2
[@taiyoslime](https://twitter.com/taiyoslime)'s solution
You can write a gdb script to leak a hint for the flag.
But you need some efforts to avoid anti-debugging codes in `is_correct` function.
FYI: this is my anti-debugging mechanism.
```c
if (__builtin_return_address(0) - (void *)check != 0x5f) {
fputs("This function may not work properly with a debugger.", stderr);
}
```
## Appendix
Here's our original C code for `check`.
```c
int check(char *flag)
{
int index = 0, count = 0;
for (int i = 0; i < 5; i++) {
if (fork()) {
count++;
} else {
count = 0;
index |= 1 << i;
dup2(open("/dev/null", O_WRONLY), 1);
}
}
int result = !is_correct(flag[index], index);
while (count--) {
int status;
wait(&status);
result |= WEXITSTATUS(status);
}
if (result == 0) {
printf("correct\n");
} else {
printf("wrong\n");
}
return result;
}
```