1、准备工作
无壳,Delphi编写,工具:DarkDe4(Delphi反汇编工具,定位事件地址)
2、OD载入运行,搜索字符串。
双击定位
0045803B为关键跳转。
[esi+0x30C] == 0x85 才提示注册成功,向上查看哪个地方有赋值操作。
在Panel1DbClick函数中发现赋值操作。
00457EFE |. C786 0C030000>mov dword ptr ds:[esi+0x30C],0x85
而给[esi+0x30C]赋值的条件是[esi+0x30C] == 0x3E,继续查看哪个地方写入了0x3E。
编辑框的chkcode函数中发现赋值操作。
而赋值条件是Call CKme.00403C。
到此知道了整个注册流程:
a.输入正确的Key
b.单击图片框
c.双击图片框,会显示图片,提示注册成功
单步运行到00457D35时,数据窗口中跟随edx就可以看到正确的Key了:“黑头Sun Brird8dseloffc-012-OK123”
此时可以分析内存关系,可以写内存注册机了。
3、内存注册机
从[ebx + 0x318]开始,向上层函数查。
00457C56 |. 8BD8 mov ebx,eax ; ebx的值由eax得来,eax是上层函数传的参数
00428185 . 8B83 C4010000 mov eax,dword ptr ds:[ebx+0x1C4] ; eax <- [ebx+0x1C4]
00428181 . 8BD8 mov ebx,eax ; ebx <- eax
此处省略几步。。。
004281FC . 53 push ebx
004281FD . 56 push esi
004281FE . 8BF2 mov esi,edx
00428200 . 8BD8 mov ebx,eax ; ebx <- eax 到这里猜想eax可能就没变化了。所以先试试。此时eax=0x02275028
00428202 . 8BD6 mov edx,esi
00428204 . 8BC3 mov eax,ebx ; 这一层相关的是eax, eax <- ebx, 再往上看
姑且把eax的值当做基值。
内存注册机:
#include <iostream>
#include <windows.h>
using namespace std;
int main(){
int pid;
int Base_adr = 0x02275028; //基值
int Key_adr;
int tmp_adr;
char key[50] = {0};
int num = 0;
unsigned char tmp;
printf("PID:");
scanf("%d", &pid);
HANDLE hProcess = OpenProcess(2035711, false, pid);
if(hProcess == NULL)
cout << "Error!" << endl;
else{
ReadProcessMemory(hProcess, (LPCVOID)(Base_adr+0x1C4), &tmp_adr, 4, NULL); //第一层关系
ReadProcessMemory(hProcess, (LPCVOID)(tmp_adr+0x318), &Key_adr, 4, NULL); //正确的Key的地址
ReadProcessMemory(hProcess, (LPCVOID)Key_adr, &tmp, 1, NULL);
while(tmp != 0 && num < 50){ //循环取值,取到0表示结束
key[num++] = tmp;
ReadProcessMemory(hProcess, (LPCVOID)Key_adr+num, &tmp, 1, NULL);
}
}
// printf("%X\n", Key_adr);
printf("%s\n", key); //当前name正确的key
return 0;
}
输入正确的Key后,单击双击操作后显示图片。
但这个内存注册机只对分析出基值的程序有效,上面所谓的基值新开一个程序就变了。好像和下面这个模块有关。
如果这个地址显示为02230000,则上面代码中基值应该是0x02235028。 022C0000则基值为0x022C5028 。暂时不知道解决办法。有机会再解决。
补:
查了一波资料,可以吧内存注册机当做一个小的调试器,以Debug方式创建进程,在Key漏出来的地方下断点,然后读取相关寄存器的值。
源码:
#include <iostream>
#include <windows.h>
using namespace std;
int main() {
char exePath[100] = {"C:\\CKme.exe"};
int int3_adr = 0x00457D35; // 设置断点的地方
unsigned char CC = 0xCC;
unsigned char E8 = 0xE8;
STARTUPINFO si;
PROCESS_INFORMATION pi;
DEBUG_EVENT devent;
CONTEXT context;
char key[50] = {0};
char tmp = 0;
int num = 0;
// LPCONTEXT lpContext;
ZeroMemory( &si, sizeof(si));
si.cb = sizeof(STARTUPINFO);
si.dwFlags = STARTF_USESHOWWINDOW;
// si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
si.wShowWindow = SW_SHOW;
ZeroMemory( &pi, sizeof(pi) );
// cout << "File Path:";
// scanf("%s", exePath);
if(CreateProcess(exePath,
NULL, NULL, NULL,
FALSE,
DEBUG_PROCESS,
NULL, NULL, &si, &pi)){ // 以Debug方式创建进程
cout << "OK" << endl;
while(TRUE){
if(WaitForDebugEvent(&devent, INFINITE)){ //等待Debug事件
// cout << "Lai MI" << endl;
switch(devent.dwDebugEventCode){ // 过滤事件
case CREATE_PROCESS_DEBUG_EVENT:
// cout << pi.dwProcessId << endl;
WriteProcessMemory(pi.hProcess, (LPVOID)int3_adr, &CC, 1, NULL); //进程被创建时下断点
break;
case EXCEPTION_DEBUG_EVENT:
// SuspendThread(pi.hThread);
if(devent.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT){ // 判断异常信息(int 3 断点信息为0x80000003)
SuspendThread(pi.hThread); // 暂停线程
context.ContextFlags = CONTEXT_CONTROL; // 此Flags表示get或set Eip,Ebp...等寄存器
if(GetThreadContext(pi.hThread, &context)){
cout << "Eip:" << hex << context.Eip << endl;
if(context.Eip != int3_adr + 1){ // 判断异常的地方是否是我们想要的地方
// cout << hex << devent.u.Exception.ExceptionRecord.ExceptionCode << endl;
ResumeThread(pi.hThread); // 其他地方的断点直接恢复线程
}else{
context.ContextFlags = CONTEXT_INTEGER; // 此Flags表示get或set Eax,Ebx...等寄存器
if(GetThreadContext(pi.hThread, &context)){ // 这里我们要get Edx的值,Edx指向了注册码
cout << "Edx:" << hex << context.Edx << endl;
ReadProcessMemory(pi.hProcess, (LPCVOID)context.Edx, &tmp, 1, NULL);
while(tmp != 0 && num < 50){
key[num++] = tmp;
ReadProcessMemory(pi.hProcess, (LPCVOID)(context.Edx+num), &tmp, 1, NULL);
}
cout << "This is the registration code:";
cout << key << endl; //输出注册码
}
WriteProcessMemory(pi.hProcess, (LPVOID)int3_adr, &E8, 1, NULL); // 还原断点
context.ContextFlags = CONTEXT_CONTROL;
context.Eip -= 1;
SetThreadContext(pi.hThread, &context); // 还原 Eip
ResumeThread(pi.hThread); // 恢复线程
}
}
}
break;
}
ContinueDebugEvent(devent.dwProcessId, devent.dwThreadId, DBG_CONTINUE); // 继续Debug事件
}else{
cout << "Error";
cout << GetLastError();
return 0;
}
}
}else{
cout << "Error";
cout << GetLastError();
}
return 0;
}
效果图:程序运行起来后先随便几个字符。
4、追码
00457C40 /. 55 push ebp ; chkcode 真正判断函数
00457C41 |. 8BEC mov ebp,esp
00457C43 |. 51 push ecx
00457C44 |. B9 05000000 mov ecx,0x5
00457C49 |> 6A 00 /push 0x0
00457C4B |. 6A 00 |push 0x0
00457C4D |. 49 |dec ecx
00457C4E |.^ 75 F9 \jnz XCKme.00457C49
00457C50 |. 51 push ecx
00457C51 |. 874D FC xchg [local.1],ecx
00457C54 |. 53 push ebx
00457C55 |. 56 push esi
00457C56 |. 8BD8 mov ebx,eax
00457C58 |. 33C0 xor eax,eax
00457C5A |. 55 push ebp
00457C5B |. 68 3D7E4500 push CKme.00457E3D
00457C60 |. 64:FF30 push dword ptr fs:[eax]
00457C63 |. 64:8920 mov dword ptr fs:[eax],esp
00457C66 |. 8BB3 F8020000 mov esi,dword ptr ds:[ebx+0x2F8] ; name的长度?
00457C6C |. 83C6 05 add esi,0x5
00457C6F |. FFB3 10030000 push dword ptr ds:[ebx+0x310]
00457C75 |. 8D55 F8 lea edx,[local.2]
00457C78 |. 8BC6 mov eax,esi
00457C7A |. E8 85FEFAFF call CKme.00407B04
00457C7F |. FF75 F8 push [local.2] ; 参数1:str(len(name)+5)
00457C82 |. FFB3 14030000 push dword ptr ds:[ebx+0x314] ; 参数2:'dseloffc-012-OK'
00457C88 |. 8D55 F4 lea edx,[local.3]
00457C8B |. 8B83 D4020000 mov eax,dword ptr ds:[ebx+0x2D4]
00457C91 |. E8 B2B6FCFF call CKme.00423348 ; 获取name
00457C96 |. FF75 F4 push [local.3] ; 参数3:name
00457C99 |. 8D83 18030000 lea eax,dword ptr ds:[ebx+0x318] ; 参数4:目标地址
00457C9F |. BA 04000000 mov edx,0x4 ; 参数5:4
00457CA4 |. E8 93BFFAFF call CKme.00403C3C ; 连接字符串
00457CA9 |. 33D2 xor edx,edx
00457CAB |. 8B83 F4020000 mov eax,dword ptr ds:[ebx+0x2F4]
这一段还不是太明白,只知道大致效果是这样。
写注册机:
name = input("Name:")
key = '黑头Sun Bird' + str(len(name)+5) + 'dseloffc-012-OK' + name
print('Key:', key, sep='')