首先生成了这个拼图,就是上面的那个图片的样子,然后我们可以看到有很多小碎片,而且是慢慢出现的,于是我就考虑是不是通过网络来请求的呢,于是用了chrome的审查元素,发现没有多与的http请求,于是我又想会不会是flash通过tcp请求来获得的呢,于是用了wireshark抓包,发现依然毫无所获,看来应该是图片都已经存储在flash文件里了。审查元素可以看到 http://three.flash-gear.com/npuz/puz.php?c=z&o=2&id=3986907&k=72291300&s=30&w=180&h=180&f_dm=three 这个请求的时间最长了,于是猜测这个应该是小碎片的flash文件,于是用下面的命令我们可以得到这个flash文件。
- wget "http://three.flash-gear.com/npuz/puz.php?c=z&o=2&id=3986907&k=72291300&s=30&w=180&h=180&f_dm=three" -O test.swf
-
ok,文件得到了,打开看看吧,发现这个文件只是简单的把小碎片放到里面,用文本编辑器打开这个文件,发现里面有大量的“CREATOR: gd-jpeg v1.0 (using IJG JPEG v62)”这样的字样,这篇文章里有介绍swf的格式,和如何从swf文件取出图片,其实我们不用这么麻烦,既然图片都是直接包装在里面的,那么我们可以直接用正则取出图片。在这里可以看到jpeg头文件的相关说明。
- import re
- re_jpg = re.compile(r'(.{6}JFIF(.(?!(.{5}JFIF)))*)', re.M|re.S)
- swf = open('test.swf', 'rb')
- cnt = 0
- for jpg in re_jpg.findall(swf.read()):
- f = open('%d.jpg' % cnt, 'wb')
- cnt += 1
- f.write(jpg[0])
- f.close()
-
运行程序我们便得到了所有的小碎片。
这里我们用到了一个小trick,就是只要图片文件正确,我们向后面添加多余内容,不影响图片的正常解析,所以有的加密软件就是帮你把秘密添加到某个图片的后面,这样外表看上去是图片。
我们发现小图片竟然都是正方形的,而且竟然都是按照顺序给的,那么剩下的工作就很简单了,用PIL把图片拼起来就好了。注意PIL的(0,0)点是左上角,并且(x,y)是先第x列第y行的意思,这里经常容易出错。
- from PIL import Image, ImageDraw
- total = 36
- row = 6
- col = 6
- w = 60
- h = 60
- im = Image.new('RGB', (w*col, h*row))
- draw = ImageDraw.Draw(im)
- for i in range(row):
- for j in range(col):
- now = Image.open('%d.jpg' % (i*col+j))
- for x in range(h):
- for y in range(w):
- draw.point((j*h+x, i*w+y), now.getpixel((x, y)))
- im.save('test.jpg')
-
图片生成后是这个样子
很奇怪啊,我们再仔细观察发现原来图片是有重叠的,于是我们只要拿每张图片的右下角的1/4来组成就可以了,于是最后我们就完成了最后的拼图。
swf文件提取图片代码(python)
- # Dump all JPEG tags from an SWF file
- import os
- import zlib
- import struct
- import StringIO
-
- import Image
-
- # Helpers for reading SWF files
- def CalcMaskShift(pos, len):
- shift = pos - len + 1
- return (pow(2, len) - 1) << shift, shift
-
- class BitStream(object):
- lut = dict(((pos, len), CalcMaskShift(pos, len)) for pos in range(8) for len in range(1, pos+2))
-
- def __init__(self, fp):
- self.fp = fp
- self.next()
-
- def next(self):
- c = self.fp.read(1)
- if (c): self.curr_byte = ord(c)
- else: self.curr_byte = None
- self.bit_pos = 7
-
- def tell(self):
- return (self.fp.tell()-1, self.bit_pos)
-
- def seek(self, curr_byte, bit_pos=7):
- self.fp.seek(curr_byte)
- self.next()
- self.bit_pos = bit_pos
-
- def align(self):
- if (self.bit_pos != 7): self.next()
-
- def make_signed(self, val, size):
- flag = pow(2, size-1)
- if (val >= flag):
- return val - flag - flag
- else:
- return val
-
- # Bit order is preserved; msb stored in msb
- def read_partial_byte(self, size):
- mask, shift = self.lut[(self.bit_pos, size)]
- rv = (self.curr_byte & mask) >> shift
- self.bit_pos = (self.bit_pos - size) & 0x7
- if (self.bit_pos == 7): self.next()
- return rv
-
- # Bitfields are stored in MSB order
- def read_bits(self, size, signedp):
- # Segment read
- head_len = min(size, self.bit_pos+1)
- body_len = (size - head_len) / 8
- tail_len = (size - head_len) & 0x7
- # Perform read
- rv = 0
- if (head_len):
- rv = self.read_partial_byte(head_len)
- while (body_len):
- rv = rv * 256 + self.curr_byte
- self.next(); body_len -= 1
- if (tail_len):
- rv = (rv << tail_len) + self.read_partial_byte(tail_len)
- if (signedp):
- rv = self.make_signed(rv, size)
- return rv
-
- # Byte values are stored in LSB order
- def read_bytes(self, size, signedp):
- self.align(); rv = 0; factor = 1; nbits = size*8
- while (size):
- rv += (self.curr_byte*factor)
- self.next(); factor *= 256; size -= 1
- if (signedp):
- rv = self.make_signed(rv, nbits)
- return rv
-
- def read_raw(self, size):
- self.align()
- data = chr(self.curr_byte) + self.fp.read(size-1)
- self.next()
- return data
-
- # Low-level extraction code
- def skip_bytes(bs, size):
- byte_pos, bit_pos = bs.tell()
- bs.seek(byte_pos+size)
-
- def read_rect(bs):
- nbits = bs.read_bits(5, False)
- xmin = bs.read_bits(nbits, True)
- xmax = bs.read_bits(nbits, True)
- ymin = bs.read_bits(nbits, True)
- ymax = bs.read_bits(nbits, True)
- return (xmin, xmax, ymin, ymax)
-
- def read_movie_header(bs):
- rect = read_rect(bs)
- fps = bs.read_bytes(2, False)/256.0
- nframes = bs.read_bytes(2, False)
- return ([n/20.0 for n in rect], fps, nframes)
-
- def read_jpeg_table(bs, length):
- # Strip the trailing end-of-stream
- rv = bs.read_raw(length-2)
- skip_bytes(bs, 2)
- return rv
-
- def read_jpeg_bits(bs, length, table):
- # Get the bitmap ID
- id = bs.read_bytes(2, False)
- # Omit the opening beginning-of-stream
- skip_bytes(bs, 2)
- # Return a complete JPEG
- return (id, table+bs.read_raw(length-4))
-
- def read_jpeg_bits_2(bs, length):
- # Contrary to documentation, this appears to consist of only a single
- # JPEG stream - there is no FF D9 FF D8 quad in the datastream
- id = bs.read_bytes(2, False)
- return (id, bs.read_raw(length-2))
-
- def read_jpeg_bits_3(bs, length):
- # Most apps don't like SWF's two-stream-per-file business, so this
- # crudely strips out the end-of-stream / start-of-stream tag pair.
- # A little risky, but there's only a 2**-32 chance of it occuring randomly
- id = bs.read_bytes(2, False)
- jpg_len = bs.read_bytes(4, False)
- img_data = bs.read_raw(jpg_len).replace('\xff\xd9\xff\xd8', '')
- alpha_data = zlib.decompress(bs.read_raw(length-6-jpg_len))
- return (id, img_data, alpha_data)
-
- # Extraction utility fxn
- def dump_jpegs(fn):
- pn = os.path.split(fn)[0]
- fp = file(fn, 'rb')
-
- sig, ver, length = struct.unpack('<3sBL', fp.read(8))
- if (sig == 'CWS'):
- bs = BitStream(StringIO.StringIO(zlib.decompress(fp.read())))
- elif (sig == 'FWS'):
- bs = BitStream(fp)
- else:
- return
-
- rect, fps, nframes = read_movie_header(bs)
- print 'sig: ' + sig
- print 'version: %d' % ver
- print 'length: %d' % length
- print 'screen: %.1fx%.1f' % (rect[1]-rect[0], rect[3]-rect[2])
- print 'fps: %.1f' % fps
- print 'num frames: %d' % nframes
-
- table = None
- while (1):
- # Read tag header
- code = bs.read_bytes(2, False)
- tag = code >> 6
- length = code & 0x3f
- if (length == 63):
- length = bs.read_bytes(4, False)
- # Process JPEG tags, or skip
- if (tag == 0):
- break
- elif (tag == 8):
- table = read_jpeg_table(bs, length)
- elif (tag == 6):
- id, bits = read_jpeg_bits(bs, length, table)
- file(os.path.join(pn, '%d.jpg'%id), 'wb').write(bits)
- elif (tag == 21):
- id, bits = read_jpeg_bits_2(bs, length)
- file(os.path.join(pn, '%d.jpg'%id), 'wb').write(bits)
- elif (tag == 35):
- id, img_bits, alpha_bits = read_jpeg_bits_3(bs, length)
- img = Image.open(StringIO.StringIO(img_bits)).convert('RGBA')
- img.putalpha(Image.fromstring('L', img.size, alpha_bits))
- img.save(os.path.join(pn, '%d.png'%id))
- else:
- skip_bytes(bs, length)