Sharif CTF 2016: Asian Cheetah (Misc 50)

Category: Misc
Points: 50
Solves: 198

This challenge was the easiest of the misc challenges. It was all just simple steganography.

Description

We have hidden a message in png file using jar file. Flag is hidden message. Flag is in this format:
SharifCTF{flag}

Deliverables

  • Hide.jar
  • AsianCheetah1.png

We are told that the file Hide.jar was used to insert a hidden message in the image. The image looks something like this:

Cheetah

Nothing here is immediately obvious, but of course the jar file might be of some use

Java Reversing

Reversing jar files is pretty straight forward. The java bytecode can easily be converted into source code with a tool such as JAD.

import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import javax.imageio.ImageIO;

public class Hide
{
  protected void steg(String paramString1, String paramString2)
  {
    BufferedImage localBufferedImage1 = loadImage(paramString2 + ".png");
    
    BufferedImage localBufferedImage2 = steg(paramString1, localBufferedImage1);
    try
    {
      ImageIO.write(localBufferedImage2, "png", new File(paramString2 + "_out.png"));
    }
    catch (IOException localIOException)
    {
      throw new RuntimeException("Unable to write image!");
    }
  }
  
  protected BufferedImage steg(String paramString, BufferedImage paramBufferedImage)
  {
    paramString = paramString.length() + ":" + paramString;
    if (paramString.length() * 8 > paramBufferedImage.getWidth() * paramBufferedImage.getHeight())
    {
      System.out.println("There won't be enough space to store this message!");
      System.out.println("Message length: " + paramString.length() + " bytes. " + "Image can hold a maximum of " + paramBufferedImage.getWidth() * paramBufferedImage.getHeight() / 8);
      
      throw new RuntimeException("There won't be enough space to store this message!");
    }
    byte[] arrayOfByte1 = paramString.getBytes();
    Point localPoint = new Point(0, 0);
    for (int k : arrayOfByte1) {
      for (int m = 0; m < 8; m++)
      {
        if ((k & 0x80) == 128) {
          paramBufferedImage.setRGB(localPoint.x, localPoint.y, setLeastSignificantBit(paramBufferedImage.getRGB(localPoint.x, localPoint.y), true));
        } else {
          paramBufferedImage.setRGB(localPoint.x, localPoint.y, setLeastSignificantBit(paramBufferedImage.getRGB(localPoint.x, localPoint.y), false));
        }
        k <<= 1;
        movePointer(localPoint, paramBufferedImage);
      }
    }
    return paramBufferedImage;
  }
  
  protected int setLeastSignificantBit(int paramInt, boolean paramBoolean)
  {
    paramInt >>= 1;
    paramInt <<= 1;
    if (paramBoolean) {
      paramInt++;
    }
    return paramInt;
  }
  
  protected void movePointer(Point paramPoint, BufferedImage paramBufferedImage)
  {
    if (paramPoint.x == paramBufferedImage.getWidth() - 1)
    {
      paramPoint.x = -1;
      paramPoint.y += 1;
    }
    paramPoint.x += 1;
    if (paramPoint.y == paramBufferedImage.getHeight()) {
      throw new RuntimeException("Pointer moved beyond the end of the image");
    }
  }
  
  private BufferedImage loadImage(String paramString)
  {
    try
    {
      return ImageIO.read(new File(paramString));
    }
    catch (IOException localIOException)
    {
      System.out.println("Unable to load \"" + paramString + "\"");
      System.exit(0);
    }
    return null;
  }
  
  public static void main(String[] paramArrayOfString)
  {
    if (paramArrayOfString.length < 2)
    {
      System.out.println("Input Arguments Is Not Valid.");
      System.out.println("Run By 'java -jar Hide.jar `file-path` `message`'");
      
      return;
    }
    System.out.println("Welcome!\nDecrypt The Image And Capture The Flag!");
    
    Hide localHide = new Hide();
    
    localHide.steg(paramArrayOfString[1], paramArrayOfString[0]);
  }
}

Looking at the code, steg(String paramString, BufferedImage paramBufferedImage) Looks like it could be interesting. It contains a for loop that iterates through all 8 bits in all ASCII characters that were passed into the jar.

    for (int k : arrayOfByte1) {
      for (int m = 0; m < 8; m++)
      {
        if ((k & 0x80) == 128) {
          paramBufferedImage.setRGB(localPoint.x, localPoint.y, setLeastSignificantBit(paramBufferedImage.getRGB(localPoint.x, localPoint.y), true));
        } else {
          paramBufferedImage.setRGB(localPoint.x, localPoint.y, setLeastSignificantBit(paramBufferedImage.getRGB(localPoint.x, localPoint.y), false));
        }
        k <<= 1;
        movePointer(localPoint, paramBufferedImage);
      }
    }

k contains the ASCII character that we are testing. The first if statement tests the bit that we're testing. The bit that we're testing will always be in 0x80. Notice at the end of the four loop, k is shifted over 1 bit to move the testing bit. If the bit is a 1, the jar will insert a 1 into the least significant bit in the RGB value.

Solution

This is the ruby script I ended up using for extracting the data:

require 'rubygems'
require 'rmagick'

Signal.trap("PIPE", "EXIT")

Magick::Image.read('AsianCheetah1.png')[0].each_pixel do |pixel, col, row|
  print (pixel.blue & 1)
end

Now, we can get the first 64 characters in binary with this command:

$ ruby steg.rb | head -c 512
00110100001100110011101001010011011010000110000101110010011010010110011001000011010101000100011001111011011001010011100001100101001100010011001001100100011000100011001001100110011000110011011000110101001101000110011000110011011000100011010100110000011001100011001101100100011000010011010000111001001100000011000101100001011000100011100100111000001101100110010101111101010100111111111111011100000110000011111111100001110000100111011001101011110010101001100110111000000011110000110010001110110011100011000101010100

Converting this data to ASCII values we get:

43:SharifCTF{e8e12db2fc654f3b50f3da4901ab986e}S��?��vkʙ���1T

Well, there's the flag! Go submit it.