PicoCTF 2019 Writeup: Forensics

Oct 12, 2019 00:00 · 2680 words · 13 minute read ctf cyber-security write-up picoctf forensics

Glory of the Garden

Problem

This garden contains more than it seems. You can also find the file in /problems/glory-of-the-garden_5_eeb712a9a3bc1998ffcd626af9d63f98 on the shell server.

file

Solution

There’s addition text in the file that can be extracted with strings.

$ strings garden.jpg | grep pico
Here is a flag "picoCTF{more_than_m33ts_the_3y3cD8bA96C}"

flag: picoCTF{more_than_m33ts_the_3y3cD8bA96C}

unzip

Problem

Can you unzip this file and get the flag?

file

Solution

just unzip the file to get flag.png

flag: unz1pp1ng_1s_3a5y

So Meta

Problem

Find the flag in this picture. You can also find the file in /problems/so-meta_1_ab9d99603935344b81d7f07973e70155.

file

Solution

The flag is hidden in the EXIF data of the image. It can be extracted with the exiftool:

$ exiftool pico_img.png | grep Artist
Artist                          : picoCTF{s0_m3ta_368a0341}

flag: picoCTF{s0_m3ta_368a0341}

What Lies Within

Problem

Theres something in the building. Can you retrieve the flag?

file

Solution

This is a challenge where the flag is hidden in the least significant bit of each pixel value. It can be extracted with zsteg:

$ zsteg buildings.png 
b1,r,lsb,xy         .. text: "^5>R5YZrG"
b1,rgb,lsb,xy       .. text: "picoCTF{h1d1ng_1n_th3_b1t5}"
b1,abgr,msb,xy      .. file: PGP\011Secret Sub-key -
b2,b,lsb,xy         .. text: "XuH}p#8Iy="
b3,abgr,msb,xy      .. text: "t@Wp-_tH_v\r"
b4,r,lsb,xy         .. text: "fdD\"\"\"\" "
b4,r,msb,xy         .. text: "%Q#gpSv0c05"
b4,g,lsb,xy         .. text: "fDfffDD\"\""
b4,g,msb,xy         .. text: "f\"fff\"\"DD"
b4,b,lsb,xy         .. text: "\"$BDDDDf"
b4,b,msb,xy         .. text: "wwBDDDfUU53w"
b4,rgb,msb,xy       .. text: "dUcv%F#A`"
b4,bgr,msb,xy       .. text: " V\"c7Ga4"
b4,abgr,msb,xy      .. text: "gOC_$_@o"

flag: picoCTF{h1d1ng_1n_th3_b1t5}

extensions

Problem

This is a really weird text file TXT? Can you find the flag?

file

Solution

A quick file type check with file reveals that we have a PNG file instead of a TXT file:

$ file flag.txt 
flag.txt: PNG image data, 1697 x 608, 8-bit/color RGB, non-interlaced

Simply changing the filename to flag.png yields the flag.

flag: picoCTF{now_you_know_about_extensions}

shark on wire 1

Problem

We found this packet capture. Recover the flag. You can also find the file in /problems/shark-on-wire-1_0_13d709ec13952807e477ba1b5404e620.

file

Solution

We are given a pcap network capture that can be opened in wireshark. When we ope the file, we see many udp packets. By following udp streams, we can obtain the flag. Specifically, apply the filter udp.stream eq 6 and then right-click the follow udp option:

flag: picoCTF{StaT31355_636f6e6e}

WhitePages

Problem

I stopped using YellowPages and moved onto WhitePages… but the page they gave me is all blank!

file

Solution

A quick hexdump with xxd shows that there are two different patterns: e28083 and 20:

s$ xxd whitepages.txt 
00000000: e280 83e2 8083 e280 83e2 8083 20e2 8083  ............ ...
00000010: 20e2 8083 e280 83e2 8083 e280 83e2 8083   ...............
00000020: 20e2 8083 e280 8320 e280 83e2 8083 e280   ...... ........
00000030: 83e2 8083 20e2 8083 e280 8320 e280 8320  .... ...... ... 
00000040: 2020 e280 83e2 8083 e280 83e2 8083 e280    ..............
00000050: 8320 20e2 8083 20e2 8083 e280 8320 e280  .  ... ...... ..
...

Treating e28083 as 0 and 20 as 1 gives us the flag in binary:

from pwn import *

with open('./whitepages.txt', 'rb') as f:
  data = f.read()

data  = data.replace('e28083'.decode('hex'), '0').replace(' ', '1')

print unbits(data)
$ python main.py 

        picoCTF

        SEE PUBLIC RECORDS & BACKGROUND REPORT
        5000 Forbes Ave, Pittsburgh, PA 15213
        picoCTF{not_all_spaces_are_created_equal_dd5c2e2f77f89f3051c82bfee7d996ef}
        

flag: picoCTF{not_all_spaces_are_created_equal_dd5c2e2f77f89f3051c82bfee7d996ef}

c0rrupt

Problem

We found this file. Recover the flag. You can also find the file in /problems/c0rrupt_0_1fcad1344c25a122a00721e4af86de13.

file

Solution

This writeup is by my teammate Theo Lee

Opening the file in a hex edior, we see that the file header is very simillar to a PNG signature. We used this website to understand the hex values.

A brief overview of PNG datastream structure: (This covers all knowledge needed to complete the problem.)

5.2 PNG Signature
89 50 4E 47 0D 0A 1A 0A (translated to hex)
This signature indicates that the remainder of the datastream contains a single PNG image, consisting of a series of chunks beginning with an IHDR chunk and ending with an IEND chunk.

5.3 PNG Chunk Layout
Each chunk consists of three or four fields.
LENGTH-CHUNKTYPE-CHUNKDATA-CRC
or
LENGTH(=0)-CHUNKTYPE-CRC
The length field is a four byte integer giving the length of the chunkdata field.
Chunktype is a four byte sequence defining the chunk type.
Chunkdata gives data for the image.
crc is a four byte sequence which uses an algorithm on the preceding bytes to check for corruption in the data.

5.6 Chunk Ordering
Critical chunks
(shall appear in this order, except PLTE is optional)
Chunk name    Multiple allowed        Ordering constraints
IHDR          No                      Shall be first
PLTE          No                      Before first IDAT
IDAT          Yes                     Multiple IDAT chunks shall be consecutive
IEND          No                      Shall be last

Ancillary chunks
(need not appear in this order)
Chunk name    Multiple allowed        Ordering constraints
cHRM          No                      Before PLTE and IDAT
gAMA          No                      Before PLTE and IDAT
iCCP          No                      Before PLTE and IDAT. If the iCCP chunk is present, the sRGB chunk should not be present.
sBIT          No                      Before PLTE and IDAT
sRGB          No                      Before PLTE and IDAT. If the sRGB chunk is present, the iCCP chunk should not be present.
bKGD          No                      After PLTE; before IDAT
hIST          No                      After PLTE; before IDAT
tRNS          No                      After PLTE; before IDAT
pHYs          No                      Before IDAT
sPLT          Yes                     Before IDAT
tIME          No                      None
iTXt          Yes                     None
tEXt          Yes                     None
zTXt          Yes                     None

We’ve now mastered PNG chunks.

The first 8 bytes of the mystery file can be fixed to the correct PNG signature.

Now running command in terminal

$ pngcheck mystery
mystery: invalid chunk name "C"DR" (43 22 44 52)

We see the bytes 43 22 44 52 are in the first chunk’s chunktype field, after the 8-byte PNG signature and the 4-byte length field. Refering to the 5.6 chunk ordering table, we see that the IHDR chunk must be the first in the file. Traversing to section 11.2.2 IHDR Image Header, we see the chunk type field must contain the hex values 49 48 44 52.

Now running command in terminal

$ pngcheck mystery
mystery CRC error in chunk pHYs (computed 38d82c82, expected 495224f0)

This tells us the calculated CRC value from the data field, and the current CRC(expected). We can simply try replacing the expected hex values with the computed CRC.

Now running command in terminal

$ pngcheck mystery
mystery invalid chunk length (too large)

Since this does not specify a chunk, we must begin at the start and check each chunk, with the knowledge of the format of chunks and each field’s length: 4bytes(length)-4bytes(chunk type)-lengthbytes(data)-4bytes(crc). IHDR is length 13. sRGB is length 1. gAMA is length 4. pHYs is length 9. The next chunk with chunktype AB 44 45 54 is corrupt with name �DET. The name is very simmilar to IDAT, and IDAT complies the chunk ordering rules in the 5.6 table. We replace the chunktype with hex values 49 44 41 54. The other obvious problem is this chunk’s length: AA AA FF A5. Way too big. Since we cannot identify CRCs, to find the end of the chunk, we must look for the next chunktype field. It is most likely IDAT as they must be consecutive. We find the next IDAT at offset 10008. The first IDAT was at offset 57. The difference is FFB1. We must subtract 4 bytes for the length field of the second IDAT, subtract 4 bytes for the CRC of the first IDAT, and subtract 4 bytes again for the chunktype of the first IDAT. Subtracting 12 in total, we get FFA5. Replace the length field with 00 00 FF A5.

Now running command in terminal

$ pngcheck mystery
OK: mystery (1642x1095, 24-bit RGB, non-interlaced, 96.3%).

Success! Opening the file on any PNG viewer gives the flag.

flag: picoCTF{c0rrupt10n_1847995}

like1000

Problem

This .tar file got tarred alot. Also available at /problems/like1000_0_369bbdba2af17750ddf10cc415672f1c.

file

Solution

I solved this with a short python script and the unzipping utility unar:

from os import system

system('unar ./1000.tar')
for i in range(999, -1, -1):
  system('unar ./{}/{}.tar'.format(i+1, i))

We obtain the flag.png nested in 1000 tar file which has the flag.

flag: picoCTF{l0t5_0f_TAR5}

m00nwalk

Problem

Decode this message from the moon. You can also find the file in /problems/m00nwalk_2_ddfd37932ded29f58963e8d9c526c2fa.

Solution

This writeup is by my teammate Theo Lee

This audio file was encoded by slow-scan television(SSTV), which was the method used in the moon landing. To decode this, we downloaded this program. The program automatically detects the RX option and produces an image with the flag upside down.

flag: picoCTF{beep_boop_im_in_space}

Investigative Reversing 0

Problem

We have recovered a binary and an image. See what you can make of it. There should be a flag somewhere. Its also found in /problems/investigative-reversing-0_6_2d92ee3bac4838493cb68ec16e086ac6 on the shell server.

image

binary

Solution

Reversing the binary shows that the flag is encoded and then appended to the image:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  signed int i; // [rsp+4h] [rbp-4Ch]
  signed int j; // [rsp+8h] [rbp-48h]
  FILE *stream; // [rsp+10h] [rbp-40h]
  FILE *v7; // [rsp+18h] [rbp-38h]
  char ptr; // [rsp+20h] [rbp-30h]
  char v9; // [rsp+21h] [rbp-2Fh]
  char v10; // [rsp+22h] [rbp-2Eh]
  char v11; // [rsp+23h] [rbp-2Dh]
  char v12; // [rsp+24h] [rbp-2Ch]
  char v13; // [rsp+25h] [rbp-2Bh]
  char v14; // [rsp+2Fh] [rbp-21h]
  unsigned __int64 v15; // [rsp+48h] [rbp-8h]

  v15 = __readfsqword(0x28u);
  stream = fopen("flag.txt", "r");
  v7 = fopen("mystery.png", "a");
  if ( !stream )
    puts("No flag found, please make sure this is run on the server");
  if ( !v7 )
    puts("mystery.png is missing, please run this on the server");
  if ( (signed int)fread(&ptr, 0x1AuLL, 1uLL, stream) <= 0 )
    exit(0);
  puts("at insert");
  fputc(ptr, v7);
  fputc(v9, v7);
  fputc(v10, v7);
  fputc(v11, v7);
  fputc(v12, v7);
  fputc(v13, v7);
  for ( i = 6; i <= 14; ++i )
    fputc((char)(*(&ptr + i) + 5), v7);
  fputc((char)(v14 - 3), v7);
  for ( j = 16; j <= 25; ++j )
    fputc(*(&ptr + j), v7);
  fclose(v7);
  fclose(stream);
  return __readfsqword(0x28u) ^ v15;
}

As shown above, the 6th to 14th byte are added by 5 and the 15th byte is subtracted by 3. We can used xxd to extract the encoded hex and decode it with a short python script:

data = '7069636f43544b806b357a73696436715f65656165633438627d'.decode('hex')
data = bytearray(data)

for i in range(6, 15):
  data[i] -= 5

data[15] += 3

print data

flag: picoCTF{f0und_1t_eeaec48b}

m00nwalk2

Problem

Revisit the last transmission. We think this transmission contains a hidden message. There are also some clues clue 1, clue 2, clue 3. You can also find the files in /problems/m00nwalk2_4_db2f361610e04b41a70a92cd8b7b2533.

Solution

This writeup is by my teammate Theo Lee

Use the same program as the first m00nwalk problem.

Each give an image with text Clue 1: Password hidden_stegosaurus Clue 2: The quieter you are the more you can HEAR Clue 3: Alan Eliasen the FutureBoy

Clue 3 leads us to this website and reading the description, it looks like a message was encoded using steganography.

Write in console:

$ steghide extract -sf message.wav -p hidden_stegosaurus
wrote extracted data to "steganopayload12154.txt"

flag: picoCTF{the_answer_lies_hidden_in_plain_sight}

Investigative Reversing 1

Problem

We have recovered a binary and a few images: image, image2, image3. See what you can make of it. There should be a flag somewhere. Its also found in /problems/investigative-reversing-1_0_329e7a12e90f3f127c8ab2489b08bcf1 on the shell server.

binary

image1

image2

image3

Solution

Similar to Investigative Reversing 0, we need to reverse the binary and decode the flag:

from pwn import *
s1 = unhex('43467b416e315f62313739313135657d')
s2 = unhex('8573')
s3 = unhex('696354307468615f')

out = bytearray('0'*0x1a)

out[1] = s3[0]
out[21] = s2[0]
out[2] = s3[1]
out[5] = s3[2]
out[4] = s1[0]

for i in range(6,10):
  out[i] = s1[i-5]

out[3] = chr(ord(s2[1])-4)

for i in range(10, 15):
  out[i] = s3[i-7]

for i in range(15,26):
  out[i] = s1[i-10]

print out

flag: picoCTF{An0tha_1_b179115e}

Investigative Reversing 2

Problem

We have recovered a binary and an image See what you can make of it. There should be a flag somewhere. Its also found in /problems/investigative-reversing-2_5_b294e24c9063edbf722b9554e7750d19 on the shell server.

binary

image

Solution

Same concept as before, we need to reverse the binary and decode the flag:

with open('./encoded.bmp', 'rb') as f:
  data = f.read()

data = data[2000:2000+(50*8)]

out = ''

for i in range(50):
  c = 0
  for j in range(8):
    c = c | (ord(data[i*8+(7-j)])&1)
    c = c << 1
  c = c >> 1
  out += chr(c+5)
  print c
  print out

flag: picoCTF{n3xt_0n30000000000000000000000000f69eb8c8}

pastaAAA

Problem

This pasta is up to no good. There MUST be something behind it.

file

Solution

Flag is hidden in one of the RGB planes and can be extracted with stegsolve:

flag: picoCTF{pa$ta_1s_lyf3}

Investigative Reversing 3

Problem

We have recovered a binary and an image See what you can make of it. There should be a flag somewhere. Its also found in /problems/investigative-reversing-3_5_bb1b39c0e6a6ea43ea4f44c5b6f44200 on the shell server.

binary

image

Solution

This challenge is building on top of Investigative Reversing 2. Here is the decode script:

with open('./encoded.bmp', 'rb') as f:
  data = f.read()

data = data[723:723+(50*9)]

out = ''

for i in range(50):
  c = 0
  for j in range(8):
    c = c | (ord(data[i*9+(7-j)])&1)
    c = c << 1
  c = c >> 1
  out += chr(c)
  print c
  print out

flag: picoCTF{4n0th3r_L5b_pr0bl3m_0000000000000aa9faea3}

Investigative Reversing 4

Problem

We have recovered a binary and 5 images: image01, image02, image03, image04, image05. See what you can make of it. There should be a flag somewhere. Its also found in /problems/investigative-reversing-4_5_908aeadf9411ff79b32829c8651b185a on the shell server.

binary

image01

image02

image03

image04

image05

Solution

LSB but with different images. Here is decode script:

arr = []
for i in range(5, 0, -1):
  with open('./Item0{}_cp.bmp'.format(i), 'rb') as f:
    data = f.read()[2019:2019+10*8+40*1]
    arr.extend(data)

out = ''

for i in range(50):
  c = 0
  for j in range(8):
    c = c | (ord(arr[i*12+(7-j)])&1)
    c = c << 1
  c = c >> 1
  out += chr(c)
  print c
  print out
 

flag: picoCTF{N1c3_R3ver51ng_5k1115_00000000000ade0499b}

investigation_encoded_1

Problem

We have recovered a binary and 1 file: image01. See what you can make of it. Its also found in /problems/investigation-encoded-1_6_172edc378b5282150ec24be19ff8342b on the shell server. NOTE: The flag is not in the normal picoCTF{XXX} format.

binary

image

Solution

The program maps each character to a stream of n bits. By reversing the program, we can recover this mapping, therefore, obtain the flag:

import string
from pwn import *

context.log_level = 'error'

v1 = '000000000C000000080000000E000000140000000A00000022000000040000002C0000000C000000300000000C0000003C0000000A00000048000000060000005200000010000000580000000C000000680000000C000000740000000A00000080000000080000008A0000000E000000920000000E000000A000000010000000AE0000000A000000BE00000008000000C800000006000000D00000000A000000D60000000C000000E00000000C000000EC0000000E000000F800000010000000060100000E000000160100000400000024010000'
v1 = unhex(v1)
temp = []
for i in range(0, len(v1), 4):
  temp.append(u32(v1[i:i+4]))
temp = temp[::2]
print len(temp)
v1 = temp

v2 = '08000000000000000C000000080000000E000000140000000A00000022000000040000002C0000000C000000300000000C0000003C0000000A00000048000000060000005200000010000000580000000C000000680000000C000000740000000A00000080000000080000008A0000000E000000920000000E000000A000000010000000AE0000000A000000BE00000008000000C800000006000000D00000000A000000D60000000C000000E00000000C000000EC0000000E000000F800000010000000060100000E000000160100000400000024010000'
v2 = unhex(v2)
temp = []
for i in range(0, len(v2), 4):
  temp.append(u32(v2[i:i+4]))
temp = temp[::2]
print len(temp)
v2 = temp
print v2

secret = 'B8EA8EBA3A88AE8EE8AA28BBB8EB8BA8EE3A3BB8BBA3BAE2E8A8E2B8AB8BB8EAE3AEE3BA8000000000000000000000000000000000000000000000000000000008'
secret = unhex(secret)

def getValue(a1):
  return (ord(secret[a1 // 8]) >> (7 - a1 % 8)) & 1;

d = []

for each in range(27):
  out = []
  for i in range(v1[each], v2[each]+v1[each]):
    out.append(getValue(i))
  d.append([each, ''.join(map(str, out))])

d.sort(key=lambda x: len(x[1]), reverse=True)

print d

with open('./output', 'rb') as f:
  data = ''.join(map(str,bits(f.read())))

i = 0
flag = ''
while i < len(data):
  for index, enc in d:
    
    if data[i:i+len(enc)] == enc:
      flag += chr(ord('a')+index)
      i += len(enc)
      print flag
print flag

flag: encodedgxmurhtuou

investigation_encoded_2

Problem

We have recovered a binary and 1 file: image01. See what you can make of it. Its also found in /problems/investigation-encoded-2_2_4d97294fc1696ff16af8ce3c0e6b3b95 on the shell server. NOTE: The flag is not in the normal picoCTF{XXX} format.

binary

image

Solution

Similar to investigation_encoded_1 but with more characters.

import string
from pwn import *

context.log_level = 'error'

v1 = '000000000400000012000000280000003C0000005200000064000000780000008E0000009E000000B4000000C8000000DA000000EA000000FC0000000E0100001E01000034010000480100005A0100006A01000072010000800100008C0100009A010000AA010000BC010000C8010000D6010000E0010000EA010000F0010000000200000A02000016020000220200003002000034020000'
v1 = unhex(v1)
print len(v1)
temp = []
for i in range(0, len(v1), 4):
  temp.append(u32(v1[i:i+4]))
# temp = temp[::2]
print len(temp)
v1 = temp
print v1

secret = '8BAA2EEEE8BBAE8EBBAE3AEE8EEEA8EEAEE3AAE3AEBB8BAEB8EAAE2EBA2EAE8AEEA3ABA3BBBB8BBBB8AEEE2AEE2E2AB8AA8EAA3BAA3BBA8EA8EBA3A8AA28BBB8AE2AE2EE3AB80000000000000000000000000000000000000000000000000000'
secret = unhex(secret)

def getValue(a1):
  return (ord(secret[a1 // 8]) >> (7 - a1 % 8)) & 1;

def enc(v):
  v = ord(v)
  if v == 32:
    v = 133
  if v > 47 and v <= 57:
    v += 75
  v -= 97
  if v != 36:
    v = (v+18)%36
  out = []
  for i in range(v1[v], v1[v+1]):
    out.append(getValue(i))
  return out

d = []

str_list = string.lowercase+' '+string.digits
print 'start'
for each in str_list:
  out = enc(each)
  print 'expect {}'.format(out)
  # print '       {}'.format(test(str_list[each]))
  d.append([each, ''.join(map(str, out))])
print 'end'

d.sort(key=lambda x: len(x[1]), reverse=True)

print d

with open('./real_output', 'rb') as f:
  data = ''.join(map(str,bits(f.read())))
print data
i = 0
flag = ''
while i < len(data):
  for char, enc in d:
    # print index
    # print enc
    if data[i:i+len(enc)] == enc:
      flag += char
      i += len(enc)
      print flag
print flag

flag: t1m3f1i3500000000000501af001

tweet Share