【FE-CTF2023】Writeup

# 初めに

どうも、クソ雑魚のなんちゃてエンジニアです。
本記事は __FE-CTF 2023__ の2題のWrtiteupとなります。

https://fe-ctf.dk/

2題だけ解けたので記載します。
![スクリーンショット 2023-10-30 095803.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2036848/188cc1a7-a8d6-6be1-cafe-0401b358a1a5.png)


# [OMGACM] What Giefs?
> Now look, we're going to be dealing with some real serious stuff today. If you haven't heard of it it's called math! And without it, none of us would even exist.
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2036848/de0f4c6b-04a6-821b-17e1-cee4a2ef53fb.png)

接続してみろってことね。
接続してみる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2036848/b5a7318f-de31-3db0-ecce-c433203c63fa.png)
あーこれ`eval`するやつだ。というわけで解いてみる。
`*`を`x`と表現されていたり、べき乗が`^`と表現されていたり、平方根が`sqrt`と表現されていたりしたので`replace`もしておく。いったんのsolverは以下だ。
```python:solv.py
from pwn import *
import math

context.log_level = "debug"
p = remote("what-giefs.hack.fe-ctf.dk", 1337)

for _ in range(100):
    p.recvuntil(b'What giefs ')
    cal = p.recvuntil(b'?')[:-1]
    p.recvuntil(b'\n')
    cal = cal.replace(b' x ', b'*').replace(b'^', b'**').replace(b'sqrt', b'math.sqrt')
    p.sendline(str(int(eval(cal))))
print(p.recvrepeat().decode())
```
これで実行すればわかるが、`0x`で16進数表現や`0b`での2進数表現、`0o`の8進数表現も途中から交じってくる。それに対して10進数表現で返すと、以下のように`AssertionError`を返される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2036848/73743fe9-6e2a-2b1b-0931-3a5f32a73c72.png)
進数表現を合わせて返さなければいけないようだ。
修正したコードは以下だ。
```python:solv.py
from pwn import *
import math

context.log_level = "debug"
p = remote("what-giefs.hack.fe-ctf.dk", 1337)

for _ in range(100):
    p.recvuntil(b'What giefs ')
    cal = p.recvuntil(b'?')[:-1]
    p.recvuntil(b'\n')
    cal = cal.replace(b' x ', b'*').replace(b'^', b'**').replace(b'sqrt', b'math.sqrt')
    if b'0x' in cal:
        p.sendline(str(hex(int(eval(cal)))))
    elif b'0b' in cal:
        p.sendline(str(bin(int(eval(cal)))))
    elif b'0o' in cal:
        p.sendline(str(oct(int(eval(cal)))))
    else:
        p.sendline(str(int(eval(cal))))
print(p.recvrepeat().decode())
```
これでもエラーを返される。ローマ数字を`eval`しないといけない。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2036848/09192d84-bb0e-1035-bdfa-99ff0a402457.png)
何もわからんときはGPTさんにぶん投げます（以下の資料を参考にどうぞ）。

https://github.com/schecthellraiser606/open_doc/blob/main/20231027/OSCP%E5%8B%89%E5%BC%B7%E4%BC%9A04.pdf

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2036848/97cf282f-220a-2d6d-82c5-19e3eca8a7cd.png)
そんなこんなでできたsolverは以下です。
```python:solv.py
from pwn import *
import math, re

context.log_level = "debug"
p = remote("what-giefs.hack.fe-ctf.dk", 1337)

def roman_to_arabic(roman):
    roman_dict = {'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, 'M': 1000}
    arabic = 0
    prev_value = 0
    for numeral in reversed(roman):
        value = roman_dict[numeral]
        if value < prev_value:
            arabic -= value
        else:
            arabic += value
        prev_value = value
    return arabic
  
def arabic_to_roman(arabic):
    roman_dict = {1: 'I', 4: 'IV', 5: 'V', 9: 'IX', 10: 'X', 40: 'XL', 50: 'L', 90: 'XC', 100: 'C', 400: 'CD', 500: 'D', 900: 'CM', 1000: 'M'}
    
    if arabic < 0:
        arabic = -arabic
        sign = '-'
    else:
        sign = ''
        
    roman = ''
    for value in sorted(roman_dict.keys(), reverse=True):
        while arabic >= value:
            roman += roman_dict[value]
            arabic -= value
            
    return sign + '0r' + roman


for _ in range(100):
    p.recvuntil(b'What giefs ')
    cal = p.recvuntil(b'?')[:-1]
    p.recvuntil(b'\n')
    cal = cal.replace(b' x ', b'*').replace(b'^', b'**').replace(b'sqrt', b'math.sqrt')
    if b'0x' in cal:
        p.sendline(str(hex(int(eval(cal)))))
    elif b'0b' in cal:
        p.sendline(str(bin(int(eval(cal)))))
    elif b'0o' in cal:
        p.sendline(str(oct(int(eval(cal)))))
    elif b'0r' in cal:
        cal_string = cal.decode()
        pattern = r'0r[IVXLCDM]+'
        matches = re.findall(pattern, cal_string)
        for match in matches:
            arabic_value = roman_to_arabic(match[2:]) 
            cal_string = cal_string.replace(match, str(arabic_value), 1)
        p.sendline(str(arabic_to_roman(int(eval(cal_string)))))
    else:
        p.sendline(str(int(eval(cal))))
print(p.recvrepeat().decode())
```
これで行けるかと思うと何やら変な文字が入ってきてます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2036848/4ad8f425-93c0-8839-feaa-5e209bc5ac49.png)
`nulla`って？？まぁ0で補完します。
```python:solv.py
from pwn import *
import math, re

context.log_level = "debug"
p = remote("what-giefs.hack.fe-ctf.dk", 1337)

def roman_to_arabic(roman):
    roman_dict = {'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, 'M': 1000}
    arabic = 0
    prev_value = 0
    for numeral in reversed(roman):
        value = roman_dict[numeral]
        if value < prev_value:
            arabic -= value
        else:
            arabic += value
        prev_value = value
    return arabic
  
def arabic_to_roman(arabic):
    roman_dict = {1: 'I', 4: 'IV', 5: 'V', 9: 'IX', 10: 'X', 40: 'XL', 50: 'L', 90: 'XC', 100: 'C', 400: 'CD', 500: 'D', 900: 'CM', 1000: 'M'}
    
    if arabic < 0:
        arabic = -arabic
        sign = '-'
    else:
        sign = ''
        
    roman = ''
    for value in sorted(roman_dict.keys(), reverse=True):
        while arabic >= value:
            roman += roman_dict[value]
            arabic -= value
            
    return sign + '0r' + roman


for _ in range(100):
    p.recvuntil(b'What giefs ')
    cal = p.recvuntil(b'?')[:-1]
    p.recvuntil(b'\n')
    cal = cal.replace(b' x ', b'*').replace(b'^', b'**').replace(b'sqrt', b'math.sqrt').replace(b'nulla', b'0')
    if b'0x' in cal:
        p.sendline(str(hex(int(eval(cal)))))
    elif b'0b' in cal:
        p.sendline(str(bin(int(eval(cal)))))
    elif b'0o' in cal:
        p.sendline(str(oct(int(eval(cal)))))
    elif b'0r' in cal:
        cal_string = cal.decode()
        pattern = r'0r[IVXLCDM]+'
        matches = re.findall(pattern, cal_string)
        for match in matches:
            arabic_value = roman_to_arabic(match[2:]) 
            cal_string = cal_string.replace(match, str(arabic_value), 1)
        p.sendline(str(arabic_to_roman(int(eval(cal_string)))))
    else:
        p.sendline(str(int(eval(cal))))
print(p.recvrepeat().decode())
```
これで行けるかと思ったがまたエラーをはいてくる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2036848/4f31b8c4-767b-27e8-cdf5-bba39b560339.png)
16進数とか色んな進数表現ととローマ数字が混在してきた。
...if分岐の順番を変えます。ローマ数字以外はそのままeval出来たはずなので。
```python:solv.py
from pwn import *
import math, re

context.log_level = "debug"
p = remote("what-giefs.hack.fe-ctf.dk", 1337)

def roman_to_arabic(roman):
    roman_dict = {'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, 'M': 1000}
    arabic = 0
    prev_value = 0
    for numeral in reversed(roman):
        value = roman_dict[numeral]
        if value < prev_value:
            arabic -= value
        else:
            arabic += value
        prev_value = value
    return arabic
  
def arabic_to_roman(arabic):
    roman_dict = {1: 'I', 4: 'IV', 5: 'V', 9: 'IX', 10: 'X', 40: 'XL', 50: 'L', 90: 'XC', 100: 'C', 400: 'CD', 500: 'D', 900: 'CM', 1000: 'M'}
    
    if arabic < 0:
        arabic = -arabic
        sign = '-'
    else:
        sign = ''
        
    roman = ''
    for value in sorted(roman_dict.keys(), reverse=True):
        while arabic >= value:
            roman += roman_dict[value]
            arabic -= value
            
    return sign + '0r' + roman


for _ in range(100):
    p.recvuntil(b'What giefs ')
    cal = p.recvuntil(b'?')[:-1]
    p.recvuntil(b'\n')
    cal = cal.replace(b' x ', b'*').replace(b'^', b'**').replace(b'sqrt', b'math.sqrt').replace(b'nulla', b'0')
    if b'0r' in cal:
        cal_string = cal.decode()
        pattern = r'0r[IVXLCDM]+'
        matches = re.findall(pattern, cal_string)
        for match in matches:
            arabic_value = roman_to_arabic(match[2:]) 
            cal_string = cal_string.replace(match, str(arabic_value), 1)
        p.sendline(str(arabic_to_roman(int(eval(cal_string)))))
    elif b'0x' in cal:
        p.sendline(str(hex(int(eval(cal)))))
    elif b'0b' in cal:
        p.sendline(str(bin(int(eval(cal)))))
    elif b'0o' in cal:
        p.sendline(str(oct(int(eval(cal)))))
    else:
        p.sendline(str(int(eval(cal))))
print(p.recvrepeat().decode())
```
これでフラグゲットだぜ！
![スクリーンショット 2023-10-29 012501.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2036848/bef9acd2-696f-5d1c-09c5-a38521b8980c.png)

# [Rev] Abacus - Minimus
>The expressiveness of C combined with the ease of reasoning of Haskell. Wait, am I doing this right?
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2036848/7078c26a-16fe-deff-226c-0017ec124211.png)

## Ghidra
バイナリが渡されるので実行してみる。
![スクリーンショット 2023-10-30 220911.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2036848/05ec7ed5-6d14-c4ad-6ee0-230ce3bdbc75.png)
フラグ判定する実行ファイルのようです。
Ghidraに食わせます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2036848/450c5b1e-7f8a-0cc3-3da9-b461632952ee.png)
`num`の中身はこんな感じです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2036848/e0d82bb4-6b8e-a8c1-f7d9-17fdeca05bf7.png)
evalの中身はこんな感じ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2036848/181030c7-88a1-64b9-c2c5-be792f43c39f.png)
assertの中身はこんな感じでpoofに飛ぶと間違いの表示になるみたい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2036848/2a5114fb-150a-6b31-781a-bb7fce76a8c3.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2036848/e0219ee4-995d-99a5-2c08-78d7fff577d1.png)

正味ここら辺のデコンパイルされた関数を深ぼっても意味わからなかったのでgdbで動的デバックを実行します。
mainで参照しているこの`data`が気になるのでこの数字の羅列は覚えておこうと思います。
ここら辺はGUESSですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2036848/592b928f-d024-ad3c-4c09-0172500c666f.png)

## gdb
フラグ判定に`flag{test}`とか適当に代入してステップ実行します。
最初に気になるcmpがありました。
ん？入力文字とraxを比較している？普通に合うわけないが？？終端文字判定か？？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2036848/53da2bfa-3b5b-3371-d387-28928fedb96e.png)
２文字目もcmpしてる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2036848/71467c2b-38ab-8235-518b-d6c898db51a8.png)
間違うところの文字列も比較している？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2036848/9a6e71fb-32fb-f6f3-79d7-f9137241d0d2.png)
ここから全ての文字を比較した後（右手のEnter何千回と押した...）に何やら先ほどのデータの先頭にあった`0xC5`と`0xa3`が出現しているところを発見する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2036848/e3f5b11d-3f5a-3ba9-f7be-5c27a66b9b3d.png)
ん？`0x66`の`f`の文字も見えるぞ？もう少しステップ見てみるか。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2036848/2980d900-da2d-988a-c288-6143d1fe399f.png)
次のdataの`0xcf`がRAXに入って前のdataがRBXに入っている。これをXORすると？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2036848/b9c701c5-468f-58db-f836-1d26ffcca03a.png)
あ、RAXに出てきた。完全に理解した。前後の文字でXORしたものがフラグだ！
`data`の文字を持ってきます。
```bash
gdb-peda$ telescope 0x555555558050
0000| 0x555555558050 --> 0xb1d3b2c9aecfa3c5 
0008| 0x555555558058 --> 0xe889eac7b4c1a2c3 
0016| 0x555555558060 --> 0xf98aff9cfd8fed8c 
0024| 0x555555558068 --> 0x8484 
0032| 0x555555558070 --> 0xffffffff00000003 
0040| 0x555555558078 --> 0xffffffffffffffff 
0048| 0x555555558080 --> 0x0 
0056| 0x555555558088 --> 0x0 
gdb-peda$ 
```
このデータからSolverを書きます。(Errorは気にしないでください。)
```python:solv.py
from functools import reduce
from pwn import *

param=reduce(lambda x,y: x+p64(y), [0xb1d3b2c9aecfa3c5,0xe889eac7b4c1a2c3,0xf98aff9cfd8fed8c,0x8484], b'')

for i in range(len(param)):
    print(chr(param[i+1]^param[i]), end='')
```
上記回せばフラグをゲットできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2036848/a7821f06-2eb1-cf49-cdf7-2664e6aecc3a.png)

# まとめ
Revが根性ゲーでした。何回Enter押したか覚えてません...
楽しかったです。ありがとうございました。