引进
咱们在调试的过程中,常常会经过查看办法的输入与输出来确定这个办法是否反常。那么咱们要怎样经过 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 作为办法返回值
内存布局函数调用可以作为独立的语句存在
咱们调试一下代码,将代码停在 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 指令输出的是一致的。
所以咱们就能够返回到函数调用怎样找到 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 也便是第二个参数。
其他参数相似。
总结
要想看办法的参数:
- 通常情况下,能够经过 kp接口 能够直接查看到。但需求有符号接口是什么且有参数信息。
- 关于参数个数在三个以内的,能够经过 kv 显现前三个参数。
- 关于多个接口英文参数的,只能手动经过 dp 去看 ebp/esp 地点地址,经过内存散布,手动计算。
- 关于 fastcall/x64 这种会经过寄存器来传参的,需求特别注意,避免寄存器被修改。
其他办法
整体下来,用 WinDbg 来查看参数还是相对杂乱了些。还有些其他东西,用起windows是什么意思来就会直观许多。
OllyDbg
OD 也是一款十分经典的 Debugger,因为它有比较友善的 UI 交互,所以用来看函数参数值就相对比较简略。这儿简略介绍下:
- 翻开文件。(因为 OD 支撑 X86,所以这儿只用 X86 的执行文件做演示,X64函数调用可以出现在表达式中吗 的还是乖乖的用 windbg 吧)
- 鼠标右键→search for→All intermodular calls→找到 KERNEL32.Creat接口测试用例设计eProcessW 的调用。然后双击。
- 所以,就能函数调用的三种方式看到一个这样的界面。能够看到 [LOCAL.7] 便是咱们要找的 CommandLine(第二个参数)
- 然后咱们把光标函数调用可以作为一个函数的形参放在 KERNEL32.CreateProcessW 那一行,F4一下appetite。看看右下角的栈:
所以,咱们就很快的看出来第二个参数的值。
附录
- 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);
}
-
CreateProcessW
界说:docs.mi函数调用的四个步骤crwindows更新有必要吗osoft.com/en-us/windo…