中国DOS联盟论坛

中国DOS联盟

-- 联合DOS 推动DOS 发展DOS --

联盟域名:www.cn-dos.net  论坛域名:www.cn-dos.net/forum
DOS,代表着自由开放与发展,我们努力起来,学习FreeDOS和Linux的自由开放与GNU精神,共同创造和发展美好的自由与GNU GPL世界吧!

游客:  注册 | 登录 | 命令行 | 会员 | 搜索 | 上传 | 帮助 »
中国DOS联盟论坛 » DOS批处理 & 脚本技术(批处理室) » [推荐][Win]闲来无事之-记用BAT实现文件下载功能
作者:
标题: [推荐][Win]闲来无事之-记用BAT实现文件下载功能 上一主题 | 下一主题
willsort
元老会员

Batchinger


积分 4432
发帖 1512
注册 2002-10-18
状态 离线
『楼 主』:  [推荐][Win]闲来无事之-记用BAT实现文件下载功能

转自:网络技术论坛 > 论坛原创及其他 > 原创代码/软件区
http://bbs.nettf.net/forums/index.php?showtopic=14389


闲来无事之--记用BAT(批处理脚本)实现文件下载功能

标题:闲来无事之--记用BAT(批处理脚本)实现文件下载功能
作者:ZV(ZVROP)
邮件:[URL=mailto:zvrop@163.com]zvrop@163.com[/URL]
主页:(被整顿掉老~~~~~>_<~~~~~~)
网站:http://www.s8s8.net
日期:2004-8-18

转载请保全文档完整,谢谢

写的仓卒繁多,错漏难免,还请各位给予斧正.

有什么问题,可以给我发email.^_^...


目录:

一.写在前面的话

二.故事的起因

三.解决-腹稿

四.解决-实战

五.解决-打造

六.包装

七.小节

八.后记

九,参考文献





正文开始:


一.写在前面的话

这篇文档讲述的并不是什么新鲜的技术,我只是起到将他们混和起来实现了自己需要的功能的作用,如果你对批处理和PE格式相当了解,那我的这篇文档就权且当作浏览温习吧...^_^...
另外,由于我本人废话比较多,这也是不想给杂志写稿子的主要原因,给我干瘪瘪的3000字能讲出什么来,不如这样没有限制的爽快(当然也没什么报酬...一_一..),所以也为了防止你在看文章的途中睡着,请自备小锥子一把.....
最后,这篇文档说是用批处理下载文件,其实它包含了很多方面的知识,如果有时间,不妨一看哈,^_^,开始买瓜了..


二.故事的起因

最初萌发这个想法的是不久前,在论坛(广告一下:http://www.s8s8.net)上的UNIX SHELL板块有个会员发了一篇帖子,内容是用BASH SHELL写的一个成批下载图片的脚本(其实是H图片...一_一..),接下来跟贴那个多啊...,有繁衍出PHP的,VBS的,C的,C#的,JAVA的,甚至交流到多线程,断点续传....引用花大哥的一句话"无语,为了MM照片,大家的动力都很足啊!"...汗~~..

在发了一份PHP和C的代码后(感觉我动力特足~大色狼...一_一..),觉得很简单(因为用C或者PHP等脚本来实现文件的下载本来就是很基础的东西)我就开始想用微软最原始的脚本--Batch(批处理)来尝试实现(本文标题中的"闲来无事"就是纪念此处,一_一.),这似乎有点不可思议,因为批处理几乎没有实现的支持网络的功能(当然,如果你说你能用TELNET下载到文件的我是很佩服的..一_一..),但也不是完全没有办法,毕竟WINDOWS里面能用的东西这么多,没有完不成的事情....在这种挑战的勾引下,我完成了用批处理下载文件的功能....现在让我一步一步回放我的思路,揭开用批处理下载文件的奥秘...


三.解决-腹稿

如果用批处理来下载文件的话,肯定会马上想到Cscript脚本(或者是JAVA脚本),那是当然,太多的批处理脚本实现一些本身并不可能实现的功能的时候都是采用ECHO出一个其他脚本的方法来解决.可是我们的目的就在于用批处理实现下载的功能,如果要用VBS来帮忙的话不如直接写VBS了.这个想法顺即告吹....

再来,记得以前有流行过一阵用RUNDLL32来加载DLL中的API,似乎和我们需要的目的沾边,因为下载文件能用的API太多了,如果RUNDLL能调用,那最好不过了.于是我打开MSDN,找了一个API: URLDownloadToFile

URLDownloadToFile函数原型:


CODE

HRESULT URLDownloadToFile(         
   LPUNKNOWN pCaller,
   LPCTSTR szURL,
   LPCTSTR szFileName,
   DWORD dwReserved,
   LPBINDSTATUSCALLBACK lpfnCB
);



URLDownloadToFile函数的一些信息:


QUOTE
Header Urlmon.h
Import library Urlmon.lib
Minimum availability Internet Explorer 3.0
Minimum operating systems Windows NT 4.0, Windows 95



根据这些,我们可以知道,这个API是在URLMON.DLL文件中的一个导出函数,简单的实现了把一个文件从WEB服务器下载本机的功能,其实用这个函数还不错的,至少它帮我们处理了断点续传,缓存等等的功能,比起直接使用SOCKET函数来实现或者用WININET里的函数来实现简单多得多了.

URLDownloadToFile有五个参数:

第一个参数是仅当调用者是一个ActiveX对象才使用,一般为NULL.

第二个参数就是要下载文件的目标URL,完整路径.

第三个是本地保存路径,也是完整路径

第四个是保留,必须为0

第五个是指向一个IBindStatusCallback接口的指针,这就类似一种回调机制,你可以参考这些来活动当前下载进度,选择是否继续下载等等.

这里面我们只关心第二和第三个参数.其他的通通设置成0.(当然你写C的时候最好设置为NULL)

嗯,敲了点键盘介绍了这个函数,是因为整篇的文档都和这个函数息息相关,有了这个函数,就可以呼叫RUNDLL32来调用它,但是很可惜,这个美好的计划马上也破裂了...

我去微软看了他们的164787号文档(http://support.microsoft.com/default.aspx?...kb;en-us;164787),该文档阐述了RUNDLL32的调用方式和能被他调用的函数的格式:

它们是这么说的:


QUOTE
Rundll and Rundll32 programs do not allow you to call any exported function from any DLL. For example, you can not use these utility programs to call the Win32 API (Application Programming Interface) calls exported from the system DLLs. The programs only allow you to call functions from a DLL that are explicitly written to be called by them.



这个是规定的格式:


CODE

void CALLBACK
EntryPoint(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow);



很不幸,我们的URLDownloadToFile小兄弟并没有符合这些条件,被RUNDLL32无情的抛弃了(汗一滴..)...但是我们并没有因此而嫌弃它(汗一滴AGAIN..),毕竟,在后来实现的过程里,它是为我们的工作省下了不少功夫.

到此,用RUNDLL32运行计划流产....(寒...)

想了一根烟功夫,现在URLDownloadToFile有了,怎么才能调用这个函数呢?总不能模仿汇编PUSH 5个参数进栈,然后CALL吧,那这个函数的地址还要用LoadLibrary()和GetProcAddress()计算得来,那这两个函数的地址.....还是放弃...等等,如果用一个EXE来实现的话就简单很多了(至少EXE是不需要任何解释器的),对,写一个EXE来下载文件.可我们的目的是用BAT来下载呢,BAT文件能包裹EXE的数据吗?答案是肯定的...往下看..

记得以前看过一篇文档<<Do All in Cmd Shell>>里面介绍过一种方法.先卖个关子.大家都知道,如果用ECHO加上重定向符来写文件的话,只能写入ASCII的一部分,也就可以显示出来的那些ASCII(也就是ASCII值小于128的那些),对于那些无法显示的字符就没有办法了.但是这让我们想起一个工具,一个微软历史上同样古老的,批处理的兄弟--DEBUG!

现在思路清晰了:可以让批处理把ECHO不能显示的字符转化为16进制数据(比如EXE中的那些数据)保存在批处理中,然后用DEBUG写道文件里,最后用BAT调用生成的EXE,下载文件!(想完了这里,我感觉还是太麻烦,不知道哪位牛人对这个实现还有什么更加简单的办法吗??)


四.解决-实战

倘若就此编写一个可下载文件的EXE,然后直接用BAT包裹,定然会被同行耻笑,不单是因为那几千个字节的数据拖着大大臃肿的BAT文件,更加让为这种简单的想法立刻现形,为了不达到这些负面效果,也为了让这篇文档不至于干瘪瘪的让人感觉没什么看头(事实上是因为早些时候看过watercloud的一篇大作感悟颇深),我决定手工写一串16进制代码来代替机器编译的EXE.既美观了界面,又增强了技术性.....(一_一...简直是在卖作...)

现在当务之急是要一个可以下载文件的EXE程序,实现这个目标只要一个URLDownloadToFile即可,放在最后实现,先来写一个PE框架:大家都知道PE文件的格式吧,不懂的就去看看那个著名的电信黑客罗某某的书.(Who!?...~)

先给出我们的PE框架,基于XP的FileAlignment对齐大小最小就支持到0x200(也就是10进制的512字节,以下有在前面加上0x的都表示16进制数值),我们的框架就打出512字节(注意,我下面留有空白表示各个PE部分,结合下面的文档,大家方便理解),这个框架里没有任何的代码或者数据:

(ZV友情提示:下面是最枯燥的部分,各位手握锥子,要有一不怕苦,二不怕痛的精神看完它....)
(如果定力不高的朋友,或者堆PE文件再熟悉不过的朋友,可以字节转到"JMP S1"处往下看.)
(如果只想知道到底怎么回事,或者对这篇作文报浏览态度的朋友,可以直接转到"JMP S2"处继续浏览)
(睡着了的继续睡觉....)


CODE

Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F

00000000   4D 5A 00 00 00 00 00 00  00 00 00 00 00 00 00 00   MZ..............
00000010   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000020   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000030   00 00 00 00 00 00 00 00  00 00 00 00 40 00 00 00   ............@...

==============================================================================

00000040   50 45 00 00 4C 01 02 00  00 00 00 00 00 00 00 00   PE..L...........
00000050   00 00 00 00 70 00 0F 01  

                                    0B 01 00 00 00 02 00 00   ....p...........
00000060   00 00 00 00 00 00 00 00  79 01 00 00 00 00 00 00   ........y.......
00000070   00 00 00 00 00 00 40 00  00 10 00 00 00 02 00 00   ......@.........
00000080   00 00 00 00 00 00 00 00  04 00 00 00 00 00 00 00   ................
00000090   00 30 00 00 00 02 00 00  00 00 00 00 02 00 00 00   .0..............
000000A0   00 01 00 00 00 00 00 00  00 01 00 00 00 10 00 00   ................
000000B0   00 00 00 00 02 00 00 00  

                                    00 00 00 00 00 00 00 00   ................
000000C0   28 11 00 00 28 00 00 00  

==============================================================================

                                    00 00 00 00 00 00 00 00   (...(...........
000000D0   00 02 00 00 00 10 00 00  00 02 00 00 00 01 00 00   ................
000000E0   00 00 00 00 00 00 00 00  00 00 00 00 60 00 00 60   ............`..`
000000F0   00 00 00 00 00 00 00 00  02 00 00 00 00 20 00 00   ............. ..
00000100   00 02 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000110   00 00 00 00 60 00 00 60  00 00 00 00 00 00 00 00   ....`..`........


00000120   58 11 00 00 00 00 00 00  50 11 00 00 00 00 00 00   X.......P.......
00000130   00 00 00 00 6E 11 00 00  20 11 00 00 00 00 00 00   ....n... .......
00000140   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000150   58 11 00 00 00 00 00 00

                                    00 00 00 00 00 00 00 00   ................
00000160   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000170   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000180   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000190   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000001A0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000001B0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000001C0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000001D0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000001E0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000001E0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................



这里简单介绍一下PE文件格式的组成:

大致来分呢,PE格式文件可以分为这三个部分(就是上述框架中用"=="分割的三个部分):


QUOTE
++++++++++++++++++++++++
+DOS信息部分          +
++++++++++++++++++++++++

++++++++++++++++++++++++
+PE信息部分            +
++++++++++++++++++++++++

++++++++++++++++++++++++
+数据部分              +
++++++++++++++++++++++++




下面来简单介绍每一部分的结构,首先的"DOS信息部分":


QUOTE
+++++++++++++++++++++++++++++++++++++++++++++
+  +++++++++++++++++++++++++++++++++++++++  +
+  +[DOS文件头][0x40]                    +  +
+  +++++++++++++++++++++++++++++++++++++++  +
+                                          + <==DOS信息部分
+  +++++++++++++++++++++++++++++++++++++++  +
+  +[DOS块][0x70,可变]                  +  +
+  +++++++++++++++++++++++++++++++++++++++  +
+++++++++++++++++++++++++++++++++++++++++++++



这部分我觉得是最冗余的地方,首先DOS文件头的结构:


CODE

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
WORD   e_magic;                       // Magic number
WORD   e_cblp;                        // Bytes on last page of file
WORD   e_cp;                          // Pages in file
WORD   e_crlc;                        // Relocations
WORD   e_cparhdr;                     // Size of header in paragraphs
WORD   e_minalloc;                    // Minimum extra paragraphs needed
WORD   e_maxalloc;                    // Maximum extra paragraphs needed
WORD   e_ss;                          // Initial (relative) SS value
WORD   e_sp;                          // Initial SP value
WORD   e_csum;                        // Checksum
WORD   e_ip;                          // Initial IP value
WORD   e_cs;                          // Initial (relative) CS value
WORD   e_lfarlc;                      // File address of relocation table
WORD   e_ovno;                        // Overlay number
WORD   e_res[4];                      // Reserved words
WORD   e_oemid;                       // OEM identifier (for e_oeminfo)
WORD   e_oeminfo;                     // OEM information; e_oemid specific
WORD   e_res2[10];                    // Reserved words
LONG   e_lfanew;                      // File address of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;



其中最重要的就是e_lfanew,它指向了下面的"PE信息部分"的起始地址(也就是俗称的PE头部).其他的是一些DOS下运行这个PE文件必须的结构,比如看注解就明白,什么代码初始化堆栈段,初始化堆栈指针,入口IP,CS等等,都是在WIN32上没有用的东西,我就不翻译拉,这些都是说DOS下的,如果这个PE文件一开始就打定在WINDOWS下运行,这些乱写都无所谓,你甚至可以把你的名字都写进去(.....一_一..).当然,你这么作后这个文件就不能在DOS下运行了..不然当机是几乎可以肯定的....(寒....).

需要记的除了e_lfanew是指向PE头的指针外还要记得这个DOS文件头结构长0x40,也就是64个字节.还有第一个参数e_magic,这个地方永远是"0x40 0x5a",也就是字符的"MZ".

DOS块部分保存的就是一段DOS下可以执行的代码,比如现在大多编译器就简单的输出一个"This program cannot be run in DOS mode"的字符串,和"DOS信息部分"一样,如果你不打算在DOS执行这个EXE文件,那么这里完全可以删除,为什么?因为WIN32的PE装载器只关心"DOS信息部分"的e_lfanew指向的而已.

综上所述,"DOS信息部分"对应框架的代码为:


CODE

Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F

00000000   4D 5A 00 5B D5 E2 C0 EF  B6 BC C3 BB D3 C3 2C 2C   MZ.[这里都没用,,
00000010   B1 C8 C8 E7 CE D2 D0 B4  3A CE D2 D2 B2 D6 BB 2C   比如我写:我也只,
00000020   CA C7 D2 BB B0 E3 CB A7  2C B2 BB CA C7 CC D8 2C   是一般帅,不是特,8
00000030   B1 F0 CB A7 B5 C4 C0 B2  5D 00 00 00 40 00 00 00   别帅的啦]...@...



可以看到最后的4个字节"40000000"也就是00000040H(下面如果直接在数值后加"H"的即表示为16进制)是指向他末尾的指针,也就是说明,我们把"DOS块"的部分给去掉了.

接下来是"PE信息部分",他的结构可以用下面的图来表示:


QUOTE
+++++++++++++++++++++++++++++++++++++++++++++
+  +++++++++++++++++++++++++++++++++++++++  +
+  +[PE标志][0x04]                      +  +
+  +++++++++++++++++++++++++++++++++++++++  +
+                                          + <==PE信息部分
+  +++++++++++++++++++++++++++++++++++++++  +
+  +[PE文件头][0x18]                    +  +
+  +++++++++++++++++++++++++++++++++++++++  +
+                                          +
+  +++++++++++++++++++++++++++++++++++++++  +
+  +[自定义数据结构][0x0e]              +  +
+  +++++++++++++++++++++++++++++++++++++++  +
+++++++++++++++++++++++++++++++++++++++++++++



整个"PE信息部分"结构是这样的:


CODE

typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;                      //"PE标志"段,总是"PE00"
IMAGE_FILE_HEADER FileHeader;         //"PE文件头"段,指向IMAGE_FILE_HEADER结构
IMAGE_OPTIONAL_HEADER OptionalHeader; //"自定义数据"段,指向IMAGE_OPTIONAL_HEADER结构
} IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;



IMAGE_FILE_HEADER结构(PE文件头)和IMAGE_OPTIONAL_HEADER结构如下:


CODE

typedef struct _IMAGE_FILE_HEADER {
WORD Machine;                         //运行平台,386的话是104CH
WORD NumberOfSections;                //文件节数目,最少为2
DWORD TimeDateStamp;                  //文件创建时间,随便设置(不过为了最后生成方便,随便设置的地方最好都设置为0)
DWORD PointerToSymbolTable;           //这里两项记用于调试,也随便设置
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader;            //下面那个IMAGE_OPTIONAL_HEADER结构的长度,一般为000EH(包括16个IMAGE_DATA_DIRECTORY结构),我们只要2个结构,所以设置为0070H
WORD Characteristics;                 //文件属性,PE文件是010H,DLL的话是210H
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;



IMAGE_FILE_HEADER说明了PE文件的基本运行信息,可是光靠这短短的结构并不能满足我们的需要,毕竟微软设计的东西还是考虑的很周全的,于是在它下面跟上了一个长长的结构(OptionHeader结构)来作为附加信息供给系统.


CODE

OptionHeader结构(自定义数据结构)定义如下:
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD    Magic;                        //EXE文件的话这里是10B
BYTE    MajorLinkerVersion;           //连接器版本,随便
BYTE    MinorLinkerVersion;
DWORD   SizeOfCode;                   //所有代码节总大小,我们就一个节,所以是512,也就是200H
DWORD   SizeOfInitializedData;        //....未初始化数据节....没有这个,设置为0
DWORD   SizeOfUninitializedData;      //....已................................
DWORD   AddressOfEntryPoint;          //代码执行起始地址,注意,这个是你代码存放的位置,[这里注意点1]
DWORD   BaseOfCode;                   //代码段......(这里三个都是内存地址),这里是0
DWORD   BaseOfData;                   //数据段......(并非硬盘文件地址),这里是0
DWORD   ImageBase;                    //建议加载位置,通常是00400000H,9X的系统可能略小于这个值,记不得了..:(
DWORD   SectionAlignment;             //内存中对齐大小,一般为1000H,也就是NT的一个内存片,4KB
DWORD   FileAlignment;                //文件..........,这里设置最小的,200H,兼容全部系统
WORD    MajorOperatingSystemVersion;  //一下几个都是系统版本相关的,随便设置
WORD    MinorOperatingSystemVersion;
WORD    MajorImageVersion;
WORD    MinorImageVersion;
WORD    MajorSubsystemVersion;        //这里要设置为04H
WORD    MinorSubsystemVersion;
DWORD   Win32VersionValue;            //未用
DWORD   SizeOfImage;                  //PE文件占用的内存空间,我们设置为3000H
DWORD   SizeOfHeaders;                //PE文件头大小(含节表),这里是200H
DWORD   CheckSum;                     //效验和(我不知道用来干嘛,PE几乎都是000000000,可能和其他方面有关,比如调试?)
WORD    Subsystem;                    //文件子系统,子系统的含义大家可以去参考NT内核,这里设置为02,03均可(控制台和窗口子系统)
WORD    DllCharacteristics;           
DWORD   SizeOfStackReserve;           //一下几个是有关堆和栈的设置,基本上随便,不过最好设置够用就行(不是0啊!)
DWORD   SizeOfStackCommit;
DWORD   SizeOfHeapReserve;
DWORD   SizeOfHeapCommit;
DWORD   LoaderFlags;                  //未用
DWORD   NumberOfRvaAndSizes;          //下面的IMAGE_DATA_DIRECTORY结构的数量,原来是16个,最少为2个
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;



有了这个IMAGE_OPTIONAL_HEADER结构,PE文件的作用和包含了什么资源都一目了然了.

IMAGE_DATA_DIRECTORY结构如下,PE文件中包含了很多数据类型,比如导出,导入函数,资源,重定位,调试和版权信息等等,这个结构最多可以有16个,就是用来定位这些数据的:


CODE

typedef struct _IMAGE_DATA_DIRECTORY {
DWORD   VirtualAddress;        
DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;



IMAGE_DATA_DIRECTORY结构就是指出了你每个数据类型的在内存中的装载位置和长度.注意,这个结构和下面要说道的节表不同,虽然他们可能指向的是同一个地址,但是,IMAGE_DATA_DIRECTORY区分的是严格的数据类型,而节表只是根据人为的定义来划分数据的种类,如果是正常的EXE,通常把各个数据种类分开存放,而这些数据通常又和数据类型用一样的方法分类,所以IMAGE_DATA_DIRECTORY结构和节表指向的地址可能是一样的,但是本文这篇例子不同,因为我们手写的PE必须尽可能的小,所以我吧几个节表的数据全部放在了一个节,这样,节表就只有一个,而IMAGE_DATA_DIRECTORY结构要从混和的数据中指向正确的数据类型地址,就和节表指向的不一样了.

综上所述,"PE信息部分"对应框架的代码为:


CODE

Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F

00000040   50 45 00 00 4C 01 02 00  00 00 00 00 00 00 00 00   PE..L...........
00000050   00 00 00 00 70 00 0F 01  

                                    0B 01 00 00 00 02 00 00   ....p...........
00000060   00 00 00 00 00 00 00 00  79 01 00 00 00 00 00 00   ........y.......
00000070   00 00 00 00 00 00 40 00  00 10 00 00 00 02 00 00   ......@.........
00000080   00 00 00 00 00 00 00 00  04 00 00 00 00 00 00 00   ................
00000090   00 30 00 00 00 02 00 00  00 00 00 00 02 00 00 00   .0..............
000000A0   00 01 00 00 00 00 00 00  00 01 00 00 00 10 00 00   ................
000000B0   00 00 00 00 02 00 00 00  

                                    00 00 00 00 00 00 00 00   ................
000000C0   28 11 00 00 28 00 00 00  



这上面的数据大多都解释过了,这里要看地址"000000C0"处的"28 11 00 00 28 00 00 00",这个是IMAGE_DATA_DIRECTORY结构的第二个,也就是导入表的地址,"00 00 00 28"这个是长度,不比多说,"00 00 11 28"这个又为何?带着这个问题看下去...[这里算作注意点2]


最后要介绍的是"数据部分":


QUOTE
+++++++++++++++++++++++++++++++++++++++++++++
+  +++++++++++++++++++++++++++++++++++++++  +
+  +[数据节表][0x24*N+1]                +  +
+  +++++++++++++++++++++++++++++++++++++++  +
+                                          + <==PE数据部分
+  +++++++++++++++++++++++++++++++++++++++  +
+  +[数据节][不定]                      +  +
+  +++++++++++++++++++++++++++++++++++++++  +
+++++++++++++++++++++++++++++++++++++++++++++



其中IMAGE_SECTION_HEADER结构(数据节表)如下:


CODE

typedef struct _IMAGE_SECTION_HEADER {
BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];//这个8字节的空间就是给你来定义这个节的名称,比如大家常见的".text .data .code"等等,我这里为了以后的填充方便,设置了空白..(00000000H),其实这里是可以随便写的,比如你定义".zvrop"也可以
union {
     DWORD   PhysicalAddress;          //这是个联合结构,说明了该节的大小,我们整个PE文件就是一个节,所以是200H
     DWORD   VirtualSize;
} Misc;
DWORD   VirtualAddress;               //定位该节在内存中的地址(相对于加载位置的偏移地址)我们这里是先不说这些.[这里算作注意点3]
DWORD   SizeOfRawData;                //文件中的尺寸,这里和上面的联合结构不同,这里是对齐后的地址,我们设置为200H
DWORD   PointerToRawData;             //该节在文件中的位置,相对于文件头,这里可以随便设置,不过设置了后面的代码指针也要跟着变动,我们这里设置100H
DWORD   PointerToRelocations;         //下面四个是给连接器用的参数,随便
DWORD   PointerToLinenumbers;
WORD    NumberOfRelocations;
WORD    NumberOfLinenumbers;
DWORD   Characteristics;              //节的属性,自己区查表,基于篇幅,这张表我就不提供了,需要的可以PM我,一般代码节为60000020H(40000000&2000000&00000020),即是可执行,可读的代码段,我们设置为60000060H,因为我们既包含了数据又包含了代码.
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;



可以看到这个结构的数量是不定的,也就是你下面有多少个节,就有多少个IMAGE_SECTION_HEADER+1的结构,因为系统需要一个全0的IMAGE_SECTION_HEADER结构来标识已经结束.另外,XP最少要两个IMAGE_SECTION_HEADER结构,,不然会报非法32位程序的(这个熟悉的提示我在完成这个东西的时候不知道出现了NN次,从此深恶痛绝!),2K则没有这个限制(引自watercloud的研究,我没多少时间去深挖这个哈...).

下面就是具体的"数据节"的内容了(我们这篇文档整个PE文件就是一个节),整个PE文件结构内容大概就是这么多.


综上所述,"数据部分"对应框架的代码为:


CODE

Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F

                                    00 00 00 00 00 00 00 00   (...(...........
000000D0   00 02 00 00 00 10 00 00  00 02 00 00 00 01 00 00   ................
000000E0   00 00 00 00 00 00 00 00  00 00 00 00 60 00 00 60   ............`..`

000000F0   00 00 00 00 00 00 00 00  02 00 00 00 00 20 00 00   ............. ..
00000100   00 02 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000110   00 00 00 00 60 00 00 60  00 00 00 00 00 00 00 00   ....`..`........




[THIS IS JMP S1]

绕了这么大个圈子才回到正题....(一_一....其实我是想把问题写的详细,这样大家看了就没有态度的疑虑嘛..),还记得上面说过的数据类型吗,其中最重要的就是导入表,我们的URLDownloadToFile小朋友已经在板凳上坐了很久了.......这个导入表就是为他量身定做的.我们的目的就是让PE文件执行URLDownloadToFile的功能,自然得把URLDownloadToFile这个函数加入导入表.

说道导入表的定义呢?就不得不先说说WINDOWS加载可执行程序时候对IAT(IMPORT ADDRESS TABLE,导入地址表)的修改,我们知道,各个系统的每个函数在内存中的位置都是不同的(至少2K,XP,2003基本上都不一样),所以才有很多写人SHELLCODE的时候,位置计算个半天..这样来说的话,在我们编译EXE的时候就不可能确定某个函数的地址.要执行这个函数,必须找到他的入口地址,而这个地址就由系统在加载PE文件的时候帮你"填空",这动态的完成函数地址的填充也就是"动态连接"这个名词的由来.

现在我简单的模拟一下系统转载PE文件并给出函数地址的步骤,首先,我们给出一个PE文件中的导入表:


CODE

Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F

00000120   58 11 00 00 00 00 00 00  
                                    50 11 00 00 00 00 00 00   X.......P.......
00000130   00 00 00 00 6E 11 00 00  20 11 00 00

                                                00 00 00 00   ....n... .......
00000140   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................

00000150   58 11 00 00 00 00 00 00




可以看到,这个表被分为四个部分,其中中间两个等长为0x14的两段就是导入表中的IMAGE_IMPORT_DESCRIPTOR结构,该结构如下:


CODE

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
   union {
       DWORD   Characteristics;            
       DWORD   OriginalFirstThunk;             //指向一个"函数列表的指针结构".
   };
   DWORD   TimeDateStamp;                      //暂时可以看作没用,0
   DWORD   ForwarderChain;                     //暂时可以看作没用,0
   DWORD   Name;                               //指向一个DLL,这个结构里面的函数必须都是这个DLL里面的
   DWORD   FirstThunk;                         //指向一个IAT表,最后操作系统修改的就是这个
} IMAGE_IMPORT_DESCRIPTOR;



注意,该结构也必须有N+1个,因为我们只需要一个函数"URLDownloadToFile",所以我们只有这一个结构,第二个结构是全0的.表示结束.

这个"函数列表指针结构"就是IMAGE_THUNK_DATA32结构:


CODE

typedef struct _IMAGE_THUNK_DATA32 {
   union {
       PBYTE   ForwarderString;
       PDWORD  Function;
       DWORD  Ordinal;
       PIMAGE_IMPORT_BY_NAME  AddressOfData;
   } u1;
} IMAGE_THUNK_DATA32;



他只有一个双字类型的值,这个值如果是1XXXXXXXH的,那么说明该函数是一序号方式导入的,序号就是除了1外的剩下的7位,如果是0XXXXXXXH的,那么这个除了0外的7位就是作为一个虚拟地址指向这个函数的名字.

关于什么是序号导入什么是名字导入,我就不说了,这些涉及到导出表的概念.本文不需要.

假设我是WINDOWS操作系统的PE装载器,我从这个PE文件格式的某些参数中定位到了这个00000128H的地址是导入表地址,现在我的目的是要把"58 11 00 00"这个地址替换为正确的函数地址(注意,是00000120H处的,00000150H处的那个"58 11 00 00"是给系统提供"URLDownloadToFile"这个字符串位置的指针,这个地址不会变动,会变的是00000120H处的"58 11 00 00",其实00000120H处的"58 11 00 00"可以随便设置的.).

我开始定位到了:


CODE

Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F

                                    50 11 00 00 00 00 00 00   X.......P.......
00000130   00 00 00 00 6E 11 00 00  20 11 00 00



的地方,发现这个函数的位置是"50 11 00 00",相关的DLL是"6E 11 00 00",于是我找到PE文件的这个位置(是内存中的相对位置),发现"50 11 00 00"位置处的IMAGE_THUNK_DATA32结构的值是"58 11 00 00",这个值不是1开头的,于是我用这个值作为地址查找,发现这个值指向的位置的内容是"31 00 URLDownloadToFile",除去前面的两个序号,找到了这个函数的名称,接下来我根据在"6E 11 00 00"位置找到的字符串"URLMON.DLL",用LoadLibrary()和GetProcAddress()找到了函数"URLDownloadToFile"在内存中的位置,假设是"XX XX XX XX",然后把"XX XX XX XX",填入到"20 11 00 00"指向的位置中...完毕.

这样来说,大家就明白了,URLDownloadToFile这个函数的存放位置应该根据"50 11 00 00"(确切的说应该是"50 11 00 00"指向的位置的指针)和"6E 11 00 00"来确定(确定这个函数存在的DLL).


五.解决-打造

[THIS IS JMP S2]

现在我们再回头整理一下整个过程...结合这张表:


CODE

Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F

00000000   4D 5A 5B 00 00 00 00 00  00 00 00 00 00 00 00 00   MZ[.............
00000010   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000020   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000030   00 00 00 00 00 00 00 00  00 00 00 5D 40 00 00 00   ...........]@...
00000040   50 45 00 00 4C 01 02 00  00 00 00 00 00 00 00 00   PE..L...........
00000050   00 00 00 00 70 00 0F 01  0B 01 00 00 00 02 00 00   ....p...........
00000060   00 00 00 00 00 00 00 00  79 01 00 00 00 00 00 00   ........y.......
00000070   00 00 00 00 00 00 40 00  00 10 00 00 00 02 00 00   ......@.........
00000080   00 00 00 00 00 00 00 00  04 00 00 00 00 00 00 00   ................
00000090   00 30 00 00 00 02 00 00  00 00 00 00 02 00 00 00   .0..............
000000A0   00 01 00 00 00 00 00 00  00 01 00 00 00 10 00 00   ................
000000B0   00 00 00 00 02 00 00 00  00 00 00 00 00 00 00 00   ................
000000C0   28 11 00 00 28 00 00 00  00 00 00 00 00 00 00 00   (...(...........
000000D0   00 02 00 00 00 10 00 00  00 02 00 00 00 01 00 00   ................
000000E0   00 00 00 00 00 00 00 00  00 00 00 00 60 00 00 60   ............`..`
000000F0   00 00 00 00 00 00 00 00  02 00 00 00 00 20 00 00   ............. ..
00000100   00 02 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000110   00 00 00 00 60 00 00 60  00 00 00 00 00 00 00 00   ....`..`........
00000120   58 11 00 00 00 00 00 00  50 11 00 00 00 00 00 00   X.......P.......
00000130   00 00 00 00 6E 11 00 00  20 11 00 00 00 00 00 00   ....n... .......
00000140   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000150   58 11 00 00 00 00 00 00  5B 00 00 00 00 00 00 00   ........[.......
00000160   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000170   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000180   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000190   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000001A0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000001B0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000001C0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000001D0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000001E0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000001E0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 5D   ...............]



这张PE文件数据图就是一个很大的"填空",除去重要的数据部分,我们可以随便写入数据的地方有2个(也就是两个大挂号挂起来的中间).

第一个是从地址00000002开始的,到地址0000003B结束的56字节.

第二个是从地址00000160开始的,到PE文件结尾的160字节.(也可以从000000158开始,这样就有168字节)

因为我们的程序很短,所以第二个168字节基本上可以满足要求全部,就不需要第一个56字节的数据了.把数据和在一起也方便呢,不是么?^_^.

那这些地方具体填写些什么东西呢?大致来说分为三个部分:

1.导入表,包括"URLDownloadToFile"这个函数的字符串和"URLMON.DLL"这个DLL的字符串.

2.文件的可执行机器码.

3.函数需要的数据.



首先是导入表,根据上一节说的那些,我们可以很容易的判断出这个"URLDownloadToFile"该填在"58 11 00 00"的位置.当然你可以改这个值,这个值只是我写的.总之你想吧这个导入表放在什么位置,这个"58 11 00 00"就要指向这个位置.于是我们在PE文件的00000158位置写入"31 00 URLDownloadToFile"字符串,前面两个16进制是序号是给转载器提供信息作为在DLL中导出地址的依据.

(对了,这里说明一个问题,这篇文档也注释了很多"注意点",为什么呢,仔细看看这些注意点,发现都是和位置有关的,那是因为:PE文件中的绝大多数的地址,都是采用文件加载后内存中的地址的,这样一方面加快了加载速度,另外一方面也省了不少加载器的工作,比如这个"58 11 00 00"的地址,因为我们加载的位置是1000H,所以根据这个位置,我们在文件中的位置就是158H,这里要申明的一点,并不是所有的地址都可以这么计算的,因为我们在PointerToRawData那里设置了100H,为的就是这样方便的计算相对地址,对于其他的PE文件,如果要根据这种内存地址计算出PE文件地址,还不是这么简单是事情,^_^..当然,网络上也有很多这种转换函数,RVA到OFFSET的)

然后把URLMON.DLL这个字符串填入"6E 11 00 00"指向的地址,当然这个值也是可以变的.

最后,我们要用笔记录一下最后这个函数被导出的地址的存放处,也就是"20 11 00 00".

[注意,以上的这些操作都和IMAGE_IMPORT_DESCRIPTOR结构和IMAGE_THUNK_DATA32结构相关,看不明白的多看看这两个结构]


接下来是可执行码.我们的目的很简单,只要这个PE文件能下载文件就行,所以我们只要调用URLDownloadToFile函数即可,写一小段汇编码(还记得前面说过的URLDownloadToFile的调用方法吗,花了点笔墨的那个):


CODE

PUSH 0                 ;6A 00
PUSH 0                 ;6A 00
PUSH XXXXXXXX          ;68 XXXXXXXX
PUSH XXXXXXXX          ;68 XXXXXXXX
PUSH 0                 ;6A 00
CALL XXXXXXXX          ;E8 XXXXXXXX



由于函数的调用是符合PASCAL调用,也就是STDCALL,自右向左压栈,所以我们的参数也是最后一个先入栈.最后CALL出这个URLDownloadToFile函数.

前两个XXXXXXXX地址是两个字符串的地址,也就是URLDownloadToFile函数的两个重要参数,最后一个XXXXXXXX是这个函数在内存中的地址(操作系统已经帮我们填充了,还记得上面说的那个用笔记录的"20 11 00 00"么?)

主要的代码就是这么多,可是不幸的事情发生了,当我用WINHEX把这些代码填入PE框架并且保存的时候,居然被杀毒软件删除了!!!!他们把这个看作病毒????想来写病毒原来是这么容易的事情(.....一_一.)....

幸好有备份(如果没有,我可是要哭死了.....),我修改了这些代码,加入了一些垃圾(比如MOV EAX,1之列的)...最终的成品代码是:


CODE

B8 01000000    ;mov eax,1
6A 00          ;push 0
6A 00          ;push 0
68 D0114000    ;push D0114000 ;指向你保存的本地路径字符串的位置,本文中是"c:\\gl123\\00204.jpg",注意是双杠.
68 A0114000    ;push A0114000 ;指向要下载的URL字符串保存的位置
6A 00          ;push 0
E8 02000000    ;call 02000000 ;也就是呼叫下两个字节的地址,这是机器中调用函数的通常做法
C9             ;leave
C3             ;ret
FF25 20114000  ;jmp 20114000  ;这个跳转地址就是"20 11 00 00",至于那个"40",
                              ;就是程序的建议起始加载地址"00400000".另外,这里是仿机器格式.
00
00
00
00



将他们写入哪里呢?这个就随便你了,不过请翻翻上面说的,有个地址是(也就是注意1所在的位置)AddressOfEntryPoint:这个就是用来定位你代码的执行入口的,我们就放在导入表的后面,也就是"00000179H"的位置.


最后就是那两个字符串的地址了,我们在程序中已经给出


CODE

68 D0114000
68 A0114000



那这两个字符串的位置就确定了,一个是"000001D0H",我们要下载的文件地址"http://www.sergeaura.net/TGP/002/images/04.jpg"就是放在这里,另外一个是"0000010A"的位置,我们要保存到的本地路径"C:\\GL123\\00204.JPG"就是保存到这里..这里我每个分配了48字节存储区域,大家也可以根据具体需要设置.别忘了还有dos头部可以保存56字节的空白可以写数据,如果需要的话,修改指向就是.

对于上面的这一堆废话,我的目的是想让大家明白,而故意介绍的格式,即是说,如果让你换做其他的API函数也能轻易的调用,而不是局限于URLDownloadToFile.^_^...比如那些...那些...功能啊....(我可没说啊...嘿嘿)..


OK,这个PE文件最后的成形PE框架是这样的:


CODE

Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F

00000000   4D 5A 00 00 00 00 00 00  00 00 00 00 00 00 00 00   MZ..............
00000010   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000020   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000030   00 00 00 00 00 00 00 00  00 00 00 00 40 00 00 00   ............@...
00000040   50 45 00 00 4C 01 02 00  00 00 00 00 00 00 00 00   PE..L...........
00000050   00 00 00 00 70 00 0F 01  0B 01 00 00 00 02 00 00   ....p...........
00000060   00 00 00 00 00 00 00 00  79 01 00 00 00 00 00 00   ........y.......
00000070   00 00 00 00 00 00 40 00  00 10 00 00 00 02 00 00   ......@.........
00000080   00 00 00 00 00 00 00 00  04 00 00 00 00 00 00 00   ................
00000090   00 30 00 00 00 02 00 00  00 00 00 00 02 00 00 00   .0..............
000000A0   00 01 00 00 00 00 00 00  00 01 00 00 00 10 00 00   ................
000000B0   00 00 00 00 02 00 00 00  00 00 00 00 00 00 00 00   ................
000000C0   28 11 00 00 28 00 00 00  00 00 00 00 00 00 00 00   (...(...........
000000D0   00 02 00 00 00 10 00 00  00 02 00 00 00 01 00 00   ................
000000E0   00 00 00 00 00 00 00 00  00 00 00 00 60 00 00 60   ............`..`
000000F0   00 00 00 00 00 00 00 00  02 00 00 00 00 20 00 00   ............. ..
00000100   00 02 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000110   00 00 00 00 60 00 00 60  00 00 00 00 00 00 00 00   ....`..`........
00000120   58 11 00 00 00 00 00 00  50 11 00 00 00 00 00 00   X.......P.......
00000130   00 00 00 00 6E 11 00 00  20 11 00 00 00 00 00 00   ....n... .......
00000140   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000150   58 11 00 00 00 00 00 00  31 00 55 52 4C 44 6F 77   X.......1.URLDow
00000160   6E 6C 6F 61 64 54 6F 46  69 6C 65 41 00 00 75 72   nloadToFileA..ur
00000170   6C 6D 6F 6E 2E 64 6C 6C  00 B8 01 00 00 00 6A 00   lmon.dll.?...j.
00000180   6A 00 68 D0 11 40 00 68  A0 11 40 00 6A 00 E8 02   j.h?@.h?@.j.?
00000190   00 00 00 C9 C3 FF 25 20  11 40 00 00 00 00 00 00   ...擅% .@......
000001A0   68 74 74 70 3A 2F 2F 77  77 77 2E 73 65 72 67 65   http://www.serge
000001B0   61 75 72 61 2E 6E 65 74  2F 54 47 50 2F 30 30 32   aura.net/TGP/002
000001C0   2F 69 6D 61 67 65 73 2F  30 34 2E 6A 70 67 00 00   /images/04.jpg..
000001D0   43 3A 5C 5C 47 4C 31 32  33 5C 5C 30 30 32 30 34   C:\\GL123\\00204
000001E0   2E 4A 50 47 00 00 00 00  00 00 00 00 00 00 00 00   .JPG............
000001F0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................





简单的运行一下这个PE文件,图片已经被下载到C盘的GL123文件夹,说明我们的工作还是成功的哈.(哇,好sex的MM啊,口水流啊流.....)

六.包装

到这里开始,我们的EXE是有了,现在开始DEBUG出场,我们的计划是用E命令写入整个PE文件数据,然后用W命令保存到临时文件中,于是就成就了这个原始BAT文件:


CODE

;echo off
;DEBUG<%~s0>nul2>nul
;GOTO BEGIN
E 100 4D 5A 00 00 00 00 00 00 00 00 00 00 00 00 00 00
E 110 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
......
......这里省略若干
......
RCX
200
N E:\tmp\tmp99.TMP
W
Q
:BEGIN
rename E:\tmp\tmp99.TMP tmp99.EXE>nul2>nul
call E:\tmp\tmp99.EXE
del E:\tmp\tmp99.EXE>nul2>nul




不过这样很不美观...于是我又想了一个办法优化,用F命令向填充512个00,然后再在相对位置写入需要的数据,于是乎就生成了下载bat脚本的bate1版本,这个是完整的批处理文件了:


CODE

;ECHO OFF
;DEBUG<%~s0>nul2>nul
;GOTO BEGIN
E 100 4D 5A
F 102 2FF 00
E 13C 40 00 00 00 50 45 00 00 4C 01 02
E 154 70 00 0F 01 0B 01 00 00 00 02
E 168 79 01
E 176 40 00 00 10 00 00 00 02
E 188 04 00 00 00 00 00 00 00 00 30 00 00 00 02
E 19C 02 00 00 00 00 01
E 1A9 01 00 00 00 10 00 00 00 00 00 00 02
E 1C0 28 11 00 00 28
E 1D1 02 00 00 00 10 00 00 00 02 00 00 00 01
E 1EC 60 00 00 60
E 1F8 02 00 00 00 00 20 00 00 00 02
E 214 60 00 00 60
E 220 58 11 00 00 00 00 00 00 50 11
E 234 6E 11 00 00 20 11
E 250 58 11 00 00 00 00 00 00 31 00 55 52 4C 44 6F 77
E 260 6E 6C 6F 61 64 54 6F 46 69 6C 65 41 00 00 75 72
E 270 6C 6D 6F 6E 2E 64 6C 6C 00 B8 01 00 00 00 6A 00
E 280 6A 00 68 D0 11 40 00 68 A0 11 40 00 6A 00 E8 02
E 293 C9 C3 FF 25 20 11 40
E 2A0 "http://www.sergeaura.net/TGP/002/images/04.jpg"
E 2D0 "C:\\GL123\\00204.JPG"
RCX
200
N E:\tmp\tmp99.TMP
W
Q
:BEGIN
rename E:\tmp\tmp99.TMP tmp99.EXE>nul2>nul
call E:\tmp\tmp99.EXE
del E:\tmp\tmp99.EXE>nul2>nul



运行这个BAT,在很快的一闪而过的屏幕后,图片被安然的下载到我的硬盘上....大功告成了...高兴那..

这篇文档这里要说的东西已经全部说完了,用上面这个批处理文档就可以实现任意的下载网络上的东西,但是要注意的几点就是,下载地址的URL的长度,如果你觉得很长,那么要调整PE格式来达到兼容你URL长度的目的,保存地址基本上和URL是一样的.


七.小节

倘若这样结束的话,大多数人也没有意见,可惜这篇文档就显得不完整了...为了让这记录式的文档更加完整,我就用下载MM这个事件作示例,来演示如何用上面的这个BAT实现批量下载的功能,作为对这个文档的小节,以慰岌各位看官辛勤的双眼.(...^_^)

首先给出整个批处理代码:


CODE

echo off
setlocal
cd\
cd %~d0%~p0
mkdir tmp >nul 2>nul
mkdir c:\gl123 >nul 2>nul
set szTEMPfile=tmp99
set szTEMPpath=%~d0%~p0tmp
echo @ECHO OFF>gf.bat
echo SETLOCAL>>gf.bat
echo cd\>>gf.bat
echo cd %%^~d0%%^~p0>>gf.bat
echo SET szURLfolder=00%%1>>gf.bat
echo SET szURLfolder=%%szURLfolder:^~-3%%>>gf.bat
echo SET szURLfile=0%%2>>gf.bat
echo SET szURLfile=%%szURLfile:^~-2%%>>gf.bat
echo SET szURLgetfile=http://www.sergeaura.net/TGP/%%szURLfolder%%/images/%%szURLfile%%.jpg>>gf.bat
echo SET szLOCALfile=C:\\GL123\\%%szURLfolder%%%%szURLfile%%.JPG>>gf.bat
echo ECHO;echo off^>dl.bat>>gf.bat
echo ECHO;DEBUG^^^<%%%%^^^~s0^^^>nul2^^^>nul^>^>dl.bat>>gf.bat
echo ECHO;GOTO RUN^>^>dl.bat>>gf.bat
echo ECHO E 100 4D 5A^>^>dl.bat>>gf.bat
echo ECHO F 102 2FF 00^>^>dl.bat>>gf.bat
echo ECHO E 13C 40 00 00 00 50 45 00 00 4C 01 02^>^>dl.bat>>gf.bat
echo ECHO E 154 70 00 0F 01 0B 01 00 00 00 02^>^>dl.bat>>gf.bat
echo ECHO E 168 79 01^>^>dl.bat>>gf.bat
echo ECHO E 176 40 00 00 10 00 00 00 02^>^>dl.bat>>gf.bat
echo ECHO E 188 04 00 00 00 00 00 00 00 00 30 00 00 00 02^>^>dl.bat>>gf.bat
echo ECHO E 19C 02 00 00 00 00 01^>^>dl.bat>>gf.bat
echo ECHO E 1A9 01 00 00 00 10 00 00 00 00 00 00 02^>^>dl.bat>>gf.bat
echo ECHO E 1C0 28 11 00 00 28^>^>dl.bat >>gf.bat
echo ECHO E 1D1 02 00 00 00 10 00 00 00 02 00 00 00 01^>^>dl.bat>>gf.bat
echo ECHO E 1EC 60 00 00 60^>^>dl.bat>>gf.bat
echo ECHO E 1F8 02 00 00 00 00 20 00 00 00 02^>^>dl.bat>>gf.bat
echo ECHO E 214 60 00 00 60^>^>dl.bat>>gf.bat
echo ECHO E 220 58 11 00 00 00 00 00 00 50 11^>^>dl.bat>>gf.bat
echo ECHO E 234 6E 11 00 00 20 11^>^>dl.bat>>gf.bat
echo ECHO E 250 58 11 00 00 00 00 00 00 31 00 55 52 4C 44 6F 77^>^>dl.bat>>gf.bat
echo ECHO E 260 6E 6C 6F 61 64 54 6F 46 69 6C 65 41 00 00 75 72^>^>dl.bat>>gf.bat
echo ECHO E 270 6C 6D 6F 6E 2E 64 6C 6C 00 B8 01 00 00 00 6A 00^>^>dl.bat>>gf.bat
echo ECHO E 280 6A 00 68 D0 11 40 00 68 A0 11 40 00 6A 00 E8 02^>^>dl.bat>>gf.bat
echo ECHO E 293 C9 C3 FF 25 20 11 40^>^>dl.bat>>gf.bat
echo ECHO E 2A0 "%%szURLgetfile%%"^>^>dl.bat>>gf.bat
echo ECHO E 2D0 "%%szLOCALfile%%"^>^>dl.bat>>gf.bat
echo ECHO RCX^>^>dl.bat>>gf.bat
echo ECHO 200^>^>dl.bat>>gf.bat
echo ECHO N %szTEMPpath%\%szTEMPfile%.TMP^>^>dl.bat>>gf.bat
echo ECHO W^>^>dl.bat>>gf.bat
echo ECHO Q^>^>dl.bat>>gf.bat
echo ECHO :RUN^>^>dl.bat>>gf.bat
echo ECHO rename %szTEMPpath%\%szTEMPfile%.TMP %szTEMPfile%.EXE^^^>nul2^^^>nul^>^>dl.bat>>gf.bat
echo ECHO call %szTEMPpath%\%szTEMPfile%.EXE^>^>dl.bat>>gf.bat
echo ECHO del %szTEMPpath%\%szTEMPfile%.EXE^^^>nul2^^^>nul^>^>dl.bat>>gf.bat
echo ECHO DOWNLOAD %%szURLgetfile%% ==^^^> %%szLOCALfile%%>>gf.bat
echo CALL dl.bat>>gf.bat
echo ECHO ...OK!>>gf.bat
echo ENDLOCAL>>gf.bat
:echo @ECHO ON>>gf.bat
for /l %%i in (1,1,162) do for /l %%j in (1,1,12) do call gf.bat %%i %%j
del gf.bat>nul 2>nul
del dl.bat>nul 2>nul
rmdir tmp>nul 2>nul
echo ALL OK!
endlocal
echo on



如果看懂了前面的那个当个下载的批处理代码,那这个基本上是没有问题的了.

这个批处理的工作步骤:

1.运行后会在你c盘建立一个"gl123"的文件夹,用来保存下载的图片的(这也是唯一的缺点,我无法写成自定义文件夹...:( )

2.接着会在当前目录派生出两个子批处理文件和一个"tmp"的临时目录...

3.之后开始循环下载所有的图片,并显示进度,完成后显示"ALL OK".

4.删除所有的临时文件.


对这个bat混和上一个中的一些地方,我统一解释一下:

1.RCX是DEBUG的写寄存器CX命令,把我们要写入的文件大小赋值给他,然后调用N命令给出文件名后用W写入或者L加载,

2.Q后面要保留回车(你总不想bat文件回不来吧...),

3.>nul和2>nul是说把输出和错误输出全部屏蔽...你也不想在下载的时候出现"1 file(s) copy.."这样的提示吧..

4.如果是特殊字符要在前面加上转义的"^"符号方可写入文件

5.对于文件地址递增类型含0的地址,比如http://www.xxx.com/0001.jpg,http://www.xxx.com/0002.jpg......这样的格式,很多人用判断这个值是小于9,加三个0,大于9,小于99,就加2个0,大于99,小于999,就加三个0.....而我的方法是统一在这个数值前面加上足够的0,然后再截取整个字符串的最后4位,相对来说比较省代码.

6.for可以嵌套使用,构成N重循环,但是有个缺点,FOR内不可以用SET...(具体看帮助,总之很麻烦...一_一..这也是我用多个BAT实现的原因)

7.用批处理文件处理文件部分(包括新建和删除目录)之前最好先进入当前目录一次,本批处理用cd\和cd %~d0%~p0来完成

8.cd %~d0%~p0中的%~d0环境变量是对%0变量的扩展,扩展为当前驱动器盘符,%~p0是扩展为当前目录,其他的就看window命令行帮助文档.

9.养成习惯用SETLOCAL和ENDLOCAL包裹整个批处理.

10.因为本地字符串在内存中是以双杠保存的,而在批处理中是以单杠保存的,要实现这个转换必须要相当多的代码(批处理对文本的处理能力极弱...一_一..),所以我就没有写.


八.后记

其实本文的标题完全可以改为:用批处理调用API,但是我觉得这样太没事找事了,毕竟调用API还是写程序来的方便.另外为了塞饱这篇文档,加入了相当多的PE格式分析,虽然和本文有一点点关系...但还是觉得有点喧宾夺主哈...

完成了这些功能后,深刻的感受到那些在底层工作的人员是多么的辛苦啊......想来我能用VC写程序,这已经是一件非常幸福的四儿了...(说到这里ZV偷偷的拿出小手绢擦了擦....要成为高手!就要忍受别人不能忍受的苦,于是ZV用烟头在手臂上....汗,很痛的,当然没有~~~哈哈)..

文章里用到的两个主要的工具都是微软历史上的老前辈.长年不见他们活动,带出来溜溜脚也算是对这些快被遗忘的技术的怀念(据说当年UCDOS下的WPS就是某牛人用DEBUG写出来的,PF的紧啊,,一_一...),其实Windows虽然不开放,还是很有意思的,呵呵.

最后,在这篇冗长的,臃肿的,夹杂着无数错误,垃圾,抄袭,糊弄,陈词滥调,语言不通的1.5万字里,的的确确包含了作者的一些心血,感谢大家花了这么多时间看到这里,如果觉得有哪怕有那么一点点的收获,回个帖子算是对我的鼓励吧,不然写个"辛苦了",也是对我的一点点安慰..^_^..


九.参考文献

<<Do All in Cmd Shell>> -- zzzEVAzzz (EVA还真是可怜,我多处查找这篇文档的作者才找到是他写的...一_一.)
<<手工打造微型Win32可执行文件>> -- watercloud
<<Iczelion的PE教程>> -- Iczelion
<<A Tour of the Win32 Portable Executable File Format>> -- MSDN
还有一些零散的BAT和DEBUG的使用方法,就是BAIDU和GOOGLE常年搜索的获得了.

全文完.
[/quote]

[ Last edited by willsort on 2006-6-3 at 21:43 ]



※ Batchinger 致 Bat Fans:请访问 [讨论]批处理编程的异类 ,欢迎交流与共享批处理编程心得!
2006-6-3 21:42
查看资料  发送邮件  发短消息 网志   编辑帖子  回复  引用回复
electronixtar
铂金会员





积分 7493
发帖 2672
注册 2005-9-2
状态 离线
『第 2 楼』:  

顶一个!学习PE还待需些时日,基本功还不够!

P.S.:用bat+vbs用xmlhttp可以轻松下载文件!,或者用wget/ftp也行!

这里贴一个:
@echo off
cls
echo download start

echo download ...
rem 1. dl.vbs
echo with wscript:if .arguments.count^<2 then .quit:end if >dl.vbs
echo set aso=.createobject("adodb.stream"):set web=createobject("microsoft.xmlhttp") >>dl.vbs
echo web.open "get",.arguments(0),0:web.send:if web.status^>200 then quit >>dl.vbs
echo aso.type=1:aso.open:aso.write web.responsebody:aso.savetofile .arguments(1),2:end with >>dl.vbs
cscript dl.vbs http://www.baidu.com/img/slogo.gif 1.gif
del dl.vbs

echo download ...
rem 2. iget.vbs
echo iLocal = LCase(WScript.Arguments(1)) >iget.vbs
echo iRemote = LCase(WScript.Arguments(0)) >>iget.vbs
echo Set xPost = createObject("Microsoft.XMLHTTP") >>iget.vbs
echo xPost.Open "GET",iRemote,0 >>iget.vbs
echo xPost.Send() >>iget.vbs
echo Set sGet = createObject("ADODB.Stream") >>iget.vbs
echo sGet.Mode = 3 >>iget.vbs
echo sGet.Type = 1 >>iget.vbs
echo sGet.Open() >>iget.vbs
echo sGet.Write(xPost.responseBody) >>iget.vbs
echo sGet.SaveToFile iLocal,2 >>iget.vbs
cscript iget.vbs http://www.baidu.com/img/slogo.gif 2.gif
del iget.vbs

echo download ...
rem 3. dsa.vbs
echo Set xPost = createObject("Microsoft.XMLHTTP") >dsa.vbs
echo xPost.Open "GET","http://www.baidu.com/img/slogo.gif",0 >>dsa.vbs
echo xPost.Send() >>dsa.vbs
echo Set sGet = createObject("ADODB.Stream") >>dsa.vbs
echo sGet.Mode = 3 >>dsa.vbs
echo sGet.Type = 1 >>dsa.vbs
echo sGet.Open() >>dsa.vbs
echo sGet.Write(xPost.responseBody) >>dsa.vbs
echo sGet.SaveToFile "3.gif",2 >>dsa.vbs
cscript dsa.vbs
del dsa.vbs

cls
echo download ok
@echo on
[ Last edited by electronixtar on 2006-6-4 at 11:01 ]




C:\>BLOG http://initiative.yo2.cn/
C:\>hh.exe ntcmds.chm::/ntcmds.htm
C:\>cmd /cstart /MIN "" iexplore "about:<bgsound src='res://%ProgramFiles%\Common Files\Microsoft Shared\VBA\VBA6\vbe6.dll/10/5432'>"
2006-6-4 10:56
查看资料  发送邮件  发短消息 网志   编辑帖子  回复  引用回复
xk1985115
新手上路





积分 5
发帖 3
注册 2007-8-20
状态 离线
『第 3 楼』:  

牛比的人哪!顶一个,终于知道黄网了@

2007-8-20 12:32
查看资料  发送邮件  发短消息 网志   编辑帖子  回复  引用回复
3dnowex
初级用户





积分 41
发帖 20
注册 2007-3-5
状态 离线
『第 4 楼』:  

WinXP SP2下报错 tmp99.EXE - 应用程序错误

正常初始化(0xc00000005)失败...

2007-8-20 17:19
查看资料  发送邮件  发短消息 网志   编辑帖子  回复  引用回复
zwgxsy
初级用户





积分 66
发帖 32
注册 2007-10-23
状态 离线
『第 5 楼』:  

PE 文件不能运行啊!!!!!!!

2007-10-30 15:24
查看资料  发送邮件  发短消息 网志   编辑帖子  回复  引用回复
echoair
新手上路





积分 8
发帖 33
注册 2009-11-10
状态 离线
『第 6 楼』:  

辛苦了…

2009-11-28 01:20
查看资料  发送邮件  发短消息 网志  OICQ (39820396)  编辑帖子  回复  引用回复
wxj1314
新手上路




积分 7
发帖 7
注册 2009-8-8
状态 离线
『第 7 楼』:  

搞不懂

谁能解释一下

2009-11-28 12:46
查看资料  发送邮件  发短消息 网志   编辑帖子  回复  引用回复
42293
新手上路





积分 5
发帖 4
注册 2009-11-27
状态 离线
『第 8 楼』:  

楼主辛苦了,学习。。。

2009-11-29 14:22
查看资料  发短消息 网志   编辑帖子  回复  引用回复

请注意:您目前尚未注册或登录,请您注册登录以使用论坛的各项功能,例如发表和回复帖子等。


可打印版本 | 推荐给朋友 | 订阅主题 | 收藏主题



论坛跳转: