CTF Writeups
WebsiteDiscordSocials..
  • CryptoCat's CTF writeups
  • 2025
    • Tsuku
      • Web
        • len_len
        • Flash
        • YAMLwaf
    • CTF@CIT
      • Web
        • Breaking Authentication
        • Commit & Order: Version Control Unit
        • How I Parsed your JSON
        • Mr. Chatbot
        • Keeping Up with the Credentials
  • 2024
    • CryptoCat
      • Summar-AI-ze
    • Intigriti
      • Warmup
        • BabyFlow
        • In Plain Sight
        • IrrORversible
        • Layers
        • Rigged Slot Machine 1
      • Game
        • Bug Squash 1
        • Bug Squash 2
      • Rev
        • Secure Bank
      • Web
        • Biocorp
        • Cat Club
        • Pizza Paradise
        • SafeNotes 2.0
      • Misc
        • Quick Recovery
        • Triage Bot 2
      • Pwn
        • Floormat Sale
        • Retro2Win
        • Rigged Slot Machine 2
        • UAP
      • Crypto
        • Schrodinger's Pad
      • Mobile
        • Cold Storage
      • OSINT
        • No Comment
        • Trackdown
        • Trackdown 2
      • Forensics
        • CTF Mind Tricks
        • Hoarded Flag
        • Password Management
    • CSAW
      • Web
        • Playing on the Backcourts
        • Log Me In
        • Lost Pyramid
        • BucketWars
    • CyberSpace
      • Web
        • Feature Unlocked
    • UIU
      • Web
        • Fare Evasion
        • Log Action
    • Wani
      • Web
        • Bad Worker
        • PoW
        • One Day One Letter
    • Akasec
      • Web
    • HTB Cyber Apocalypse
      • Web
        • Flag Command
        • TimeKORP
        • KORP Terminal
        • Labyrinth Linguist
        • Locktalk
        • SerialFlow
        • Testimonial
  • 2023
    • Intigriti
      • Gamepwn
        • Dark Secrets
      • Misc
        • Triage Bot
      • OSINT
        • Photographs
      • Pwn
        • Floormat Store
      • Web
        • Bug Report Repo
        • My Music
    • Imaginary
      • Web
        • Blank
        • IDORiot
        • Inspection
        • Login
        • Perfect Picture
        • Roks
    • Google
      • Pwn
        • Write-Flag-Where
    • Cyber Apocalypse
      • AI
        • Last Hope
        • Mysterious Learning
      • Crypto
        • Perfect Synchronization
      • Pwn
        • Getting Started
        • Labyrinth
        • Pandora's Box
        • Void
      • Rev
        • Cave System
        • Hunting License
        • Needle in a Haystack
        • Shattered Tablet
        • She Sells Sea Shells
    • Sekai
      • Rev
        • Azusawa's Gacha World
    • Amateurs
      • Web
        • Sanity
        • Waiting an Eternity
    • NahamCon
      • Web
        • Hidden Figures
        • Marmalade 5
        • Obligatory
        • Star Wars
        • Stickers
    • Angstrom
      • Pwn
        • Leek
  • 2022
    • Imaginary (iCTF)
      • Crypto
        • ASE
      • Pwn
        • Links 1
        • Links 2
        • Links 3
        • Open Doors
    • SEETF
      • Pwn
        • 4mats
        • Easy Overflow
      • Rev
        • BabyReeee
      • Web
        • Super-Secure-Requests-Forwarder
    • HTB Cyber Apocalypse
      • Pwn
        • Hellbound
    • Angstrom
      • Pwn
        • Really Obnoxious Problem
        • Wah
        • Whats My Name
        • Where Am I
      • Web
        • Crumbs
        • Xtra Salty Sardines
    • NahamCon
      • Pwn
        • Baby Steps
      • Web
        • Flaskmetal Alchemist
        • Hacker Ts
        • Two for One
    • Pico
      • Forensics
        • Side Channel
      • Pwn
        • Buffer Overflow 1
        • Buffer Overflow 2
        • Buffer Overflow 3
        • Flag Leak
        • Function Overwrite
        • ROPfu
        • RPS
        • Stack Cache
        • Wine
        • X-Sixty-What
      • Rev
        • Wizardlike
      • Web
        • Noted
    • Space Heroes
      • Pwn
        • Vader
      • Web
        • Flag in Space
    • Intigriti
      • Pwn
        • Bird
        • Cake
        • Easy Register
        • Search Engine
    • Dice
      • Pwn
        • Interview Opportunity
  • 2021
    • Pico
      • Pwn
        • Unsubscriptions Are Free
    • Crusaders of Rust (COR)
      • Crypto
        • Fibinary
      • Pwn
        • Chainblock
    • HTB Cyber Santa
      • Crypto
        • Meet Me Halfway
        • Xmas Spirit
      • Pwn
        • Minimelfistic
        • Mr. Snowy
        • Naughty List
        • Sleigh
      • Rev
        • Infiltration
        • Intercept
    • K3rn3l
      • Crypto
        • Badseed
        • Twizzty Buzzinezz
    • HTB x Synack RedTeamFive
      • Misc
        • Context
        • Hotel
      • Pwn
        • Air Supplies
        • Injection Shot
        • Library
        • Recruitment
      • Rev
        • Knock Knock
        • Split
    • KillerQueen
      • Pwn
        • A Kind of Magic
        • Tweety Birb
        • Zoom2Win
    • HacktivityCon
      • Pwn
        • Retcheck
        • The Library
        • Yabo
      • Web
        • Availability
    • CSAW
      • Pwn
        • Alien Math
        • Password Checker
      • Rev
        • Checker
    • HackyHolidays
      • Crypto
        • Cute Invoice
        • Mineslazer
      • Forensics
        • Injection Traffic
        • Power Snacks
      • Pwn
        • Deleted Flag
        • Engine Control
      • Web
        • Skylark
    • HTB Cyber Apocalypse
      • Crypto
        • Phasestream
      • Misc
        • Alien Camp
        • Build Yourself In
      • Pwn
        • Controller
        • System Drop
      • Web
        • Blitzprop
        • E-Tree
        • Wild Goose Hunt
    • Angstrom
      • Pwn
        • Sanity Checks
        • Secure Login
        • Sticky Stacks
        • Tranquil
      • Rev
        • Free Flags
        • Jailbreak
      • Web
        • Jar
Powered by GitBook
On this page
  • Challenge Description
  • Solution
  1. 2024
  2. Intigriti
  3. Mobile

Cold Storage

Writeup for Cold Storage (Mobile) - 1337UP LIVE CTF (2024) 💜

PreviousMobileNextOSINT

Last updated 4 months ago

Challenge Description

People say you should store your keys offline in cold storage, so I built this offline app! I think that's what cold storage means 🤔

Solution

Open the mobile app with android-studio or GenyMotion or w/e and you'll find a crypto cold storage wallet that requires a pin.

Now is the time to decompile the APK with a tool like jadx-gui (maybe you did this first).

function unlockVault() {
    var pin = document.getElementById("pin").value.trim();
    if (pin === "7331") {
        document.getElementById("message").innerText = "Correct PIN!";
        document.getElementById("message").style.color = "green";
        document.getElementById("keyList").style.display = "block";
        retrieveencryptedKey();
    } else {
        document.getElementById("message").innerText = "Invalid PIN!";
        document.getElementById("message").style.color = "red";
        document.getElementById("keyList").style.display = "none";
    }
}

It retrieves the encrypted key.

function retrieveencryptedKey() {
    var keyInput = document.getElementById("encryptedKey");
    var encryptedKey = keygen();
    keyInput.value = encryptedKey;
    document.getElementById("message").innerText = "Please use your standalone decryption device to complete the recovery!";
    document.getElementById("message").style.color = "blue";
}

It looks something like this (the UI and encrypted format changed a bit but I cba booting android-studio in my VM lol).

But that's no use, we want the decrypted key! Lets check the keygen() function.

(function (_0x506dbf, _0x170411) {
    const _0x12e004 = a0_0x1707,
        _0x3fbe25 = _0x506dbf();
    while (!![]) {
        try {
            const _0x3b5636 =
                (parseInt(_0x12e004(0x122)) / 0x1) * (parseInt(_0x12e004(0x117)) / 0x2) +
                (-parseInt(_0x12e004(0x111)) / 0x3) * (-parseInt(_0x12e004(0x121)) / 0x4) +
                (-parseInt(_0x12e004(0x11b)) / 0x5) * (parseInt(_0x12e004(0x11f)) / 0x6) +
                (parseInt(_0x12e004(0x113)) / 0x7) * (-parseInt(_0x12e004(0x11d)) / 0x8) +
                (parseInt(_0x12e004(0x125)) / 0x9) * (parseInt(_0x12e004(0x11e)) / 0xa) +
                -parseInt(_0x12e004(0x123)) / 0xb +
                (parseInt(_0x12e004(0x120)) / 0xc) * (parseInt(_0x12e004(0x112)) / 0xd);
            if (_0x3b5636 === _0x170411) break;
            else _0x3fbe25["push"](_0x3fbe25["shift"]());
        } catch (_0x18c02d) {
            _0x3fbe25["push"](_0x3fbe25["shift"]());
        }
    }
})(a0_0x32dd, 0x4ff3a);
function a0_0x32dd() {
    const _0xb65be8 = [
        "9425749445e494332757363353f5d6f50353b79445d7336343270373270366f586365753f546c60336f5",
        "length",
        "map",
        "38495LKnOYO",
        "substr",
        "8lZAZpw",
        "6486450oYKfNK",
        "402RerQLO",
        "12MNesgS",
        "4FulGyI",
        "528939ZPevpd",
        "861608xHrljL",
        "split",
        "9gQnkOh",
        "toString",
        "242571ENkSLa",
        "502515FcoXSF",
        "2628171KytvIJ",
        "push",
        "slice",
        "join",
        "2HiwuOL",
    ];
    a0_0x32dd = function () {
        return _0xb65be8;
    };
    return a0_0x32dd();
}
function affineEncrypt(_0x1930bc, _0x36e79b, _0x33477e) {
    return (_0x36e79b * _0x1930bc + _0x33477e) % 0x100;
}
function xor(_0x3a38fa, _0x3c3309) {
    return _0x3a38fa ^ _0x3c3309;
}
function a0_0x1707(_0x3d4d4c, _0x35b685) {
    const _0x32dd9d = a0_0x32dd();
    return (
        (a0_0x1707 = function (_0x170770, _0x4c15fe) {
            _0x170770 = _0x170770 - 0x110;
            let _0x3e6dad = _0x32dd9d[_0x170770];
            return _0x3e6dad;
        }),
        a0_0x1707(_0x3d4d4c, _0x35b685)
    );
}
function hexToBytes(_0x1d9eb0) {
    const _0x3e7222 = a0_0x1707;
    let _0x2ac99a = [];
    for (let _0x2363dc = 0x0; _0x2363dc < _0x1d9eb0[_0x3e7222(0x119)]; _0x2363dc += 0x2) {
        _0x2ac99a[_0x3e7222(0x114)](parseInt(_0x1d9eb0[_0x3e7222(0x11c)](_0x2363dc, 0x2), 0x10));
    }
    return _0x2ac99a;
}
function reverseString(_0x22dcba) {
    const _0x102ddd = a0_0x1707;
    return _0x22dcba[_0x102ddd(0x124)]("")["reverse"]()[_0x102ddd(0x116)]("");
}
function keygen() {
    const _0x588caa = a0_0x1707;
    let _0x620410 = _0x588caa(0x118),
        _0x19eb60 = [
            _0x620410[_0x588caa(0x115)](0x0, 0xe),
            _0x620410[_0x588caa(0x115)](0xe, 0x1c),
            _0x620410[_0x588caa(0x115)](0x1c, 0x2a),
            _0x620410[_0x588caa(0x115)](0x2a, 0x38),
            _0x620410["slice"](0x38, 0x46),
            _0x620410[_0x588caa(0x115)](0x46, 0x54),
        ],
        _0x4c2f5e = [_0x19eb60[0x3], _0x19eb60[0x5], _0x19eb60[0x1], _0x19eb60[0x4], _0x19eb60[0x2], _0x19eb60[0x0]],
        _0x22e526 = reverseString(_0x4c2f5e["join"]("")),
        _0x2051e9 = hexToBytes(_0x22e526),
        _0x3788f1 = 0x9,
        _0x2afabe = 0x7,
        _0x56285d = 0x33,
        _0x351569 = _0x2051e9["map"]((_0x585a6f) => xor(affineEncrypt(_0x585a6f, _0x3788f1, _0x2afabe), _0x56285d));
    return _0x351569[_0x588caa(0x11a)]((_0x5ca89b) => ("0" + _0x5ca89b[_0x588caa(0x110)](0x10))[_0x588caa(0x115)](-0x2))[_0x588caa(0x116)]("");
}

Ewww.. obfuscation 🤮 There's a lot of approaches here. You might try to manually reverse, or throw it into some de-obfuscation tool 🤔

You could also paste it into the console, then just run keygen(). It will give us abf6c8abb5daabc8ab69d7846def17b19c6dae843a6dd7e1b1173ae16db184e0b86dd7c5843ae8dee15f again, but with some breakpoints you can trace through the logic.

Maybe you focus on values you can see in the original code, e.g. what is 9425749445e494332757363353f5d6f50353b79445d7336343270373270366f586365753f546c60336f5 when unhexed? Unfortunately, not plaintext!

function affineEncrypt(_0x1930bc, _0x36e79b, _0x33477e) {
    return (_0x36e79b * _0x1930bc + _0x33477e) % 0x100;
}
function xor(_0x3a38fa, _0x3c3309) {
    return _0x3a38fa ^ _0x3c3309;
}
function hexToBytes(_0x1d9eb0) {
    let _0x2ac99a = [];
    for (let _0x2363dc = 0x0; _0x2363dc < _0x1d9eb0.length; _0x2363dc += 0x2) {
        _0x2ac99a.push(parseInt(_0x1d9eb0.substr(_0x2363dc, 0x2), 0x10));
    }
    return _0x2ac99a;
}
function reverseString(_0x22dcba) {
    return _0x22dcba.split("").reverse().join("");
}
function keygen() {
    let _0x19eb60 = [
        "9425749445e494332757363353f5d6f50353b79445d7336343270373270366f586365753f546c60336f5".slice(0x0, 0xe),
        "9425749445e494332757363353f5d6f50353b79445d7336343270373270366f586365753f546c60336f5".slice(0xe, 0x1c),
        "9425749445e494332757363353f5d6f50353b79445d7336343270373270366f586365753f546c60336f5".slice(0x1c, 0x2a),
        "9425749445e494332757363353f5d6f50353b79445d7336343270373270366f586365753f546c60336f5".slice(0x2a, 0x38),
        "9425749445e494332757363353f5d6f50353b79445d7336343270373270366f586365753f546c60336f5".slice(0x38, 0x46),
        "9425749445e494332757363353f5d6f50353b79445d7336343270373270366f586365753f546c60336f5".slice(0x46, 0x54),
    ];
    let _0x4c2f5e = [_0x19eb60[0x3], _0x19eb60[0x5], _0x19eb60[0x1], _0x19eb60[0x4], _0x19eb60[0x2], _0x19eb60[0x0]];
    let _0x22e526 = _0x4c2f5e.join("").split("").reverse().join("");
    let _0x2051e9 = hexToBytes(_0x22e526);
    let _0x351569 = _0x2051e9.map((_0x585a6f) => (0x9 * _0x585a6f + 0x7) % 0x100 ^ 0x33);
    return _0x351569.map((_0x5ca89b) => ("0" + _0x5ca89b.toString(0x10)).slice(-0x2)).join("");
}

Much better! I'll also ask ChatGPT to further deobfuscate, including variable renaming, comments etc.

// Function to perform Affine encryption on a single byte
function affineEncrypt(inputByte, multiplier, increment) {
    return (multiplier * inputByte + increment) % 256;
}

function xor(byte1, byte2) {
    return byte1 ^ byte2;
}

function hexToBytes(hexString) {
    let byteArray = [];
    // Loop through the hex string, two characters at a time
    for (let i = 0; i < hexString.length; i += 2) {
        // Convert each pair of hex characters to a byte and add to the array
        byteArray.push(parseInt(hexString.substr(i, 2), 16));
    }
    return byteArray;
}

function reverseString(str) {
    return str.split("").reverse().join("");
}

function keygen() {
    // Split the long hex string into six parts
    let parts = [
        "9425749445e494332757363353f5d6f50353b79445d7336343270373270366f586365753f546c60336f5".slice(0, 14),
        "9425749445e494332757363353f5d6f50353b79445d7336343270373270366f586365753f546c60336f5".slice(14, 28),
        "9425749445e494332757363353f5d6f50353b79445d7336343270373270366f586365753f546c60336f5".slice(28, 42),
        "9425749445e494332757363353f5d6f50353b79445d7336343270373270366f586365753f546c60336f5".slice(42, 56),
        "9425749445e494332757363353f5d6f50353b79445d7336343270373270366f586365753f546c60336f5".slice(56, 70),
        "9425749445e494332757363353f5d6f50353b79445d7336343270373270366f586365753f546c60336f5".slice(70, 84),
    ];
    // Reorder parts and join them into a single string
    let reordered = [parts[3], parts[5], parts[1], parts[4], parts[2], parts[0]].join("");
    // Reverse the string and convert it to bytes
    let byteArray = hexToBytes(reverseString(reordered));
    // Apply affine transformation and XOR to each byte
    let transformedArray = byteArray.map((byte) => (9 * byte + 7) % 256 ^ 0x33);
    // Convert the transformed bytes back to a hex string
    return transformedArray.map((byte) => ("0" + byte.toString(16)).slice(-2)).join("");
}

We can paste that into the devtools console and print out each step.

let reordered = [parts[3], parts[5], parts[1], parts[4], parts[2], parts[0]].join("");
console.log(reordered);
let byteArray = hexToBytes(reverseString(reordered));
console.log(byteArray);
let transformedArray = byteArray.map((byte) => (9 * byte + 7) % 256 ^ 0x33);
console.log(transformedArray);

return transformedArray.map((byte) => ("0" + byte.toString(16)).slice(-2)).join("");

This time, we get some new values.

Hex decoding d733634327037353f546c60336f5332757363353f5270366f5863657d6f50353b794459425749445e494 produces garbage.

Alright, let's convert the other arrays to ASCII.

function keygen() {
    // Split the long hex string into six parts
    let parts = [
        "9425749445e494332757363353f5d6f50353b79445d7336343270373270366f586365753f546c60336f5".slice(0, 14),
        "9425749445e494332757363353f5d6f50353b79445d7336343270373270366f586365753f546c60336f5".slice(14, 28),
        "9425749445e494332757363353f5d6f50353b79445d7336343270373270366f586365753f546c60336f5".slice(28, 42),
        "9425749445e494332757363353f5d6f50353b79445d7336343270373270366f586365753f546c60336f5".slice(42, 56),
        "9425749445e494332757363353f5d6f50353b79445d7336343270373270366f586365753f546c60336f5".slice(56, 70),
        "9425749445e494332757363353f5d6f50353b79445d7336343270373270366f586365753f546c60336f5".slice(70, 84),
    ];

    // Reorder parts and join them into a single string
    let reordered = [parts[3], parts[5], parts[1], parts[4], parts[2], parts[0]].join("");
    console.log("Reordered hex string:", reordered);

    // Convert reordered and reversed hex string to bytes
    let byteArray = hexToBytes(reverseString(reordered));
    console.log("Byte array (ASCII):", byteArray.map((byte) => String.fromCharCode(byte)).join(""));

    // Apply affine transformation and XOR to each byte
    let transformedArray = byteArray.map((byte) => (9 * byte + 7) % 256 ^ 0x33);
    console.log("Transformed byte array (ASCII):", transformedArray.map((byte) => String.fromCharCode(byte)).join(""));

    // Return the transformed bytes as a hex string
    return transformedArray.map((byte) => ("0" + byte.toString(16)).slice(-2)).join("");
}

// Call the keygen function
keygen();

We get our flag!

Flag: INTIGRITI{50_much_f0r_53cur3_c0ld_570r463}

You will find it is a and in the index.html is the pin, simplez!

Let's .

cordova app
deobfuscate