如何用PIL库处理图片

2020-04-19 140次浏览 0条评论  前往评论

前言


最近有几场CTF比赛,里面的题都不会做,没有思路,感觉自己现在的状态就是思而不学则殆,于是乎就在bugku里面刷杂项题。遇到一些图片的题目,除了使用kail的binwalk,foremost隐写图片和改高度宽度以及使用一些工具之外,还发现有好几题都要用到PIL库这个东西,就记录一下怎么用。

关于PIL库的更多用法可以参考:传送门

使用PIL库


安装库

from PIL import Image

加载图片,使用方法open

from PIL import Image
im = Image.open('Mycat.jpg')

im是一个Image对象,属性有format,size,mode。format是格式,size 是一个元组,表示(宽,高),mode则指的图片的模式。

from PIL import Image
im = Image.open('Mycat.jpg')
print im.format,im.size,im.mode

console输出如下:

JPEG (245, 280) RGB

呈现图片,方法show方便用来调试和测试。

from PIL import Image
im = Image.open('Mycat.jpg')
im.show()

图片的读和写

读文件用Image.open(),保存文件用Image.save(),也可以用save方法来进行图片的格式转换。使用os模块中的os.path.splitext()方法可以讲文件名和扩展名分离开来,下面的代码能够把jpg格式的图片转为png格式。

infile = 'Mycat.jpg'
f,e = os.path.splitext(infile)
outfile = f + '.png'
try:
 Image.open(infile).save(outfile)
except IOError:
 print "cannot convert",infile

图片的剪切,黏贴

(1) 图片剪切

从一张图片中剪切出一块区域,比如从图片提取矩形,使用crop()方法。

im = Image.open('Mycat.jpg')
box = (150,150,245,280)
region = im.crop(box)
region.show()

关于这个box,这是一个4元的坐标数组,坐标轴是左上角是(0,0)的卡迪尔坐标系。 假设box是(x1,y1,x2,y2),则所取区域是以各自坐标划线所围的区域。

image-20200419225057613

(2)图片黏贴

图片的黏贴就是将一张图覆盖到另一张图上面。黏贴的方法是paste()。格式为:paste(要贴的图片,要贴的图片的4元坐标组成的区域)。如下面,我们把Mycat.jpg这张图片,取区域(50,50,200,200),将该区域旋转180度后贴到原来的位置。

im = Image.open('Mycat.jpg')
box = (50,50,200,200)
region = im.crop(box)
# 将图片逆序旋转180后,黏贴到原来复制的位置 
region = region.transpose(Image.ROTATE_180)
im.paste(region,box)
im.show()

图像序列

当处理GIF这种包含多个帧的图片,称之为序列文件,PIL会自动打开序列文件的第一帧。而使用seek和tell方法可以在不同帧移动。tell是帧数,而seek是取当前帧数的图片。

使用while循环:

from PIL import Image
im = Image.open("laopo.gif")
im.seek(1)
im.show()

try:
 while 1:
 im.seek(im.tell()+1)
 im.show()
except EOFError:
 pass

如果要使用for循环,可以使用ImageSequence模块的Iterator方法。

from PIL import Image
from PIL import ImageSequence

im = Image.open("laopo.gif")
for frame in ImageSequence.Iterator(im):
  frame.show()

读取像素和修改像素

from PIL import Image
img = Image.open('Mycat.jpg')
width , height = img.size
for i in range(0,width):
  for j in range(0,height):
    tmp = img.getpixel((i,j))
    img.putpixel((i,j),(0,0,tmp[2]))
img.show()

CTF实例:

红绿灯

首先题目是一张gif,用2345看图王或者PS打开都可以,会发现有1168帧,然后保存所有帧。

image-20200419184820875

会发现多数是绿灯和红灯,偶尔有黄灯,可以推测红色和绿色对应二进制0和1,黄色作为分隔。

这样第一个黄灯之前数值为01100110或10011001,而01100110二进制转成ascii对应字符就是‘f’,依次可以验证前四个字符为flag。就可以确定绿灯对应0,红灯对应1。

这里如果手动算的话是可以一张一张这样算出flag的。也可以用python的PIL库。

脚本如下:

# -*- coding: utf-8 -*-
from PIL import Image

path='C:\\Users\\Think\\Desktop\\Traffic_Light_gif\\'

flag=""
for i in range(1,1168):
    image=Image.open(path+'Traffic_Light_'+str(i)+'.JPG')
    if image.getpixel((115,55))==(254, 0, 0): #如果上灯等于红
            flag+=str(1)
    elif image.getpixel((115,145))==(8, 253, 8): #如果下灯等于绿
            flag+=str(0)
flag= hex(int(flag,2))[2:-1].decode('hex')
print(flag)

可以先用:

print(image.getpixel((115,55)))
print(image.getpixel((115,145)))

测出(254, 0, 0)为红,(172, 172, 172)为灰,(8, 253, 8)为绿。其中

(115,55)和(115,145)是坐标。

运行脚本即可得到flag。

GIF图片合并

首先同样用2345看图王分离帧。一共有201帧。

然后观察每张图片的大小:

from PIL import Image

path='C:\\Users\\Think\\Desktop\\glance\\'

im=Image.open(path+'glance_1'+'.JPG')
print(im.size)

运行结果为(2, 600),可以看出每一帧的宽度为2。高度为600。

思路为:建一张大图,然后将每一帧依次黏贴上去。

编写脚本为:

from PIL import Image

path='C:\\Users\\Think\\Desktop\\glance\\'

im = Image.new('RGB',(2*201,600))
width = 0

for i in range(1,201):
  im.paste(Image.open(path+'glance_'+str(i)+'.JPG'),(width,0,2+width,600))
  width = width +2
im.show()
判断图片颜色

题目是:

image-20200419200921372

这里应该是黑色白色对应01,然后转成字符串。

编写脚本:

from PIL import Image

path='C:\\Users\\Think\\Desktop\\gif\\'

sumDo = ''
sumNo = ''

for i in range(104):
    if Image.open(path+str(i)+'.jpg').getcolors()[0][1][0] == 12:
        sumDo +='1'
        sumNo +='0'
    else:
        sumDo +='0'
        sumNo +='1'

print (hex(int(sumDo,2))[2:-1]).decode('hex')
print (hex(int(sumNo,2))[2:-1]).decode('hex')

即可得到flag。

LSB隐写

LSB隐写就是修改RGB颜色分量的最低二进制位也就是最低有效位(LSB)

首先来讲png图片,png图片是一种无损压缩的位图片形格式,也只有在无损压缩或者无压缩的图片(BMP)上实现lsb隐写。如果图像是jpg图片的话,就没法使用lsb隐写了,原因是jpg图片对像数进行了有损压缩,我们修改的信息就可能会在压缩的过程中被破坏。而png图片虽然也有压缩,但却是无损压缩,这样我们修改的信息也就能得到正确的表达,不至于丢失。BMP的图片也是一样的,是没有经过压缩的。BMP图片一般是特别的大的,因为BMP把所有的像数都按原样储存,没有进行压缩。

image-20200419222005146

上图我们可以看到,十进制的235表示的是绿色,我们修改了在二进制中的最低位,但是颜色看起来依旧没有变化。我们就可以修改最低位中的信息,实现信息的隐写。

这里的题目是一张png图片:

LSB是将原本的像素转8位2进制,将8位2进制的左后一位置0或者置1来隐写数据,所以我们可以枚举所有像素,当该位像素最后一位不为0时,置为255的黑点。

脚本如下:

from PIL import Image
img = Image.open('C:\\Users\\Think\\Desktop\\LSB\\01.png')
width,height=img.size
for i in range(0,width):
    for j in range(0,height):
        tmp = img.getpixel((i,j))
        if tmp&1 == 0:
            img.putpixel((i,j),0)
        else:
            img.putpixel((i,j),255)
img.show()

即可得到一张二维码。扫码即可得到flag。

image-20200419222749603



登录后回复

共有0条评论