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

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

代码如下:

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

; #########################################################################
;
;            这个程序是在Iczelion和Lucifer的技术支持下开发出来的。
;
; #########################################################################

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

      include \MASM32\INCLUDE\kernel32.inc

  ; ------------------------------------
  ; 请读文本最后的用法
  ; ------------------------------------

    ArgCl PROTO :DWORD,:DWORD

    .code

; #########################################################################

ArgCl proc ArgNum:DWORD, ItemBuffer:DWORD

    LOCAL cmdLine        :DWORD
    LOCAL cmdBuffer[192] :BYTE
    LOCAL tmpBuffer[192] :BYTE

  ; --------------
  ; 储存 esi & edi
  ; --------------
    push esi
    push edi

    invoke GetCommandLine
    mov cmdLine, eax        ; address command line

    cmp ArgNum, 0
    jne @F
    xor eax, eax
      jmp jmp_In
    @@:

    mov esi, cmdLine
    lodsb                   ;al=byte ptr [esi]
    cmp al, 34
    je @F                   ;如果不是半角双引号,退出
      pop edi
      pop esi
      xor eax, eax          ; eax=0
      ret
    @@:

  ; -------------------------------------------------
  ; 统计引号标记来确认其是否双双匹配
  ; -------------------------------------------------
    xor ecx, ecx            ; 将ecx作为引号个数的计数器
    mov esi, cmdLine

    @@:
      lodsb
      cmp al, 0             ;到达末尾
      je @F
      cmp al, 34
      jne @B                    ;不为引号则继续循环
      inc ecx                       ;ecx+=1
      jmp @B
    @@:

    push ecx                ;储存计数的值

    shr ecx, 1
    shl ecx, 1              ;ecx-=ecx%2

    pop eax                 ;eax=计数的值
    cmp eax, ecx
    je @F                   ;引号数量为偶数(匹配)则下跳
      pop edi
      pop esi
      mov eax, 3            ;返回3
      ret
    @@:

  ; -------------------------------------------
  ; 下面的代码移除(程序的)路径及文件名
  ; cmdline只留下参数
  ; -------------------------------------------
    mov esi, cmdLine        ;esi=cmdline指针
    lea edi, tmpBuffer      ;edi=tmpBuffer指针

    lodsb           ;读第一个引号

    @@:             ;去除字符直到读到第二个引号
      lodsb
      cmp al, 34
      jne @B

    wtIn:
      lodsb
      cmp al, 0
      je wtOut
      stosb         ;转移到tmpBuffer
      jmp wtIn
    wtOut:
      stosb         ;tmpBuffer结尾

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

    lea esi, tmpBuffer
    lea edi, cmdBuffer

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

    rnsSt:
      lodsb
      cmp al, 0
      je rnsEnd     ;esi指向null,即处理完毕

      .if al != 34          ;该字符不为引号
        .if edx == 1        ;前一字符为引号
          xor edx, edx  ;edx=0
          jmp rnsWrt
        .endif
      .elseif al == 34  ;该字符为引号
        .if edx == 1        ;前一字符为引号
          mov al, 255
          stosb             ;edi指向地址=255,edi++,其实就是引号换255
          mov al, 34        ;再写入一个引号
          stosb
          dec edx           ;edx=0
          jmp rnsSt
        .elseif edx == 0
          inc edx           ;标记
        .endif
      .endif

    rnsWrt:
      stosb
      jmp rnsSt

    rnsEnd:
      stosb     ;cmdBuffer结尾

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

    subSt:
      lodsb
      cmp al, 0
      jne @F
      jmp subOut        ;完成
    @@:
      cmp al, 34
      jne subNxt
      jmp subSl         ;如果有引号,进入子循环
    subNxt:
      stosb             ;写回去
      jmp subSt
    ; --------------------------
    subSl:
      lodsb
      cmp al, 32    ;是空格
      jne @F
        mov al, 254 ;空格换254
      @@:
      cmp al, 34
      jne @F
        jmp subSt   ;子循环完毕,返回
      @@:
      stosb             ;写回去
      jmp subSl
    ; --------------------------
    subOut:
      stosb         ;结尾

    ; ------------------------
    ;Tab-->空格
    ; ------------------------

    lea esi, cmdBuffer
    lea edi, cmdBuffer

    @@:
      lodsb
      cmp al, 0
      je rtOut
      cmp al, 9
      jne rtIn          ;不为Tab跳
      mov al, 32        ;9-->32
    rtIn:
      stosb
      jmp @B
    rtOut:
      stosb

  ; ----------------------------------------------------
  ; 下面的代码分割正确的arg,
  ; 并且将arg写入目的地址
  ; ----------------------------------------------------
    lea eax, cmdBuffer
    mov esi, eax
    mov edi, ItemBuffer ; 目标地址

    mov ecx, 1          ; 做计数器

  ; ---------------------------
  ; 去掉先导空格(如果有的话)
  ; ---------------------------
    @@:
      lodsb
      cmp al, 32
      je @B

    l2St:
      cmp ecx, ArgNum     ; 传入的ArgNum
      je clSubLp2               ;到达需要取参的位置
      lodsb
      cmp al, 0
      je cl2Out
      cmp al, 32
      jne cl2Ovr           ; if not space

    @@:
      lodsb
      cmp al, 32          ; 捕捉连续的空格
      je @B

      inc ecx             ;arg计数器++
      cmp al, 0
      je cl2Out

    cl2Ovr:
      jmp l2St

    clSubLp2:
      stosb             ;参数第一个字符写进去
    @@:
      lodsb
      cmp al, 32
      je cl2Out
      cmp al, 0
      je cl2Out
      stosb
      jmp @B

    cl2Out:
      mov al, 0
      stosb

  ; ---------------------------------
  ; 空格换回去
  ; ---------------------------------
    mov esi, ItemBuffer
    mov edi, ItemBuffer

    @@:
      lodsb
      cmp al, 0
      je @F
      cmp al, 254
      jne nxt1
      mov al, 32
    nxt1:
      stosb
      jmp @B
    @@:

  ; -------------------------------------------------
  ; 替换“”为0
  ; -------------------------------------------------
    mov esi, ItemBuffer
    mov edi, ItemBuffer
    lodsb
    cmp al, 255 ;见108+行
    jne @F
    mov al, 0   ;替换为0
    stosb
    mov eax, 4  ;返回4
    pop edi
    pop esi
    ret
  @@:

  ; ----------------------------------
  ; 如果参数数量不够,
  ; ItemBuffer首位设0
  ; ----------------------------------

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

    mov eax, 1  ;cmdline成功处理

    @@:

      pop edi
      pop esi

    ret

ArgCl endp

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

end

漏洞分析

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

作者的脑洞还是不够大啊(笑)

原文地址:http://blog.51cto.com/oldliu/2163569

时间: 2024-08-27 21:54:04

【老刘谈算法003】命令行参数的处理和获取——ArgCl函数实现分析的相关文章

【老刘谈算法001】这位运算玩的真溜—strlen函数的汇编实现分析

首先挂下代码, ;原函数作者为不知名老外,出处为MASM32开发包,在此表示感谢. ;中文注释修改&添加 By 老刘. .486 .model flat, stdcall option casemap :none .code OPTION PROLOGUE:NONE OPTION EPILOGUE:NONE align 4 StrLen proc item:DWORD mov eax, [esp+4] ;获得参数item,即字符串指针 lea edx, [eax+3] ;edx=指针+3 pus

powershell脚本,命令行参数传值,并绑定变量的例子

这是小技巧文章,所以文章不长.但原创唯一,非常重要.我搜了下,还真没有人发 powershell怎样 [命令行 参数 绑定],所以我决定写成博客. 搜索关键字如下: powershell 命令行 参数 绑定 powershell 传入 参数 powershell 传递 参数 powershell CmdletBinding powershell 命令行 参数 绑定 传入 传递 parameter CmdletBinding powershell 传教士 原创文章.始于 2016-09-26 允许

getopt-解析命令行参数

getopt模块用于解析 sys中的命令行参数.支持unix的 getopt() 函数的功能,并且提供了一个函数 getopt(args,options[,long_options]) 解析命令行参数,要去掉开头的运行程序引用.所以通常将args赋值为 sys.argv[1:] .options参数可以跟一串字母,每个字母表示一个选项,含有后续值的选项后面加个冒号. long_options,格式是字符串列表,选项名中不需要包含前导的"--".长选项需要在参数后附带"=&qu

如何编写一个带命令行参数的Python文件

看到别人执行一个支持命令行参数的python文件,瞬间觉得高大上起来.牛逼起来,那么如何编写一个带命令行参数的python脚本呢?不用紧张,下面将简单易懂地让你学会如何让自己的python脚本,支持命令行参数. 首先你要知道python中的sys模块的一些功能: import sys print "the number of python program's argument:",len(sys.argv) print "the value of every argument

main 函数argc , argv 主命令行参数

ARGc和ARGv中的ARG指的是"参数"(ARGuments, argument counter 和 argument vector ) 至少有两个参数至主函数:ARGc和ARGv: 首先是一个至算提供的参数到程序, 第二个是对字符串数组的指针. 基本作用: argc, argv 用命令行编译程序时有用. 主函数main中变量(int argc, char *argv[ ])的含义 有些编译器允许将main()的返回类型声明为void,这已不再是合法的C++; main(int ar

聊聊默认支持的各种配置源[内存变量,环境变量和命令行参数]

聊聊默认支持的各种配置源[内存变量,环境变量和命令行参数] 较之传统通过App.config和Web.config这两个XML文件承载的配置系统,.NET Core采用的这个全新的配置模型的最大一个优势就是针对多种不同配置源的支持.我们可以将内存变量.命令行参数.环境变量和物理文件作为原始配置数据的来源,如果采用物理文件作为配置源,我们可以选择不同的格式(比如XML.JSON和INI等) .如果这些默认支持的配置源形式还不能满足你的需求,我们还可以通过注册自定义ConfigurationSour

C命令行参数

总是忘了,在这里说明下. argc是命令行参数的实际个数,从1开始. 第一个是可执行文件的名称 argv[]的元素是字符串 每个元素是个命令行参数. #include<stdio.h> int main(int argc,char *argv[]){ printf("argc is %d\n",argc); int i=0; for(;i<=argc;i++){ printf("argv[%d] is %s\n",i,argv[i]); } }

mysql命令行参数

一,mysql命令行参数 Usage: mysql [OPTIONS] [database] //命令方式 -?, --help //显示帮助信息并退出 -I, --help //显示帮助信息并退出 --auto-rehash //自动补全功能,就像linux里面,按Tab键出提示差不多,下面有例子 -A, --no-auto-rehash //默认状态是没有自动补全功能的.-A就是不要自动补全功能 -B, --batch //ysql不使用历史文件,禁用交互 (Enables --silent

命令行参数相加问题

一,设计思想 因为命令行参数是字符串类型,所以需要进行转换.可以把每个命令行参数,依据字符串长度使用循环结构把每个字符串转换成double型,最后加和. 二,程序流程图 三,源程序代码和结果截图 public class  ComSum { public static void main(String[] args) { double sum=0; for(int i=0;i<args.length;i++) { double len; len=Integer.parseInt(args[i])