PIT or Miss

TJCTF 2018 - Steganography (80 pts).

TJCTF 2018: PIT or Miss

Challenge details

Event Challenge Category Points Solves
TJCTF 2018 PIT or Miss “Forensic” (Steg’) 80 22 solves

Download: source.py - md5: eeea2c62e599f95e479699f98f7dd5dd

Description

Author: Alaska47 > My friend sent me this image and told me that “red is an indicator”. I’m not really sure what he was saying but I don’t think it has anything to do with chemistry…

Edit: Please submit the md5 hash of the entire flag (including tjctf{}) instead of actually submitting the flag.

TL;DR

The Image was hiding data using Pixel Indicator Technique.
We had to fuzz the channels and switch the order when two channels are used.

Methology

PIT ?

By looking at the challenge name, I already know what I had to do.
I downloaded the file, it was an image. I decided to do a reverse search then compare with the given image.
I found the original here: image.jpg.
Here is a comparaison between the two image (SUB operation with StegSolve tool):
Comparison
We can identify some differences.
As I said, I’ve already worked with PIT. I also made a script on github few month ago ! (I’m sure some of the teams used it).

Here is a quick explanation of the algorithm:
First we have to defined our channels with N.
Note: N is encoded in the 8 first bytes of the image (in the 3 first pixels).
Explanation
Once the channels are defined, we can start our parsing:
Explanation
Please that the parsing start at line 2.

Adapting PIT

I decided to reuse my script and modify it as necessary. The first difference with original PIT was: Indicator Channel is set (red). We will need to fuzz to check if channel one correspond to blue or green.
The second difference with PIT: the condition with ‘01’ for ch1 and ‘10’ for ch2 are switched (or the step ‘11’ add ch2 first instead of ch1).
The last difference witch PIT: Extracted bits are decoded as a long bloc of bits instead of blocks of 8 bits (see decode function).
Last step: we will need to test diffents values for N (the size of embedded data).

Here is the final Script - md5: 03f47c412a7d54655f261f7a6dabd077.

# -*- coding:utf-8 -*-

from PIL import Image
import binascii

def get2lsb(n):
    """ Return 2 last LSB as String """
    n = n%4
    if(n == 0):
        return "00"
    if(n == 1):
        return "01"
    if(n == 2):
        return "10"
    if(n == 3):
        return "11"

def decode(i):
    """ Long int to String (thanks to #ENOENT) """
    if len(i) > 0:
        n = int(i, 2)
        n = hex(n)[2:][:-1]
        if len(n) % 2 != 0:
            n = "0"+n
        return n.decode("hex")
    return ""

###############################################################################

img = Image.open("3675a1345da00ecb4b7eee40e0d8a6c028d28b7acfff34739432d1576356e2bd_output.png")

w,h = img.size

IC = 0  # set Indicator channel
c1 = 1  # set Channel
c2 = 2  # set Channel

# RMS = Remaining Size (data embedded)
RMS = 8*1207  # Set remaining data (first was ~ 100 then I updated to get the full flag)

# pxsl = 1D list of pixels
pxsl = list(img.getdata())[w:]  # [w:] => Starting parsing at line 2

flag = ""  # Flag (bits)

i = 0  # Current px
while(RMS > 0):  # WHile there is still px to parse
    indicLSB = pxsl[i][IC]%4 # Get IC Informations
    if(indicLSB == 1):  # if IC == 01
        flag += get2lsb(pxsl[i][c1])  # Note that we used C1 instead of C2 (see diff' n° 2)
        RMS -= 2
    elif(indicLSB == 2):  # if IC ==  10
        flag += get2lsb(pxsl[i][c2])  # Note that we used C2 instead of C1 (see diff' n° 2)
        RMS -= 2
    elif(indicLSB == 3):  # if IC == 11
        flag += get2lsb(pxsl[i][c1])
        flag += get2lsb(pxsl[i][c2])
        RMS -= 4
    i += 1

### Decode flag

print(decode(flag))

Flag

echo -n "tjctf{C08DCB7AAFF28B0386D5FD47AD70162328B73B6419377F714BE8851CE99309F0E1BD679CB0FEF647DDA1E585E8EA63DAF01C43FED6F2F81FF48C2F89077716F1DEDE514C0BDDEB920BE968ADB6ABFA10B7BB4898CEACFEEAE5C044E392E24CD10F8AEC82E0BA722201DB1B8E0FEB3DE3FC38431E6CCA2D599F31F129D788C2A41BA9A5F4F3D488A488605F8FC62F77613C621A54E9A615E846770F949F09C298275DABE868C6471CDCC5AA7545D2E9741B5526563F5D28C54A3EE4BDEB5D5EAD476B4271DA27A47535E793D2605D84A86D6B15CAC57D82D200ADDFD46D72E355B256483E6155B7AD569D45A4AA7DF6126E03D24AE046C04977977900ADF0FEEC8499EF8A78502C6351330152C644B826407BF910FE117B108EA6F5358574A5B1E756FC42E845E835532C838268E1BC316C7FC73D01CA1433C778CA0BFD3609E0F4C28B881D6182CDC496B898A6CC565B99EF56306FE6B1ECBB6ECD86474355744EE7585A02B6AE71FAA3B0DEA8AB7B0BEC46C237F7EC5BE171F551B17BC74F77C509B12897E1D56881BF7E051828750AE98D50F663835D1608B4868603B897AB1A9078A3532AA41E0F4E47EA21280F1E0EC4F62D0F1A781CC2F4816EED4E5C322EC9FC4F02C659A1A457B0B52E90CE1C44A7C5955F3F14A5FFA60867072240BCE8403CBC80B502FD8B680C1FEDDEF5ACC6ADDABF710D457418DF9CD4542B9562626D8D91358CD781504A515AB13D77C02D3B79927B41176D4693AA18C0225BA161947F670D21331A615FA1E6BC5272A57DD7264BE7F3C379ED2B49E95F86E04705AC607C26178FCEC180C9447C90D56B6A63D10A56FC1847}" | md5sum

0cb362c9e55058e6ad6f8a44775e332d

Zeecka