Hello again little gopnik, today I will talk about SwampCTF2k19, Stegsolve

TL;DR

I was playing CTF and find out that Stegsolve interface's is not very great when you have to deal with big image.
So I open my reverse tool box and rewrite this software into python. Now I can automate all test and use it through command line.

Legal Stuff

Before I show you how I reverse the program, let's me quote the website author's of Stegsolve, you can find it on "A Challengers Handbook by Caesum"

The second program is Stegsolve by myself. It will perform analysis of the file structure as well as allowing each bit plane to be viewed on its own. It also allows extracts to be taken from bits, and contains a stereogram solver. Both programs are open source and so you can modify them in whatever way you wish.

Now let's talk technical stuff.

The problem

I was recently playing "Last Transmission" challenge. The problem is that I missed the flag because the image to use was to big for Stegsolve and there is now way to unzoom it (at least I am not aware of).

So I was looking for an alternative tool, something that I could use through CLI. and automate-able. Unfortunately I don't found what I was looking for, so there still only one solution: reverse engineering Stegsolve to find a way to adapt its filter.

The solution

After all Stegsolve is just a JAR archive, I will be simple to retrieve source code from it.

So I use jd-gui to take a look at source code

After a little search I found the class in charge of transformation of the image!

The whole class is 400 lines long, so if you want to take a look, here's a link.

The class is composed by the 8 following functions:

private void transfrombit(int d)
  => Transform a each bit of the image
      by doing Unsigned right shift of d
      then AND bitwise with 1 and the result of the shift
      if result is > 0
        then the value of new color is Full white
      Else
        apply the new value (the shifted one)


private void transmask(int mask)
  => For each pixels of the image
      v = value of the current pixel
      col = make AND shift between mask & v
      If (col > 0xffffff ||col < 0)
        col=col >>> 8
      Else
        apply the value col to the pixel

private void inversion()
  => simply inverse pixel value

private void graybits()
  => Highlights just the pixels for which r=g=b

private void random_colormap()
  => Randomises the colours of the image for a truecolour image

private void random_indexmap()
  => Randomises the colours of the image for an indexed palette image

private void randommap()
  =>  Randomises the colours of the image
  * depending on the type of image

private void calcTrans()
  =>  Calculates the current image transformation

In fact all basic transformations of Stegsolve are:

# "Normal Image";
Original Image()

# "Colour Inversion (Xor)";
inversion()

# Alpha plane
transfrombit(31)
transfrombit(30);
transfrombit(29);
transfrombit(28);
transfrombit(27);
transfrombit(26);
transfrombit(25);
transfrombit(24);

# Red plane
transfrombit(23);
transfrombit(22);
transfrombit(21);
transfrombit(20);
transfrombit(19);
transfrombit(18);
transfrombit(17);
transfrombit(16);

# Green plane
transfrombit(15);
transfrombit(14);
transfrombit(13);
transfrombit(12);
transfrombit(11);
transfrombit(10);
transfrombit(9);
transfrombit(8);

# Blue plane
transfrombit(7);
transfrombit(6);
transfrombit(5);
transfrombit(4);
transfrombit(3);
transfrombit(2);
transfrombit(1);
transfrombit(0);

# Full Alpha
transmask(0xff000000);

# Full Red
transmask(0x00ff0000);

# Full green
transmask(0x0000ff00);

# Full Blue
transmask(0x000000ff);

# Random colour map 1
randommap();

# Random colour map 2
randommap();

# Random colour map 3
randommap();

# Gray bits
graybits();

There's a total of 41 transformations available.

Since I don't need to re-create a GUI and I will only need "Red plane", "Blue plane", "Green plane" and "Alpha plane" transformations. The most interesting function is transfrombit. This function is a simple "bitshifter".

    /**
     * Makes an image from a bit plane
     * @param d bit to use
     */
    private void transfrombit(int d)
    {
		/*  Create a buffer "transform" to store the new image data */
        transform = new BufferedImage(originalImage.getWidth(), originalImage.getHeight(), BufferedImage.TYPE_INT_RGB);

		/* For each pixel of the original image */
        for(int i=0;i<originalImage.getWidth();i++)
            for(int j=0;j<originalImage.getHeight();j++)
            {
				/* Retrieve the value of the current pixel */
                int col=0;
                int fcol = originalImage.getRGB(i,j);

				/* Shift the value by d bits */
				/* If the value is once shiffted */ 
				/* had its last bit set to 1 */
				/* Then the new value of pixel is full white */
                if(((fcol>>>d)&1)>0)
                   col=0xffffff;

				/* Store the new pixel on the buffer */
                transform.setRGB(i, j, col);
             }
    }

As I said, it will be easy to reuse this Java code and adapt it to python. Since I want a full automate tool, I wrote a code that will apply all transformations and save the results of each. From that I will only need a good image viewer program to never miss a flag again.

So here's my prototype.

NB: For now, it only works with PNG images.

#!/usr/bin/env python 

from PIL import Image
import sys
import time

im = Image.open(sys.argv[1])

def transfrombit(d, filename, modif):

  # For performance measurement purpose
  start = time.clock()

  pixelMap = im.load()

  img = Image.new( im.mode, im.size)
  pixelsNew = img.load()

  # For each pixel of the original image
  for i in range(im.size[0]):
      for j in range(im.size[1]):
          col = 0

		  # Retrieve the value of the current pixel
          red = pixelMap[i,j][0]
          green = pixelMap[i,j][1]
          blue  = pixelMap[i,j][2]

		  # Convert the 3 item tuple into 1 integer
          pixel_as_int = red << 16 | green << 8 | blue

          if (((pixel_as_int >> d)&1)>0):
            col=0xFFFFFF

		  # Convert the shifted value into 3-items tuple
          int_as_pixel_a = pixelMap[i,j][3]
          int_as_pixel_r = (col >> 16)&0xFF
          int_as_pixel_g = (col >> 8)&0XFF
          int_as_pixel_b = (col >> 0)&0XFF

		  # Store the new value
          pixelsNew[i,j] = (int_as_pixel_r, int_as_pixel_g, int_as_pixel_b, int_as_pixel_a)

  # Adapt the name of the file
  if d >= 0 and d <= 7:
    nb = d

  elif d >= 8 and d <= 15:
    nb = d - 8

  elif d >= 16 and d <= 23:
    nb = d - 16

  elif d >= 24 and d <= 31:
    nb = d - 24

  # Use a name as: small_BluePlane0.png
  # To be sure to keep trace of the transformation made
  filename += "_" + modif + str(nb) + ".png"
  print "File: " + str(filename) +" writed"
  print "Time: " + str(time.clock() - start)
  print "Transform: " + str(d) + "/ 30"
  print ""
  img.save(filename)

                          
                          
if __name__ == '__main__':
  f = sys.argv[1].split('.')[0] # Get the first part of the filename

  for i in range(0, 7):
    transfrombit(i, f, "BluePlane")

  for i in range(8, 15):
    transfrombit(i, f, "GreenPlane")

  for i in range(16, 23):
    transfrombit(i, f, "RedPlane")

  for i in range(24, 31):
    transfrombit(i, f, "AlphaPlane")

In order to display transformed image I use

feh -g 640x480 --scale-down
Results comparison between my prototype and Stegsolve

And never miss a flag again !

On background: result of Stegsolve, On foreground: result of my adaptation

Conclusion: Let's relativize things

The prototype works well but is slow for big image. I've tried to compile the python with cpython, I've improved performance by about 40%.

Off course you're free to re-use this code and even improve it !


Social stuff / Questions / Comments

Feel free to reach or tips me !

Mail: a_ghost_soul@protonmail.com
Twitter: @GhostAgs

If you appreciate my work please consider make a donation
Tipeee: https://fr.tipeee.com/ags-syndrome