Rating:

# Cheat Console
---
**Points:** 329 | **Solves:** 25/1035 | **Category:** Reverse

Grandpa Bill told you that in his youth he loved playing arcade machines. He was a notorious cheater and rarely played fair. He told you that one day in his favorite arcade (Pacman) he played around with the known cheat codes, trying to discover something new. Contrary to his expectation one code that he tried did not make him invincible against all enemies but showed a prompt asking for a password. Sadly he never was able to figure out the password and discovered what is hidden behind the prompt. Eventually the arcade center shut down and he couldn't try anymore. Unwilling to give up he contacted the developers which gave him a small program and said that it's the part which he was shown back then and that if he discovers the password he'll know what was hidden behind that prompt. Be a good grandson/granddaughter and help him with your awesome reversing skills.

On a side note: You might wanna lie to him once you solved the challenge (and found the hidden message in the password). ;)

The flag is the password used for the program. This challenge does not use the standard flag format.

[Download](CheatConsole_4648d68145298cca67d3a6b97d6dbe50.zip)
---

[Bahasa Indonesia](#bahasa-indonesia)

## English

### TL;DR
- Patch the binary so we can decompile.
- Ensure `SIGTRAP` signal is sent and always cause `SIGFPE` to be sent if possible.
- Dynamic analysis with GDB to get the flag.
- Use Z3 (and guessing) to get the final part of the flag.

### Detailed Steps
We were given an ELF 32-bit binary. In this binary, there's a lot of functions that can't be decompiled by IDA because of `int3` instructions (opcode = `0xCC`).

```asm
.text:080486BB main:
...
.text:08048741 jnz short loc_804872A
.text:08048743 mov [ebp-68h], ecx
.text:08048743 ; ----------------------------------------------
.text:08048746 db 2 dup(0CCh)
.text:08048748 ; ----------------------------------------------
.text:08048748 cmp dword ptr [ebp-68h], 32h
.text:0804874C jnz loc_8048890
```

We patched the binary and replaced the instructions to `nop` (opcode = `0x90`) with hex editor. After that, functions can be decompiled. Let's take a look at `main` function.

```c
int __cdecl main(int a1, char **a2) {
int v2; // ecx
unsigned int v3; // ebx
int v4; // ST00_4
int result; // eax
char **v6; // [esp+0h] [ebp-7Ch]
char v7; // [esp+18h] [ebp-64h]
unsigned int v8; // [esp+60h] [ebp-1Ch]
int *v9; // [esp+6Ch] [ebp-10h]

v9 = &a1;
v6 = a2;
v8 = __readgsdword(0x14u);
if ( a1 == 2 ) {
... // performs checking
}
else {
puts("You might want to submit the password to gain access.");
result = 1;
}
return result;
}
```

It takes an argument as a password then does some checking. Now take a look at function `sub_80488B2`.

```C
signed int __cdecl sub_80488B2(const char *a1)
{
size_t v1; // eax
signed int i; // [esp+18h] [ebp-A0h]
char v4; // [esp+1Ch] [ebp-9Ch]
char v5[32]; // [esp+8Ch] [ebp-2Ch]
unsigned int v6; // [esp+ACh] [ebp-Ch]

v6 = __readgsdword(0x14u);
SHA256_Init(&v4;;
v1 = strlen(a1);
SHA256_Update(&v4, a1, v1);
SHA256_Final(v5, &v4;;
for ( i = 0; i <= 31; ++i )
{
if ( (v5[i] ^ 0x42) != *(i + 0x804B060) )
{
__asm { int 80h; LINUX - }
return 0;
}
}
return 1;
}
```

It calculates SHA-256 of our password, xored with `0x42`, and compared to `0x804B060` which will be translated into a string `Well done, but you were mislead.`. We took a note that there might be many misleading functions and fake flags in this binary.

We noticed that there's an `int 0x80` instruction (or `syscall`) in the function. Let's take a look at the disassembly.

```asm
.text:08048965 movzx eax, al
.text:08048968 cmp edx, eax
.text:0804896A jz short loc_8048997
.text:0804896C movzx edx, [ebp+var_A2]
.text:08048973 mov esi, offset sub_80489C6
.text:08048978 xor eax, eax
.text:0804897A mov al, dl
.text:0804897C inc eax
.text:0804897D push 8
.text:0804897F pop ebx
.text:08048980 push 0
.text:08048982 push 0
.text:08048984 push 0
.text:08048986 push esi
.text:08048987 mov ecx, esp
.text:08048989 xor edx, edx
.text:0804898B int 80h ; LINUX -
.text:0804898D sub esp, 10h
.text:08048990 mov eax, 0
.text:08048995 jmp short loc_80489AE
```

We learned that IDA can't decompile the function (and many other functions) completely. We decided to do dynamic analysis and set breakpoint at `0x0804898B` to know what `syscall` is made.

```
$ gdb public/challenge
...
gdb-peda$ b *0x0804898B
Breakpoint 1 at 0x804898b
gdb-peda$ r asd
[----------------------------------registers-----------------------------------]
EAX: 0x43 ('C')
EBX: 0x8
ECX: 0xffffcda0 --> 0x80489c6 (push ebp)
EDX: 0x0
ESI: 0x80489c6 (push ebp)
EDI: 0x0
EBP: 0xffffce68 --> 0xffffcf08 --> 0x0
ESP: 0xffffcda0 --> 0x80489c6 (push ebp)
EIP: 0x804898b (int 0x80)
EFLAGS: 0x200246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
...
Breakpoint 1, 0x0804898b in ?? ()
gdb-peda$
```

The system call number (`EAX`) is `0x43` or `sys_sigaction`. In short, it registers a signal handler with signal number (`EBX`) `0x8` or `SIGFPE`, and to be handled by a function (`ECX`) in `0x80489c6`. `SIGFPE` is a floating point exception that will be occured in arithmetic operation error, such as division by zero.

Now let's take a look at function `0x80489c6`, we'll call this `handle_SIGFPE` from now on.

```c
int __cdecl handle_SIGFPE(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10, int a11, unsigned __int8 *a12, int a13, int a14, int a15, int a16) {
int v16; // eax
int result; // eax

if ( dword_804B084 == 'REGT' ) {
v16 = sys_mprotect((main & 0xFFFFF000), 0x2000u, 7);
result = a16 - 1;
*result = 0x5B056A51;
*(result + 4) = 0x8B58306A;
*(result + 8) = 0x80CD944D;
*(result + 12) = 89;
}
else {
result = dword_804B084;
if ( dword_804B084 != 'NOPP' ) {
if ( dword_804B084 == 'FG03' ) {
result = *a12;
}
else {
result = dword_804B084;
if ( dword_804B084 == 'FG41' )
dword_804B084 = 'NOPP';
}
}
}
return result;
}
```

There's interesting code, if `dword_804B084 == 'REGT'` it will call `mprotect` and will modify address pointed by `a16 - 1`. We guessed it's modifying its code, so we used `pwntools` to disassembly the code.

```
>>> from pwn import *
>>> print disasm(p32(0x5B056A51)+p32(0x8B58306A)+p32(0x80CD944D)+chr(89))
0: 51 push ecx
1: 6a 05 push 0x5
3: 5b pop ebx
4: 6a 30 push 0x30
6: 58 pop eax
7: 8b 4d 94 mov ecx,DWORD PTR [ebp-0x6c]
a: cd 80 int 0x80
c: 59 pop ecx
```

Oh, another `int 0x80`! So to reach this code, we have to set `dword_804B084` to `'REGT'` and cause an arithmetic error so `SIGFPE` signal will be sent.

Now back to main function.

```c
int __cdecl main(int a1, char **a2) {
...
dword_804B084 = 'REGT';
if ( sub_80488B2(*a2) ) {
if ( strlen(v6[1]) != 42 )
exit(1);
}
else {
v2 = 0;
do
v3 = v6[1][v2++];
while ( 0x42424242 % v3 );
if ( v2 == 50 ) {
if ( sub_8048B14(v6[1] + 3) ) {
if ( sub_8048D7D(v6, v6[1] + 16) ) {
...
}
}
```

We will never satisfy function `sub_80488B2`, therefore we concluded that password length checking, `strlen(v6[1]) != 42`, is fake.

More checking will be done if `v2 == 50`. To satisfy this, simply set the 50th character of our password to `B` or `!` (they're divisible by `0x42424242`). But remember, we already had `SIGFPE` handler! So if the 50th character is null byte and there's no `!` and `B`, division by zero error will occur and cause `handle_SIGFPE` to be called. Also, `dword_804B084` is already set to `'REGT'` (to reach the modifying code). We concluded that our password length must be `49`.

We set breakpoint at `0x080489FC` where modifying code is executed inside `handle_SIGFPE` and run with 49 chars as password.

```
$ gdb public/challenge
gdb-peda$ b *0x080489FC
Breakpoint 1 at 0x80489fc
gdb-peda$ r 1234567890123456789012345678901234567890123456789
Stopped reason: SIGFPE
0x08048737 in ?? ()
gdb-peda$ c
Breakpoint 1, 0x080489fc in ?? ()
gdb-peda$ i r
eax 0x8048736 0x8048736
...
```

So it's modifying code at `0x8048736`, let's continue stepping in.

```
gdb-peda$ ni
0x08048a02 in ?? ()
...
gdb-peda$ ni
0x08048736 in ?? ()
gdb-peda$ x/15i $eip
=> 0x8048736: push ecx
0x8048737: push 0x5
0x8048739: pop ebx
0x804873a: push 0x30
0x804873c: pop eax
0x804873d: mov ecx,DWORD PTR [ebp-0x6c]
0x8048740: int 0x80
0x8048742: pop ecx
0x8048743: mov DWORD PTR [ebp-0x68],ecx
0x8048746: int3
0x8048747: int3
0x8048748: cmp DWORD PTR [ebp-0x68],0x32
0x804874c: jne 0x8048890
gdb-peda$ ni
...
0x08048740 in ?? ()
gdb-peda$ i r
eax 0x30 0x30
ecx 0x8048a7f 0x8048a7f
edx 0x0 0x0
ebx 0x5 0x5
```

In short, it registers signal `SIGTRAP` to be handled by function at `0x8048a7f`. We will call this function `handle_SIGTRAP`.

Now, if you observe disassembly of `handle_SIGFPE` and `handle_SIGTRAP`, there's a lot of code that is not decompiled into pseudocode. For example,

```
.text:08048A27 cmp eax, 'FG03'
.text:08048A2C jnz short loc_8048A51
.text:08048A2E mov dword ptr [esp+38h], 0
.text:08048A36 mov dword ptr [esp+34h], 64h
.text:08048A3E mov ecx, [esp+3Ch]
.text:08048A42 xor eax, eax
.text:08048A44 mov al, [ecx]
.text:08048A46 mov [esp+40h], al
.text:08048A4A add dword ptr [esp+3Ch], 4
.text:08048A4F jmp short loc_8048A7B
```

only decompiled into

```c
...
if ( dword_804B084 == 'FG03' ) {
result = *a12;
}
else {
...
```
So, we decided to do full dynamic analysis from here. Unfortunately, `handle_SIGTRAP` and `handle_SIGFPE` modify memory and can't be ignored. Therefore, we send `SIGTRAP` signal manually if we encounter `int3` (we can do that by sending `signal SIGTRAP` command in GDB). For `SIGFPE`, we tried to cause division by zero error if possible. For example, in function `sub_8048B14`,
```c
signed int __cdecl sub_8048B14(unsigned __int8 *a1) {
dword_804B084 = 'FG03';
if ( (*a1 % (100 - *a1)) >= 1 )
exit(4);
...
```
we made sure that `*a1` is `100` so it will cause `SIGFPE`. After a while doing dynamic analysis, we get the flag (without last checking part) `xxxdxx_Ob50l3te_and_UnS0lv4bl3_l3v3l_f0r_CH3AT3R5`.

Phew okay, now for the last checking part, function `sub_80490A0`.
```c
_BOOL4 __cdecl sub_80490A0(_BYTE *a1) {
_BYTE *v2; // [esp+Ch] [ebp-4h]

v2 = a1 + 4;
return a1[4] & 4
&& *v2 & 0x40
&& *v2 & 1
&& ~a1[5] == 145
&& !((a1[1] & 0x20) + (*a1 & 0x20) + (a1[2] & 0x20))
&& *a1 & 8
&& a1[2] & 4
&& a1[1] != *a1;
}
```

Since it's a constraint checking function, we can solve this with Z3.

```python
from z3 import *

s = Solver()
xs = [BitVec('x%d' % i, 8) for i in xrange(6)]
for x in xs:
s.add(Or(
And(x >= ord('A'), x <= ord('Z')),
And(x >= ord('a'), x <= ord('z')),
And(x >= ord('0'), x <= ord('9')),
))

s.add(xs[4] & 4 != 0)
s.add(xs[4] & 0x40 != 0)
s.add(xs[4] & 1 != 0)
s.add(xs[5] == ord('n'))
s.add((xs[1] & 0x20) + (xs[0] & 0x20) + (xs[2] & 0x20) == 0)
s.add(xs[0] & 8 != 0)
s.add(xs[2] & 4 != 0)
s.add(xs[1] != xs[0])
s.add(xs[3] == ord('d'))

while s.check() == sat:
m = s.model()
print ''.join([chr(int(m[xs[i]].as_long())) for i in xrange(6)])
s.add(Or([d() != m[d] for d in m]))
```
But there are more than one solutions that satisfy the constraints (CTF organizer had confirmed this). Since the 4th and 6th character is `d` and `n`, we guessed some possible words that make sense. We tried `HIDdEn_Ob50l3te_and_UnS0lv4bl3_l3v3l_f0r_CH3AT3R5` and got the flag!

## Bahasa Indonesia

### Rangkuman
- Patch binary sehingga bisa didekompilasi.
- Pastikan signal `SIGTRAP` terpanggil dan jika memungkinkan buat kondisi sehingga `SIGFPE` terpanggil.
- Lakukan dynamic analysis dengan GDB untuk mendapatkan flag.
- Gunakan Z3 (dan sedikit tebakan) untuk mendapatkan bagian flag terakhir.

### Langkah Detail
Diberikan sebuah binary ELF 32-bit. Banyak fungsi yang tidak bisa didekompilasi oleh IDA pada binary ini. Hal ini disebabkan oleh adanya instruksi `int3` dengan opcode `0xCC`.

```asm
.text:080486BB main:
...
.text:08048741 jnz short loc_804872A
.text:08048743 mov [ebp-68h], ecx
.text:08048743 ; ----------------------------------------------
.text:08048746 db 2 dup(0CCh)
.text:08048748 ; ----------------------------------------------
.text:08048748 cmp dword ptr [ebp-68h], 32h
.text:0804874C jnz loc_8048890
```

Kami melakukan patch binary yaitu mengganti instruksinya menjadi `nop` dengan opcode `0x90` dengan hex editor. Setelah itu baru fungsi-fungsinya dapat didekompilasi oleh IDA. Sekarang kita lihat fungsi `main`.

```c
int __cdecl main(int a1, char **a2) {
int v2; // ecx
unsigned int v3; // ebx
int v4; // ST00_4
int result; // eax
char **v6; // [esp+0h] [ebp-7Ch]
char v7; // [esp+18h] [ebp-64h]
unsigned int v8; // [esp+60h] [ebp-1Ch]
int *v9; // [esp+6Ch] [ebp-10h]

v9 = &a1;
v6 = a2;
v8 = __readgsdword(0x14u);
if ( a1 == 2 ) {
... // performs checking
}
else {
puts("You might want to submit the password to gain access.");
result = 1;
}
return result;
}
```

Binary ini membutuhkan satu parameter sebagai password untuk dilakukan pengecekan. Sekarang kita lihat fungsi `sub_80488B2`.

```C
signed int __cdecl sub_80488B2(const char *a1) {
size_t v1; // eax
signed int i; // [esp+18h] [ebp-A0h]
char v4; // [esp+1Ch] [ebp-9Ch]
char v5[32]; // [esp+8Ch] [ebp-2Ch]
unsigned int v6; // [esp+ACh] [ebp-Ch]

v6 = __readgsdword(0x14u);
SHA256_Init(&v4;;
v1 = strlen(a1);
SHA256_Update(&v4, a1, v1);
SHA256_Final(v5, &v4;;
for ( i = 0; i <= 31; ++i )
{
if ( (v5[i] ^ 0x42) != *(i + 0x804B060) )
{
__asm { int 80h; LINUX - }
return 0;
}
}
return 1;
}
```

Fungsi ini menghitung hash SHA-256 dari password, melakukan operasi xor dengan `0x42`, dan dicocokkan dengan `0x804B060` yang jika dihitung akan menghasilkan string `Well done, but you were mislead.`. Dari sini kami menduga bahwa banyak fungsi menjebak dan flag palsu pada binary.

Kami juga melihat ada instruksi `int 0x80` (semacam `syscall`) pada fungsi tersebut. Kita lihat disassembly-nya.

```asm
.text:08048965 movzx eax, al
.text:08048968 cmp edx, eax
.text:0804896A jz short loc_8048997
.text:0804896C movzx edx, [ebp+var_A2]
.text:08048973 mov esi, offset sub_80489C6
.text:08048978 xor eax, eax
.text:0804897A mov al, dl
.text:0804897C inc eax
.text:0804897D push 8
.text:0804897F pop ebx
.text:08048980 push 0
.text:08048982 push 0
.text:08048984 push 0
.text:08048986 push esi
.text:08048987 mov ecx, esp
.text:08048989 xor edx, edx
.text:0804898B int 80h ; LINUX -
.text:0804898D sub esp, 10h
.text:08048990 mov eax, 0
.text:08048995 jmp short loc_80489AE
```

Kami menyadari bahwa IDA tidak dapat melakukan dekompilasi fungsi ini (dan banyak fungsi lainnya) secara lengkap. Dari sini kami memutuskan untuk melakukan dynamic analysis dan memasang breakpoint pada `0x0804898B` untuk mengetahui `syscall` apa yang dipanggil.

```
$ gdb public/challenge
...
gdb-peda$ b *0x0804898B
Breakpoint 1 at 0x804898b
gdb-peda$ r asd
[----------------------------------registers-----------------------------------]
EAX: 0x43 ('C')
EBX: 0x8
ECX: 0xffffcda0 --> 0x80489c6 (push ebp)
EDX: 0x0
ESI: 0x80489c6 (push ebp)
EDI: 0x0
EBP: 0xffffce68 --> 0xffffcf08 --> 0x0
ESP: 0xffffcda0 --> 0x80489c6 (push ebp)
EIP: 0x804898b (int 0x80)
EFLAGS: 0x200246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
...
Breakpoint 1, 0x0804898b in ?? ()
gdb-peda$
```
Nomor system call (`EAX`) adalah `0x43` yang merupakan `sys_sigaction`. Singkatnya, instruksi tersebut mendaftarkan signal handler dengan nomor signal (`EBX`) `0x8` yang merupakan `SIGFPE`, yang akan di-handle oleh fungsi (`ECX`) pada alamat `0x80489c6`. `SIGFPE` adalah *floating point exception* yang terjadi jika terdapat error pada operasi aritmatika, seperti pembagian dengan 0.

Kita lihat fungsi `0x80489c6`, yang akan kita sebut sebagai fungsi `handle_SIGFPE`.

```c
int __cdecl handle_SIGFPE(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10, int a11, unsigned __int8 *a12, int a13, int a14, int a15, int a16) {
int v16; // eax
int result; // eax

if ( dword_804B084 == 'REGT' ) {
v16 = sys_mprotect((main & 0xFFFFF000), 0x2000u, 7);
result = a16 - 1;
*result = 0x5B056A51;
*(result + 4) = 0x8B58306A;
*(result + 8) = 0x80CD944D;
*(result + 12) = 89;
}
else {
result = dword_804B084;
if ( dword_804B084 != 'NOPP' ) {
if ( dword_804B084 == 'FG03' ) {
result = *a12;
}
else {
result = dword_804B084;
if ( dword_804B084 == 'FG41' )
dword_804B084 = 'NOPP';
}
}
}
return result;
}
```

Terdapat kode yang menarik, jika `dword_804B084 == 'REGT'` fungsi tersebut akan memanggil `mprotect` dan mengubah memory pada alamat yang ditunjuk oleh `a16 - 1`. Kami menduga fungsi tersebut mengubah kodenya sendiri, lalu kami menggunakan `pwntools` untuk melihat disassembly-nya.

```
>>> from pwn import *
>>> print disasm(p32(0x5B056A51)+p32(0x8B58306A)+p32(0x80CD944D)+chr(89))
0: 51 push ecx
1: 6a 05 push 0x5
3: 5b pop ebx
4: 6a 30 push 0x30
6: 58 pop eax
7: 8b 4d 94 mov ecx,DWORD PTR [ebp-0x6c]
a: cd 80 int 0x80
c: 59 pop ecx
```

Terdapat `int 0x80` lagi! Untuk mencapai kode ini, kita harus membuat `dword_804B084` bernilai `'REGT'` dan membuat error aritmatika sehingga signal `SIGFPE` akan terpanggil.

Sekarang kita kembali ke fungsi `main`.

```c
int __cdecl main(int a1, char **a2) {
...
dword_804B084 = 'REGT';
if ( sub_80488B2(*a2) ) {
if ( strlen(v6[1]) != 42 )
exit(1);
}
else {
v2 = 0;
do
v3 = v6[1][v2++];
while ( 0x42424242 % v3 );
if ( v2 == 50 ) {
if ( sub_8048B14(v6[1] + 3) ) {
if ( sub_8048D7D(v6, v6[1] + 16) ) {
...
}
}
```

Fungsi `sub_80488B2` tidak akan pernah bernilai `true`, dari sini kami menyimpulkan pengecekan panjang password, `strlen(v6[1]) != 42`, adalah palsu.

Pengecekan lebih lanjut jika `v2 == 50` terpenuhi. Untuk memenuhi ini, cara gampangnya adalah membuat karakter ke-50 password kita bernilai `B` or `!` (karena nilai tersebut habis membagi `0x42424242`). Akan tetapi ingat, kita telah mempunyai handler untuk `SIGFPE`! Sehingga jika karakter ke-50 password kita adalah null byte dan tidak ada karakter `B` dan `!`, error karena pembagian dengan 0 akan terjadi dan `handle_SIGFPE` terpanggil. Selain itu, `dword_804B084` juga telah bernilai `'REGT'` untuk mencapai kode aneh (yang mengubah kode lain). Kami menyimpulkan bahwa panjang password adalah `49`.

Kami memasang breakpoint pada `0x080489FC` di mana kode tersebut dieksekusi dan menjalankan binary tersebut dengan password sepanjang 49 karakter.

```
$ gdb public/challenge
gdb-peda$ b *0x080489FC
Breakpoint 1 at 0x80489fc
gdb-peda$ r 1234567890123456789012345678901234567890123456789
Stopped reason: SIGFPE
0x08048737 in ?? ()
gdb-peda$ c
Breakpoint 1, 0x080489fc in ?? ()
gdb-peda$ i r
eax 0x8048736 0x8048736
...
```

Kode tersebut mengubah kode pada `0x8048736`. Lalu lanjutkan analisisnya.

```
gdb-peda$ ni
0x08048a02 in ?? ()
...
gdb-peda$ ni
0x08048736 in ?? ()
gdb-peda$ x/15i $eip
=> 0x8048736: push ecx
0x8048737: push 0x5
0x8048739: pop ebx
0x804873a: push 0x30
0x804873c: pop eax
0x804873d: mov ecx,DWORD PTR [ebp-0x6c]
0x8048740: int 0x80
0x8048742: pop ecx
0x8048743: mov DWORD PTR [ebp-0x68],ecx
0x8048746: int3
0x8048747: int3
0x8048748: cmp DWORD PTR [ebp-0x68],0x32
0x804874c: jne 0x8048890
gdb-peda$ ni
...
0x08048740 in ?? ()
gdb-peda$ i r
eax 0x30 0x30
ecx 0x8048a7f 0x8048a7f
edx 0x0 0x0
ebx 0x5 0x5
```

Singkatnya, kode tersebut mendaftarkan signal `SIGTRAP` untuk di-handle oleh fungsi pada `0x8048a7f`. Kita akan menyebut fungsi itu sebagai `handle_SIGTRAP`.

Jika kita lihat disassembly dari fungsi `handle_SIGFPE` dan `handle_SIGTRAP`, banyak kode yang tidak berhasil didekompilasi oleh IDA menjadi pseudocode. Sebagai contoh,

```
.text:08048A27 cmp eax, 'FG03'
.text:08048A2C jnz short loc_8048A51
.text:08048A2E mov dword ptr [esp+38h], 0
.text:08048A36 mov dword ptr [esp+34h], 64h
.text:08048A3E mov ecx, [esp+3Ch]
.text:08048A42 xor eax, eax
.text:08048A44 mov al, [ecx]
.text:08048A46 mov [esp+40h], al
.text:08048A4A add dword ptr [esp+3Ch], 4
.text:08048A4F jmp short loc_8048A7B
```

hanya berhasil didekompilasi menjadi

```c
if ( dword_804B084 == 'FG03' ) {
result = *a12;
}
else {
```

Dari sini kami memutuskan untuk melakukan dynamic analysis secara penuh. Sayangnya, `handle_SIGTRAP` dan `handle_SIGFPE` mengubah memory sehingga tidak dapat diabaikan begitu saja. Untuk itu, kami mengirim balik signal `SIGTRAP` secara manual jika kita mendapati instruksi `int3`. Hal ini dapat dilakukan dengan memberikan command `signal SIGTRAP` pada GDB. Untuk `SIGFPE`, kami mencoba untuk membuat error pembagian dengan 0 jika dimungkinkan. Contohnya, pada fungsi `sub_8048B14`,
```c
signed int __cdecl sub_8048B14(unsigned __int8 *a1) {
dword_804B084 = 'FG03';
if ( (*a1 % (100 - *a1)) >= 1 )
exit(4);
...
```
kami membuat `*a1` bernilai `100` sehingga akan menyebabkan `SIGFPE`. Setelah beberapa saat dynamic analysis, kami mendapatkan potongan flag `xxxdxx_Ob50l3te_and_UnS0lv4bl3_l3v3l_f0r_CH3AT3R5`.

Huft oke, sekarang saatnya bagian pengecekan terakhir, fungsi `sub_80490A0`.
```c
_BOOL4 __cdecl sub_80490A0(_BYTE *a1) {
_BYTE *v2; // [esp+Ch] [ebp-4h]

v2 = a1 + 4;
return a1[4] & 4
&& *v2 & 0x40
&& *v2 & 1
&& ~a1[5] == 145
&& !((a1[1] & 0x20) + (*a1 & 0x20) + (a1[2] & 0x20))
&& *a1 & 8
&& a1[2] & 4
&& a1[1] != *a1;
}
```

Karena fungsi tersebut adalah pengecekan constraint, kita dapat menyelesaikannya dengan Z3.

```python
from z3 import *

s = Solver()
xs = [BitVec('x%d' % i, 8) for i in xrange(6)]
for x in xs:
s.add(Or(
And(x >= ord('A'), x <= ord('Z')),
And(x >= ord('a'), x <= ord('z')),
And(x >= ord('0'), x <= ord('9')),
))

s.add(xs[4] & 4 != 0)
s.add(xs[4] & 0x40 != 0)
s.add(xs[4] & 1 != 0)
s.add(xs[5] == ord('n'))
s.add((xs[1] & 0x20) + (xs[0] & 0x20) + (xs[2] & 0x20) == 0)
s.add(xs[0] & 8 != 0)
s.add(xs[2] & 4 != 0)
s.add(xs[1] != xs[0])
s.add(xs[3] == ord('d'))

while s.check() == sat:
m = s.model()
print ''.join([chr(int(m[xs[i]].as_long())) for i in xrange(6)])
s.add(Or([d() != m[d] for d in m]))
```
Akan tetapi, terdapat lebih dari solusi yang memenuhi constraint tersebut (pihak panitia juga telah mengonfirmasi hal ini). Karena karakter ke-4 dan ke-6 adalah huruf `d` and `n`, kami menebak beberapa kata yang masuk akal. Kami mencoba string `HIDdEn_Ob50l3te_and_UnS0lv4bl3_l3v3l_f0r_CH3AT3R5` dan ternyata mendapatkan poin untuk flag!

Original writeup (https://github.com/PDKT-Team/ctf/tree/master/hacklu2018/cheat-console).