引进

咱们在调试的过程中,常常会经过查看办法的输入与输出来确定这个办法是否反常。那么咱们要怎样经过 WinDbg 来获取办法的参数值呢?

WinDbg 中主要包含三种指令:规范指令、元指令(以 . 开始)和扩展指令(以 ! 开始)。

经过规范指令获取参数值

k 指令能够获取栈回溯。

其间 kP 能够把参数和参数值都以函数原型格appearance式显现出来,可是需求有符号。接口测试用例设计如下:

0:000> kP
 # Child-SP          RetAddr               Call Site
00 0000001b`7b0fdb78 00007ffc`718366fb     ntdll!NtCreateUserProcess
01 0000001b`7b0fdb80 00007ffc`718732f6     KERNELBASE!CreateProcessInternalW+0x115b
02 0000001b`7b0ff510 00007ffc`728560c4     KERNELBASE!CreateProcessW+0x66
03 0000001b`7b0ff580 00007ff6`14a61960     KERNEL32!CreateProcessWStub+0x54
04 0000001b`7b0ff5e0 00007ff6`14a62419     CreateProcessWithCpp!main(
			int argc = 0n1, 
			wchar_t ** argv = 0x00000208`0b637d00)+0xe0 [C:Usersfrendsourcereposdebug-testAdavageDebugCreateProcessWithCppCreateProcessWithCpp.cpp @ 20] 
05 0000001b`7b0ff800 00007ff6`14a622be     CreateProcessWithCpp!invoke_main(void)+0x39 [D:a_work1ssrcvctoolscrtvcstartupsrcstartupexe_common.inl @ 79] 
06 0000001b`7b0ff850 00007ff6`14a6217e     CreateProcessWithCpp!__scrt_common_main_seh(void)+0x12e [D:a_work1ssrcvctoolscrtvcstartupsrcstartupexe_common.inl @ 288] 
07 0000001b`7b0ff8c0 00007ff6`14a624ae     CreateProcessWithCpp!__scrt_common_main(void)+0xe [D:a_work1ssrcvctoolscrtvcstartupsrcstartupexe_common.inl @ 331] 
08 0000001b`7b0ff8f0 00007ffc`7285244d     CreateProcessWithCpp!mainCRTStartup(
			void * __formal = 0x0000001b`7aeca000)+0xe [D:a_work1ssrcvctoolscrtvcstartupsrcstartupexe_main.cpp @ 17] 
09 0000001b`7b0ff920 00007ffc`740cdf88     KERNEL32!BaseThreadInitThunk+0x1d
0a 0000001b`7b0ff950 00000000`00000000     ntdll!RtlUserThreadStart+0x28
0:000> dc 0x00000208`0b637d00
00000208`0b637d00  0b637d10 00000208 00000000 00000000  .}c.............
00000208`0b637d10  555c3a43 73726573 6572665c 735c646e  C:Usersfrends
00000208`0b637d20  6372756f 65725c65 5c736f70 75626564  ourcereposdebu
00000208`0b637d30  65742d67 415c7473 61766164 65446567  g-testAdavageDe
00000208`0b637d40  5c677562 5c343678 75626544 72435c67  bugx64DebugCr
00000208`0b637d50  65746165 636f7250 57737365 43687469  eateProcessWithC
00000208`0b637d60  652e7070 fd006578 abfdfdfd abababab  pp.exe..........
00000208`0b637d70  abababab abababab feababab feeefeee  ................

能够看到,部分办法的参数和对应的值都显现出来了,这儿用 CreatePrappstoreocessWithCpp!main 为例。

同时,函数调用语句apple能够看到部分办法尽管有有符号,也不一定能显现效率计算公式出来。比如 ntdll!NtCreateUserProcess

假如咱们就要看 ntdll!NtCreateUserProcess 的参数值呢?

还能够经过 kv 指令 显现出前函数调用面的三个参数。例如:

0:000> kv L
 # Child-SP          RetAddr               : Args to Child                                                           : Call Site
00 0000001b`7b0fdb78 00007ffc`718366fb     : 0000001b`7b0fe1f8 0000001b`7b0fe3f0 0000001b`00000001 0000001b`7b0fdf34 : ntdll!NtCreateUserProcess
01 0000001b`7b0fdb80 00007ffc`718732f6     : 00000000`00000000 00000000`00000000 00007ff6`14a610eb 580000ff`ec77c5b6 : KERNELBASE!CreateProcessInternalW+0x115b
02 0000001b`7b0ff510 00007ffc`728560c4     : 0000001b`7b0ff588 00760065`0044005c 005c0065`00630069 00640072`00610048 : KERNELBASE!CreateProcessW+0x66
03 0000001b`7b0ff580 00007ff6`14a61960     : 00007ff6`14a710ac 00620065`0064005c 0074002d`00670075 005c0074`00730065 : KERNEL32!CreateProcessWStub+0x54
04 0000001b`7b0ff5e0 00007ff6`14a62419     : 00007891`00000001 00000208`0b637d00 00000000`00000000 00007ff6`14a63aed : CreateProcessWithCpp!main+0xe0
05 0000001b`7b0ff800 00007ff6`14a622be     : 00007ff6`14a69000 00007ff6`14a69220 00000000`00000000 00000000`00000000 : CreateProcessWithCpp!invoke_main+0x39
06 0000001b`7b0ff850 00007ff6`14a6217e     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : CreateProcessWithCpp!__scrt_common_main_seh+0x12e
07 0000001b`7b0ff8c0 00007ff6`14a624ae     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : CreateProcessWithCpp!__scrt_common_main+0xe
08 0000001b`7b0ff8f0 00007ffc`7285244d     : 0000001b`7aeca000 00000000`00000000 00000000`00000000 00000000`00000000 : CreateProcessWithCpp!mainCRTStartup+0xe
09 0000001b`7b0ff920 00007ffc`740cdf88     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x1d
0a 0000001b`7b0ff950 00000000`00000000     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x28

所以咱们能够看到所有windows更新有必要吗办法的参数值了。但遗憾的是:只能看到三个参数。

已然 Wwindows7怎么重装系统inDbg 能获取到,那咱们是不是也能够在内存中找到对应的参数。

在找参数在内存中的位置之前,咱们需求了解办法调用的一些约好,针对这些约好,咱们叫它:调用协application

调用协议

界说

  • 函数效率计算公式调用约好,是指当一个函数被调用时,函数的参数会被传递给被调用的函数和返回值会被返回给调用函数。
  • 函数的调用约好便是描绘参数是怎样传递和由谁平衡仓库的,当然还有返回值

分类windows更新有必要吗

cd效率高发票查验ecl 约好

c/c++ 默认的调用约好。

规矩:

  • 参数选用栈传递
  • 从右到左入栈
  • 参数由调用方整理
  • 由 eax 作为办法返回值

s效率的英文tdcall 约好

startard call 的缩写。微软的规范约好,大多数 Win32 api 选用的都是 stdca接口是什么ll

规矩:

  • 参数选用栈传递
  • 从右到左入栈
  • 参数由被调用方整理
  • 由 eax 作为办法返回值

参数从右向左入栈,是因为栈是栈是 FILO 结构windows怎么激活,栈底在大地址。要想读取的时分是顺序读取的,设置参数的时分就需求逆转参数的方向。

fastCall 约好

fastCall 选用 ecx 和 edx 两个寄存器来传递参数,优化效效率集

规矩:

  • 前两个参数别离选用 ecx ed函数调用栈x 传递,其windows10激活密钥他参数依然选用栈传递
  • 从右到左入栈
  • 参数由被调用方整理
  • 由 eax 作为办法返回值

X64 约好

针对函数调用栈 64 位平台的 fastcall 变种,选用 ecx, edx, r8, r9 四个寄存器来传递办法的前四个参数

规矩:

  • 前四个参数别离选用 ecx, edx, r8, r9 传递,其他参数依然选用栈传递
  • 从右到左入栈
  • 参数由被调用方接口测试用例设计
  • 由 eax 作为办法返回值

内存布局函数调用可以作为独立的语句存在

怎么经过 WinDbg 获取办法参数值

咱们调试一下代码,将代码停在 getSum → auto sum = a + b ,咱们看看当时栈和参数,以及现在 ebp 地点内存地址的值。

0:000> kv L
 # ChildEBP RetAddr      Args to Child              
00 0111f708 010119a0     0000000a 0000000c 01011023 Example_4_1_2!getsum+0x25 (FPO: [Non-Fpo]) (CONV: cdecl)
01 0111f808 01012173     00000001 013db990 013dc6f8 Example_4_1_2!main+0x40 (FPO: [Non-Fpo]) (CONV: cdecl)
02 0111f828 01011fc7     037e2288 01011023 01011023 Example_4_1_2!invoke_main+0x33 (FPO: [Non-Fpo]) (CONV: cdecl)
03 0111f884 01011e5d     0111f894 010121f8 0111f8a4 Example_4_1_2!__scrt_common_main_seh+0x157 (FPO: [Non-Fpo]) (CONV: cdecl)
04 0111f88c 010121f8     0111f8a4 76267ba9 00e8c000 Example_4_1_2!__scrt_common_main+0xd (FPO: [Non-Fpo]) (CONV: cdecl)
05 0111f894 76267ba9     00e8c000 76267b90 0111f8fc Example_4_1_2!mainCRTStartup+0x8 (FPO: [Non-Fpo]) (CONV: cdecl)
06 0111f8a4 771eb7db     00e8c000 f2ae73dd 00000000 KERNEL32!BaseThreadInitThunk+0x19 (FPO: [Non-Fpo])
07 0111f8fc 771eb75f     ffffffff 7721869e 00000000 ntdll!__RtlUserThreadStart+0x2b (FPO: [Non-Fpo])
08 0111f90c 00000000     01011023 00e8c000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])
0:000> dp ebp
0111f708  0111f808 010119a0 0000000a 0000000c
0111f718  01011023 01011023 00e8c000 010118b1
0111f728  01011023 01011023 00e8c000 0111f750
0111f738  0111f750 5cb4259c cb13e9ed fffffffe
0111f748  0111f758 5cb3fa93 0edf5aca 0000001d
0111f758  0111f774 0111f774 5cb4259c 0111f77c
0111f768  5cb42c02 76fad650 0111f788 771e0559
0111f778  0111f788 5cb3fa93 0edf5aca 0000001d

能够看到,ebp 在内存中对应的值便是调用方的 ChildEBPAPP ,也便是其间的0111fapproach808;ebp + 4 即对应着当时办法的返回地址,也便windows更新有必要吗010119a0 ;而后面则是当时办法的参数值,也是跟 kv 指令输出的是一致的。

也能够看出来 kv 其实便是粗犷的把 ebp + 0x8,ebp + 0xc,ebp + 0x10 显现出来,作为改Windows接口是什么数的三个参数。

所以咱们就能够返回到函数调用怎样找到 ntdll!NtCreateUs接口自动化erProcess 的参数值了。

直接去栈上去找

因为ntdll!NtCreateUserProcess 没有官方文档来描绘它的接口界说,所以这儿不用它来验证了。接口类型选用有文档能够验证的办法:KERNELBASE!CreateProcessW 。其 Microsoft Docs 地址:CreateProcessW function (prappstoreocessthreadsapi.h) – Win32 apps | Mwindows系统icrosoft Docs

从文档中把 KERNELBASE!CreateProcessW 的界说抄下来:

BOOL CreateProcessW(
  [in, optional]      LPCWSTR               lpApplicationName,
  [in, out, optional] LPWSTR                lpCommandLine,
  [in, optional]      LPSECURITY_ATTRIBUTES lpProcessAttributes,
  [in, optional]      LPSECURITY_ATTRIBUTES lpThreadAttributes,
  [in]                BOOL                  bInheritHandles,
  [in]                DWORD                 dwCreationFlags,
  [in, optional]      LPVOID                lpEnvironment,
  [in, optional]      LPCWSTR               lpCurrentDirectory,
  [in]                LPSTARTUPINFOW        lpStartupInfo,
  [out]               LPPROCESS_INFORMATION lpProcessInformation
);

咱们先把断点断在 KERNELBASE!CreateProcessW ,然后再来看栈和内存。这儿咱们以找 lpComman接口文档dLine (第二个appstore参数)为例:

关于 32bit 的使用

x函数调用语句86 的 Win32 使用选用的是 stdcall 的调用束缚。所以咱们需求去栈中找(appetite这儿断在栈首):

0:000> bu KERNELBASE!CreateProcessW
0:000> g
Breakpoint 0 hit
KERNELBASE!CreateProcessW:
76fc4eb0 8bff            mov     edi,edi
0:000> k L
 # ChildEBP RetAddr      
00 00cffa04 008c1915     KERNELBASE!CreateProcessW
01 00cffb88 008c2213     CreateProcessWithCpp!main+0xb5
02 00cffba8 008c2067     CreateProcessWithCpp!invoke_main+0x33
03 00cffc04 008c1efd     CreateProcessWithCpp!__scrt_common_main_seh+0x157
04 00cffc0c 008c2298     CreateProcessWithCpp!__scrt_common_main+0xd
05 00cffc14 76267ba9     CreateProcessWithCpp!mainCRTStartup+0x8
06 00cffc24 771eb7db     KERNEL32!BaseThreadInitThunk+0x19
07 00cffc7c 771eb75f     ntdll!__RtlUserThreadStart+0x2b
08 00cffc8c 00000000     ntdll!_RtlUserThreadStart+0x1b

然后咱们先看看寄存器上的值。

0:000> r
eax=00cffb24 ebx=00a03000 ecx=00cffb3c edx=00cffb04 esi=00cffa34 edi=00cffb88
eip=76fc4eb0 esp=00cffa08 ebp=00cffb88 iopl=0         nv up ei pl nz ac po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000212
KERNELBASE!CreateProcessW:
76fc4eb0 8bff            mov     edi,edi

再来看看内存上的值函数调用可以作为独立的语句存在

0:000> dp esp
00cffa08  008c1915 00000000 00cffb04 00000000
00cffa18  00000000 00000000 00000000 00000000
00cffa28  00000000 00cffb3c 00cffb24 008c1023
00cffa38  008c1023 00a03000 008c1023 00a03000
00cffa48  00cffa60 e8824047 00cffa5c 689407f5
00cffa58  68a24080 00cffa9c 00000000 00cffa70
00cffa68  008c1023 008c1023 00a03000 00cffa98
00cffa78  6890c88f 00cffa88 689407f5 68a24080

咱们要找到参数区的第二个参数。

再来看看上图,第一个值 008c1915 为 ESP 的值,也便是前一个栈的栈顶。依函数调用栈据内存散布,下一个值便是参数了。

第一个参数是 00000000,第二个参数便是 00cffb04. 那咱们再来看看 00cffb04 里面的值:

0:000> dc 00cffb04 L8
00cffb04  006f006e 00650074 00610070 002e0064  n.o.t.e.p.a.d...
00cffb14  00780065 00000065 cccccccc cccccccc  e.x.e...........

所以,就能够看到,正是咱们要找的 “notepad.exe”。

关于 64bit 使用

X64 使用中,调用接口束缚选用的是 X64 的束缚。也便是前四个参数会别离存在 ecx, edx, r8, r9 中。咱们这儿要找的是第二个参数,所以咱们直接口文档接去看 edx(rdx) 就能够了(当然,这儿断点需求断在栈帧首,避免被修改)

0:000> k L
 # Child-SP          RetAddr               Call Site
00 000000ef`4073f768 00007ffc`728560c4     KERNELBASE!CreateProcessW
01 000000ef`4073f770 00007ff6`e3f91960     KERNEL32!CreateProcessWStub+0x54
02 000000ef`4073f7d0 00007ff6`e3f92419     CreateProcessWithCpp!main+0xe0
03 000000ef`4073f9f0 00007ff6`e3f922be     CreateProcessWithCpp!invoke_main+0x39
04 000000ef`4073fa40 00007ff6`e3f9217e     CreateProcessWithCpp!__scrt_common_main_seh+0x12e
05 000000ef`4073fab0 00007ff6`e3f924ae     CreateProcessWithCpp!__scrt_common_main+0xe
06 000000ef`4073fae0 00007ffc`7285244d     CreateProcessWithCpp!mainCRTStartup+0xe
07 000000ef`4073fb10 00007ffc`740cdf88     KERNEL32!BaseThreadInitThunk+0x1d
08 000000ef`4073fb40 00000000`00000000     ntdll!RtlUserThreadStart+0x28
0:000> r
rax=0000000000000000 rbx=0000000000000000 rcx=0000000000000000
rdx=000000ef4073f8e8 rsi=00007ff6e3f99d58 rdi=000000ef4073f900
rip=00007ffc71873290 rsp=000000ef4073f768 rbp=000000ef4073f820
 r8=0000000000000000  r9=0000000000000000 r10=00007ffba21c0000
r11=000000ef4073f7c8 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei pl nz na pe nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
KERNELBASE!CreateProcessW:
00007ffc`71873290 4c8bdc          mov     r11,rsp

能够看到 rdx=000000ef4073f8e8,然后咱们看看这个地址中存的东西。

0:000>  dc 000000ef4073f8e8 L8
000000ef`4073f8e8  006f006e 00650074 00610070 002e0064  n.o.t.e.p.a.d...
000000ef`4073f8f8  00780065 00000065 cccccccc cccccccc  e.x.e...........

所以咱们就找到了 notepad.exe 也便是第二个参数。

其他参数相似。

总结

要想看办法的参数:

  1. 通常情况下,能够经过 kp接口 能够直接查看到。但需求有符号接口是什么且有参数信息。
  2. 关于参数个数在三个以内的,能够经过 kv 显现前三个参数。
  3. 关于多个接口英文参数的,只能手动经过 dp 去看 ebp/esp 地点地址,经过内存散布,手动计算。
  4. 关于 fastcall/x64 这种会经过寄存器来传参的,需求特别注意,避免寄存器被修改。

其他办法

整体下来,用 WinDbg 来查看参数还是相对杂乱了些。还有些其他东西,用起windows是什么意思来就会直观许多。

OllyDbg

OD 也是一款十分经典的 Debugger,因为它有比较友善的 UI 交互,所以用来看函数参数值就相对比较简略。这儿简略介绍下:

  1. 翻开文件。(因为 OD 支撑 X86,所以这儿只用 X86 的执行文件做演示,X64函数调用可以出现在表达式中吗 的还是乖乖的用 windbg 吧)
  2. 鼠标右键→search for→All intermodular calls→找到 KERNEL32.Creat接口测试用例设计eProcessW 的调用。然后双击。
  3. 所以,就能函数调用的三种方式看到一个这样的界面。能够看到 [LOCAL.7] 便是咱们要找的 CommandLine(第二个参数)

怎么经过 WinDbg 获取办法参数值

  1. 然后咱们把光标函数调用可以作为一个函数的形参放在 KERNEL32.CreateProcessW 那一行,F4一下appetite。看看右下角的栈:

怎么经过 WinDbg 获取办法参数值

所以,咱们就很快的看出来第二个参数的值。

附录

  1. CreateProcessWithCpp.cpp
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
void main(int argc, TCHAR* argv[])
{
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    ZeroMemory(&si, sizeof(si));
    ZeroMemory(&pi, sizeof(pi));
    si.cb = sizeof(si);
    wchar_t cmd[] = L"notepad.exe";
    if (!CreateProcess(NULL, cmd, NULL, NULL, false, 0, NULL, NULL, &si, &pi))
    {
        printf("CreateProcess failed (%d).n", GetLastError());
        return;
    }
    WaitForSingleObject(pi.hProcess, INFINITE);
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
}
  1. CreateProcessW 界说:docs.mi函数调用的四个步骤crwindows更新有必要吗osoft.com/en-us/windo…