↑ Top
↩ Go Back
• 7 min read

Sekai CTF 2022 : Symbolic Needs 1 & 2

Sekai CTF 2022 : Symbolic Needs 1 & 2

A linux memory forensics challenge in two parts.

Symbolic Needs 1

Description

symbolic-needs-1.png

We recently got hold of a cryptocurrency scammer and confiscated his laptop.

Analyze the memdump. Submit the string you find wrapped with SEKAI{}.

Attachment md5sum: 4be69c88e6f19dd9c9f8e6c52bc93c28

Author: BattleMonger

https://storage.googleapis.com/sekaictf/Forensics/memdump.zip

  • Points: 467
  • Solves: 24

File Mirror

Writeup

Judging from the magic numbers, the provided file is a linux memory dump. We’ll use volatility3 for analysis (for some reasons vol2 doesn’t support this particular OS / kernel). Before we can get volatility to work, we need to build a profile for the memory dump using some kernel debugging info. We can run this command:

$ python3 volatility3/vol.py -s ./profile/ -f dump.mem banner                           
Volatility 3 Framework 2.4.0
Progress:  100.00               PDB scanning finished                      
Offset  Banner

0x42400200      Linux version 5.15.0-43-generic (buildd@lcy02-amd64-076) (gcc (Ubuntu 11.2.0-19ubuntu1) 11.2.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #46-Ubuntu SMP Tue Jul 12 10:30:17 UTC 2022 (Ubuntu 5.15.0-43.46-generic 5.15.39)
0x437c3718      Linux version 5.15.0-43-generic (buildd@lcy02-amd64-076) (gcc (Ubuntu 11.2.0-19ubuntu1) 11.2.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #46-Ubuntu SMP Tue Jul 12 10:30:17 UTC 2022 (Ubuntu 5.15.0-43.46-generic 5.15.39)9)

Great, so our memory dump is from Ubuntu, with kernel version 5.15.0-43-generic. Let’s grab our debugging kernel at http://ddebs.ubuntu.com/pool/main/l/linux/linux-image-unsigned-5.15.0-43-generic-dbgsym_5.15.0-43.46_amd64.ddeb, unzip it and run dwarf2json to create our profile:

$ mkdir profile
$ dwarf2json linux --elf usr/lib/debug/boot/vmlinux-5.15.0-43-generic > ./profile/vmlinux-5.15.0-43-generic.json

With our profile working let’s try some plugins:

$ python3 volatility3/vol.py -s ./profile/ -f dump.mem linux.bash.Bash
Volatility 3 Framework 2.4.0

PID Process Command Time Command

1863	bash	2022-08-29 13:45:56.000000 	72.48.117.53.84.48.110.95.119.51.95.52.114.51.95.49.110.33.33.33

Looks like ASCII codes. Decode it and we have the flag!

> String.fromCharCode(72,48,117,53,84,48,110,95,119,51,95,52,114,51,95,49,110,33,33,33)
'H0u5T0n_w3_4r3_1n!!!'

flag: SEKAICTF{H0u5T0n_w3_4r3_1n!!!}


Symbolic Needs 2

Description

symbolic-needs-2.png

Recover the private key of the wallet address 0xACa5872e497F0Cc626d1E9bA28bAEC149315266e. Submit the key wrapped with SEKAI{}.

Attachment md5sum: 4be69c88e6f19dd9c9f8e6c52bc93c28

Author: BattleMonger

Hint: Stick to what you used for Part 1, you all are also missing a very basic but extremely useful command.

  • Points: 482
  • Solves: 18

Writeup

We decided to run all plugins to see what’s left. My teammate found something interesting in linux.psaux.PsAux:

$ python3 volatility3/vol.py -s ./profile/ -f dump.mem linux.psaux.PsAux
...
1863	1845	bash	bash
1878	1863	ncat	ncat -lvnp 1234 -c echo N4GQ2CQAAAAAAEFG5JRPEAIAADRQAAAAAAAAAAAAAAAAAAAAAAAAACIAAAAEAAAAABZ6QAAAABSAAZABNQAFUAD2A5SQA2QBMQBBSAC2AJLQA3QLAEAACAABABSQGZADQMAQCADFASBQAAIALEAGOAC2AVSQMZAEMQCYGAUPBZNAOZIHUAEKCAFABGQQAWQFK4AGIAIEAACABAYDAEAG4CBRABZS65YBAEAACAABABMQAAIAMQDFUCTFBNSQVAYBMQDWIAMFAIMQAWQKMUGGKCVABVSQ4ZIKQMAWICDFBZSQVAYBMQEBMAAYAALQBIIBQMAVUCTHABNA6ZIQMQAGKDTFBKBQCZAIQMBUIAC5CRNBCZIPUAJGKBLFCNSQUZIRMUIWICAXACCQEGIAMQDYGATEAIMAAGIAUEAQCADRLFSQGZAJQMAQCADEAFJQAKIK5EAAAAAAJ3UQCAAAAB5BQVLTMFTWKORAFYXXOYLMNRSXIIDQMFZXG53POJSHUDLCNFYDGOLMNFZXILTUPB2NUALSNQKAAAAAABS6KHWNHGMBH4AWTJ3BE3XKCYFWPVQQSR6WWFSHC2WEUE3P5AP7G46OA3IBWAIA5EBAAAAA5EGAAAAA3ICVO4TPNZTSSFG2ANZXS462ARQXEZ3W3IEHAYLTON3W64TE3ICXA4TJNZ2NUBDFPBUXJWQFO5XXEZDT3ICG64DFN3NACZW2ARZGKYLE3IFHG4DMNF2GY2LOMVZ5UBDDN5SGLWQDMJUW5WQDON2HFWQFPJTGS3DM3IBWYZLO3IEG23TFNVXW42LD3ICXEYLOM5S5UALJ3IDGC4DQMVXGJWQDNFXHJKIAOINQAAAAOINQAAAA7IEHIZLTOQZC44DZ3IEDY3LPMR2WYZJ6AEAAAADTEIAAAAAIAABAEDQBAYAQQAIIAECAGDACBYARZ7YEAMIAGIQBAQBRIARGAEGAE=== | base32 -d > file.pyc
1886	1147	update-notifier	update-notifier
1911	1863	sudo	sudo insmod LiME/src/lime-5.15.0-43-generic.ko path=dump.mem format=lime
...

That’s a python bytecode file and we can decompile it. Let’s try pycdc:

$ cmake . && make -j 30 # build pycdc

$ ./pycdc ./file.pyc
# Source Generated with Decompyle++
# File: file.pyc (Python 3.10)

Unsupported opcode: WITH_EXCEPT_START
import sys
# WARNING: Decompyle incomplete

It failed. Python 3.10 is rather new and pycdc isn’t ready for it yet. So we did it in a hacky way: modify the source code in ASTree.cpp and force it to continue, instead of throwing errors:

/* ASTree.cpp */
/* Remove the return statements in default and insert a break ;) */
...
case Pyc::SETUP_ANNOTATIONS:
    variable_annotations = true;
	break;
default:
	break;
    // fprintf(stderr, "Unsupported opcode: %s\n", Pyc::OpcodeName(opcode & 0xFF));
    // cleanBuild = false;
    // return new ASTNodeList(defblock->nodes());
...

With ./pycdc ./file.pyc > test2.py, that’ll give us much more decompiled code. Not perfect, but almost!

# Source Generated with Decompyle++
# File: file.pyc (Python 3.10)

import sys

try:
    password = sys.argv[1]
finally:
    pass
print('Usage: ./wallet password')
exit()
words = []
with open('bip39list.txt', 'r') as f:
    words = f.read().splitlines()
    None(None, None, None)
if not None:
    pass

code = 0x26F4036773F33FD1BC4E55616472CD7F65086B670B2DD5B84BB4D16F02730E734F72E500L
code = bin(code)[2:]
code = str(code.zfill(len(code) + (12 - len(code) % 12)))
mnemonic = []
for i in range(0, len(code), 12):
    mnemonic.append(words[int(code[i:i + 12], 2) - 1])
print('Wrong')

The program seems to be generating a passphrase from a code. If you look up bip39, you’ll realize it’s a way to use a passphrase to store wallet address and private keys. Since we don’t have access to the wordlist bip39list.txt in the dump, we’ll assume the standard one is used. Let’s clean up the code and generate the passphrase:

words = []
with open('bip39list.txt', 'r') as f:
    words = f.read().splitlines()

code = 0x26F4036773F33FD1BC4E55616472CD7F65086B670B2DD5B84BB4D16F02730E734F72E500
code = bin(code)[2:]
code = str(code.zfill(len(code) + (12 - len(code) % 12)))
mnemonic = []
for i in range(0, len(code), 12):
    mnemonic.append(words[int(code[i:i + 12], 2) - 1])

print(' '.join(mnemonic))
$ python3 test2.py
evidence leopard solution layer legend danger orient project silver flower wrong path stove throw fortune report nuclear old target exact broom hawk toss paper

We’ve got the mnemonics! Let’s head to https://iancoleman.io/bip39/:

bip39-conf.png

The first derived address (0xACa5872e497F0Cc626d1E9bA28bAEC149315266e) matches the description. We need the private key:

bip39-key.png

flag: SEKAICTF{0x81c458e9fae445de18385a3379513acc8e191e4c2667c85aa0a52a32ec4e6d55}

Share on: Twitter