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

[代码合集] [讨论]浅论动态环境变量 cmdcmdline 的缺陷

前几天尝试解决命令行参数中含有特殊字符导致错误划分的问题时出了一点小问题,检查代码发现没有什么明显错误,就开始对关键部分断点纠错,奇怪事情发生了,在仅使用 echo 的情况下,变量的值居然前后不同。
(关于解决命令行参数中含有特殊字符的讨论帖原帖链接:http://bbs.bathome.net/thread-15349-1-1.html
这个测试中出现的诡异的现象让我纳闷了许久,最后发现问题出在 cmdcmdline 身上,每当对 %cmdcmdline% 使用变量偏移或变量替换时(哪怕是在 echo 中),%cmdcmdline% 就会被改变,请参考代码示例:
  1. @echo off
  2. setlocal enabledelayedexpansion
  3. echo 原命令行参数:%cmdcmdline%
  4. echo;
  5. echo 显示第三个字符之后的内容:%cmdcmdline:~3%
  6. echo;
  7. echo 再次显示命令行参数:%cmdcmdline%
  8. echo;
  9. echo 未作任何赋值操作,但是却改变了动态环境变量 %%cmdcmdline%%...
  10. pause>nul
复制代码
而且这个 cmd 进程的命令行参数还真的被改变了...
  1. @echo off
  2. setlocal enabledelayedexpansion
  3. echo 原命令行参数:
  4. wmic process where name="cmd.exe" get commandline /value
  5. echo 按任意键查看 cmd 的实际命令行参数:
  6. pause>nul %cmdcmdline:~,1% %cmdcmdline:c=cmd /c 国家机密,禁止泄露%
  7. echo;
  8. echo 再次显示命令行参数:
  9. wmic process where name="cmd.exe" get commandline /value
  10. echo 可见这里是改变了当前 cmd 的命令行参数,进而使动态环境变量 cmdcmdline 也随之变更。
  11. pause>nul
复制代码
测试后发现仅 cmdcmdline 变量存在此现象,这是否说明,绝大多数变量的引用是通过传值来实现的,而 cmdcmdline 是传址调用的呢?
或者从更高的角度来思考,cmd 对于部分动态环境变量的引用是否都是通过传址实现的呢?

有意思的是,因为标签和注释也需要经历预处理,所以我们可以不执行任何语句,单纯靠预处理来完成对 cmdcmdline 的修改
  1. @echo off&setlocal enabledelayedexpansion
  2. echo A:!cmdcmdline!
  3. :%cmdcmdline:*" =%
  4. :%cmdcmdline:""="%
  5. :%cmdcmdline:^=^^%
  6. :%cmdcmdline:&=^&%
  7. :%cmdcmdline:(=^(%
  8. :%cmdcmdline:)=^)%
  9. set "str=%cmdcmdline%"
  10. echo;
  11. echo B:!str!
  12. pause>nul
复制代码
更有趣的是,它居然还不受常规的预处理流程限制,同一行中的两个 %cmdcmdline% 的变量替换都生效了,
由于对它的修改,仅依赖于预处理中的变量替换和偏移,并不是一条语句,所以不需要连接符也能一次预处理中完成多次替换,不过也因为无法执行语句,所以无法进行变量嵌套
  1. @echo off&setlocal enabledelayedexpansion
  2. echo A:!cmdcmdline!
  3. :%cmdcmdline:~,7% %cmdcmdline:cmd=cmd(批处理宿主)% %cmdcmdline:/c=/c(执行字符串指定的命令然后终止)%
  4. echo;
  5. echo B:!cmdcmdline!
  6. pause>nul
复制代码
真是有趣而又诡异的现象,其局限性比较大,但是也有一定的实用性,以下这个代码就对它的实用性稍作探索:
  1. @echo off&setlocal enabledelayedexpansion
  2. rem %cmdcmdline:~,1% %cmdcmdline:c=#%
  3. set test=#
  4. rem 以上为初始化,%cmdcmdline% 与 %test% 分别用于两处对比测试。
  5. echo %time%
  6. for /l %%a in (1 1 5000) do (
  7. break !cmdcmdline:*#=%%a#!!cmdcmdline:0=零!!cmdcmdline:1=一!!cmdcmdline:2=二!!cmdcmdline:3=三!!cmdcmdline:4=四!!cmdcmdline:5=五!!cmdcmdline:6=六!!cmdcmdline:7=七!!cmdcmdline:8=八!!cmdcmdline:9=九!
  8. )
  9. echo 5000=!cmdcmdline!
  10. rem 利用 cmdcmdline 的特殊性在一条语句中完成十个替换
  11. echo %time%
  12. for /l %%a in (1 1 5000) do (
  13. set test=!test:*#=%%a#!
  14. for %%b in ("0=零" "1=一" "2=二" "3=三" "4=四" "5=五" "6=六" "7=七" "8=八" "9=九") do set test=!test:%%~b!
  15. )
  16. echo 5000=!test!
  17. rem 常规方案,用十条语句分别进行替换
  18. echo %time%
  19. rem 可以发现前者的用时要少得多
  20. pause
复制代码
当然除此以外,也可以用来为 cmd.exe 伪造身份,使命令行参数中的隐私信息不泄露。

另外,需要注意的是,因为动态环境变量独立于变量表,所以 setlocal 与 endlocal 无法对其进行“备份还原”的操作:
  1. @echo off
  2. echo setlocal 前:【%cmdcmdline%】
  3. setlocal
  4. echo 改变 %cmdcmdline%
  5. :%cmdcmdline:~-10%
  6. echo setlocal 后:【%cmdcmdline%】
  7. endlocal
  8. echo endlocal 后:【%cmdcmdline%】
  9. pause
复制代码
2

评分人数

回复 4# Demon


   
哈,透过现象看本质
本来应该多加些分,不过现在的权限以扣分和删帖为主,只能 +1 技术分,口头感谢哈...

TOP

并不是传址,用OllyDbg调试可知,%cmdcmdline%是通过GetCommandLine函数来获取的:

GetCommandLine函数返回指向当前命令行缓冲区的指针,一般来说,程序是不应该修改这个缓冲区中的数据的,而是应该先拷贝一份再进行修改,

然而CMD在进行变量偏移或变量替换时,却直接修改了缓冲区中的数据,导致之后调用GetCommandLine时返回的都是修改后的值。

例如,在替换%cmdcmdline:~m,n%时,CMD大致是这么做的:
  1. wchar_t *argv = GetCommandLine();
  2. wcsncpy(argv, argv+m, n);
复制代码
所以在下次调用GetCommandLine时仍然是修改后的值。
1

评分人数

    • CrLf: 汇编万能技术 + 1

TOP

熟么是使命行?

TOP

嘿嘿,曾经用cmdcmdline伪装过使命行,在资源管理器中看到的命令行是修改后的内容。考虑到这玩意可以用来干干事,没有公开发布,只在QQ群里提过。
喜欢研究的人最终可以发现。

TOP

返回列表