Embed Ziped Archive in Jpeg Binary


I found david's tweet in Hacker News and I got Interest about it. On his twitter, he posted a jpeg file which includes RAR files embedded as ZIP. I think this is interesting method taking advantage of characteristics of jpeg and ZIP format.

Jpeg and ZIP, both formats consist of segments. so if I can inject data of a segment into a segment of other format, I can create binary data that holds in either formats.

Any way, this is Python code that embeds ziped data into jpeg.

ZipInJpeg.py
import os
import sys
import struct

#Class which creates binary file byte by byte
class BinWriter:
    fout = None
    pos = 0
    def __init__(self, path):
        self.fout = open(path,'wb')
    def write(self, data):
        self.fout.write(data.to_bytes(1,byteorder='little'))
        self.pos += 1
    def getCurrentPos(self):
        return self.pos

#Class which analyse pk header in given zip archive 
class ZipAnalyser:
    zfin = None
    zipSize = 0
    pk34entrys = []
    pk34entrys_new = []
    pk34sizes = []
    pk12entry = 0
    pk12entry_new = 0
    pk12num = 0
    pk12size = 0
    pk56entry = 0
    def __init__(self, zipPath):
        self.zipSize = os.path.getsize(zipPath)
        self.zfin = open(zipPath,'rb')
    def __get_pk56entry(self):
        for i in range(self.zipSize):
            pos = self.zipSize - i - 22
            self.zfin.seek(pos)
            header = self.zfin.read(4)
            if(header[0] == 0x50 and header[1] == 0x4b and header[2] == 0x05 and header[3] == 0x06):
                return pos
        return -1
    def load(self):
        self.pk56entry = self.__get_pk56entry()
        if(self.pk56entry < 0):
            return -1
        #move to PK56 header 
        self.zfin.seek(self.pk56entry)
        pk56header = self.zfin.read(22) 
        self.pk12num = byte2int(0,0,pk56header[9], pk56header[8])
        self.pk12size =  byte2int(pk56header[15], pk56header[14], pk56header[13], pk56header[12])
        self.pk12entry = byte2int(pk56header[19], pk56header[18], pk56header[17], pk56header[16])
        self.pk12entry_new = byte2int(pk56header[19], pk56header[18], pk56header[17], pk56header[16])
        #move to PK12 header
        filenamesize = 0
        offset = 0
        for i in range(self.pk12num):
            self.zfin.seek(self.pk12entry + i * 46 + offset)
            pk12header = self.zfin.read(46)
            filenamesize = byte2int(0, 0, pk12header[29], pk12header[28])
            offset += filenamesize
            pk34entry = byte2int(pk12header[45], pk12header[44], pk12header[43], pk12header[42])
            pk34size = 30 + filenamesize + byte2int(pk12header[23],pk12header[22],pk12header[21],pk12header[20])
            if(pk34size > 65519):
                return -1
            self.pk34entrys.append(pk34entry)
            self.pk34entrys_new.append(pk34entry)
            self.pk34sizes.append(pk34size)
        return 0
    def getFileNum(self):
        return self.pk12num
    def getPk34Data(self, i):
        self.zfin.seek(self.pk34entrys[i])
        return self.zfin.read(self.pk34sizes[i])
    def setPK34EntryPoint(self, i, newentrypoint):
        self.pk34entrys_new[i] = newentrypoint
    def getPK12Data(self):
        self.zfin.seek(self.pk12entry)
        pk12data = bytearray(self.zfin.read(self.pk12size))
        offset = 0
        for i in range(self.pk12num):
            filenamesize = byte2int(0, 0, pk12data[offset+29], pk12data[offset+28])
            newentrypoint = self.pk34entrys_new[i]
            nep4 = int(newentrypoint / (256 * 256 * 256))
            nep3 = int(newentrypoint / (256 * 256 ) % (256))
            nep2 = int(newentrypoint / (256) % (256))
            nep1 = int(newentrypoint % 256)
            pk12data[offset+42] = nep1 
            pk12data[offset+43] = nep2 
            pk12data[offset+44] = nep3 
            pk12data[offset+45] = nep4 
            offset += 46 + filenamesize
        return pk12data
    def setPK12EntryPoint(self, newentrypoint):
        self.pk12entry_new = newentrypoint
    def getPK56Data(self):
        self.zfin.seek(self.pk56entry)
        pk56data = bytearray(self.zfin.read(22))
        newentrypoint = self.pk12entry_new
        nep4 = int(newentrypoint / (256 * 256 * 256))
        nep3 = int(newentrypoint / (256 * 256 ) % (256))
        nep2 = int(newentrypoint / (256) % (256))
        nep1 = int(newentrypoint % 256)
        pk56data[16] = nep1 
        pk56data[17] = nep2 
        pk56data[18] = nep3 
        pk56data[19] = nep4 
        return pk56data

#usuful functions
def getHex(value):
    hexcode = hex(value)
    if(len(hexcode) == 4):
        return hexcode[-2:]
    if(len(hexcode) == 3):
        return ('0' + hexcode[-1:])
    return ''
def byte2int(b1,b2,b3,b4):
    return (256 * 256 * 256 * b1 + 256 * 256 * b2 + 256 * b3 + b4)

#original jpeg file to import compressed file
#original file must be accordance with jpeg format 
bin_data = open("W:\\original.jpg", 'rb').read()

#output jpeg file writing class
binWriter = BinWriter("W:\\unzipme.jpeg")

#load embed zip
#all archived data in zip must not be over 0xFFEF bytes 
zipAnalyser = ZipAnalyser("w:\\embed.zip.zip")
if(zipAnalyser.load() != 0):
    print('zip load error!')
    sys.exit()

#main process start
#SOI
binary_size = len(bin_data)
idx = 0
tag1 = getHex(bin_data[idx])
binWriter.write(bin_data[idx])
idx = idx + 1
tag2 = getHex(bin_data[idx])
binWriter.write(bin_data[idx])
idx = idx + 1
print(tag1 + tag2)

picadd = 0
while idx < binary_size:
    tag1 = getHex(bin_data[idx])
    binWriter.write(bin_data[idx])
    idx = idx + 1
    tag2 = getHex(bin_data[idx])
    binWriter.write(bin_data[idx])
    idx = idx + 1

    # is EOI?
    if((tag1 + tag2) == 'ffd9'):
        print(tag1 + tag2)
        
        #PK56 headers
        pk56data = zipAnalyser.getPK56Data()
        pk56size = len(pk56data)
        binWriter.write(int(255))
        binWriter.write(int(226))
        binWriter.write(int((pk56size + 2)/256))
        binWriter.write(int((pk56size + 2)%256))
        for i in range(pk56size):
            binWriter.write(pk56data[i])
            
        break

    if(tag1 != 'ff'):
        #Image area
        for i in range(idx, binary_size - 2):
            binWriter.write(bin_data[idx])
            idx = idx + 1
        print('Image : ...')
        continue

    #calc segment length and output segment data as it is
    ulen = bin_data[idx]
    binWriter.write(bin_data[idx])
    idx = idx + 1
    llen = bin_data[idx]
    binWriter.write(bin_data[idx])
    idx = idx + 1
    segment_size = ulen * 256 + llen
    record = ""
    print(tag1 + tag2 + ' : Len=' + str(segment_size))
    for i in range(idx, idx + segment_size - 2):
        binWriter.write(bin_data[idx])
        idx = idx + 1
 
    if(picadd == 0):
        #embed zip archive in FFE2 segment
        #PK34 heraders and archived data
        filenum = zipAnalyser.getFileNum()
        for i in range (filenum):
            pk34data = zipAnalyser.getPk34Data(i)
            pk34size = len(pk34data)
            binWriter.write(int(255))
            binWriter.write(int(226))
            binWriter.write(int((pk34size + 2)/256))
            binWriter.write(int((pk34size + 2)%256))
            zipAnalyser.setPK34EntryPoint(i, binWriter.getCurrentPos())
            for j in range(pk34size):
                binWriter.write(pk34data[j])
        #PK12 headers
        pk12data = zipAnalyser.getPK12Data()
        pk12size = len(pk12data)
        binWriter.write(int(255))
        binWriter.write(int(226))
        binWriter.write(int((pk12size + 2)/256))
        binWriter.write(int((pk12size + 2)%256))
        zipAnalyser.setPK12EntryPoint(binWriter.getCurrentPos())
        for i in range(pk12size):
            binWriter.write(pk12data[i])
        ##PK56 headers
        #pk56data = zipAnalyser.getPK56Data()
        #pk56size = len(pk56data)
        #binWriter.write(int(255))
        #binWriter.write(int(226))
        #binWriter.write(int((pk56size + 2)/256))
        #binWriter.write(int((pk56size + 2)%256))
        #for i in range(pk56size):
        #    binWriter.write(pk56data[i])
            
        picadd = 1
    
Profile
I have technical job experience in enbedded software development and server side infrastructure/application engineering. I'm interested in programming and computer security.
Objective
To write down my technical knowledge in the place where I can access from anywhere. To share my program source code. To train my writing skill.
Link
  • LinkedIn (preparing)

  • Twitter

  • Facebook (preparing)

  • GitHub

  • StackOverFlow (preparing)

Archives