鱼C论坛

 找回密码
 立即注册
查看: 2348|回复: 0

[技术交流] 【老刘谈算法003】命令行参数的处理和获取——ArgCl函数实现分析

[复制链接]
发表于 2019-2-24 09:42:59 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
在非汇编语言中,处理并分割命令行参数(CmdLine)一般是由编译器在可执行文件中预置处理代码或者调用运行时库完成,
而在汇编中,我们需要手动调用Windows的API——GetCommandLine函数来获得一份杂乱的字符串(包含了可执行文件路径以及命令行参数),
还好在MASM32Library中,汇编爱好者们提供了一个取得命令行参数的函数,省去了我们亲自造轮子的功夫,
对于各种不按套路出牌的CmdLine,该函数将如何处理?这激起了我分析这段代码的兴趣。
当然,下面所列举的算法及出现特殊情况时的处置办法,可能与其他语言的处理方式不同,仅供参考。
算法流程如下(流程图绘制有bug,所以下面的一些文字可能有些怪异):

                               
登录/注册后可看大图

代码如下:

  1. ;原出处:MASM32 SDK
  2. ;注释修改&中文注释添加 By 老刘

  3. ; #########################################################################
  4. ;
  5. ;            这个程序是在Iczelion和Lucifer的技术支持下开发出来的。
  6. ;
  7. ; #########################################################################

  8.       .386
  9.       .model flat, stdcall  ; 32 bit memory model
  10.       option casemap :none  ; case sensitive

  11.       include \MASM32\INCLUDE\kernel32.inc

  12.   ; ------------------------------------
  13.   ; 请读文本最后的用法
  14.   ; ------------------------------------

  15.     ArgCl PROTO :DWORD,:DWORD

  16.     .code

  17. ; #########################################################################

  18. ArgCl proc ArgNum:DWORD, ItemBuffer:DWORD

  19.     LOCAL cmdLine        :DWORD
  20.     LOCAL cmdBuffer[192] :BYTE
  21.     LOCAL tmpBuffer[192] :BYTE

  22.   ; --------------
  23.   ; 储存 esi & edi
  24.   ; --------------
  25.     push esi
  26.     push edi

  27.     invoke GetCommandLine
  28.     mov cmdLine, eax        ; address command line

  29.     cmp ArgNum, 0
  30.     jne @F
  31.     xor eax, eax
  32.       jmp jmp_In
  33.     @@:

  34.     mov esi, cmdLine
  35.     lodsb                   ;al=byte ptr [esi]
  36.     cmp al, 34
  37.     je @F                   ;如果不是半角双引号,退出
  38.       pop edi
  39.       pop esi
  40.       xor eax, eax          ; eax=0
  41.       ret
  42.     @@:
  43.    
  44.   ; -------------------------------------------------
  45.   ; 统计引号标记来确认其是否双双匹配
  46.   ; -------------------------------------------------
  47.     xor ecx, ecx            ; 将ecx作为引号个数的计数器
  48.     mov esi, cmdLine
  49.    
  50.     @@:
  51.       lodsb
  52.       cmp al, 0                                ;到达末尾
  53.       je @F
  54.       cmp al, 34            
  55.       jne @B                                        ;不为引号则继续循环
  56.       inc ecx                               ;ecx+=1
  57.       jmp @B
  58.     @@:

  59.     push ecx                ;储存计数的值

  60.     shr ecx, 1
  61.     shl ecx, 1              ;ecx-=ecx%2

  62.     pop eax                 ;eax=计数的值
  63.     cmp eax, ecx
  64.     je @F                                        ;引号数量为偶数(匹配)则下跳
  65.       pop edi
  66.       pop esi
  67.       mov eax, 3            ;返回3
  68.       ret
  69.     @@:

  70.   ; -------------------------------------------
  71.   ; 下面的代码移除(程序的)路径及文件名
  72.   ; cmdline只留下参数
  73.   ; -------------------------------------------
  74.     mov esi, cmdLine                ;esi=cmdline指针
  75.     lea edi, tmpBuffer                ;edi=tmpBuffer指针
  76.    
  77.     lodsb           ;读第一个引号

  78.     @@:                                ;去除字符直到读到第二个引号
  79.       lodsb
  80.       cmp al, 34
  81.       jne @B
  82.       
  83.     wtIn:
  84.       lodsb
  85.       cmp al, 0
  86.       je wtOut
  87.       stosb         ;转移到tmpBuffer
  88.       jmp wtIn
  89.     wtOut:
  90.       stosb         ;tmpBuffer结尾

  91.   ; -----------------------------------
  92.   ; 处理空参数""(其实就是""->255)
  93.   ; -----------------------------------

  94.     lea esi, tmpBuffer
  95.     lea edi, cmdBuffer

  96.     xor edx, edx    ;清0,作为标志寄存器使用

  97.     rnsSt:
  98.       lodsb
  99.       cmp al, 0       
  100.       je rnsEnd                ;esi指向null,即处理完毕

  101.       .if al != 34                        ;该字符不为引号
  102.         .if edx == 1                ;前一字符为引号
  103.           xor edx, edx        ;edx=0
  104.           jmp rnsWrt
  105.         .endif
  106.       .elseif al == 34        ;该字符为引号
  107.         .if edx == 1                ;前一字符为引号
  108.           mov al, 255
  109.           stosb                                ;edi指向地址=255,edi++,其实就是引号换255
  110.           mov al, 34                ;再写入一个引号
  111.           stosb
  112.           dec edx                        ;edx=0
  113.           jmp rnsSt
  114.         .elseif edx == 0
  115.           inc edx                        ;标记
  116.         .endif
  117.       .endif

  118.     rnsWrt:
  119.       stosb
  120.       jmp rnsSt

  121.     rnsEnd:
  122.       stosb                ;cmdBuffer结尾

  123.   ; ----------------------------------
  124.   ; 替换有引号包裹的空格(引号中的空格->254)
  125.   ; 删除引号(上面""换成的255不会被删除)
  126.   ; ----------------------------------
  127.     lea esi, cmdBuffer                ;下面操作的是同一个地址,但由于先读后写,并不冲突。
  128.     lea edi, cmdBuffer

  129.     subSt:
  130.       lodsb
  131.       cmp al, 0
  132.       jne @F
  133.       jmp subOut                ;完成
  134.     @@:
  135.       cmp al, 34
  136.       jne subNxt
  137.       jmp subSl                        ;如果有引号,进入子循环
  138.     subNxt:
  139.       stosb                                ;写回去
  140.       jmp subSt
  141.     ; --------------------------
  142.     subSl:
  143.       lodsb
  144.       cmp al, 32    ;是空格
  145.       jne @F
  146.         mov al, 254 ;空格换254
  147.       @@:
  148.       cmp al, 34
  149.       jne @F
  150.         jmp subSt        ;子循环完毕,返回
  151.       @@:
  152.       stosb                                ;写回去
  153.       jmp subSl
  154.     ; --------------------------
  155.     subOut:
  156.       stosb         ;结尾

  157.     ; ------------------------
  158.     ;Tab-->空格
  159.     ; ------------------------

  160.     lea esi, cmdBuffer
  161.     lea edi, cmdBuffer

  162.     @@:
  163.       lodsb
  164.       cmp al, 0
  165.       je rtOut
  166.       cmp al, 9
  167.       jne rtIn                        ;不为Tab跳
  168.       mov al, 32                ;9-->32
  169.     rtIn:
  170.       stosb
  171.       jmp @B
  172.     rtOut:
  173.       stosb

  174.   ; ----------------------------------------------------
  175.   ; 下面的代码分割正确的arg,
  176.   ; 并且将arg写入目的地址
  177.   ; ----------------------------------------------------
  178.     lea eax, cmdBuffer
  179.     mov esi, eax
  180.     mov edi, ItemBuffer ; 目标地址

  181.     mov ecx, 1          ; 做计数器

  182.   ; ---------------------------
  183.   ; 去掉先导空格(如果有的话)
  184.   ; ---------------------------
  185.     @@:
  186.       lodsb
  187.       cmp al, 32
  188.       je @B

  189.     l2St:
  190.       cmp ecx, ArgNum     ; 传入的ArgNum
  191.       je clSubLp2                                ;到达需要取参的位置
  192.       lodsb
  193.       cmp al, 0
  194.       je cl2Out
  195.       cmp al, 32
  196.       jne cl2Ovr           ; if not space

  197.     @@:
  198.       lodsb
  199.       cmp al, 32          ; 捕捉连续的空格
  200.       je @B

  201.       inc ecx             ;arg计数器++
  202.       cmp al, 0
  203.       je cl2Out

  204.     cl2Ovr:
  205.       jmp l2St

  206.     clSubLp2:
  207.       stosb                                ;参数第一个字符写进去
  208.     @@:
  209.       lodsb
  210.       cmp al, 32
  211.       je cl2Out
  212.       cmp al, 0
  213.       je cl2Out
  214.       stosb
  215.       jmp @B

  216.     cl2Out:
  217.       mov al, 0
  218.       stosb

  219.   ; ---------------------------------
  220.   ; 空格换回去
  221.   ; ---------------------------------
  222.     mov esi, ItemBuffer
  223.     mov edi, ItemBuffer

  224.     @@:
  225.       lodsb
  226.       cmp al, 0
  227.       je @F
  228.       cmp al, 254
  229.       jne nxt1
  230.       mov al, 32
  231.     nxt1:
  232.       stosb
  233.       jmp @B
  234.     @@:

  235.   ; -------------------------------------------------
  236.   ; 替换“”为0
  237.   ; -------------------------------------------------
  238.     mov esi, ItemBuffer
  239.     mov edi, ItemBuffer
  240.     lodsb
  241.     cmp al, 255 ;见108+行
  242.     jne @F
  243.     mov al, 0   ;替换为0
  244.     stosb
  245.     mov eax, 4  ;返回4
  246.     pop edi
  247.     pop esi
  248.     ret
  249.   @@:

  250.   ; ----------------------------------
  251.   ; 如果参数数量不够,
  252.   ; ItemBuffer首位设0
  253.   ; ----------------------------------

  254.     .if ecx < ArgNum
  255.     jmp_In:
  256.       mov edi, ItemBuffer
  257.       mov al, 0
  258.       stosb                        ;mov byte ptr [edi],al
  259.       mov eax, 2  ;返回值为2说明参数数量不够或ArgNum为0
  260.       jmp @F
  261.     .endif

  262.     mov eax, 1  ;cmdline成功处理

  263.     @@:

  264.       pop edi
  265.       pop esi

  266.     ret

  267. ArgCl endp

  268. ; #########################################################################
  269. ;
  270. ;      这个子程序接收2个参数
  271. ;
  272. ;       1.  你要获取的命令行参数的序号。
  273. ;       2.  结果的缓存区地址。
  274. ;      
  275. ;       Example: 如果有4个参数放置于CmdLine中,
  276. ;       如progname arg1 arg2 arg3 arg4 , 使用如下手段调用该函数,
  277. ;
  278. ;       invoke ArgCl,3,ADDR buffer
  279. ;
  280. ;       将会将arg3放入buffer。
  281. ;
  282. ;       注意,引用的Arg支持长文件名。
  283. ;
  284. ;       确保缓存区的大小足够接收参数,
  285. ;       cmdline最大只能有128字节长(现在为32kb(非CMD中传参),该代码的编写日期为1999年),
  286. ;       在一个函数中,使用
  287. ;
  288. ;       LOCAL Buffer[128]:BYTE
  289. ;
  290. ;       eax中的返回值
  291. ;
  292. ;       0 = GetCommandLine返回Null
  293. ;       1 = cmdline成功处理
  294. ;       2 = 已经尝试处理,但是参数数量不够或ArgNum为0
  295. ;       3 = 引号数目不匹配(不为偶数)
  296. ;       4 = 空的引号标记(类似"")
  297. ;
  298. ;       Tab和空格都为界定字符
  299. ;
  300. ; #########################################################################

  301. end
复制代码
## 漏洞分析 ##
当有效参数长度>128时,或当PE文件路径后面的参数长>192时,会发生溢出bug。
当传入参数的字符串内部出现半角双引号时(如        arg""arg),会触发"空引号标记"未换回BUG,原因为Line280~Line291作者只判断了第一个字符是否被标记为"空引号标记"。

作者的脑洞还是不够大啊(笑)
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-6-10 15:23

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表