# Deobf Me

| Difficulty | Points |
| ---------- | ------ |
| Medium     | 300    |

## Description

> The symbols are speaking, what do they want?

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

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

## Solution

### TL;DR

#### Long Method

1. One way is to replace the lambda functions with actual functions.
2. Seeing what they do in the `compiler.py`, we can replicate those functions in order to remove the lambda and print the flag.

#### Quick Method

1. Find the portion of the code which represents the flag.
2. Replace "Give it another try!" with the code so it prints out the flag.
3. win

{% hint style="info" %}
Can be written in 4 lines of Python code.
{% endhint %}

### Analysis

We can basically see that `chall.py` contains our obfuscated code. Running `python3 chall.py`, we are prompted for the flag.

<figure><img src="/files/6jhoBKxp342q14gtzcWZ" alt=""><figcaption><p>chall.py Program</p></figcaption></figure>

We are also given the compiler that presumably creates the code in `chall.py`.

<details>

<summary>compiler.py</summary>

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

# Created by Jong Han, thank me later

import random, string
from secret import flag

def eval_obf(x:int)->str:
  global c5; global c6
  add_funcs={'+1':c5,'+2':c6}
  starters=[1,0]
  counter = random.choice(starters)
  result = str(counter)
  while counter < x:
    chosen_func = random.choice(list(add_funcs.values()))
    if chosen_func == add_funcs['+1']:
      counter += 1
    else:
      counter += 2
    if counter > x:
      tmp_list = list(add_funcs.values())
      tmp_list.remove(chosen_func)
      chosen_func = tmp_list[0]
    result =  chosen_func + '(' + result + ')'
  return result

def str_to_chrs(x:str,func_of_chr:str,obf_method:callable)->str:
  return '+'.join(map(lambda a:f'{func_of_chr}('+obf_method(ord(a))+')',x))

def initialize(code:str,flag:str)->str:
  global c1; global c2; global c3; global c4; global c5; global c6; global c7; global c8; global c9
  c1,c2,c3,c4,c5,c6,c7,c8,c9 = random.sample(string.ascii_lowercase,9)


  l = 'lambda'
  char = str_to_chrs('chr','chr',str)
  ladd1 = str_to_chrs('lambda a:a+1',c2,str)
  ladd2 = str_to_chrs('lambda b:b+2',c2,str)
  code = str_to_chrs(code,c4,eval_obf)
  p = str_to_chrs('print',c4,str)
  ifstr = str_to_chrs('if',c4,str)
  flag_obf = str_to_chrs(flag,c4,eval_obf)
  result = f'({l} {c1}:({l} {c1},{c2}:({l} {c3},{c4},{c5},{c6}:(lambda {c7},{c8},{c9}: {c9}("You R l33t Python RE master.") if {c7}=={c8} else {c9}("Give it another try!"))({c3}({code}),{flag_obf},{c3}({p})))({c1},{c2},({c1}({ladd1})),({c1}({ladd2}))))({c1},{c1}({char})))(eval)'
  return result

code = 'input("Enter flag: ")'
with open('chall.py','w') as f: f.write(initialize(code,flag))

```

</details>

We can see in `compiler.py` that the `flag` and the `input(...)` code are given as arguments into the `initalize` function.

```python
with open('chall.py','w') as f: f.write(initialize(code,flag))
```

In the initialize function, we see that the `result` variable is the important one to focus on as it returns the code in `chall.py`. I have rearranged result so that it can be understood more easily.

```python
({l} {c1}:
({l} {c1},{c2}:
({l} {c3},{c4},{c5},{c6}:
(lambda {c7},{c8},{c9}: {c9}("You R l33t Python RE master.") if {c7}=={c8} else {c9}("Give it another try!"))
({c3}({code}),{flag_obf},{c3}({p})))
({c1},{c2},({c1}({ladd1})),({c1}({ladd2}))))
({c1},{c1}({char})))
(eval)
```

### Solving Through The Long Method

Analyzing `result`, we see that the `eval` function will always be passed into `c1`.

So {c1}({char}) means it just evaluates `char` into the `chr` function. This is then passed into c2 in the next lambda function.

We then see that eval(ladd1) and eval(ladd2) is made, where ladd1 and ladd2 are just functions to add 1 and add 2 to the given number respectively.

So in the next lambda function, we can see `c3`=eval(), `c4`=chr(), `c5`=+1func, `c6`=+2func. We can verify that we have identified `c5` and `c6` correctly as it is provided in the `eval_obf` function as `+1` and `+2` respectively.

In the next lambda function, we can see that `c7`=eval(input("Enter flag: ")), `c8`=`flag_obf`, and `c9`=eval("print") (which is basically the print function))

We go back to looking at the `flag_obf` variable. It is provided as an argument into the last lambda function. We can filter and take in only the portion of the obfuscated code where `({c3}({code}),{flag_obf},{c3}({p})))`, create our own functions for `c3`,`c4`,`c5`,`c6`, and evaluate `print(flag_obf)`

{% tabs %}
{% tab title="long\_solve.py" %}

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

def c3_func(c):
    print(c)
    return eval(c)

def c4_func(c):
    return chr(c)

def c5_func(c):
    return c+1

def c6_func(c):
    return c+2
    
obfcode = open ('chall.py','r').read()
simplified_code = obfcode.split('("Give it another try!"))(')[1]
c1 = re.search(r'lambda [a-z]:',obfcode)[0][-2]
simplified_code = simplified_code.split(f'))({c1}')[0]
find_c = re.search(r'lambda ([a-z],){3}[a-z]',obfcode)[0].split('lambda ')[-1]
c3,c4,c5,c6 = find_c.split(',')
exec(f'{c3}=c3_func;{c4}=c4_func;{c5}=c5_func;{c6}=c6_func')
eval_code, flag, eval_print = simplified_code.split(',')
eval(f"print({flag})")

```

{% endtab %}
{% endtabs %}

### Solving Through The Quick Method

This just replaces the "Give it another try!" string with the flag.

We can easily find the obfuscated flag by using the `,` as a delimiter.

{% tabs %}
{% tab title="quick\_solve.py" %}

```python
obfcode = open ('chall.py','r').read()
flagcode = obfcode.split(',')[7]
code = obfcode.replace('"Give it another try!"',flagcode)
eval(code)
```

{% endtab %}
{% endtabs %}


---

# 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://jonghan.gitbook.io/ctf-writeups/authored/nyp-infosec-ctf-december-2022/reverse/deobf-me.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.
