Visual Hashing Hard

Ins'Hack 2018 - RE/Prog.

Ins’Hack 2018 - RE/Prog

Challenge details

Event Challenge Category Points Solves
Ins’Hack 2018 Visual Hashing Hard RE/Prog 85 44

Description

We recommend you to have a look at the first part of this chall (Visual hashing - easy) before attempting this one.
Our teams managed to infect a French Bureau of Investigation expert who uses the same extension, however our spy forgot to click Remove USB safely so now the video file is all corrupted.
We managed to save one of the frames from the capture, see what you can do with it. Also, we know that the password has the format INSA{lowercase letters}. Good luck.
extension: https://chrome.google.com/webstore/detail/visual-hashing/lkoelcpcjjehbjcchcbddggjmphfaiie

TL;DR

In this challenge, we had a browser extension which made 4 color on user input for a given input and a screenshot of a password with the associated colors.
This kind of coloration can be compared with a “visual hashing” of the password: each text input has his own colors.
We had to first reverse the web extension: the color was made with a SHA1() of the input which was translated to 4 colors in hexadecimals.
The final step of the algorithm was adding few random on the colors (+/- 3 shade of red, green and blue).
To accomplish the task, we had only 5 chars to bruteforce, compute the 4 colors for each pass and verify that each colors correspond to te original one (+/- 3 tone).

Ressources

Secret password:

Visual Hashing Hard secret
Web Extension:
https://chrome.google.com/webstore/detail/visual-hashing/lkoelcpcjjehbjcchcbddggjmphfaiie

Reversing

First step of the challenge was to install chrome, download the extension and looking at the source code located at the following path:

C:\Users\Zeecka\AppData\Local\Google\Chrome\User Data\Default\Extensions\lkoelcpcjjehbjcchcbddggjmphfaiie\1.0.1_0\

We can indentify 2 files: visual.js and utils.js. After few simplification we can identify the following functions:

var passwordHash = SHA1(elem.value); // Zeecka: extracted from main

function getDataURLForHash(passwordHash,inputWidth,inputHeight) {
    var win = window;
    try {
        win = unsafeWindow;   
    }
    catch(e) {}
    var canvas = win.document.createElement('canvas');
    canvas.height = inputHeight;
    canvas.width = inputWidth;
    var context = canvas.getContext('2d');

    passwordHash = randomizeHash(passwordHash); // Zeecka: The line we care !

    for (var hashBandX = 0; hashBandX < 4; hashBandX++) {
        context.fillStyle='#' + passwordHash.substr(hashBandX*6,6);
        context.fillRect(hashBandX/4*inputWidth,0,inputWidth/4,inputHeight);

        context.fillStyle='#000000';
        context.fillRect(((hashBandX+1)/4*inputWidth)-1,0,2,inputHeight);
    }

    context.strokeStyle='#000000';
    context.strokeRect(0,0,inputWidth,inputHeight);

    return canvas.toDataURL();
}

function randomizeHash(passwordHash) {
    // Add a little bit of randomness to each byte
    for (var byteIdx = 0; byteIdx < passwordHash.length/2; byteIdx++) {
        var byte = parseInt(passwordHash.substr(byteIdx*2,2),16);
        // +/- 3, within 0-255
        byte = Math.min(Math.max(byte + parseInt(Math.random()*6)-3,0),255);
        var hexStr = byte.toString(16).length == 2 ? byte.toString(16) : '0' + byte.toString(16);
        passwordHash = passwordHash.substr(0,byteIdx*2) + hexStr + passwordHash.substr(byteIdx*2+2);
    }
    return passwordHash;
}

From this we can identify how the colors a generated: the text of the input is hashed with the SHA1 function (also in the script).
This hash is modified with +/- 3 (hex) every 2 digits. For example a given hash “3333333333333333333333333333333333333333” could be modified to “3531323034363536333130333436323130323135”.
Then, the modified hash is trunctated to generate 4 colors in hexadecimals (for our example: #353132 #303436 #353633 #313033).
We can verify the process with a custom secret and computing the colors of our own secret.

Recovering the password

By looking at the corrupted screenshot , we can identify the 4 following colors: #1d9c0d, #b55855, #bc6478 and #a047c9.
If we take care about the randomness: the originals colors can be modified with +/- 3 tons.
The challenge gived us the flag format INSA{lowercase letters} and the hidden password on the picture is 11 letters long.
From that we can deduce that we have only 5 lowercase letter to bruteforce: INSA{*****} (26**5 = 11,881,376).

Time to bruteforce ! Here is my script:

import hashlib
import math
import string
import itertools


def SHA1(p):
	""" SHA1 function """
    m = hashlib.sha1()
    m.update(p.encode("utf-8"))
    return m.hexdigest()

def getColor(password):
	""" Return list of 4 colors from a given password """
    passwordHash = SHA1(password)
    color1 = passwordHash[0:6]
    color2 = passwordHash[6:12]
    color3 = passwordHash[12:18]
    color4 = passwordHash[18:24]
    return [color1,color2,color3,color4]

def isSameColor(a,c):
	""" Verify if a color is equal to the associated 'c' color (+/-3) """
    x = 3 # Randomness
    colors = ["1d9c0d","b55855","bc6478","a047c9"] # Extracted colors from pictures
    r = int("0x"+a[:2],16)
    g = int("0x"+a[2:4],16)
    b = int("0x"+a[4:6],16)
    return (r >= int("0x"+colors[c][:2],16)-x # R G B in [-3;+3] ?
    and r <= int("0x"+colors[c][:2],16)+x
    and g >= int("0x"+colors[c][2:4],16)-x
    and g <= int("0x"+colors[c][2:4],16)+x
    and b >= int("0x"+colors[c][4:6],16)-x
    and b <= int("0x"+colors[c][4:6],16)+x)

charset = string.ascii_lowercase # abcdefg...

for f in itertools.product(charset, repeat=5): # each combinaison of 5 chars
	tab = getColor("INSA{"+''.join(f)+"}")
	if (isSameColor(tab[0],0) # Check if each colors is equal (+/-3)
	and isSameColor(tab[1],1)
	and isSameColor(tab[2],2)
	and isSameColor(tab[3],3)):
		print("Flag: "+"INSA{"+''.join(f)+"} !")

FLAG !

FLAG

INSA{hctib}

Zeecka