分享

某通信工具收费表情安全性研究

 西北望msm66g9f 2019-10-02

本文的阅读对象是具有一定Android开发经验的开发人员以及想学习安全技术的吃瓜群众,这里只上核心代码。

一、前言

在【某通信工具】表情里,我特别喜欢“乖巧**”系列的表情,简明清新以及萌萌哒的设计风格,颇得年轻人的喜爱。

数月前在【某通信工具】“XX表情付费篇” 页面看中了“乖巧**6” 里的一个表情,发现要付费 1元才能使用。。。是的,就一块钱,但对于“抠门”的我来说,肯定不愿意掏的。所以就没买,直接关闭了付费窗口。

而前两天,在XXX技术群里,看见某大佬连发了好几个“乖巧**6” 的表情,我问他是不是买的表情。他说“NO”,这些是他使用frida提取出来的表情。我就比较好奇,frida是什么?以及他是如何提取这些表情的?经过几天研究,终于弄懂了这一切,特撰此文。

二、准备工具

本人环境为windows,linux 类同。

1、安装python (内含pip)、frida。frida 是一个动态hook框架,支持hook java代码和native代码(so)。它主要提供了功能简单的python接口和功能丰富的js接口。官网地址:https://www. 。python 和frida 入门安装教程具体可以看这里https://blog.csdn.net/tabactivity/article/details/88106511和 https://blog.csdn.net/tabactivity/article/details/88130653

2、准备一台root 的安卓手机,并安装好【某通信工具】App

三、分析

1、我们要hook【某通信工具】付费表情,我们必须先知道 这个表情的ImageView的ID。

打开【某通信工具】-》我-》表情-》朋友表情 下面滚动的图片里选择 “XX表情付费篇”

然后选择 你喜欢的表情,并点击预览

2、启动 Android Device Monitor ,点击 Dump View Hierarchy for UI Automator ,鼠标放到表情处。如下图

我们就知道了用来表情的ImageView的Id是 :com.tencent.mm:id/bg6 ,也就是com.tencent.mm包里bg6。我们要做的就是把ImageView里的图片提取出来,可以通过hook ImageView 的onDraw(Canvas canavs)事件,将需要绘制到canvas内容,也同样绘制到我们用Bitmap创建的Canvas上,然后将Bitmap保存png文件。随着onDraw 一次一次调用,动图表情的多个png图片帧就保存下来了。最后,我们只需要将png图片合成 gif 就能在【某通信工具】里随意发了。

四、实施(必须将步骤二搞好 才能开始此步骤)

1、启动frida

2、编写frida脚本代码。wxface.py


























































































import fridaimport sysimport ioimport osimport timedevice = frida.get_usb_device()pid = device.spawn(['com.tencent.mm'])session = device.attach(pid)
src_tencent_mm = '''
Java.perform(function(){
var ImageView = Java.use('android.widget.ImageView'); var Bitmap = Java.use('android.graphics.Bitmap'); var Bitmap_Config = Java.use('android.graphics.Bitmap$Config'); //var bufBitmap = Bitmap$new(394, 394, 5); var bitmap_va = Bitmap_Config.ARGB_8888.value; console.log('bitmap_va = ' + bitmap_va); var Canvas = Java.use('android.graphics.Canvas'); console.log('Canvas = ' + Canvas); var ByteArrayOutputStream = Java.use('java.io.ByteArrayOutputStream'); console.log('ByteArrayOutputStream = ' + ByteArrayOutputStream); var CompressFormat = Java.use('android.graphics.Bitmap$CompressFormat'); console.log('CompressFormat value= ' + CompressFormat.PNG.value); var FileOutputStream = Java.use('java.io.FileOutputStream'); var System = Java.use('java.lang.System'); var index = 0; var startTime = 0; var endTime = 0; //创建存储表情帧的 目录 var File = Java.use('java.io.File'); File.$new('/sdcard/mmface').mkdirs(); ImageView.onDraw.implementation = function(canvas){ this.onDraw(canvas); var viewId = this.getResources().getIdentifier('bg6', 'id', 'com.tencent.mm'); if(this.getId() != viewId){ return; } console.log('ImageView onDraw.....'); if(startTime == 0){ startTime = System.currentTimeMillis(); }else{ endTime = System.currentTimeMillis(); console.log('git更新间隔为:'+(endTime - startTime)); startTime = endTime; } console.log('gitd draw entry! ' + canvas.getWidth() + ','+ canvas.getHeight()); //将ImageView 要绘制的内容 也绘制到我们创建的 Bitmap中 var bufBitmap = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), bitmap_va); console.log('bufBitmap = ' + bufBitmap); var tempCanvas = Canvas.$new(bufBitmap); console.log('tempCanvas = ' + tempCanvas); this.onDraw(tempCanvas); var bos = ByteArrayOutputStream.$new(); console.log('bos = ' + bos); console.log('bos size= ' + bos.size()); bufBitmap.compress(CompressFormat.PNG.value, 100, bos); console.log('222 bos size= ' + bos.size()); var bytesss = bos.toByteArray(); console.log('bytesss length = ' + bytesss.length); var fos = FileOutputStream.$new('/sdcard/mmface/' +index +'.png'); fos.write(bytesss); fos.flush(); fos.close(); console.log('保存成功!index=' +index ); index++; } });
'''#message['payload'] message为map,取出key payload 的valuedef on_message(message, data): print(message)
#time.sleep(5) script = session.create_script(src_tencent_mm)#设置message 回调函数为 on_message。js 调用send 就会发到 on_message#script.on('message', on_byte_message)script.on('message', on_message)script.load() device.resume(pid) sys.stdin.read()

3、打开CMD,执行:python wxface.py

然后【某通信工具】会自动重新启动, 待【某通信工具】启动后,点击我-》表情-》朋友表情 下面滚动的图片里选择 “XX表情付费篇”,选择你喜欢的表情包,接着点击一个你喜欢的表情 预览,此时 ,frida 会将 动图表情 的没一帧 都保存到 手机的/sdcard/mmface/里。确认表情循环显示完毕后,关闭表情预览 窗口。

4、执行:adb pull /sdcard/mmface/ mmface/

mmface的表情帧全部 拉取到 电脑上。我们可以手动 删除重复的 图片帧(推荐),也可以自动删除重复的图片帧(下面会讲)。最后 将这些图片帧合成gif

去重后

5、多张图片,合成gif 。可以使用网上任意一种工具。本文直接基于python合成。编写gif.py



















































from PIL import Imageimport osimport sysimport zlibimport imageio#处理透明gifdef create_gif_2(image_list, gif_name):    frames = []    im_tmp = Image.open(image_list[0])         mask = Image.new('RGBA', im_tmp.size, (255, 255, 255, 0))       for image_item in image_list:        im = Image.open(image_item)              frames.append(Image.alpha_composite(mask, im));    #img = Image.new('RGBA', im.size, (255, 255, 255, 0))    first = frames.pop(0);        first.save(gif_name, save_all=True, append_images=frames, loop=0, transparency=0, duration=100,disposal=2)  #处理非透明gifdef create_gif(image_list, gif_name):    frames = []    for image_item in image_list:        im = Image.open(image_item)        alpha = im.getchannel('A')        print('alpha = ', alpha)         # Convert the image into P mode but only use 255 colors in the palette out of 256        im = im.convert('RGBA').convert('P', palette=Image.ADAPTIVE, colors=255)
# Set all pixel values below 128 to 255 , and the rest to 0 mask = Image.eval(alpha, lambda a: 255 if a <=128 else 0)
# Paste the color of index 255 and use alpha as a mask im.paste(0, mask) # The transparency index is 255 im.info['transparency'] = 0 frames.append(im) #frames.append( im = Image.open(path).imread(image_item, 'PNG')) frames[0].save(gif_name, save_all=True, append_images=frames, loop=0, duration=100 ) returndef crc32(filepath): block_size = 1024 *1024 crc = 0 fd = open(filepath, 'rb') while True: buffer = fd.read(block_size) if len(buffer) == 0: fd.close() if sys.version_info[0] < 3 and crc < 0: crc += 2 ** 32 return crc crc = zlib.crc32(buffer, crc) def filter_png(dirpath): image_crc = [] image_files = os.listdir(dirpath) for filename in image_files: path = os.path.join(dirpath, filename) if os.path.isfile(path): find = False crc = crc32(path) for crc_item in image_crc: if crc_item == crc: find = True break if find == False: image_crc.append(crc) else: os.remove(path) def main(argv): image_list = [] image_names = os.listdir(argv[1]) image_names.sort(key= lambda x:int(x[:-4])) if len(argv) > 3 and argv[3]=='-f': filter_png(argv[1]) for filename in image_names: path = os.path.join(argv[1], filename) if os.path.isfile(path): image_list.append(path) create_gif_2(image_list, argv[2])if __name__ == '__main__': main(sys.argv)

上诉gif.py 可以传递2-3个参数。

第1个为 图片帧所在的目录

第2个为保存的文件名(如果包含目录,目录必须已经存在)

第3个为可选参数-f ,设置此参数,图片帧将根据crc32 自动去重。

执行gif.py:python gif.py C:\Users\Administrator\Desktop\mmface gif/saodong.gif

确保 “当前工作目录/gif” 目录存在,像我下面,工作目录就是 xxx/py/PyTest/src/com/test,然后在该目录创建一个gif目录。然后 就会在gif目录 生成 saodong.gif。

saodong.gif效果如下。保存到手机了,到【某通信工具】里 选择发送图片就行了。

当然,我们也可以做的更人性化一点。当检测到【某通信工具】表情预览窗口关闭,就自动adb pull /sdcard/mmface mmface/,然后调用gif.py将图片帧合成gif。之后清空手机/sdcard/mmface 和电脑上的mmface/,这样 点开一个收费表情 就自动转存到电脑上了。

五、安全防护

自己的成果被别人窃取心里的滋味肯定不好受。那问题来了 —— 如何防住上诉破解呢?

最简单有效的方法是特征码检查。在APK运行时,我们可以读取/proc/self/maps得到当前进程的内存映射关系,检查映射里是否包含 “frida” 字符,如果有,我们就提示用户当前运行环境异常,并退出。

例如:在android中调用so,so里执行以下代码










char line[512];FILE* fp;fp = fopen('/proc/self/maps', 'r');if(!fp){   //打开proc/self/maps 失败   return -1;}while (fgets(line, 512, fp)) {    if (strstr(line, 'frida')) {   //检测到了frida,执行退出操作         exit(0);    }}fclose(fp);return 0;

在电脑上,我们也可以通过adb查看。以【某通信工具】为例子,我们看下【某通信工具】的进程是否包含frida:

不出意外,【某通信工具】的进程已经映射了frida 了,安全起见,此时可以提示环境异常或退出。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多