第80章 SAP

80.1 关闭客户端的网络数据包压缩功能

根据公开资料的记载,默认情况下,SAP GUI(客户端)和SAP服务端之间的通信是压缩通信,而非加密通信。[1]

决定SAP GUI(客户端)是否采用网络数据包压缩功能的环境变量是TDW_NOCOMPRESS。若把这个变量设置为1,那么就可以关闭客户端的网络数据压缩功能。不过,如果当真进行了这种设置,客户端程序就会弹出如图80.1所示的提示窗口,而且这个弹出窗口根本关不掉。

..\tu\8001.tif

图80.1 截屏

本节的任务就是去除这个提示窗口。

在此之前,我们可以确定的事实是:

① 在SAP GUI客户端程序里,环境变量TDW_ NOCOMPRESS被设为1。

② 程序的某个文件里应当存在字符串“data compression switched off”。

借助文件管理器FAR程序[2],我们可在SAPguilib.dll里找到这个字符串。

然后,我们用IDA程序打开文件SAPguilib.dll,在文件里搜索字符串“TDW_NOCOMPRESS”。所幸的是,我们可以找到这个字符串,而且整个文件只有一处指令调用了这个字符串。

该文件的相关指令为:[3]

.text:6440D51B                  lea     eax, [ebp+2108h+var_211C]
.text:6440D51E                  push    eax                ; int
.text:6440D51F                  push    offset aTdw_nocompress ; "TDW_NOCOMPRESS"
.text:6440D524                  mov     byte ptr [edi+15h], 0
.text:6440D528                  call    chk_env
.text:6440D52D                  pop     ecx
.text:6440D52E                  pop     ecx
.text:6440D52F                  push    offset byte_64443AF8
.text:6440D534                  lea     ecx, [ebp+2108h+var_211C]

; demangled name: int ATL::CStringT::Compare(char const *)const
.text:6440D537                  call    ds:mfc90_1603
.text:6440D53D                  test    eax, eax
.text:6440D53F                  jz      short loc_6440D55A
.text:6440D541                  lea     ecx, [ebp+2108h+var_211C]

; demangled name: const char* ATL::CSimpleStringT::operator PCXSTR
.text:6440D544                  call    ds:mfc90_910
.text:6440D54A                  push    eax                ; Str
.text:6440D54B                  call    ds:atoi
.text:6440D551                  test    eax, eax
.text:6440D553                  setnz   al
.text:6440D556                  pop     ecx
.text:6440D557                  mov     [edi+15h], al

据此可知,chk_env()函数通过第二个参数获取环境变量字符串,然后MFC字符串处理函数会继续处理这个字符串,接下来标准C函数atoi()(从字符串转换为数字)再从这个字符串里提取数值。最终,程序会把解析出来的环境变量的变量值存储在地址edi+15h里。

接下来,我们一起分析chk_env()函数[4]

.text:64413F20 ; int __cdecl chk_env(char *VarName, int)
.text:64413F20 chk_env          proc near
.text:64413F20
.text:64413F20 DstSize          = dword ptr -0Ch
.text:64413F20 var_8            = dword ptr -8
.text:64413F20 DstBuf           = dword ptr -4
.text:64413F20 VarName          = dword ptr  8
.text:64413F20 arg_4            = dword ptr  0Ch
.text:64413F20
.text:64413F20                  push    ebp
.text:64413F21                  mov     ebp, esp
.text:64413F23                  sub     esp, 0Ch
.text:64413F26                  mov     [ebp+DstSize], 0
.text:64413F2D                  mov     [ebp+DstBuf], 0
.text:64413F34                  push    offset unk_6444C88C
.text:64413F39                  mov     ecx, [ebp+arg_4]

; (demangled name) ATL::CStringT::operator=(char const *)
.text:64413F3C                  call    ds:mfc90_820
.text:64413F42                  mov     eax, [ebp+VarName]
.text:64413F45                  push    eax              ; VarName
.text:64413F46                  mov     ecx, [ebp+DstSize]
.text:64413F49                  push    ecx              ; DstSize
.text:64413F4A                  mov     edx, [ebp+DstBuf]
.text:64413F4D                  push    edx              ; DstBuf
.text:64413F4E                  lea     eax, [ebp+DstSize]
.text:64413F51                  push    eax              ; ReturnSize
.text:64413F52                  call    ds:getenv_s
.text:64413F58                  add     esp, 10h
.text:64413F5B                  mov     [ebp+var_8], eax
.text:64413F5E                  cmp     [ebp+var_8], 0
.text:64413F62                  jz      short loc_64413F68
.text:64413F64                  xor     eax, eax
.text:64413F66                  jmp     short loc_64413FBC
.text:64413F68
.text:64413F68 loc_64413F68:
.text:64413F68                  cmp     [ebp+DstSize], 0
.text:64413F6C                  jnz     short loc_64413F72
.text:64413F6E                  xor     eax, eax
.text:64413F70                  jmp     short loc_64413FBC
.text:64413F72  
.text:64413F72 loc_64413F72:
.text:64413F72                  jmp     short loc_64413FBC
.text:64413F75                  push    ecx
.text:64413F76                  mov     ecx, [ebp+arg_4]

; demangled name: ATL::CSimpleStringT<char, 1>::Preallocate(int)
.text:64413F79                  call    ds:mfc90_2691
.text:64413F7F                  mov     [ebp+DstBuf], eax
.text:64413F82                  mov     edx, [ebp+VarName]
.text:64413F85                  push    edx              ; VarName
.text:64413F86                  mov     eax, [ebp+DstSize]
.text:64413F89                  push    eax              ; DstSize
.text:64413F8A                  mov     ecx, [ebp+DstBuf]
.text:64413F8D                  push    ecx              ; DstBuf
.text:64413F8E                  lea     edx, [ebp+DstSize]
.text:64413F91                  push    edx              ; ReturnSize
.text:64413F92                  call    ds:getenv_s
.text:64413F98                  add     esp, 10h
.text:64413F9B                  mov     [ebp+var_8], eax
.text:64413F9E                  push    0FFFFFFFFh
.text:64413FA0                  mov     ecx, [ebp+arg_4]

; demangled name: ATL::CSimpleStringT::ReleaseBuffer(int)
.text:64413FA3                  call    ds:mfc90_5835
.text:64413FA9                  cmp     [ebp+var_8], 0
.text:64413FAD                  jz      short loc_64413FB3
.text:64413FAF                  xor     eax, eax
.text:64413FB1                  jmp     short loc_64413FBC
.text:64413FB3
.text:64413FB3 loc_64413FB3:
.text:64413FB3                  mov     ecx, [ebp+arg_4]

; demangled name: const char* ATL::CSimpleStringT::operator PCXSTR
.text:64413FB6                  call    ds:mfc90_910
.text:64413FBC
.text:64413FBC loc_64413FBC:
.text:64413FBC
.text:64413FBC                  mov     esp, ebp
.text:64413FBE                  mov     esp, ebp
.text:64413FBF                  retn
.text:64413FBF chk_env          endp

其中,getenv_s()函数[5]是微软退出的改进版getenv()函数[6],它提升了原有函数的安全特性。

这个函数多处调用了MFC字符串处理函数。

不仅如此,它还检测了其他的环境变量。如果打开SAP GUI(客户端)程序的日志记录功能,就会在trace log里看到它检测的环境变量,如下表所示。

DPTRACE “GUI-OPTION: Trace set to %d”
TDW_HEXDUMP “GUI-OPTION: Hexdump enabled”
TDW_WORKDIR “GUI-OPTION: working directory ‘%s’ ”
TDW_SPLASHSRCEENOFF “GUI-OPTION: Splash Screen Off” / “GUI-OPTION: Splash Screen On”
TDW_REPLYTIMEOUT “GUI-OPTION: reply timeout %d milliseconds”
TDW_PLAYBACKTIMEOUT “GUI-OPTION: PlaybackTimeout set to %d milliseconds”
TDW_NOCOMPRESS “GUI-OPTION: no compression read”
TDW_EXPERT “GUI-OPTION: expert mode”
TDW_PLAYBACKPROGRESS “GUI-OPTION: PlaybackProgress”
TDW_PLAYBACKNETTRAFFIC “GUI-OPTION: PlaybackNetTraffic”
TDW_PLAYLOG “GUI-OPTION: /PlayLog is YES, file %s”
TDW_PLAYTIME “GUI-OPTION: /PlayTime set to %d milliseconds”
TDW_LOGFILE “GUI-OPTION: TDW_LOGFILE ‘%s’ ”
TDW_WAN “GUI-OPTION: WAN - low speed connection enabled”
TDW_FULLMENU “GUI-OPTION: FullMenu enabled”
SAP_CP / SAP_CODEPAGE “GUI-OPTION: SAP_CODEPAGE ‘%d’ ”
UPDOWNLOAD_CP “GUI-OPTION: UPDOWNLOAD_CP ‘%d’ ”
SNC_PARTNERNAME “GUI-OPTION: SNC name ‘%s’ ”
SNC_QOP “GUI-OPTION: SNC_QOP ‘%s’ ”
SNC_LIB “GUI-OPTION: SNC is set to: %s”
SAPGUI_INPLACE “GUI-OPTION: environment variable SAPGUI_INPLACE is on”

函数把这些变量都存储在数组里,而且把EDI寄存器当作这个数组的指针。在调用chk_evn()函数之前,程序首先设置了EDI的值:

.text:6440EE00                lea     edi, [ebp+2884h+var_2884] ; options here like +0x15...
.text:6440EE03                lea     ecx, [esi+24h]
.text:6440EE06                call    load_command_line
.text:6440EE0B                mov     edi, eax
.text:6440EE0D                xor     ebx, ebx
.text:6440EE0F                cmp     edi, ebx
.text:6440EE11                jz      short loc_6440EE42
.text:6440EE13                push    edi
.text:6440EE14                push    offset aSapguiStoppedA ; "Sapgui stopped after ↙
↘ commandline interp"...
.text:6440EE19                push    dword_644F93E8
.text:6440EE1F                call    FEWTraceError

那么,我们关注的“data record mode switched on”字符串在这个文件里吗?整个文件里,只有CDwsGui::PrepareInfoWindow()构造函数调用了这个字符串。我们可通过日志文件的调试调用(debugging calls)信息了解各个class/method的名字。

例如,下述调试调用信息就透露了构造函数的函数名称:

.text:64405160                  push    dword ptr [esi+2854h]
.text:64405166                  push    offset aCdwsguiPrepare ; "\nCDwsGui::PrepareInfoWindow: ↙
    ↘ sapgui env"...
.text:6440516B                  push    dword ptr [esi+2848h]
.text:64405171                  call    dbg
.text:64405176                  add     esp, 0Ch

以及:

.text:6440237A                  push    eax
.text:6440237B                  push    offset aCclientStart_6 ; "CClient::Start: set shortcut ↙
    ↘ user to '\%"...
.text:64402380                  push    dword ptr [edi+4]
.text:64402383                  call    dbg
.text:64402388                  add     esp, 0Ch

这些信息的作用很大。

接下来,我们直奔那个令人恼火的弹出窗口:

.text:64404F4F CDwsGui__PrepareInfoWindow proc near
.text:64404F4F
.text:64404F4F pvParam         = byte ptr -3Ch
.text:64404F4F var_38          = dword ptr -38h
.text:64404F4F var_34          = dword ptr -34h
.text:64404F4F rc              = tagRECT ptr -2Ch
.text:64404F4F cy              = dword ptr -1Ch
.text:64404F4F h               = dword ptr -18h
.text:64404F4F var_14          = dword ptr -14h
.text:64404F4F var_10          = dword ptr -10h
.text:64404F4F var_4           = dword ptr -4
.text:64404F4F        
.text:64404F4F                 push    30h
.text:64404F51                 mov     eax, offset loc_64438E00
.text:64404F56                 call    __EH_prolog3
.text:64404F5B                 mov     esi, ecx          ; ECX is pointer to object
.text:64404F5D                 xor     ebx, ebx
.text:64404F5F                 lea     ecx, [ebp+var_14]
.text:64404F62                 mov     [ebp+var_10], ebx

; demangled name: ATL::CStringT(void)
.text:64404F65                 call    ds:mfc90_316
.text:64404F6B                 mov     [ebp+var_4], ebx
.text:64404F6E                 lea     edi, [esi+2854h]
.text:64404F74                 push    offset aEnvironmentInf ; "Environment information:\n"
.text:64404F79                 mov     ecx, edi

; demangled name: ATL::CStringT::operator=(char const *)
.text:64404F7B                 call    ds:mfc90_820
.text:64404F81                 cmp     [esi+38h], ebx
.text:64404F84                 mov     ebx, ds:mfc90_2539
.text:64404F8A                 jbe     short loc_64404FA9
.text:64404F8C                 push    dword ptr [esi+34h]
.text:64404F8F                 lea     eax, [ebp+var_14]
.text:64404F92                 push    offset aWorkingDirecto ; "working directory: '\%s'\n"
.text:64404F97                 push    eax

; demangled name: ATL::CStringT::Format(char const *,...)
.text:64404F98                 call    ebx ; mfc90_2539
.text:64404F9A                 add     esp, 0Ch
.text:64404F9D                 lea     eax, [ebp+var_14]
.text:64404FA0                 push    eax
.text:64404FA1                 mov     ecx, edi

; demangled name: ATL::CStringT::operator+=(class ATL::CSimpleStringT<char, 1> const &)
.text:64404FA3                 call    ds:mfc90_941
.text:64404FA9
.text:64404FA9 loc_64404FA9:
.text:64404FA9                 mov     eax, [esi+38h]
.text:64404FAC                 test    eax, eax
.text:64404FAE                 jbe     short loc_64404FD3
.text:64404FB0                 push    eax
.text:64404FB1                 lea     eax, [ebp+var_14]
.text:64404FB4                 push    offset aTraceLevelDAct ; "trace level \%d activated\n"
.text:64404FB9                 push    eax

; demangled name: ATL::CStringT::Format(char const *,...)
.text:64404FBA                 call    ebx ; mfc90_2539
.text:64404FBC                 add     esp, 0Ch
.text:64404FBF                 lea     eax, [ebp+var_14]
.text:64404FC2                 push    eax
.text:64404FC3                 mov     ecx, edi

; demangled name: ATL::CStringT::operator+=(class ATL::CSimpleStringT<char, 1> const &)
.text:64404FC5                 call    ds:mfc90_941
.text:64404FCB                 xor     ebx, ebx
.text:64404FCD                 inc     ebx
.text:64404FCE                 mov     [ebp+var_10], ebx
.text:64404FD1                 jmp     short loc_64404FD6
.text:64404FD3
.text:64404FD3 loc_64404FD3:
.text:64404FD3                 xor     ebx, ebx
.text:64404FD5                 inc     ebx
.text:64404FD6
.text:64404FD6 loc_64404FD6:
.text:64404FD6                 cmp     [esi+38h], ebx
.text:64404FD9                 jbe     short loc_64404FF1
.text:64404FDB                 cmp     dword ptr [esi+2978h], 0
.text:64404FE2                 jz      short loc_64404FF1
.text:64404FE4                 push    offset aHexdumpInTrace ; "hexdump in trace activated\n"
.text:64404FE9                 mov     ecx, edi

; demangled name: ATL::CStringT::operator+=(char const *)
.text:64404FEB                 call    ds:mfc90_945
.text:64404FF1
.text:64404FF1 loc_64404FF1:
.text:64404FF1
.text:64404FF1                 cmp     byte ptr [esi+78h], 0
.text:64404FF5                 jz      short loc_64405007
.text:64404FF7                 push    offset aLoggingActivat ; "logging activated\n"
.text:64404FFC                 mov     ecx, edi

; demangled name: ATL::CStringT::operator+=(char const *)
.text:64404FFE                 call    ds:mfc90_945
.text:64405004                 mov     [ebp+var_10], ebx
.text:64405007
.text:64405007 loc_64405007:
.text:64405007                 cmp     byte ptr [esi+3Dh], 0
.text:6440500B                 jz      short bypass
.text:6440500D                 push    offset aDataCompressio ;"data compression switched off\n"
.text:64405012                 mov     ecx, edi

; demangled name: ATL::CStringT::operator+=(char const *)
.text:64405014                 call    ds:mfc90_945
.text:6440501A                 mov     [ebp+var_10], ebx
.text:6440501D
.text:6440501D bypass:
.text:6440501D                 mov     eax, [esi+20h]
.text:64405020                 test    eax, eax
.text:64405022                 jz      short loc_6440503A
.text:64405024                 cmp     dword ptr [eax+28h], 0
.text:64405028                 jz      short loc_6440503A
.text:6440502A                 push    offset aDataRecordMode ; "data record mode switched on\n"
.text:6440502F                 mov     ecx, edi

; demangled name: ATL::CStringT::operator+=(char const *)
.text:64405031                 call    ds:mfc90_945
.text:64405037                 mov     [ebp+var_10], ebx
.text:6440503A
.text:6440503A loc_6440503A:
.text:6440503A
.text:6440503A                 mov     ecx, edi
.text:6440503C                 cmp     [ebp+var_10], ebx
.text:6440503F                 jnz     loc_64405142
.text:64405045                 push    offset aForMaximumData ; "\nFor maximum data security ↙
    ↘ delete\nthe s"

; demangled name: ATL::CStringT::operator+=(char const *)
.text:6440504A                 call    ds:mfc90_945
.text:64405050                 xor     edi, edi
.text:64405052                 push    edi                ; fWinIni
.text:64405053                 lea     eax, [ebp+pvParam]
.text:64405056                 push    eax                ; PvParam
.text:64405057                 push    edi                ; uiParam
.text:64405058                 push    30h                ; uiAction
.text:6440505A                 call    ds:SystemParametersInfoA
.text:64405060                 mov     eax, [ebp+var_34]
.text:64405063                 cmp     eax, 1600
.text:64405068                 jle     short loc_64405072
.text:6440506A                 cdq
.text:6440506B                 sub     eax, edx
.text:6440506D                 sar     eax, 1
.text:6440506F                 mov     [ebp+var_34], eax
.text:64405072
.text:64405072 loc_64405072:
.text:64405072                 push    edi                ; hWnd
.text:64405073                 mov     [ebp+cy], 0A0h
.text:6440507A                 call    ds:GetDC
.text:64405080                 mov     [ebp+var_10], eax
.text:64405083                 mov     ebx, 12Ch
.text:64405088                 cmp     eax, edi
.text:6440508A                 jz      loc_64405113
.text:64405090                 push    11h                ; i
.text:64405092                 call    ds:GetStockObject
.text:64405098                 mov     edi, ds:SelectObject
.text:6440509E                 push    eax                ;h
.text:6440509F                 push    [ebp+var_10]       ; hdc
.text:644050A2                 call    edi ; SelectObject
.text:644050A4                 and     [ebp+rc.left], 0
.text:644050A8                 and     [ebp+rc.top], 0
.text:644050AC                 mov     [ebp+h], eax
.text:644050AF                 push    401h               ; format
.text:644050B4                 lea     eax, [ebp+rc]
.text:644050B7                 push    eax                ; lprc
.text:644050B8                 lea     ecx, [esi+2854h]
.text:644050BE                 mov     [ebp+rc.right], ebx
.text:644050C1                 mov     [ebp+rc.bottom], 0B4h

; demangled name: ATL::CSimpleStringT::GetLength(void)
.text:644050C8                 call    ds:mfc90_3178
.text:644050CE                 push    eax                ; cchText
.text:644050CF                 lea     ecx, [esi+2854h]

; demangled name: const char* ATL::CSimpleStringT::operator PCXSTR
.text:644050D5                 call    ds:mfc90_910
.text:644050DB                 push    eax                ; lpchText
.text:644050DC                 push    [ebp+var_10]       ; hdc
.text:644050DF                 call    ds:DrawTextA
.text:644050E5                 push    4                  ; nIndex
.text:644050E7                 call    ds:GetSystemMetrics
.text:644050ED                 mov     ecx, [ebp+rc.bottom]
.text:644050F0                 sub     ecx, [ebp+rc.top]
.text:644050F3                 cmp     [ebp+h], 0
.text:644050F7                 lea     eax, [eax+ecx+28h]
.text:644050FB                 mov     [ebp+cy], eax
.text:644050FE                 jz      short loc_64405108
.text:64405100                 push    [ebp+h]           ; h
.text:64405103                 push    [ebp+var_10]      ; hdc
.text:64405106                 call    edi ; SelectObject
.text:64405108
.text:64405108 loc_64405108:
.text:64405108                 push    [ebp+var_10]      ; hDC
.text:6440510B                 push    0                 ; hWnd
.text:6440510D                 call    ds:ReleaseDC
.text:64405113
.text:64405113 loc_64405113:
.text:64405113                 mov     eax, [ebp+var_38]
.text:64405116                 push    80h               ; uFlags
.text:6440511B                 push    [ebp+cy]          ; cy
.text:6440511E                 inc     eax
.text:6440511F                 push    ebx                ; cx
.text:64405120                 push    eax                ; Y
.text:64405121                 mov     eax, [ebp+var_34]
.text:64405124                 add     eax, 0FFFFFED4h
.text:64405129                 cdq
.text:6440512A                 sub     eax, edx
.text:6440512C                 sar     eax, 1
.text:6440512E                 push    eax                 ; X
.text:6440512F                 push    0                   ; hWndInsertAfter
.text:64405131                 push    dword ptr [esi+285Ch] ; hWnd
.text:64405137                 call    ds:SetWindowPos
.text:6440513D                 xor     ebx, ebx
.text:6440513F                 inc     ebx
.text:64405140                 jmp     short loc_6440514D
.text:64405142
.text:64405142 loc_64405142:
.text:64405142                 push    offset byte_64443AF8

; demangled name: ATL::CStringT::operator=(char const *)
.text:64405147                 call    ds:mfc90_820
.text:6440514D
.text:6440514D loc_6440514D:
.text:6440514D                 cmp     dword_6450B970, ebx
.text:64405153                 jl      short loc_64405188
.text:64405155                 call    sub_6441C910
.text:6440515A                 mov     dword_644F858C, ebx
.text:64405160                 push    dword ptr [esi+2854h]
.text:64405166                 push    offset aCdwsguiPrepare ; "\nCDwsGui::PrepareInfoWindow: ↙
    ↘ sapgui env"... 
.text:6440516B                 push    dword ptr [esi+2848h]
.text:64405171                 call    dbg
.text:64405176                 add     esp, 0Ch
.text:64405179                 mov     dword_644F858C, 2
.text:64405183                 call    sub_6441C920
.text:64405188
.text:64405188 loc_64405188:
.text:64405188                 or      [ebp+var_4], 0FFFFFFFFh
.text:6440518C                 lea     ecx, [ebp+var_14]

; demangled name: ATL::CStringT::~CStringT()
.text:6440518F                 call    ds:mfc90_601
.text:64405195                 call    __EH_epilog3
.text:6440519A                 retn
.text:6440519A CDwsGui__PrepareInfoWindow endp

在执行上述函数的最初几个指令时,数据对象(thiscall)的指针存储于ECX寄存器。[7]本例中,对象明显使用了CDwsGui类。它所记录的选项开关,直接决定着函数窗口的提示信息。

如果地址this+0x3D的值不是0,那么整个程序就会关闭网络数据的压缩功能:

.text:64405007 loc_64405007:
.text:64405007                  cmp    byte ptr [esi+3Dh], 0
.text:6440500B                  jz     short bypass
.text:6440500D                  push   offset aDataCompressio ; "data compression switched off\n"
.text:64405012                  mov    ecx, edi

; demangled name: ATL::CStringT::operator+=(char const *)
.text:64405014                  call   ds:mfc90_945
.text:6440501A                  mov    [ebp+var_10], ebx
.text:6440501D
.text:6440501D bypass:

更有意思的是,决定程序是否显示提示窗口的关键因素是变量var_10:

.text:6440503C                 cmp    [ebp+var_10], ebx
.text:6440503F                 jnz    exit ; bypass drawing

; add strings "For maximum data security delete" / "the setting(s) as soon as possible !":

.text:64405045                 push   offset aForMaximumData ; "\nFor maximum data security ↙
    ↘ delete\nthe s"...     
.text:6440504A                 call   ds:mfc90_945 ; ATL::CStringT::operator+=(char const *)
.text:64405050                 xor    edi, edi
.text:64405052                 push   edi                 ; fWinIni
.text:64405053                 lea    eax, [ebp+pvParam]
.text:64405056                 push   eax                 ; pvParam
.text:64405057                 push   edi                 ; uiParam
.text:64405058                 push   30h                 ; uiAction
.text:6440505A                 call   ds:SystemParametersInfoA
.text:64405060                 mov    eax, [ebp+var_34]
.text:64405063                 cmp    eax, 1600
.text:64405068                 jle    short loc_64405072
.text:6440506A                 cdq
.text:6440506B                 sub    eax, edx
.text:6440506D                 sar    eax, 1  
.text:6440506F                 mov    [ebp+var_34], eax
.text:64405072
.text:64405072 loc_64405072:

start drawing:

.text:64405072                 push   edi                 ; hWnd
.text:64405073                 mov    [ebp+cy], 0A0h
.text:6440507A                 call   ds:GetDC

那么,我们通过实践来验证刚才这些推测吧。

首先找到这个JNZ指令:

.text:6440503F                 jnz    exit ; bypass drawing

把它改为JMP之后,SAPGUI程序就再也不会显示恼人的提示窗口了!

下一步,我们找到load_command_line()函数(函数名称是笔者命名的名字)里偏移量为0x15的数据,以及CDwsGui::PrepareInfoWindow里的变量this+0x3D。这两个值是相等的值么?

为了验证这一猜测,笔者在程序里搜索与偏移量0x15有关的全部指令。在SAPGUI这样的小型程序里,某个规定变量一般只会被同一个文件调用;换而言之,我们不必检索其他文件。

在当前文件里,第一处赋值的指令如下:

.text:64404C19 sub_64404C19     proc near
.text:64404C19
.text:64404C19 arg_0            = dword ptr  4
.text:64404C19
.text:64404C19                  push    ebx
.text:64404C1A                  push    ebp
.text:64404C1B                  push    esi
.text:64404C1C                  push    edi
.text:64404C1D                  mov     edi, [esp+10h+arg_0]
.text:64404C21                  mov     eax, [edi]
.text:64404C23                  mov     esi, ecx ; ESI/ECX are pointers to some unknown object.
.text:64404C25                  mov     [esi], eax
.text:64404C27                  mov     eax, [edi+4]
.text:64404C2A                  mov     [esi+4], eax
.text:64404C2D                  mov     eax, [edi+8]
.text:64404C30                  mov     [esi+8], eax
.text:64404C33                  lea     eax, [edi+0Ch]
.text:64404C36                  push    eax
.text:64404C37                  lea     ecx, [esi+0Ch]

; demangled name:  ATL::CStringT::operator=(class ATL::CStringT ... &)
.text:64404C3A                  call    ds:mfc90_817
.text:64404C40                  mov     eax, [edi+10h]
.text:64404C43                  mov     [esi+10h], eax
.text:64404C46                  mov     al, [edi+14h]
.text:64404C49                  mov     [esi+14h], al
.text:64404C4C                  mov     al, [edi+15h] ; copy byte from 0x15 offset
.text:64404C4F                  mov     [esi+15h], al ; to 0x15 offset in CDwsGui object

上述函数的调用方函数是CDwsGui::CopyOptions。这些名字都是参照调试信息命名的。

但是在整理程序流程之后,我们会发现在“时间上”第一次调用上述函数的调用方函数是CDWsGui::Init():

.text:6440B0BF loc_6440B0BF:
.text:6440B0BF                  mov     eax, [ebp+arg_0]
.text:6440B0C2                  push    [ebp+arg_4]
.text:6440B0C5                  mov     [esi+2844h], eax
.text:6440B0CB                  lea     eax, [esi+28h] ; ESI is pointer to CDwsGui object
.text:6440B0CE                  push    eax
.text:6440B0CF                  call    CDwsGui__CopyOptions

综合上述分析可知:由load_command_line()函数填充的数组,是CDwsGui class(类)的一部分。这个数组的地址是this+0x28。0x15+0x28=0x3D。这应该就是数据被传递的终点站。

接下来,我们在程序里查找“使用偏移量0x3D的指令”。我们发现CDwsGui::SapguiRun函数(根据调试信息进行命名)就使用了这个偏移量:

.text:64409D58                cmp     [esi+3Dh], bl ; ESI is pointer to CDwsGui object
.text:64409D5B                lea     ecx, [esi+2B8h]
.text:64409D61                setz    al
.text:64409D64                push    eax           ; arg_10 of CConnectionContext:: ↙
    ↘ CreateNetwork
.text:64409D65                push    dword ptr [esi+64h]

; demangled name: const char* ATL::CSimpleStringT::operator PCXSTR
.text:64409D68                call    ds:mfc90_910
.text:64409D68                                        ; no arguments
.text:64409D6E                push    eax
.text:64409D6F                lea     ecx, [esi+2BCh]

; demangled name: const char* ATL::CSimpleStringT::operator PCXSTR
.text:64409D75                call     ds:mfc90_910
.text:64409D75                                      ; no arguments
.text:64409D7B                push    eax
.text:64409D7C                push    esi
.text:64409D7D                lea     ecx, [esi+8]
.text:64409D80                call    CConnectionContext__CreateNetwork

而后,验证我们的推测:把“setz al”换为“xor eax, eax / nop”指令,清除环境变量TDW_NOCOMPRESS,然后再次运行SAPGUI。此后,令人不快的提示窗口果然不见了,而且Wireshark显示网络包不再压缩了!显然,这种修改可以对CConnectionContext对象的压缩标识进行直接操作。

可见,压缩标识传递到了CConnectionContext::CreateNetwork的第五个参数。不过这个构造函数还调用了其他函数:

...
.text:64403476                  push    [ebp+compression]
.text:64403479                  push    [ebp+arg_C]
.text:6440347C                  push    [ebp+arg_8]
.text:6440347F                  push    [ebp+arg_4]
.text:64403482                  push    [ebp+arg_0]
.text:64403485                  call    CNetwork__CNetwork

压缩标识接着被传递到构造函数CNetwork::CNetwork的第五个参数。根据这个参数,构造函数CNetwork在对象体CNetwork设置标识、并设置另一个与压缩传输可能有关的变量。这个构造函数的有关操作如下:

.text:64411DF1                  cmp     [ebp+compression], esi
.text:64411DF7                  jz      short set_EAX_to_0
.text:64411DF9                  mov     al, [ebx+78h]    ; another value may affect compression?
.text:64411DFC                  cmp     al, '3'
.text:64411DFE                  jz      short set_EAX_to_1
.text:64411E00                  cmp     al, '4'
.text:64411E02                  jnz     short set_EAX_to_0
.text:64411E04
.text:64411E04 set_EAX_to_1:
.text:64411E04                  xor     eax, eax
.text:64411E06                  inc     eax              ; EAX -> 1
.text:64411E07                  jmp     short loc_64411E0B
.text:64411E09
.text:64411E09 set_EAX_to_0:
.text:64411E09
.text:64411E09                  xor     eax, eax         ; EAX -> 0
.text:64411E0B
.text:64411E0B loc_64411E0B:
.text:64411E0B                  mov     [ebx+3A4h], eax ; EBX is pointer to CNetwork object

综上,我们可以确定CNetwork class存储压缩标识的相对地址是this+0x3A4。

然后我们以0x3A4为着手点,继续分析SAPguilib.dll。这个偏移量再次出现在CDwsGui::OnClientMessageWrite里。当然,笔者还是通过调试信息才能确定构造函数的准确名称:

.text:64406F76 loc_64406F76:
.text:64406F76                  mov     ecx, [ebp+7728h+var_7794]
.text:64406F79                  cmp     dword ptr [ecx+3A4h], 1
.text:64406F80                  jnz     compression_flag_is_zero
.text:64406F86                  mov     byte ptr [ebx+7], 1
.text:64406F8A                  mov     eax, [esi+18h]
.text:64406F8D                  mov     ecx, eax
.text:64406F8F                  test    eax, eax
.text:64406F91                  ja      short loc_64406FFF
.text:64406F93                  mov     ecx, [esi+14h]
.text:64406F96                  mov     eax, [esi+20h]
.text:64406F99
.text:64406F99 loc_64406F99:
.text:64406F99                  push    dword ptr [edi+2868h] ; int
.text:64406F9F                  lea     edx, [ebp+7728h+var_77A4]
.text:64406FA2                  push    edx                ; int
.text:64406FA3                  push    30000              ; int
.text:64406FA8                  lea     edx, [ebp+7728h+Dst]
.text:64406FAB                  push    edx                ; Dst
.text:64406FAC                  push    ecx                ; int
.text:64406FAD                  push    eax                ; Src
.text:64406FAE                  push    dword ptr [edi+28C0h] ; int
.text:64406FB4                  call    sub_644055C5     ; actual compression routine
.text:64406FB9                  add     esp, 1Ch
.text:64406FBC                  cmp     eax, 0FFFFFFF6h
.text:64406FBF                  jz      short loc_64407004
.text:64406FC1                  cmp     eax, 1
.text:64406FC4                  jz       loc_6440708C
.text:64406FCA                  cmp     eax, 2
.text:64406FCD                  jz      short loc_64407004
.text:64406FCF                  push    eax
.text:64406FD0                  push    offset aCompressionErr ; "compression error [rc = \%d]- program wi" ...
.text:64406FD5                  push    offset aGui_err_compre ; "GUI_ERR_COMPRESS"
.text:64406FDA                  push    dword ptr [edi+28D0h]
.text:64406FE0                  call    SapPcTxtRead

由此可见,压缩网络数据的关键函数是 sub_644055C5。这个函数分别调用了 memcpy()函数,以及函数sub_64417440(IDA显示的函数名)。而sub_64417440的指令是:

.text:6441747C                  push    offset aErrorCsrcompre ; "\nERROR: CsRCompress: invalid  handle"
.text:64417481                  call    eax ; dword_644F94C8
.text:64417483                  add     esp, 4

到此为止,我们完整地分析了压缩网络数据包的函数。参照笔者先前的分析[8]可知,这个网络包压缩函数是SAP和开源项目MaxDB的公用函数(上述两个产品都是SAP开发的)。因此,实际上我们可以找到它的源代码。

最后要分析的是:

.text:64406F79                  cmp     dword ptr [ecx+3A4h], 1
.text:64406F80                  jnz     compression_flag_is_zero

把此处的JNZ替换为无条件转移指令JMP,删除环境变量TDW_NOCOMPRESS。瞧!再用WireShark分析网络数据包时,我们就会发现SAPGUI不再压缩网络数据了。

综上,在找到环境变量与数据压缩功能的切合点之后,我们可以强行启用这个功能,也可以强制程序关闭这项功能。

80.2 SAP 6.0的密码验证函数

某天,在VMware平台上打开SAP 6.0 IDES时,笔者发现自己忘记SAP账户名了。几经周折找到了账户名之后,我尝试着用常用密码进行登录。结果可想而知,笔者最终遇到了提示信息“Password logon no longer possible-too many failed attempts”,再也无法登录。

好消息是SAP官方提供了完整的disp+work.pdb文件。这个PDB文件涵盖的信息还十分全面:函数名、结构体、类型、局部变量及参数名等等,简直是应有尽有。

为了便于挖掘信息,笔者使用TYPEINFODUMP程序[9],把PDB文件转换为了人类可读的文本文件。

转换后的文本文件含有函数名称、函数参数、局部变量等信息:

FUNCTION ThVmcSysEvent
  Address:       10143190 Size:      675 bytes   Index:     60483  TypeIndex:     60484
  Type: int NEAR_C ThVmcSysEvent (unsigned int, unsigned char, unsigned short*)
Flags: 0
PARAMETER events
  Address: Reg335+288 Size:          4 bytes   Index:    60488  TypeIndex:   60489
  Type: unsigned int
Flags: d0
PARAMETER opcode
  Address: Reg335+296 Size:          1 bytes   Index:    60490  TypeIndex:   60491
  Type: unsigned char
Flags: d0
PARAMETER serverName
  Address: Reg335+304 Size:          8 bytes   Index:    60492  TypeIndex:   60493
  Type: unsigned short*
Flags: d0
STATIC_LOCAL_VAR func
  Address:       12274af0 Size:      8 bytes  Index:     60495  TypeIndex:   60496
  Type: wchar_t*
Flags: 80
LOCAL_VAR admhead
  Address: Reg335+304 Size:          8 bytes  Index:    60498  TypeIndex:     60499
  Type: unsigned char*
Flags: 90
LOCAL_VAR record
  Address: Reg335+64 Size:         204 bytes  Index:    60501  TypeIndex:      60502
  Type: AD_RECORD
Flags: 90
LOCAL_VAR adlen
  Address: Reg335+296 Size:        4 bytes  Index:    60508  TypeIndex:      60509
  Type: int
Flags: 90

不仅如此,它还解释了结构体的有关信息:

STRUCT DBSL_STMTID
Size: 120  Variables: 4  Functions: 0  Base classes: 0
MEMBER moduletype
  Type:  DBSL_MODULETYPE
  Offset:         0   Index:        3  TypeIndex:    38653
MEMBER module
  Type:  wchar_t module[40]
  Offset:         4   Index:        3  TypeIndex:    831
MEMBER stmtnum
  Type: long
  Offset:         84   Index:       3  TypeIndex:    440
MEMBER timestamp
  Type:  wchar_t timestamp[15]
  Offset:         88   Index:       3  TypeIndex:    6612

此外,调试呼叫(debugging calls)也可提供大量信息。

不久,笔者就注意到设置日志详细程度的全局变量ct_level。SAP官方对这个变量有详细的解释: http://help.sap.com/saphelp_nwpi71/helpdata/en/46/962416a5a613e8e10000000a155369/ content.htm

disp+work.exe文件保留了大量的调试信息:

cmp     cs:ct_level, 1
jl      short loc_1400375DA
call    DpLock
lea     rcx, aDpxxtool4_c ; "dpxxtool4.c"
mov     edx, 4Eh          ; line
call    CTrcSaveLocation
mov     r8, cs:func_48
mov     rcx, cs:hdl      ; hdl
lea     rdx, aSDpreadmemvalu ; "%s: DpReadMemValue (%d)"
mov     r9d, ebx
call    DpTrcErr
call    DpUnlock

如果ctl_level的值大于或等于程序预设的某个阈值,那么程序将会按照相应的详细程度记录dev_wo、 dev_disp等dev-日志文件。

在使用TYPEINFODUMP程序把PDB文件转换为文本文件之后,我们使用grep指令搜索与密码有关的函数名称:

cat     "disp+work.pdb.d" | grep FUNCTION | grep -i password

上述指令的返回结果是:

FUNCTION rcui::AgiPassword::DiagISelection
FUNCTION ssf_password_encrypt
FUNCTION ssf_password_decrypt
FUNCTION password_logon_disabled
FUNCTION dySignSkipUserPassword
FUNCTION migrate_password_history
FUNCTION password_is_initial
FUNCTION rcui::AgiPassword::IsVisible
FUNCTION password_distance_ok
FUNCTION get_password_downwards_compatibility
FUNCTION dySignUnSkipUserPassword
FUNCTION rcui::AgiPassword::GetTypeName
FUNCTION 'rcui::AgiPassword::AgiPassword'::'1'::dtor$2
FUNCTION 'rcui::AgiPassword::AgiPassword'::'1'::dtor$0
FUNCTION 'rcui::AgiPassword::AgiPassword'::'1'::dtor$1
FUNCTION usm_set_password
FUNCTION rcui::AgiPassword::TraceTo
FUNCTION days_since_last_password_change
FUNCTION rsecgrp_generate_random_password
FUNCTION rcui::AgiPassword::`scalar deleting destructor'
FUNCTION password_attempt_limit_exceeded
FUNCTION handle_incorrect_password
FUNCTION 'rcui::AgiPassword::`scalar deleting destructor''::'1'::dtor$1
FUNCTION calculate_new_password_hash
FUNCTION shift_password_to_history
FUNCTION rcui::AgiPassword::GetType
FUNCTION found_password_in_history
FUNCTION `rcui::AgiPassword::`scalar deleting destructor''::'1'::dtor$0
FUNCTION rcui::AgiObj::IsaPassword
FUNCTION password_idle_check
FUNCTION SlicHwPasswordForDay
FUNCTION rcui::AgiPassword::IsaPassword
FUNCTION rcui::AgiPassword::AgiPassword
FUNCTION delete_user_password
FUNCTION usm_set_user_password
FUNCTION Password_API
FUNCTION get_password_change_for_SSO
FUNCTION password_in_USR40
FUNCTION rsec_agrp_abap_generate_random_password

根据提示信息,接下来我们在调试信息里搜索关键词“password”和“locked”。略加分析之后,笔者发现password_attempt_limit_exceeded()函数会调用关键字符串“user was locked by subsequently failed password logon attempts”。

这个函数还会在日志文件里记录“password logon attempt will be rejected immediately (preventing dictionary attacks)”“failed-logon lock: expired (but not removed due to ‘read-only’ operation)”以及“failed-logon lock: expired => removed”。

进一步的研究表明,这个函数就是登录保护函数。它会被密码验证函数——chckpass()函数调用。

首先要验证上述推测是否正确。使用笔者开发的tracer程序进行分析:

tracer64.exe -a:disp+work.exe bpf=disp+work.exe!chckpass,args:3,unicode
PID=2236|TID=2248|(0) disp+work.exe!chckpass (0x202c770, L"Brewered1 ", 0x41) (called from ↙
    ↘ 0x1402f1060 (disp+work.exe!usrexist+0x3c0))
PID=2236|TID=2248|(0) disp+work.exe!chckpass -> 0x35

调用逻辑是syssigni()→DyISigni()→dychkusr()→usrexist()→chckpass()。

数字0x35是chckpass()函数返回的错误信息编号:

.text:00000001402ED567 loc_1402ED567:                         ; CODE XREF: chckpass+B4
.text:00000001402ED567               mov    rcx, rbx          ; usr02
.text:00000001402ED56A               call   password_idle_check
.text:00000001402ED56F               cmp    eax, 33h
.text:00000001402ED572               jz     loc_1402EDB4E
.text:00000001402ED578               cmp    eax, 36h
.text:00000001402ED57B               jz     loc_1402EDB3D
.text:00000001402ED581               xor    edx, edx          ; usr02_readonly
.text:00000001402ED583               mov    rcx, rbx          ; usr02
.text:00000001402ED586               call   password_attempt_limit_exceeded
.text:00000001402ED58B               test   al, al
.text:00000001402ED58D               jz     short loc_1402ED5A0
.text:00000001402ED58F               mov    eax, 35h
.text:00000001402ED594               add    rsp, 60h
.text:00000001402ED598               pop    r14
.text:00000001402ED59A               pop    r12
.text:00000001402ED59C               pop    rdi
.text:00000001402ED59D               pop    rsi
.text:00000001402ED59E               pop    rbx
.text:00000001402ED59F               retn

然后进行试验:

tracer64.exe -a:disp+work.exe bpf=disp+work.exe!password_attempt_limit_exceeded,args:4,unicode, rt:0
 
PID=2744|TID=360|(0) disp+work.exe!password_attempt_limit_exceeded (0x202c770, 0, 0x257758, 0) ↙
    ↘ (called from 0x1402ed58b (disp+work.exe!chckpass+0xeb))
PID=2744|TID=360|(0) disp+work.exe!password_attempt_limit_exceeded -> 1
PID=2744|TID=360|We modify return value (EAX/RAX) of this function to 0
PID=2744|TID=360|(0) disp+work.exe!password_attempt_limit_exceeded (0x202c770, 0, 0, 0) (called ↙
    ↘ from 0x1402e9794 (disp+work.exe!chngpass+0xe4))
PID=2744|TID=360|(0) disp+work.exe!password_attempt_limit_exceeded -> 1
PID=2744|TID=360|We modify return value (EAX/RAX) of this function to 0

此后我们就可以进行登录了。

顺便提一下,如果忘记密码的话,可以把chckpass()函数的返回值强制改为0,那样它就不会进行密码验证了:

tracer64.exe -a:disp+work.exe bpf=disp+work.exe!chckpass,args:3,unicode,rt:0
 
PID=2744|TID=360|(0) disp+work.exe!chckpass (0x202c770, L"bogus ", 0x41) (called from 0x1402f1060 ↙
    ↘ (disp+work.exe!usrexist+0x3c0))
PID=2744|TID=360|(0) disp+work.exe!chckpass -> 0x35
PID=2744|TID=360|We modify return value (EAX/RAX) of this function to 0

在分析password_attemp_limit_exceeded()函数时,我们可以看到函数的前几行指令是:

lea     rcx, aLoginFailed_us ; "login/failed_user_auto_unlock"
call    sapgparam
test    rax, rax
jz      short loc_1402E19DE
movzx   eax, word ptr [rax]
cmp     ax, 'N'
jz      short loc_1402E19D4
cmp     ax, 'n'
jz      short loc_1402E19D4
cmp     ax, '0'
jnz     short loc_1402E19DE

很显然,sapgparam()函数的作用是获取配置参数。整个程序有1768处指令调用这个函数。据此推测,只要追踪这个函数的调用关系,就可以分析特定参数对整个程序的影响。

不得不说,SAP要比Oracle RDBMS亲切得多。前者提供的函数名等信息远比后者清晰。不过disp+work程序具有C++程序的特征,莫非官方最近重新编写了它的源程序?


[1] 关于SAP采用的密码传输和指令传输技术,请参见笔者的博客:http://blog.yurichev.com/ node/44http://blog.yurichev.com/node/47。

[2] http://www.farmanager.com/。

[3] 本章演示的程序是SAP GUI v720 for Win32,其版本号为7200,1,0,9009。如果版本不同,那么相关偏移量应当会是不同值。

[4] 这个函数名称是笔者命名的。

[5] https://msdn.microsoft.com/en-us/library/tb2sfw2z(VS.80).aspx。

[6] 返回环境变量的标准C函数。

[7] 这属于thiscall类型的函数。有关thiscall类型的函数,可参见本书51.1.1节。

[8] http://conus.info/utils/SAP_pkt_decompr.txt。

[9] http://www.debuginfo.com/tools/typeinfodump.html。