首先生成了这个拼图,就是上面的那个图片的样子,然后我们可以看到有很多小碎片,而且是慢慢出现的,于是我就考虑是不是通过网络来请求的呢,于是用了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)