Showing posts with label image. Show all posts
Showing posts with label image. Show all posts

Monday, March 12, 2018

N1CTF 2018 - Lipstick


This challenge was very interesting! It included multiple types of stego and some web scraping.

We're given the image shown above. Looking very innocuous with flat colors, it seems one would try to hide lsb data in it. Let's try zsteg to extract some values:

$ zsteg -a 603fda27-2893-4e60-9d9f-39962b8c8440.png

b1,bgr,lsb,xy       .. text: "flag.txt"
b2,g,lsb,xy         .. file: 5View capture file
b2,g,msb,xy         .. file: VISX image file
b2,b,msb,xy         .. text: "UUuUUWUUUUUuUUUU"
b2,bgr,lsb,xy       .. text: "AAAP@QUDQ"
b3,b,lsb,xy         .. file: GLS_BINARY_MSB_FIRST
b4,r,lsb,xy         .. text: "UDTTUEEDDEDEUEDDTEDETDEDDEDDDDTDUEEETDUDEUDDDDDDDDDDDDUDDDDDDDDDDDDDDDDDDDD\"322\"223##\"\"\"\"\"\"\"\"\"\"\"\"#\"#\"\"\"3\"#"
b4,r,msb,xy         .. text: "333333333;"
b4,g,lsb,xy         .. file: 5View capture file
b4,g,msb,xy         .. file: VISX image file
b4,b,msb,xy         .. text: ["w" repeated 12 times]
b6,g,msb,xy         .. text: "4ES4EQ4MS4M"

Looks like one of the first hits is promising! "flag.txt", maybe we should extract that portion alone, first let's inspect it.

Passing the -v flag into zsteg, we can get the hex dump of that extraction:

zsteg -v 603fda27-2893-4e60-9d9f-39962b8c8440.png b1,bgr,lsb,xy
b1,bgr,lsb,xy       .. text: "flag.txt"
    00000000: 00 00 00 00 00 00 00 e3  50 4b 03 04 14 00 01 08  |........PK......|
    00000010: 08 00 70 42 67 4c 37 20  ff 48 4d 00 00 00 43 00  |..pBgL7 .HM...C.|
    00000020: 00 00 08 00 00 00 66 6c  61 67 2e 74 78 74 ce 98  |......flag.txt..|
    00000030: 5e 65 7a ba 27 91 a6 eb  1b 25 54 f3 b9 6d 0f ca  |^ez.'....%T..m..|
    00000040: 78 7c 20 6a 79 e8 39 99  c8 df ad 3d 9a c8 4b fc  |x| jy.9....=..K.|
    00000050: 53 1d db 6e 95 9d 07 ae  1c 91 eb fe a7 18 33 00  |S..n..........3.|
    00000060: 9e f5 12 8e 7f c6 e6 7e  1a c8 3d cf 94 97 2f b0  |.......~..=.../.|
    00000070: 96 64 f3 a7 d1 94 64 23  98 3f 47 50 4b 01 02 3f  |.d....d#.?GPK..?|
    00000080: 00 14 00 01 08 08 00 70  42 67 4c 37 20 ff 48 4d  |.......pBgL7 .HM|
    00000090: 00 00 00 43 00 00 00 08  00 24 00 00 00 00 00 00  |...C.....$......|
    000000a0: 00 20 00 00 00 00 00 00  00 66 6c 61 67 2e 74 78  |. .......flag.tx|
    000000b0: 74 0a 00 20 00 00 00 00  00 01 00 18 00 5b d9 a1  |t.. .........[..|
    000000c0: f6 a9 b5 d3 01 a5 f5 f6  76 a9 b5 d3 01 a5 f5 f6  |........v.......|
    000000d0: 76 a9 b5 d3 01 50 4b 05  06 00 00 00 00 01 00 01  |v....PK.........|
    000000e0: 00 5a 00 00 00 73 00 00  00 00 00 92 49 24 92 49  |.Z...s......I$.I|
    000000f0: 24 92 49 24 92 49 24 92  49 24 92 49 24 92 49 24  |$.I$.I$.I$.I$.I$|

On the top there's PK, that's the magic byte for zip! A zip hidden in lsb.

Extracting it with zsteg we have some trailing bytes, we can extract the zip itself with binwalk and verify using 7z:

$ zsteg 603fda27-2893-4e60-9d9f-39962b8c8440.png -E b1,bgr,lsb,xy > out
$ binwalk -e out
$ cd _out.extracted
$ 7z l 8.zip

   Date      Time    Attr         Size   Compressed  Name
------------------- ----- ------------ ------------  ------------------------
2018-03-06 17:19:31 ....A           67           77  flag.txt
------------------- ----- ------------ ------------  ------------------------
2018-03-06 17:19:31                 67           77  1 files


Next we needed to get the password. At this point we could assume they decided to go with a value from rockyou.txt, but asking the admins it turned out there was another part to the challenge missing.

Reframing with the idea of the challenge named "Lipstick" it seemed to suggest more than just lsb. There are 21 colors, which may map to some character for the zip, but we would need to map the colors to numbers somehow. It is also important for the stego designer to make this somewhat deterministic.

Looking through the color channels again in StegSolve, this turns up:


YSL: Yves Saint Laurent. A recognizable fashion brand which sells lipstick. This may lead us to something predictable!

Looking for lipstick on their online store we can find the full color palette - example.


The other interesting part here is that these colors have clear class names and distinct background colors.  This is perfect for identifying colors in the challenge!

Here's a small script which was used to scrape the colors from the palette:

a = {};

for (let i=0; i<59; ++i) {
  a[$('.swatch_color')[i].style['background-color']] = $('.swatch_color')[i].title;
}

JSON.stringify(a, null, 2);

This gives us a convenient JavaScript object we may query:

{
  "rgb(188, 11, 40)": "1 Le Rouge - Blood Red (Satin)",
  "rgb(184, 52, 75)": "04 Rouge Vermillon - Raspberry Red (Satin)",
  "rgb(221, 136, 133)": "06 Rose Bergamasque - Delicate Nude Pink (Satin)",
  "rgb(207, 26, 119)": "7 Le Fuchsia - Pure Saturated Fuschia (Satin)",
  "rgb(206, 135, 133)": "10 Beige Tribute - Dark Nude (Satin)",
  "rgb(194, 105, 111)": "11 Rose Carnation - Soft Peony Rose (Satin)",
  "rgb(213, 29, 55)": "13 Le Orange - Bright Orange (Satin)",
  "rgb(234, 94, 107)": "17 Rose Dahlia - Coral Orange (Satin)",
  "rgb(161, 24, 96)": "19 Fuchsia Pink - Bright Fuschia Pink (Satin)",
  "rgb(238, 133, 199)": "22 Pink Celebration - Bubble Gum Pink (Satin)",
  "rgb(235, 130, 98)": "23 Coral Poetique - Pink Coral (Satin)",
  "rgb(233, 152, 131)": "24 Blond Ingenu - Frosted Coral (Satin)",
  "rgb(218, 116, 155)": "26 Rose Libertin - Bright Purple Pink (Satin)",
  "rgb(208, 65, 121)": "27 Fuchsia Innocent - Hot Pink (Satin)",
  "rgb(235, 105, 92)": "36 Corail Legende - Orange Coral (Satin)",
  "rgb(202, 102, 174)": "49 Tropical Pink - Hot Pink (Satin)",
  "rgb(212, 18, 29)": "50 Rouge Neon - Bright Red (Satin)",
  "rgb(235, 85, 71)": "51 Corail Urbain - Medium Peach Orange (Satin)",
  "rgb(246, 98, 96)": "52 Rosy Coral - Rosy Coral (Satin)",
  "rgb(126, 69, 58)": "53 Beige Promenade - Browd Red (Satin)",
  "rgb(94, 35, 41)": "54 Prune Avenue - Dark Maroon (Satin)",
  "rgb(193, 9, 43)": "56 Orange Indie - Mauve (Satin)",
  "rgb(192, 8, 62)": "57 Luminous Pink - Magenta (Satin)",
  "rgb(165, 63, 103)": "58 Luminous Mauve - Bubblegum Pink (Satin)",
  "rgb(212, 122, 111)": "59 Golden Melon - Golden Orange (Satin)",
  "rgb(170, 64, 64)": "66 Rosewood - Soft Pink Nude (Satin)",
  "rgb(220, 53, 77)": "67 Rouge Heroine - Gold Metallic (Satin)",
  "rgb(203, 132, 123)": "70 Le Nu - Basic Nude (Satin)",
  "rgb(148, 23, 41)": "72 Rouge Vinyle - Deep Raspberry (Satin)",
  "rgb(215, 11, 22)": "73 Rhythm Red - Magenta Pink (Satin)",
  "rgb(229, 35, 23)": "74 Orange Electro - Electric Orange (Satin)",
  "rgb(183, 48, 62)": "75 Rose Mix - Deep Rose (Satin)",
  "rgb(209, 50, 116)": "76 Explicit Pink - Spring Pink (Satin)",
  "rgb(206, 10, 74)": "77 Fuschia Live - Blush Rose (Satin)",
  "rgb(161, 0, 6)": "201 Orange Imagine - Orange Red (Matte)",
  "rgb(161, 0, 52)": "202 Rose Crazy - Cranberry Red (Matte)",
  "rgb(167, 11, 35)": "203 Rouge Rock - Rich Red (Matte)",
  "rgb(131, 35, 37)": "204 Rouge Scandal - Maroon Red (Matte)",
  "rgb(97, 37, 29)": "206 Grenat Satisfaction - Brown Red (Matte)",
  "rgb(184, 20, 70)": "208 Fuchsia Fetiche - Raspberry Red (Matte)",
  "rgb(231, 51, 37)": "213 Orange Seventies - Orange with Brown undertones (Matte)",
  "rgb(215, 91, 89)": "214 Wood On Fire - Pinky Nude (Matte)",
  "rgb(184, 38, 59)": "216 Red Clash - Mauvey Red (Matte)",
  "rgb(201, 88, 108)": "217 Nude Trouble - Mauvey Beige (Matte)",
  "rgb(203, 93, 70)": "218 Coral Remix - Coral Nude (Matte)",
  "rgb(195, 1, 9)": "219 Rouge Tatouage – Vibrant Pink Red (Matte)",
  "rgb(230, 26, 1)": "220 Crazy Tangerine – Electric Orange (Matte)",
  "rgb(106, 19, 25)": "222 Black Red Code – Rust Red (Matte)",
  "rgb(51, 26, 19)": "225 - Minimal Black - Matte Finish - Black (Matte)",
  "rgb(165, 106, 92)": "340 Golden Copper - Reddish Pink (Satin)",
  "rgb(163, 77, 86)": "09 Rose Stiletto - Rich Berry Rose (Satin)",
  "rgb(92, 47, 44)": "205 Prune Virgin - Burgundy (Matte)",
  "rgb(174, 67, 95)": "207 Rose Perfecto - Blush Pink (Matte)",
  "rgb(226, 0, 74)": "211 Decadent Pink - Bright Pink (Matte)",
  "rgb(126, 32, 50)": "212 Alternative Plum - Burgundy Wine (Matte)",
  "rgb(207, 44, 125)": "215 Lust For Pink - Purple Bubblegum Pink (Matte)",
  "rgb(214, 0, 112)": "221 Rose Ink – Deep Mauve Pink (Matte)",
  "rgb(232, 11, 44)": "223 Corail Anti-Mainstream – Neon Coral (Matte)",
  "rgb(219, 90, 121)": "224 Rose Illicite – Creamy Dusty Pink (Matte)"
}

We can now query this based on values found inside the image. But how do we get those values in the image? At first trying macOS's digital color picker didn't work too well, instead let's use Python!

from PIL import Image

im = Image.open('603fda27-2893-4e60-9d9f-39962b8c8440.png')
rgb_im = im.convert('RGB')

for x in range(0, 21):
  r, g, b = rgb_im.getpixel((50 + (150 * x), 1))
  print(r, g, b)

This yields:

(188, 11, 40)
(208, 65, 121)
(212, 122, 111)
(194, 105, 111)
(235, 130, 98)
(207, 26, 119)
(192, 8, 62)
(188, 11, 40)
(188, 11, 40)
(209, 50, 116)
(106, 19, 25)
(188, 11, 40)
(188, 11, 40)
(212, 18, 29)
(215, 91, 89)
(221, 136, 133)
(206, 10, 74)
(212, 18, 29)
(126, 69, 58)
(215, 91, 89)
(221, 136, 133)

Now we can grep for each color in the JSON result, and produce integer values for each color's associated edition number:

$ IFS=$'\n'; for i in `cat image-colors` ; do grep $i ./lipsticks.json >> found; done
$ cat found | sed 's/.*: "//g;s/ .*//;s/^0*//'
1
27
59
11
23
7
57
1
1
76
222
1
1
50
214
6
77
50
53
214
6
$ cat found | sed 's/.*: "//g;s/ .*//;s/^0*//' | tr -d '\n'
1275911237571176222115021467750532146

Attempting this number didn't work for the password. Another hint came out about using binary, let's try with that:

$ cat found | cut -d: -f1 | xargs python -c 'import sys; print "".join([bin(int(x)).lstrip("0b") for x in sys.argv[1:]])'
1011111101011111001011100010101000101100011010100101010111001000111001010101101011100

Still doesn't work. Maybe we need to convert this to the character representation:

$ cat found | sed 's/.*: "//g;s/ .*//;s/^0*//' | xargs python -c 'import sys; print "".join([bin(int(x)).lstrip("0b") for x in sys.argv[1:]])' | perl -lpe '$_=pack"B*",$_'
白学家

Then extracting the archive we scraped earlier using this password, we get the flag:

$ cat flag.txt
flag{White_Album_is_Really_worth_watching_on_White_Valentine's_Day}

Sunday, May 1, 2016

Google CTF 2016 - Magic Codes (250)



Google's opened up their first CTF out there this year and it was a very fun one!
This particular challenge was Stego, found in the Forensics category.
In the description it gave you "Can you recover the magic code?" with a link to an image:






Initial thoughts are... Okay, we have this image that's very retro with a DVD, a Satelite, a QR code & something that looks like a frequency visualization of some sort...

What could this possibly be, maybe QR Code mixed with the result of the frequency visualization turned into audio ? (I may be this sinister if I ever start designing challenges)

I first tried the QR code, and it seemed to be some trolling or self-advertising (or both), that linked to the CTF page. (https://capturetheflag.withgoogle.com/)




This wasn't it so I moved on to my usual routine with stego images.
Fired up StegSolve and looked at the bit planes of the image.

For anyone interested in the full process, I've included a step-by-step instruction below, you can find the link to the StegSolve on this list - https://github.com/apsdehal/awesome-ctf#stegano



First open up the StegSolve program:


You should see StegSolve pop up:


 Now open the file you would like to analyze:


You'll see the image, and at the bottom there is a left and right arrow used to cycle through common image manipulation operations that could help with identifying hidden data:


Ended up stopping at Alpha Plane 0, this seemed like a very clear chunk of data hidden in the lowest alpha plane:


Go ahead and save the image somewhere, I saved it using the default name (solved.bmp):



Now was the tricky part... what do we do with this data?

I started by reading the bits in with python, unfortunately nothing too useful looking came out of that..

Looking at the image I reconsidered the possibility of the visual frequency image being part of the challenge, but didn't know how.  Eventually googled all the items on the image together to see if they had any relationship.  I searched for:  "dvd satellite qr code" - http://lmgtfy.com/?q=dvd+satellite+qr+code

And one of the first results was a page on Reed-Solomon error correction codes.  It mentioned on that page, that all of these mediums shown in the image used Reed-Solomon error correction.... that couldn't just be a coincidence.


So looking around for a python library to handle this I found one quickly on PyPi - https://pypi.python.org/pypi/reedsolo


Started to write a few examples up pulling in the alpha image that was extracted, but found nothing.  I wasn't really sure which Codec to use at first.  Even started writing a brute-forcer to check each Codec available up to 100.


After a while (giving up and looking at other challenges) -- I saw an update on the Magic Code challenge, mentioning the metadata was updated.  Running exiftool again now gave this interesting result:

» exiftool MagicCode.png
ExifTool Version Number         : 10.08
File Name                       : MagicCode.png
Directory                       : .
File Size                       : 187 kB
File Modification Date/Time     : 2016:05:01 11:22:52-07:00
File Access Date/Time           : 2016:05:01 11:31:48-07:00
File Inode Change Date/Time     : 2016:05:01 11:22:52-07:00
File Permissions                : rw-r--r--
File Type                       : PNG
File Type Extension             : png
MIME Type                       : image/png
Image Width                     : 320
Image Height                    : 320
Bit Depth                       : 8
Color Type                      : RGB with Alpha
Compression                     : Deflate/Inflate
Filter                          : Adaptive
Interlace                       : Noninterlaced
Comment                         : (40,8) bytes
Image Size                      : 320x320
Megapixels                      : 0.102


Interesting! There was now a new Comment!  This also looks like it may be the Codec I was looking for, but why were there two values?  Had to look more into how reed solomon correction codes worked....


Comment                         : (40,8) bytes


I immediately noticed 40 * 8 = 320 (size of the image width/height) -- so it must have to do with how the image was formed, and maybe the developer went with that for convenience.


Also started looking at the constructor for this python reedsolo lib, because it showed only one value being passed into the codec constructor in the examples.
The constructor can be found here - https://github.com/tomerfiliba/reedsolomon/blob/master/reedsolo.py#L746


    def __init__(self, nsym=10, nsize=255, fcr=0, prim=0x11d, generator=2, c_exp=8):
        '''Initialize the Reed-Solomon codec. Note that different parameters change the internal values (the ecc symbols, look-up table values, etc) but not the output result (whether your message can be repaired or not, there is no influence of the parameters).'''
        self.nsym = nsym # number of ecc symbols (ie, the repairing rate will be r=(nsym/2)/nsize, so for example if you have nsym=5 and nsize=10, you have a rate r=0.25, so you can correct up to 0.25% errors (or exactly 2 symbols out of 10), and 0.5% erasures (5 symbols out of 10).
        self.nsize = nsize # maximum length of one chunk (ie, message + ecc symbols after encoding, for the message alone it's nsize-nsym)
        self.fcr = fcr # first consecutive root, can be any value between 0 and (2**c_exp)-1
        self.prim = prim # prime irreducible polynomial, use find_prime_polys() to find a prime poly
        self.generator = generator # generator integer, must be prime
        self.c_exp = c_exp # exponent of the field's characteristic. This both defines the maximum value per symbol and the maximum length of one chunk. By default it's GF(2^8), do not change if you're not sure what it means.


So it looks like the first argument they show in the examples is nsym...
In the comment they're showing two values (40, 8) -- in conventional reed solomon this will be (nsize, ndata) but in this library they're using (nsym, nsize).  The nysm value is just (nsize-ndata).  More information can be found on that here - http://iris.elf.stuba.sk/JEEEC/data/pdf/11-12_103-05.pdf

So now we know the second argument (nsize) is 40, and the first argument is (nsize-ndata) or 32.
The codec portion of this took me the longest to figure out, but with all of that in place, we have the final decoder! (I had noticed this when anything came out of the decoder that wasn't \x00 or an empty string...)


#!/usr/bin/env python
import sys
import reedsolo
from PIL import Image

if (len(sys.argv) > 1):
  IMG_FILE = sys.argv[1]
else:
  print "Usage: $ readImg "
  exit(1)

img = Image.open(IMG_FILE).convert('1')
data = img.getdata()
pixels = img.load()

bits = []

for x in range(img.size[0]):
  for y in range(img.size[1]):
    pixel = pixels[y,x]
    if pixel == 0:
      bits.append("0")
    else:
      bits.append("1")

b = "".join([str(x) for x in bits])
bytes = bytearray(int(b[x:x+8], 2) for x in range(0, len(b), 8)).rstrip("\xff")

rs = reedsolo.RSCodec(32, 40)
print repr(rs.decode(bytes))

f = open("SomeBytez", "w")
f.write(rs.decode(bytes))
f.close()



This dropped a gzip file from the looks of it using file:

» file SomeBytez
SomeBytez: gzip compressed data, last modified: Wed Apr 27 14:43:24 2016, max compression


We have to move it to a .gz file to uncompress it as gunzip requires this.

» mv SomeBytez someBytez.gz
» gunzip someBytez.gz
» file someBytez
someBytez: ASCII text, with no line terminators
» cat someBytez
Congratulations, Voyager!  Your flag is: CTF{who_knew_math_helped_with_anything_but_crypto}%


We've got the Flag!!! Never thought this one was going to work, but it did!
Learned a lot about erasure codes during this process! Good Ol' Information Theory :D

CTF{who_knew_math_helped_with_anything_but_crypto}


Monday, September 7, 2015

MMA CTF 2015 - Nagoya Castle (100)


This challenge was a fun relaxing one, playing around in GIMP and eventually discovering a new tool which looks useful for future stego challenges.

The Image provided can be seen above. An upshot of a Beautiful Castle in Central Japan.

My first intuition was to go into GIMP and start playing around with threshold, color balance and curves.
Out of that I got a partial reveal of the Flag:


After taking a break and coming back, I decided to research what other people were doing to solve CTF stego challenges out there.
I stumbled upon Balda's Stegpy Release page -
http://www.balda.ch/posts/2013/Jun/04/release-stegpy/

Github link - https://github.com/Baldanos/Stegpy


After playing around with this tool (Which was very nice to use), I found the flag within seconds:
$ python stegpy.py -V castle.png