python bytecode解析

python bytecode解析

前言

我们的电脑是怎么运行的呢?计算机内部的 CPU 处理器是个硅片,上面雕刻着精心布置的电路,输入特定的电流,就能得到另一种模式的电流,而且模式可以预测,给这些模式起上名字并赋予含义,我们就可以说这种电流模式代表加法,电脑的工作原理就是如此,我们起的这些名字叫做 CPU 指令,有时也被成为机器码。[引自:James Bennett] 我们的编程语言是怎么运行的呢?一些语言通过编译器,直接将源代码编译成机器码,这些语言就是编译语言,还有一些语言解除解释器,直接在运行时把源代码解释为机器码,这些就是解释型语言。不过还有第三种语言,介于源代码和机器码之间,一些语言编译得到的指令,但是这种指令不能被现有的CPU直接运行,而需要解释器去理解,并将这些指令翻译为真实的 CPU 接受的二进制码,这种中间指令就是我们今天要说的bytecode(字节码),有很多语言属于此类比如java,C#,还有python。

Java编译的字节码运行在java虚拟机上,C#编译的字节码运行在.Net 虚拟机上,而Python 编译的字节码运行在 Python 虚拟机上。

工作原理

CPython解释器在内部会将Python源代码编译成字节码,并缓存在.pyc​文件中,目的是当再次执行该文件时,直接读取.pyc​文件会更快,这样可以避免从源码重新编译到字节码,当然,Python再找到符合文件后,检查此文件的时间戳,如果发现字节码文件(文件在导入时就被编译完成)比源代码文件时间戳早(比如你修改过原文件),那么就会重新生成字节码,否则就会跳过此步骤。如果,Python在搜索时只找到了字节码而没有找到源代码文件,那么就会直接执行字节码文件(如果没有印象,请回想在模块导入时发生了什么)。然后,Python虚拟机执行字节码编译器发出的字节码。

面向栈

这个是在看码农高天(一个非常厉害的pytohn核心开发者)的视频里学到的概念,CPython使用一个基于栈的虚拟机,也就是说,它完全是面向栈,这种数据结构的。就是不断地push、pop。

CPython使用3种类型的栈:

  • 调用栈(call stack)。这是运行Python程序的主要结构,它为每个当前活动的函数调用,使用了一个东西帧(frame)​,栈底是程序的入口点,每个函数调用推送一个新的帧到调用栈,当函数调用返回后,这个帧被销毁
  • 计算栈(evaluation stack,或称数据栈data stack)。在每个帧中,计算栈就是函数运行的地方,运行的代码大多数是由推入到这个栈中的东西组成的。在栈中操作它们,当函数被返回后,销毁它们。
  • 块栈(block stack)。在每个帧中,块栈被Python用于跟踪某些类型的控制结构,如循环、try/except​块和with ... as ...​块 ,这些控制结构全部被推入到块栈中,当退出这些控制结构式,块栈被销毁,这将帮助Python了解任意给定时刻哪个块是活动的,比如一个continue或者break语句,这些可能影响结果的块。

大多数Python字节码指令操作的是当前调用栈的计算栈,虽然还有些指令可以做其他的事情,比如跳转到指定指令,或者操作块栈。

字节码的阅读

其实还有 代码对象字节码的工作 这两个概念没说,因为本文主要讲怎么阅读字节码,能够通过字节码手搓出py源码(是的,我是个CTFer),想了解更多的可以去本文末的推荐链接里看。

dis模块

因为python代码运行是到字节码再到机器码一气呵成的,我们想要看到中间指令,需要借助python的标准库dis模块,它可以将py代码翻译成字节码。如下:

image

案例

2024网鼎杯青龙组初赛的MISC02题目(1 解),是一个linux内存镜像取证,前面繁琐的步骤略过,最后一步获得一个 flag.txt 文件,里面是python字节码,明显需要我们手搓还原py代码,如下:

 31         226 PUSH_NULL228 LOAD_NAME                8 (key_encode)230 LOAD_NAME                7 (key)232 PRECALL                  1236 CALL                     1246 STORE_NAME               7 (key)32         248 PUSH_NULL250 LOAD_NAME               10 (len)252 LOAD_NAME                7 (key)254 PRECALL                  1258 CALL                     1268 LOAD_CONST               7 (16)270 COMPARE_OP               2 (==)276 POP_JUMP_FORWARD_IF_FALSE    43 (to 364)33         278 PUSH_NULL280 LOAD_NAME                9 (sm4_encode)282 LOAD_NAME                7 (key)284 LOAD_NAME                5 (flag)286 PRECALL                  2290 CALL                     2300 LOAD_METHOD             11 (hex)322 PRECALL                  0326 CALL                     0336 STORE_NAME              12 (encrypted_data)34         338 PUSH_NULL340 LOAD_NAME                6 (print)342 LOAD_NAME               12 (encrypted_data)344 PRECALL                  1348 CALL                     1358 POP_TOP360 LOAD_CONST               2 (None)362 RETURN_VALUE32     >>  364 LOAD_CONST               2 (None)366 RETURN_VALUEDisassembly of <code object key_encode at 0x14e048a00, file "make.py", line 10>:10           0 RESUME                   011           2 LOAD_GLOBAL              1 (NULL + list)14 LOAD_FAST                0 (key)16 PRECALL                  120 CALL                     130 STORE_FAST               1 (magic_key)12          32 LOAD_GLOBAL              3 (NULL + range)44 LOAD_CONST               1 (1)46 LOAD_GLOBAL              5 (NULL + len)58 LOAD_FAST                1 (magic_key)60 PRECALL                  164 CALL                     174 PRECALL                  278 CALL                     288 GET_ITER>>   90 FOR_ITER               105 (to 302)92 STORE_FAST               2 (i)13          94 LOAD_GLOBAL              7 (NULL + str)106 LOAD_GLOBAL              9 (NULL + hex)118 LOAD_GLOBAL             11 (NULL + int)130 LOAD_CONST               2 ('0x')132 LOAD_FAST                1 (magic_key)134 LOAD_FAST                2 (i)136 BINARY_SUBSCR146 BINARY_OP                0 (+)150 LOAD_CONST               3 (16)152 PRECALL                  2156 CALL                     2166 LOAD_GLOBAL             11 (NULL + int)178 LOAD_CONST               2 ('0x')180 LOAD_FAST                1 (magic_key)182 LOAD_FAST                2 (i)184 LOAD_CONST               1 (1)186 BINARY_OP               10 (-)190 BINARY_SUBSCR200 BINARY_OP                0 (+)204 LOAD_CONST               3 (16)206 PRECALL                  2210 CALL                     2220 BINARY_OP               12 (^)224 PRECALL                  1228 CALL                     1238 PRECALL                  1242 CALL                     1252 LOAD_METHOD              6 (replace)274 LOAD_CONST               2 ('0x')276 LOAD_CONST               4 ('')278 PRECALL                  2282 CALL                     2292 LOAD_FAST                1 (magic_key)294 LOAD_FAST                2 (i)296 STORE_SUBSCR300 JUMP_BACKWARD          106 (to 90)15     >>  302 LOAD_GLOBAL              3 (NULL + range)314 LOAD_CONST               5 (0)316 LOAD_GLOBAL              5 (NULL + len)328 LOAD_FAST                0 (key)330 PRECALL                  1334 CALL                     1344 LOAD_CONST               6 (2)346 PRECALL                  3350 CALL                     3360 GET_ITER>>  362 FOR_ITER               105 (to 574)364 STORE_FAST               2 (i)16         366 LOAD_GLOBAL              7 (NULL + str)378 LOAD_GLOBAL              9 (NULL + hex)390 LOAD_GLOBAL             11 (NULL + int)402 LOAD_CONST               2 ('0x')404 LOAD_FAST                1 (magic_key)406 LOAD_FAST                2 (i)408 BINARY_SUBSCR418 BINARY_OP                0 (+)422 LOAD_CONST               3 (16)424 PRECALL                  2428 CALL                     2438 LOAD_GLOBAL             11 (NULL + int)450 LOAD_CONST               2 ('0x')452 LOAD_FAST                1 (magic_key)454 LOAD_FAST                2 (i)456 LOAD_CONST               1 (1)458 BINARY_OP                0 (+)462 BINARY_SUBSCR472 BINARY_OP                0 (+)476 LOAD_CONST               3 (16)478 PRECALL                  2482 CALL                     2492 BINARY_OP               12 (^)496 PRECALL                  1500 CALL                     1510 PRECALL                  1514 CALL                     1524 LOAD_METHOD              6 (replace)546 LOAD_CONST               2 ('0x')548 LOAD_CONST               4 ('')550 PRECALL                  2554 CALL                     2564 LOAD_FAST                1 (magic_key)566 LOAD_FAST                2 (i)568 STORE_SUBSCR572 JUMP_BACKWARD          106 (to 362)18     >>  574 LOAD_CONST               4 ('')576 LOAD_METHOD              7 (join)598 LOAD_FAST                1 (magic_key)600 PRECALL                  1604 CALL                     1614 STORE_FAST               1 (magic_key)19         616 LOAD_GLOBAL             17 (NULL + print)628 LOAD_FAST                1 (magic_key)630 PRECALL                  1634 CALL                     1644 POP_TOP20         646 LOAD_GLOBAL              7 (NULL + str)658 LOAD_GLOBAL              9 (NULL + hex)670 LOAD_GLOBAL             11 (NULL + int)682 LOAD_CONST               2 ('0x')684 LOAD_FAST                1 (magic_key)686 BINARY_OP                0 (+)690 LOAD_CONST               3 (16)692 PRECALL                  2696 CALL                     2706 LOAD_GLOBAL             11 (NULL + int)718 LOAD_CONST               2 ('0x')720 LOAD_FAST                0 (key)722 BINARY_OP                0 (+)726 LOAD_CONST               3 (16)728 PRECALL                  2732 CALL                     2742 BINARY_OP               12 (^)746 PRECALL                  1750 CALL                     1760 PRECALL                  1764 CALL                     1774 LOAD_METHOD              6 (replace)796 LOAD_CONST               2 ('0x')798 LOAD_CONST               4 ('')800 PRECALL                  2804 CALL                     2814 STORE_FAST               3 (wdb_key)21         816 LOAD_GLOBAL             17 (NULL + print)828 LOAD_FAST                3 (wdb_key)830 PRECALL                  1834 CALL                     1844 POP_TOP22         846 LOAD_FAST                3 (wdb_key)848 RETURN_VALUEmagic_key:7a107ecf29325423
encrypted_data:f2c85bd042247896b43345e589e3ad025fba1770e4ac0d274c1f7c2a670830379195aa5547d78bcee7ae649bc3b914da

我们从key_encode​函数源码第11行(字节码第一列是源码行号)开始看:

Disassembly of <code object key_encode at 0x14e048a00, file "make.py", line 10>:10           0 RESUME                   011           2 LOAD_GLOBAL              1 (NULL + list)14 LOAD_FAST                0 (key)16 PRECALL                  120 CALL                     130 STORE_FAST               1 (magic_key)

第十行是定义key_encode​函数,LOAD_GLOBAL​ 加载内置list函数,LOAD_FAST​ 加载key参数,PRECALL​准备调用list函数,参数数量为一,CALL​ 执行函数调用,

STORE_FAST​ 将结果存储在局部变量magic_key中。所以源码就是:

magic_key = list(key)

然后我们看源码第12行:

12          32 LOAD_GLOBAL              3 (NULL + range)44 LOAD_CONST               1 (1)46 LOAD_GLOBAL              5 (NULL + len)58 LOAD_FAST                1 (magic_key)60 PRECALL                  164 CALL                     174 PRECALL                  278 CALL                     288 GET_ITER>>   90 FOR_ITER               105 (to 302)92 STORE_FAST               2 (i)

前四行LOAD_GLOBAL​、LOAD_CONST​、LOAD_FAST​分别将range、1、len、magic_key压入栈中,即加载,第一组PRECALL​和CALL​是调用len计算magic_key的长度,第二组PRECALL​和CALL​是调用range,GET_ITER、FOR_ITER​开始循环,直到地址302结束,STORE_FAST​将栈顶弹出存入变量 i 中。所以源码为:

magic_key = list(key)
for i in range(1,len(magic_key)):

然后我们看源码第13行:

 13          94 LOAD_GLOBAL              7 (NULL + str)106 LOAD_GLOBAL              9 (NULL + hex)118 LOAD_GLOBAL             11 (NULL + int)130 LOAD_CONST               2 ('0x')132 LOAD_FAST                1 (magic_key)134 LOAD_FAST                2 (i)136 BINARY_SUBSCR146 BINARY_OP                0 (+)150 LOAD_CONST               3 (16)152 PRECALL                  2156 CALL                     2166 LOAD_GLOBAL             11 (NULL + int)178 LOAD_CONST               2 ('0x')180 LOAD_FAST                1 (magic_key)182 LOAD_FAST                2 (i)184 LOAD_CONST               1 (1)186 BINARY_OP               10 (-)190 BINARY_SUBSCR200 BINARY_OP                0 (+)204 LOAD_CONST               3 (16)206 PRECALL                  2210 CALL                     2220 BINARY_OP               12 (^)224 PRECALL                  1228 CALL                     1238 PRECALL                  1242 CALL                     1252 LOAD_METHOD              6 (replace)274 LOAD_CONST               2 ('0x')276 LOAD_CONST               4 ('')278 PRECALL                  2282 CALL                     2292 LOAD_FAST                1 (magic_key)294 LOAD_FAST                2 (i)296 STORE_SUBSCR300 JUMP_BACKWARD          106 (to 90)

LOAD_GLOBAL​、LOAD_CONST​、LOAD_FAST​ 分别将全局常量str、hex、int压栈,常量'0x'压栈,变量magic_key和 i 压栈,BINARY_SUBSCR​索引动作,即magic_key[i],BINARY_OP​执行+操作,LOAD_CONST​将常量16压栈,PRECALL​和CALL​执行函数调用,在这里我们先暂停一下,强调一下:因为是不断的面向栈操作,我们还原源码时一定要和进栈的顺序对应上,所以我们此时可以还原int('0x'+magic_key[i],16)​,继续往后看,将int、'0x'、magic_key、i、1压栈,BINARY_OP​执行 - 操作,BINARY_SUBSCR​索引,即magic_key[i-1],LOAD_CONST​将16压栈,PRECALL​和CALL​执行函数调用,此时可以还原 int('0x'+magic_key[i-1],16)​,然后BINARY_OP​执行 ^ 操作,两组PRECALL​和CALL​执行函数调用,此时还原到 str(hex(int('0x'+magic_key[i],16) ^ int('0x'+magic_key[i-1],16)))​,LOAD_METHOD​将方法replace压栈,后面将'0x'和''压栈,然后调用函数,即replace('0x',''),然后将magic_key、i压栈,进行索引存储,所以源码为:

magic_key = list(key)
for i in range(1,len(magic_key)):magic_key[i] = str(hex(int('0x'+magic_key[i],16) ^ int('0x'+magic_key[i-1],16))).replace('0x','')

后面的同理,不再赘叙,第15行、16行 :

magic_key = list(key)
for i in range(1,len(magic_key)):magic_key[i] = str(hex(int('0x'+magic_key[i],16) ^ int('0x'+magic_key[i-1],16))).replace('0x','')for i in range(0,len(key),2):magic_key[i] = str(hex(int('0x'+magic_key[i],16) ^ int('0x'+magic_key[i+1],16))).replace('0x','')

第18行:

magic_key = list(key)
for i in range(1,len(magic_key)):magic_key[i] = str(hex(int('0x'+magic_key[i],16) ^ int('0x'+magic_key[i-1],16))).replace('0x','')for i in range(0,len(key),2):magic_key[i] = str(hex(int('0x'+magic_key[i],16) ^ int('0x'+magic_key[i+1],16))).replace('0x','')magic_key = ''.join(magic_key)

第19行:

magic_key = list(key)
for i in range(1,len(magic_key)):magic_key[i] = str(hex(int('0x'+magic_key[i],16) ^ int('0x'+magic_key[i-1],16))).replace('0x','')for i in range(0,len(key),2):magic_key[i] = str(hex(int('0x'+magic_key[i],16) ^ int('0x'+magic_key[i+1],16))).replace('0x','')magic_key = ''.join(magic_key)
print(magic_key)

第20行:

magic_key = list(key)
for i in range(1,len(magic_key)):magic_key[i] = str(hex(int('0x'+magic_key[i],16) ^ int('0x'+magic_key[i-1],16))).replace('0x','')for i in range(0,len(key),2):magic_key[i] = str(hex(int('0x'+magic_key[i],16) ^ int('0x'+magic_key[i+1],16))).replace('0x','')magic_key = ''.join(magic_key)
print(magic_key)
wdb_key = str(hex(int('0x'+magic_key) ^ int('0x'+key,16))).replace('0x','')

第21行、22行,此时key_encode​函数结束:

def key_encode():magic_key = list(key)for i in range(1,len(magic_key)):magic_key[i] = str(hex(int('0x'+magic_key[i],16) ^ int('0x'+magic_key[i-1],16))).replace('0x','')for i in range(0,len(key),2):magic_key[i] = str(hex(int('0x'+magic_key[i],16) ^ int('0x'+magic_key[i+1],16))).replace('0x','')magic_key = ''.join(magic_key)print(magic_key)wdb_key = str(hex(int('0x'+magic_key) ^ int('0x'+key,16))).replace('0x','')print(wdb_key)return wdb_key

然后我们看31行:

key = key_encode(key)

第32行:

key = key_encode(key)
if len(key) == 16:

第33行:

key = key_encode(key)
if len(key) == 16:encrypted_data = hex(sm4_encode(key,flag))

第34行:

def key_encode(key):magic_key = list(key)for i in range(1,len(magic_key)):magic_key[i] = str(hex(int('0x'+magic_key[i],16) ^ int('0x'+magic_key[i-1],16))).replace('0x','')for i in range(0,len(key),2):magic_key[i] = str(hex(int('0x'+magic_key[i],16) ^ int('0x'+magic_key[i+1],16))).replace('0x','')magic_key = ''.join(magic_key)print(magic_key)wdb_key = str(hex(int('0x'+magic_key) ^ int('0x'+key,16))).replace('0x','')print(wdb_key)return wdb_keykey = key_encode(key)
if len(key) == 16:encrypted_data = hex(sm4_encode(key,flag))print(encrypted_data)

至此我们的源码就搓出来了,题目还给了如下信息:

magic_key:7a107ecf29325423
encrypted_data:f2c85bd042247896b43345e589e3ad025fba1770e4ac0d274c1f7c2a670830379195aa5547d78bcee7ae649bc3b914da

分析可知,我们已知magic_key​和encrypted_data​,encrypted_data​是由flag​经过sm4加密得到的,密钥为key​,所以我们需要知道key​,就可以sm4解密得到flag​,所以我们需要对 key_encode 函数的逻辑进行逆向得到key​,最后exp如下:

def key_encode(key):magic_key = list(key)for i in range(1,len(magic_key)):magic_key[i] = str(hex(int('0x'+magic_key[i],16) ^ int('0x'+magic_key[i-1],16))).replace('0x','')for i in range(0,len(key),2):magic_key[i] = str(hex(int('0x'+magic_key[i],16) ^ int('0x'+magic_key[i+1],16))).replace('0x','')magic_key = ''.join(magic_key)# print(magic_key)wdb_key = str(hex(int('0x'+magic_key,16) ^ int('0x'+key,16))).replace('0x','')# print(wdb_key)return wdb_keymagic_key = list("7a107ecf29325423")for i in range(0,16,2):magic_key[i] = str(hex(int('0x'+magic_key[i],16) ^ int('0x'+magic_key[i+1],16))).replace('0x','')for i in range(len(magic_key)-1,0,-1):magic_key[i] = str(hex(int('0x'+magic_key[i],16) ^ int('0x'+magic_key[i-1],16))).replace('0x','')key = "".join(magic_key)
print(key_encode(key))# 输出:ada1e9136bb16171

然后去赛博厨子解密即可拿到flag:wdflag{815ad4647b0b181b994eb4b731efa8a0}

image

参考链接:

PyCon 2018:James Bennett--理解 Python 字节码 掘金翻译计划

码农高天:字节码和虚拟机?python代码竟然是这么执行的!

王战山的学习笔记:Python中的字节码

python官方文档:dis — Disassembler for Python bytecode

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/825516.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

09-XSS键盘监听、cookie窃取文件上传绕过

1、XSS (1)使用pikachu平台练习XSS键盘记录、前台XSS盲打攻击获取cookie,利用cookie实现登录XSS键盘记录docker打开pikachu靶场,进入pikachu后端修改/var/www/html/pkxss/rkeypress/rk.js文件在存储型XSS模块输入payload(以dvwa靶场测试)监听记录成功获取cookie信息实现登…

2.TiUP 部署 DM 集群

TiUP 是 TiDB 4.0 版本引入的集群运维工具,TiUP DM 是 TiUP 提供的使用 Golang 编写的集群管理组件,通过 TiUP DM 组件就可以进行日常的运维工作,包括部署、启动、关闭、销毁、扩缩容、升级 DM 集群以及管理 DM 集群参数。 安装 TiUP DM 组件: tiup install dm dmctl 生成模…

PNGify:将文本和文件编码为 PNG 图像的命令行工具

原创 Ots安全PNGify 是一款用 Go 编写的命令行工具,可让您将文本和文件编码为 PNG 图像,然后将其解码回原始格式。这种方法融合了文本和图像处理,提供了一种有趣的数据存储和检索方式。这个实验项目的目标是探索创造性的可能性。 安装 确保您的计算机上安装了 Go 编译器,然…

即将到来!

已经到一百三十节课了,努力这周就搞完!

什么是IT技术

IT技术是指应用计算机技术和通信技术来处理和传输信息的一系列技术和方法。它涵盖了多个方面,包括计算机硬件和软件技术、网络技术、数据库技术、信息安全技术等。IT技术的发展使得信息的获取、传递和处理更加迅速和高效,推动了信息社会的快速发展。一、IT技术的定义 IT技术(…

20241101 数据结构与算法期中机试收获

1.一种神奇的打印较多字符(成片的那种)的方法。 2. 机试教训,可以直接输出不管输入来试着骗基础样例分,2分钟拿5/100分还是很不错

如何在step7上使用SCL

如何在经典step7上使用SCL 最近因为工作需求的变化,又回头用上了很多年前的step7。用习惯博图之后再回到step7上确实是一个很逆向的事情,但是没办法,项目需要也就只能遵守这个规则。 东西再老,只要认真去做,也会有新发现。 1. 搭建一个全新的SCL块首先新建项目,选择SCL s…

Eexi6.7 安装Win11

技能点:核心是解决绕过TPM的检查、绕过Win11无网络连接无法设置的问题。熟悉PE引导镜像、熟悉Windows系统启动项(引导方式)、了解Esxi在Web管理端如何配置虚拟机。 1、安装前需要准备的镜像:(1)找PE引导镜像,如:UQi_USBsys_2017.iso,用法自行百度。(2)Win11的系统镜…

【Spring开发】Spring中的IoC和AOP: 核心思想

一、IoC1.1 什么是IoC?1.2 IoC解决了什么问题1.3 IoC和DI的区别二、AOP2.1 什么是AOP?2.2 AOP解决的什么问题2.3 为什么叫面向切面编程核心思想 IoC和AOP不是spring提出来的,在spring之前就已经存在,只不过更偏向理论化,spring在技术层面把这两个思想做了非常好的实现。在…

【供应链安全】2024年我国软件供应链安全代表性厂商推荐:孝道科技

杭州孝道科技有限公司(又称“安全玻璃盒”)成立于2014年,是专注于为用户提供软件供应链安全的国家级高新技术企业、省级专精特新企业。公司坚持科技创新,基于AI模型和卷积神经网络,自主研发了全链路智能动态污点分析、函数级智能基因检测与自动化验证等核心技术与产品。其…

2024-2025-1 20241305 《计算机基础与程序设计》第六周学习总结

作业信息这个作业属于哪个课程 <班级的链接>(如2024-2025-1-计算机基础与程序设计)这个作业要求在哪里 2024-2025-1计算机基础与程序设计第六周作业这个作业的目标 1、Polya如何解决问题 2、简单类型与组合类型 3、复合数据结构 4、查找与排序算法 5、算法复杂度 6、递…

数据采集与融合技术作业3

作业3 我的getee仓库链接 https://gitee.com/LLLzt-III/crawl_project 作业3代码链接 https://gitee.com/LLLzt-III/crawl_project/tree/master/作业3 一、作业①:要求:指定一个网站,爬取这个网站中的所有的图片,例如:中国气象网(http://www.weather.com.cn)。使用scra…