IOLI-crackme程序下载
以下通过ODbgScript
来找到密码和修改程序。
;修改strcmp的返回值eax
var start
var addr_pass
var pass
var strcmp
;reset = control+f2
;clear
lclr
gmi eip,entry
mov start,$RESULT
eval "entry = {start}"
log $RESULT
;rax2 -S "Password OK" 50617373776f7264204f4b
findmem #50617373776f7264204f4b#,start
;log $RESULT 00404041
sub $RESULT,1a
mov addr_pass,$RESULT
;00404027 250382
gstr addr_pass
mov pass,$RESULT
log pass
;push addr_pass
;pop addr_pass
;findcmd start,"test eax,eax"
;00401373 >|. E8 98190000 call 00402D20 strcmp
findop start,#E898190000#
mov strcmp,$RESULT
eval "strcmp = {$RESULT}"
log $RESULT
bp strcmp
esto
sto
log eip
log eax
mov eax,0
esto
;run
ret
;修改je
var start
var strcmp
var jump
var tmp
lclr
gmi eip,entry
mov start,$RESULT
eval "entry = {start}"
log $RESULT
;00401373 >|. E8 98190000 call 00402D20 strcmp
findop start,#E898190000#
mov strcmp,$RESULT
bp strcmp
mov jump,strcmp
add jump,7
log jump
;0040137A /74 0E /je short 0040138A /jmp 0040138A /EB 0E
repl jump,#740E#,#EB0E#,2
调用strcmp
00401368 |. C74424 04 274>mov dword ptr [esp+4], 00404027 ; ||ASCII "250382"
00401370 |. 890424 mov dword ptr [esp], eax ; ||
00401373 >|. E8 98190000 call ; |\strcmp
00401378 |. 85C0 test eax, eax ; |
0040137A 74 0E je short 0040138A
;strcmp
77089B40 > 8B5424 04 mov edx, dword ptr [esp+4]
77089B44 8B4C24 08 mov ecx, dword ptr [esp+8]
77089B48 F7C2 03000000 test edx, 3
...
77089B82 C3 retn
调用strcmp前的参数来源(按[esp]地址大小) strcmp(eax,pstr)
调用strcmp前时的参数来源 strcmp(edx,ecx)
调用strcmp返回值 eax = ffffffff(-1),0,1
调试源码 : microsoft doc
// crt_strcmp.c
#include
#include
#include
char string1[] = "The quick brown dog jumps over the lazy fox";
char string2[] = "The QUICK brown dog jumps over the lazy fox";
int main( void )
{
char tmp[20];
int result;
// Case sensitive
printf( "Compare strings:\n %s\n %s\n\n", string1, string2 );
result = strcmp( string1, string2 );
if( result > 0 )
strcpy_s( tmp, _countof(tmp), "greater than" );
else if( result < 0 )
strcpy_s( tmp, _countof (tmp), "less than" );
else
strcpy_s( tmp, _countof (tmp), "equal to" );
printf( " strcmp: String 1 is %s string 2\n", tmp );
// Case insensitive (could use equivalent _stricmp)
result = _stricmp( string1, string2 );
if( result > 0 )
strcpy_s( tmp, _countof (tmp), "greater than" );
else if( result < 0 )
strcpy_s( tmp, _countof (tmp), "less than" );
else
strcpy_s( tmp, _countof (tmp), "equal to" );
printf( " _stricmp: String 1 is %s string 2\n", tmp );
}
环境:Visual Studio
,安装必选项:C++ 桌面应用程序开发 Windows Sdk
设置断点后调试,在调试\窗口\寄存器
打开寄存器窗口,先逐步跟踪,再看源码所在的文件,最后打开反汇编窗口来调试。记录调用的堆栈如下:
main.cpp
printf
//stdio.h
__crt_va_start(_ArgList, _Format);
//VC/include/vardefs.h
void __vcrt_va_start_verify_argument_type() throw()
_Result = _vfprintf_l(stdout, _Format, NULL, _ArgList);
return __stdio_common_vfprintf(_CRT_INTERNAL_LOCAL_PRINTF_OPTIONS, _Stream, _Format, _Locale, _ArgList);
//windows kit/10/include/VRESION/ucrt/correct_stdio_config.h
__declspec(noinline) __inline unsigned __int64* __CRTDECL __local_stdio_printf_options(void)
{
static unsigned __int64 _OptionsStorage;
return &_OptionsStorage;
}
__crt_va_end(_ArgList);
return _Result;
result = strcmp(string1, string2);
static int __cdecl invoke_main() throw()
{
return main(__argc, __argv, _get_initial_narrow_environment());
}
//vcruntime/exe_common.inl
int const main_result = invoke_main();
if (!__scrt_is_managed_app())
exit(main_result);
//vcruntime/utility_desktop.cpp
extern "C" bool __cdecl __scrt_is_managed_app()
{
PIMAGE_DOS_HEADER const dos_header = reinterpret_cast<PIMAGE_DOS_HEADER>(GetModuleHandleW(nullptr));
if (dos_header == nullptr)
return false;
...
PIMAGE_NT_HEADERS const pe_header = reinterpret_cast<PIMAGE_NT_HEADERS>(
reinterpret_cast<BYTE*>(dos_header) + dos_header->e_lfanew);
if (pe_header->OptionalHeader.NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR)
return false;
static __declspec(noinline) int __cdecl __scrt_common_main_seh() throw()
if (!__scrt_is_managed_app())
exit(main_result);
可以看出:在退出程序之前会通过__scrt_is_managed_app()
来检查PE结构
。PIMAGE_NT_HEADERS
的相关结构可以直接使用,这就是有时候写文件补丁时,会写的比较简洁的原因了。而如果对msvc
的源码不够了解,就把握不了相关的结构和类型了,最好是自己动手写相关的结构和字段。
对比源码调试和反汇编调试:
源码调试
时过程会相对简洁,但相关的定义很多,不知道具体是调用哪个dll里的函数。
反汇编调试
过程多,通过内存映射,可以找到相应的dll,不会有那么多的定义,靠寄存器和内存的环境来反映。
查找
调整程序窗口,同时查看程序的输出和OllyDBG的堆栈,一直按F7,直到出现输出,alt+m
打开内存映射,可定位到ntdll
。
在OD中打开crackme0x00
,定位到ntdll
代码区。
相关函数调用:ntdll.ZwDeviceIoControlFile KERNELBA ntdll.ZwWriteFile
输出
output
77191DC0 > B8 07001B00 mov eax, 1B0007
77191DC5 BA 308D1A77 mov edx, 771A8D30
77191DCA FFD2 call edx
77191DCC C2 2800 retn 28
77191DCF 90 nop
输入
77191DD0 > B8 08001A00 mov eax, 1A0008 ; UNICODE "00"
77191DD5 BA 308D1A77 mov edx, 771A8D30
77191DDA FFD2 call edx
77191DDC C2 2400 retn 24
77191DDF 90 nop
结束
end
77192030 > B8 2C000700 mov eax, 7002C
77192035 BA 308D1A77 mov edx, 771A8D30
7719203A FFD2 call edx
7719203C C2 0800 retn 8
7719203F 90 nop
重启时ntdll
装入地址发生了变化,用ODbgScript
来查找代码并设置断点
;ntdll
;循环查找opcode
ntdll:
var start
var end
lclr
gma "ntdll", codebase
mov start,$RESULT
log start
gma "ntdll", codesize
mov end,$RESULT
add end,start
sub end,2
log end
lclr
compare:
;output : B8 07001B00 mov eax, 1B0007
;findcmd start,"mov edi,edx"
findop start,#B807001B00#
cmp $RESULT,0
jne find
je finished
find:
log $RESULT
bp $RESULT
;bc $RESULT
wrta "s.txt",$RESULT
add $RESULT,1
cmp end,$RESULT
je finished
jne add:
add:
mov start,$RESULT
jmp compare
finished:
ret