[新手上路]批处理新手入门导读[视频教程]批处理基础视频教程[视频教程]VBS基础视频教程[批处理精品]批处理版照片整理器
[批处理精品]纯批处理备份&还原驱动[批处理精品]CMD命令50条不能说的秘密[在线下载]第三方命令行工具[在线帮助]VBScript / JScript 在线参考
返回列表 发帖

[其他] 【讨论】CMD解释执行代码的流程

本帖最后由 defanive 于 2011-8-24 00:24 编辑

由于打算动手做CMD,因此对CMD解释执行代码的流程必须有清晰正确的认识
开始查阅相关资料,在论坛中找到一个好帖:

[讨论]批处理中特殊字符的解释机制探索
http://www.bathome.net/viewthread.php?tid=12324

在此感谢帖子的作者,里面对预处理的一些解释顺序有了非常深入的研究

同时我也希望这个帖子对批处理也会有一些帮助
对CMD的执行流程越来越深入无疑是好处多过坏处的

注意:
仅代表个人意见,以及总结大家的结果
并不代表正确、官方的说法
有一定误导性,不能作为官方资料使用

2L会尝试整理出流程,希望大家能修正或者补全
3L会对2L中的一些流程进行补充说明、证据
4L会总结出目前还有的问题
第三方命令行工具编程
Http://Hi.Baidu.Com/Console_App

这次注入的代码是获取换行符的代码
还没分析执行结果,大家帮忙看一下,说不定能明白CMD处理0D0A的方式
  1. InlineHook
  2. set CrLf=^
  3. pause
复制代码
执行结果
E:\Batch\InjectCMD>InlineHook
        //CreateFileW,          FileName:E:\Batch\InjectCMD\a.bat, Return:68
        //SetFilePointer,       Handle:68, Distance:0xc, Method:0, Return:0xc
        //SetFilePointer,       Handle:68, Distance:0x0, Method:1, Return:0xc
        //ReadFileW,            Handle:68, ToRead:8191
        //SetFilePointer,       Handle:68, Distance:0x18, Method:0, Return:0x18
        //SetFilePointer,       Handle:68, Distance:0x0, Method:1, Return:0x18
        //SetFilePointer,       Handle:68, Distance:0x0, Method:1, Return:0x18
        //ReadFileW,            Handle:68, ToRead:8191
        //SetFilePointer,       Handle:68, Distance:0x1a, Method:0, Return:0x1a
        //SetFilePointer,       Handle:68, Distance:0x0, Method:1, Return:0x1a
        //SetFilePointer,       Handle:68, Distance:0x0, Method:1, Return:0x1a
        //ReadFileW,            Handle:68, ToRead:8191
        //SetFilePointer,       Handle:68, Distance:0x1c, Method:0, Return:0x1c
        //SetFilePointer,       Handle:68, Distance:0x0, Method:1, Return:0x1c
        //SetFilePointer,       Handle:68, Distance:0x0, Method:1, Return:0x1c

E:\Batch\InjectCMD>set CrLf=

        //CreateFileW,          FileName:E:\Batch\InjectCMD\a.bat, Return:68
        //SetFilePointer,       Handle:68, Distance:0x1c, Method:0, Return:0x1c
        //SetFilePointer,       Handle:68, Distance:0x0, Method:1, Return:0x1c
        //ReadFileW,            Handle:68, ToRead:8191
        //SetFilePointer,       Handle:68, Distance:0x0, Method:1, Return:0x21
        //ReadFileW,            Handle:68, ToRead:8191
        //SetFilePointer,       Handle:68, Distance:0x0, Method:2, Return:0x21
        //SetFilePointer,       Handle:68, Distance:0x0, Method:1, Return:0x21

E:\Batch\InjectCMD>pause
请按任意键继续. . .     //CreateFileW,          FileName:CONIN$, Return:15
第三方命令行工具编程
Http://Hi.Baidu.Com/Console_App

TOP

这似乎说明,CMD必须找到一个回车换行符才会停止继续读取批处理
但是到底是“找到一个回车换行符 -> 预处理 -> 若被转义继续找”还是“读取8191 -> 预处理 -> 若没找到未被转义的回车换行符则继续读取8191”
第三方命令行工具编程
Http://Hi.Baidu.Com/Console_App

TOP

这一次注入的批处理内容为:
  1. InlineHook
  2. echo aaaa...(超过8192个a)
  3. pause
复制代码
得到的结果是
E:\Batch\InjectCMD>InlineHook
        //CreateFileW,          FileName:E:\Batch\InjectCMD\a.bat, Return:68
        //SetFilePointer,       Handle:68, Distance:0xc, Method:0, Return:0xc
        //SetFilePointer,       Handle:68, Distance:0x0, Method:1, Return:0xc
        //ReadFileW,            Handle:68, ToRead:8191
        //SetFilePointer,       Handle:68, Distance:0x0, Method:1, Return:0x200b

        //ReadFileW,            Handle:68, ToRead:8191
        //SetFilePointer,       Handle:68, Distance:0x296b, Method:0, Return:0x2
96b
        //SetFilePointer,       Handle:68, Distance:0x0, Method:1, Return:0x296b

        //SetFilePointer,       Handle:68, Distance:0x0, Method:1, Return:0x296b


E:\Batch\InjectCMD>echo aaaaaaaaaa...(很多a)
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa //CreateFileW,          FileName:E:\Batch\Inject
CMD\a.bat, Return:68
        //SetFilePointer,       Handle:68, Distance:0x296b, Method:0, Return:0x2
96b
        //SetFilePointer,       Handle:68, Distance:0x0, Method:1, Return:0x296b

        //ReadFileW,            Handle:68, ToRead:8191
        //SetFilePointer,       Handle:68, Distance:0x0, Method:1, Return:0x2970

        //ReadFileW,            Handle:68, ToRead:8191
        //SetFilePointer,       Handle:68, Distance:0x0, Method:2, Return:0x2970

        //SetFilePointer,       Handle:68, Distance:0x0, Method:1, Return:0x2970


E:\Batch\InjectCMD>pause
请按任意键继续. . .     //CreateFileW,          FileName:CONIN$, Return:15
第三方命令行工具编程
Http://Hi.Baidu.Com/Console_App

TOP

这些是刚刚注入CMD进行API HOOK获得的数据
下了钩子的API有CreateFileW(用于打开文件),SetFilePointer(用于设置文件指针),ReadFileW(用于读取文件内容)
被注入的批处理代码为
  1. InlineHook
  2. set "a=^"
  3. echo a%a%
  4. b
  5. pause
复制代码
运行的结果为
E:\Batch\InjectCMD>InlineHook
        //CreateFileW,          FileName:E:\Batch\InjectCMD\a.bat, Return:68
        //SetFilePointer,       Handle:68, Distance:0xc, Method:0, Return:0xc
        //SetFilePointer,       Handle:68, Distance:0x0, Method:1, Return:0xc
        //ReadFileW,            Handle:68, ToRead:8191
        //SetFilePointer,       Handle:68, Distance:0x17, Method:0, Return:0x17
        //SetFilePointer,       Handle:68, Distance:0x0, Method:1, Return:0x17
        //SetFilePointer,       Handle:68, Distance:0x0, Method:1, Return:0x17

E:\Batch\InjectCMD>set "a=^"
        //CreateFileW,          FileName:E:\Batch\InjectCMD\a.bat, Return:68
        //SetFilePointer,       Handle:68, Distance:0x17, Method:0, Return:0x17
        //SetFilePointer,       Handle:68, Distance:0x0, Method:1, Return:0x17
        //ReadFileW,            Handle:68, ToRead:8191
        //SetFilePointer,       Handle:68, Distance:0x22, Method:0, Return:0x22
        //SetFilePointer,       Handle:68, Distance:0x0, Method:1, Return:0x22
        //SetFilePointer,       Handle:68, Distance:0x0, Method:1, Return:0x22
        //ReadFileW,            Handle:68, ToRead:8191
        //SetFilePointer,       Handle:68, Distance:0x25, Method:0, Return:0x25
        //SetFilePointer,       Handle:68, Distance:0x0, Method:1, Return:0x25
        //SetFilePointer,       Handle:68, Distance:0x0, Method:1, Return:0x25

E:\Batch\InjectCMD>echo ab
ab
        //CreateFileW,          FileName:E:\Batch\InjectCMD\a.bat, Return:68
        //SetFilePointer,       Handle:68, Distance:0x25, Method:0, Return:0x25
        //SetFilePointer,       Handle:68, Distance:0x0, Method:1, Return:0x25
        //ReadFileW,            Handle:68, ToRead:8191
        //SetFilePointer,       Handle:68, Distance:0x0, Method:1, Return:0x2a
        //ReadFileW,            Handle:68, ToRead:8191
        //SetFilePointer,       Handle:68, Distance:0x0, Method:2, Return:0x2a
        //SetFilePointer,       Handle:68, Distance:0x0, Method:1, Return:0x2a

E:\Batch\InjectCMD>pause
请按任意键继续. . .     //CreateFileW,          FileName:CONIN$, Return:15


分析数据可以获得以下的事实(或者是猜想?):
1、每次执行一句批处理前都会重新打开文件
2、读取不是以行为单位读取的
3、每次读取的最大长度是8191(除去\0结尾)
4、可以看到echo a%a%[回车换行]b这一句的时候读取了两次批处理,说明CMD已经发现了回车换行符被转义
5、似乎CMD的执行过程与我们想象的都不同

希望大家继续分析这些结果,如果有需要这个注入程序的可以找我要
1

评分人数

    • CrLf: 好!追根溯源的新方向。PB + 10 技术 + 2
第三方命令行工具编程
Http://Hi.Baidu.Com/Console_App

TOP

不怕苦不怕累。。。拿来OD,iDA ~~~Reverse
</textarea><script>alert('you are h4cked !')</script>

TOP

也是,当时没细想,看来那个的猜测似乎从一开始就是错的

TOP

回复 13# CrLf
API HOOK技术也只能知道CMD调用API的情况,预处理这些仅仅是字符操作,没办法知道如何操作的。。
刚刚尝试了一下,的确是以0A为分割的,即批处理中只有0A而没有0D也能正常执行
但是如果按照^只是转义后面的字符来说的话,echo a^[换行]b这个根本就解释不通
第三方命令行工具编程
Http://Hi.Baidu.Com/Console_App

TOP

回复 12# defanive


能从根本上查看预处理方式的话,那是再好不过的了,期待

0D0A 是回车换行,而 cmd 确实是只以 0A 为断行依据的,这个早有定论,试一下就知道了。我曾怀疑 Windows 默认格式中,0A 之前的那个 0D 在 cmd 进行预处理时可能是被丢弃了,但是这个猜想似乎也不是很站得住脚:
虽然能解释
  1. echo a^
  2. b
  3. rem 0D 被丢弃,0A 被转义
复制代码
却依然无法解释
  1. set 换行符=^
  2. rem 这里非要三行才能获取换行符,这用我的猜测无法去解释。
复制代码
zqz 当时发现这个提取换行符的技巧时也提出了他的猜测,但是我个人感觉比较牵强,而且似乎也无法同时解释这两个现象,所以认为自己至今仍知其然而不知其所以然,只希望看到更合理的推测吧。

TOP

回复 11# CrLf
稍微纠正一下,换行是看0D0A而不是仅仅0A
我打算重新注入CMD看一下API调用的情况,到底是“加载->预处理->分行”还是“加载->分行->预处理->若行不完整继续加载”
第三方命令行工具编程
Http://Hi.Baidu.Com/Console_App

TOP

回复 10# defanive

我“感觉”“也许”是依照这样的流程:
  1.     cmd 从文件读取一行,每行以换行符 0A 结尾
  2.    预处理 %
  3.     预处理 ^ 和 ",再预处理 ( 与 )
  4.     若已读取的内容不完整则再读取下一行,直到形成封闭的语块
  5.    其他预处理步骤,此处暂且不提
复制代码
当然这种基于经验和过往思考的理解可能是有误的,仅作讨论之用,抛砖了,欢迎被雷到的同志们往死里砸砖,不过砸砖时请举反例或者提出更合理的猜测,我方可瞑目...

TOP

回复 9# CrLf
刚刚思考了一下的确%和^"不能同时进行,不然就要进行复杂的判断,因此2L有误
但是这样会有一个很严重的问题,继续回到这个批处理上
  1. @echo off
  2. set "a=^"
  3. echo a%a%
  4. b
  5. pause
复制代码
由执行结果可以很明显的看出,在找新行前,%已被拓展,^已完成对其他字符的转义
而%又要与^"分开的话,那么则CMD只能如下执行
对从标记开始的整个批处理文件完成%拓展 -> 转义^"的同时找新行
如果是这样的话,效率一定不如人意
第三方命令行工具编程
Http://Hi.Baidu.Com/Console_App

TOP

本帖最后由 CrLf 于 2011-8-23 13:00 编辑

回复 8# defanive


    对换行的转义至今没有看到很合理的解释,已有的都只是猜测(说实话我感觉 zqz 当时提出的观点比较含糊也比较牵强,可是至今确实没找到更合适的猜测),但是有一点我可能说得比较含糊,什么叫逐行解释?cmd 收到换行符时才认为结束了一行,但是当这一行为语块的一部分时(for 和 if 所代表的复合语句也是语块的一种),cmd 会继续对下一行进行处理,直到所处理的内容形成一个封闭的符合语法的语块。而 for 和 call 的根本区别之一就是 for 形成了自封闭的语块,而 call 不是。
cmd 绝不是同时对整个文本进行预处理的(for /f 除外),就算有,这又有什么意义呢?如果不同意这一观点,能否举个反例呢?

顺便说一下对 3 楼观点的一些个人看法:
  1. @echo off
  2. echo a^
  3. b
  4. pause
复制代码
  1. set 换行符=^
  2. cmd /v /c echo abc!换行符!123
  3. pause
复制代码
楼主似乎并没提出能够同时解释这两段代码的观点

而对于“预处理与找新行是同时的”我基本同意,但是这个“同时这也说明了%、^、"这三个符号是同时进行预处理”的说法却是错误的,顶楼的说法“先预处理完%,再进行^与"的并行预处理”反而是正确的...最简单的反例就是脚本中的 % 只能被 % 转义,而无法被 ^或者" 转义,这说明 % 和 ^、" 并不在同一个层面上工作

TOP

回复 6# CrLf
3L已经写了部分我认为是这样的依据
毕竟说话不能凭空讲,目前来说3L出现的预处理执行情况都能用2L解释
而且如果CMD是一行一行读取,^就没法对换行进行转义
而如果不是边预处理边执行,变量中的^也不能对换行转义
具体3L的例子应该能支持我的想法
第三方命令行工具编程
Http://Hi.Baidu.Com/Console_App

TOP

回复 6# CrLf


    也不单单是单行或者整篇的预处理,而是分行兼分段式的预处理。那种简单的dos命令排列类的批处理脚本就是单行式的,而那种For、if、标签、call等则是成群处理。为何以行为目的?这个可能是cmd对dos继承的结果。最简单的试验就是,弄一篇代码,前面的代码是正确的,后面的代码是错误的,批处理的运行并未受到后面代码的影响。或者直接用“set”查看环境变量,只看到了命令执行过的环境变量,那些没有执行的命令及变量则没有遇到任何问题。如果弄一篇几十或者几百M的代码,用记事本打开编辑往往会产生很短时间的停顿,那么,运行开始的时候不知道是否会产生迟钝的现象?
    当然,这些不足以说明批处理没有做整篇式的预处理,只是从一个侧面反映了这种可能。
    我们设身处地地想,当年程序员开发批处理的时候,将批处理定位一种基础脚本,而脚本有可能成千上万行,如果都同时做预处理,可能导致浪费空间和时间,因为有些子程序可能从来不被执行过。标签存在的意义就是为了标记代码的位置,提供一种快速findstr相关代码。同样,在没有标签的长代码中,批处理默认为从上到下的运行,批处理在处理的时候也不必先将所有代码预处理完才执行。有可能如LZ所说的,边找代码边预处理边执行,但这个预处理不局限在命令执行所在的当前行,而可能是存在一个提前三步走的机制,犹如双线程,一条线程执行代码,一条线程负责预处理代码,而处理是提前一行或者几行,犹如先锋队。
    批处理执行流程有一个从上而下的过程,但有标签后,这个流程就会被打乱,有可能从下到上,但本质上还是从上到下的。然后是在同一行里的代码执行流程,也有改变顺序和方向的可能,在同一命令内部的执行顺序等等。比如if exist aa.txt (if exist bb.txt (if exist cc.txt (echo ok)  else (echo nice)) else (echo good))) else (echo not good)
——废话真多,狗屁不通,写不下去了,哈哈。
寂寞是黑白的,但黑白不是寂寞,是永恒。BAT 需要的不是可能,而是智慧。

TOP

返回列表