我想调用驻留在第 3 方 .exe 中的函数并获取其结果。似乎应该有办法,只要我知道函数地址,调用约定等......但我不知道如何。
有谁知道我会怎么做?
我意识到任何解决方案都是非标准的破解,但必须有办法!
我的非恶意用例:我正在为我的软件逆向工程文件格式。这个函数的计算太复杂了,我的小脑袋想不通;我已经能够将汇编代码直接拉到我自己的 DLL 中进行测试,但我当然不能发布它,因为那会偷窃。我将假设用户已经预先安装了这个特定的应用程序,所以我的软件可以运行。
我想调用驻留在第 3 方 .exe 中的函数并获取其结果。似乎应该有办法,只要我知道函数地址,调用约定等......但我不知道如何。
有谁知道我会怎么做?
我意识到任何解决方案都是非标准的破解,但必须有办法!
我的非恶意用例:我正在为我的软件逆向工程文件格式。这个函数的计算太复杂了,我的小脑袋想不通;我已经能够将汇编代码直接拉到我自己的 DLL 中进行测试,但我当然不能发布它,因为那会偷窃。我将假设用户已经预先安装了这个特定的应用程序,所以我的软件可以运行。
这是可能的,但并非微不足道。是的,这是一个非常肮脏的黑客行为。
在某些情况下,使用 LoadLibrary 加载 EXE 文件就足够了。返回的 HMODULE 实际上是加载的 EXE 的基地址。将其转换为合适的 int 类型,将您的相对函数地址添加到其中,将其转换回函数指针并通过该指针调用函数。
不幸的是,EXE 文件的重定位信息可能会被剥离。这意味着 EXE 将期望从特定地址运行。在这种情况下,您必须更改自己程序的基地址以避免冲突。查看链接器的文档,应该有一个选项可以做到这一点。之后,LoadLibrary 会将 EXE 加载到其首选的基地址中,希望一切正常。
这里有一些非常有用的信息。请务必检查页面末尾的更新,以了解在某些情况下可能会更好的不同技术。
编辑:正如亚历克斯在下面的评论中正确指出的那样,如果函数依赖于一些初始化值,或者它调用这样的函数,包括大多数 C 运行时函数,那么让它工作起来会困难得多。可以识别初始化函数并预先调用它们,但在这些情况下使用调试 API可能是您最好的选择。
好的,我已经组装了一个原型。
该程序创建另一个自身实例作为已调试的子进程。
在 main() 和 CRT 初始化代码之前会遇到一个自动断点。这是我们可以更改被调试进程的内存和寄存器以使其执行感兴趣的功能的时候。这就是程序所做的。
它试图捕捉和处理所有不良情况(例如意外异常)并将它们报告为错误。
一种糟糕的情况实际上是一种好的情况。这是程序放入被调试进程的 UD2 指令中的#UD 异常。它使用此#UD 在感兴趣的函数返回后停止流程执行。
还有一些注意事项:
此代码仅为 32 位。我什至没有尝试让它 64 位可编译或支持 64 位子进程。
此代码可能会泄漏句柄。请参阅 MSDN 上的 Windows Debug API 函数描述以找出需要关闭它们的位置。
此代码仅是概念证明,不支持通过 EAX、ECX 和 EDX 以外的指针或寄存器传递和返回数据。您必须根据需要扩展它。
此代码需要一些权限才能创建和完全调试进程。如果您的程序的用户不是管理员,您可能不得不担心这一点。
享受。
代码:
// file: unexported.c
//
// compile with Open Watcom C/C++: wcl386 /q /wx /we /s unexported.c
// (Note: "/s" is needed to avoid stack check calls from the "unexported"
// functions, these calls are through a pointer, and it'll be
// uninitialized in our case.)
//
// compile with MinGW gcc 4.6.2: gcc unexported.c -o unexported.exe
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <limits.h>
#ifndef C_ASSERT
#define C_ASSERT(expr) extern char CAssertExtern[(expr)?1:-1]
#endif
// Compile as a 32-bit app only.
C_ASSERT(sizeof(void*) * CHAR_BIT == 32);
#define EXC_CODE_AND_NAME(X) { X, #X }
const struct
{
DWORD Code;
PCSTR Name;
} ExcCodesAndNames[] =
{
EXC_CODE_AND_NAME(EXCEPTION_ACCESS_VIOLATION),
EXC_CODE_AND_NAME(EXCEPTION_ARRAY_BOUNDS_EXCEEDED),
EXC_CODE_AND_NAME(EXCEPTION_BREAKPOINT),
EXC_CODE_AND_NAME(EXCEPTION_DATATYPE_MISALIGNMENT),
EXC_CODE_AND_NAME(EXCEPTION_FLT_DENORMAL_OPERAND),
EXC_CODE_AND_NAME(EXCEPTION_FLT_DIVIDE_BY_ZERO),
EXC_CODE_AND_NAME(EXCEPTION_FLT_INEXACT_RESULT),
EXC_CODE_AND_NAME(EXCEPTION_FLT_INVALID_OPERATION),
EXC_CODE_AND_NAME(EXCEPTION_FLT_OVERFLOW),
EXC_CODE_AND_NAME(EXCEPTION_FLT_STACK_CHECK),
EXC_CODE_AND_NAME(EXCEPTION_FLT_UNDERFLOW),
EXC_CODE_AND_NAME(EXCEPTION_ILLEGAL_INSTRUCTION),
EXC_CODE_AND_NAME(EXCEPTION_IN_PAGE_ERROR),
EXC_CODE_AND_NAME(EXCEPTION_INT_DIVIDE_BY_ZERO),
EXC_CODE_AND_NAME(EXCEPTION_INT_OVERFLOW),
EXC_CODE_AND_NAME(EXCEPTION_INVALID_DISPOSITION),
EXC_CODE_AND_NAME(EXCEPTION_NONCONTINUABLE_EXCEPTION),
EXC_CODE_AND_NAME(EXCEPTION_PRIV_INSTRUCTION),
EXC_CODE_AND_NAME(EXCEPTION_SINGLE_STEP),
EXC_CODE_AND_NAME(EXCEPTION_STACK_OVERFLOW),
EXC_CODE_AND_NAME(EXCEPTION_GUARD_PAGE),
EXC_CODE_AND_NAME(DBG_CONTROL_C),
{ 0xE06D7363, "C++ EH exception" }
};
PCSTR GetExceptionName(DWORD code)
{
DWORD i;
for (i = 0; i < sizeof(ExcCodesAndNames) / sizeof(ExcCodesAndNames[0]); i++)
{
if (ExcCodesAndNames[i].Code == code)
{
return ExcCodesAndNames[i].Name;
}
}
return "?";
}
typedef enum tCallConv
{
CallConvCdecl, // Params on stack; caller removes params
CallConvStdCall, // Params on stack; callee removes params
CallConvFastCall // Params in ECX, EDX and on stack; callee removes params
} tCallConv;
DWORD Execute32bitFunctionFromExe(PCSTR ExeName,
int FunctionAddressIsRelative,
DWORD FunctionAddress,
tCallConv CallConvention,
DWORD CodeDataStackSize,
ULONG64* ResultEdxEax,
DWORD DwordParamsCount,
.../* DWORD params */)
{
STARTUPINFO startupInfo;
PROCESS_INFORMATION processInfo;
DWORD dwContinueStatus = DBG_CONTINUE; // exception continuation
DEBUG_EVENT dbgEvt;
UCHAR* procMem = NULL;
DWORD breakPointCount = 0;
DWORD err = ERROR_SUCCESS;
DWORD ecxEdxParams[2] = { 0, 0 };
DWORD imageBase = 0;
CONTEXT ctx;
va_list ap;
va_start(ap, DwordParamsCount);
*ResultEdxEax = 0;
memset(&startupInfo, 0, sizeof(startupInfo));
startupInfo.cb = sizeof(startupInfo);
memset(&processInfo, 0, sizeof(processInfo));
if (!CreateProcess(
NULL,
(LPSTR)ExeName,
NULL,
NULL,
FALSE,
DEBUG_ONLY_THIS_PROCESS, // DEBUG_PROCESS,
NULL,
NULL,
&startupInfo,
&processInfo))
{
printf("CreateProcess() failed with error 0x%08X\n",
err = GetLastError());
goto Cleanup;
}
printf("Process 0x%08X (0x%08X) \"%s\" created,\n"
" Thread 0x%08X (0x%08X) created\n",
processInfo.dwProcessId,
processInfo.hProcess,
ExeName,
processInfo.dwThreadId,
processInfo.hThread);
procMem = VirtualAllocEx(
processInfo.hProcess,
NULL,
CodeDataStackSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
if (procMem == NULL)
{
printf("VirtualAllocEx() failed with error 0x%08X\n",
err = GetLastError());
goto Cleanup;
}
printf("Allocated RWX memory in process 0x%08X (0x%08X) "
"at address 0x%08X\n",
processInfo.dwProcessId,
processInfo.hProcess,
procMem);
while (dwContinueStatus)
{
// Wait for a debugging event to occur. The second parameter indicates
// that the function does not return until a debugging event occurs.
if (!WaitForDebugEvent(&dbgEvt, INFINITE))
{
printf("WaitForDebugEvent() failed with error 0x%08X\n",
err = GetLastError());
goto Cleanup;
}
// Process the debugging event code.
switch (dbgEvt.dwDebugEventCode)
{
case EXCEPTION_DEBUG_EVENT:
// Process the exception code. When handling
// exceptions, remember to set the continuation
// status parameter (dwContinueStatus). This value
// is used by the ContinueDebugEvent function.
printf("%s (%s) Exception in process 0x%08X, thread 0x%08X\n"
" Exc. Code = 0x%08X (%s), Instr. Address = 0x%08X",
dbgEvt.u.Exception.dwFirstChance ?
"First Chance" : "Last Chance",
dbgEvt.u.Exception.ExceptionRecord.ExceptionFlags ?
"non-continuable" : "continuable",
dbgEvt.dwProcessId,
dbgEvt.dwThreadId,
dbgEvt.u.Exception.ExceptionRecord.ExceptionCode,
GetExceptionName(dbgEvt.u.Exception.ExceptionRecord.ExceptionCode),
dbgEvt.u.Exception.ExceptionRecord.ExceptionAddress);
if (dbgEvt.u.Exception.ExceptionRecord.ExceptionCode ==
EXCEPTION_ACCESS_VIOLATION)
{
ULONG_PTR* info = dbgEvt.u.Exception.ExceptionRecord.ExceptionInformation;
printf(",\n Access Address = 0x%08X, Access = 0x%08X (%s)",
(DWORD)info[1],
(DWORD)info[0],
(info[0] == 0) ?
"read" : ((info[0] == 1) ? "write" : "execute")); // 8 = DEP
}
printf("\n");
// Get the thread context (register state).
// We'll need to either display it (in case of unexpected exceptions) or
// modify it (to execute our code) or read it (to get the results of
// execution).
memset(&ctx, 0, sizeof(ctx));
ctx.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL;
if (!GetThreadContext(processInfo.hThread, &ctx))
{
printf("GetThreadContext() failed with error 0x%08X\n",
err = GetLastError());
goto Cleanup;
}
#if 0
printf(" EAX=0x%08X EBX=0x%08X ECX=0x%08X EDX=0x%08X EFLAGS=0x%08X\n"
" ESI=0x%08X EDI=0x%08X EBP=0x%08X ESP=0x%08X EIP=0x%08X\n",
ctx.Eax, ctx.Ebx, ctx.Ecx, ctx.Edx, ctx.EFlags,
ctx.Esi, ctx.Edi, ctx.Ebp, ctx.Esp, ctx.Eip);
#endif
if (dbgEvt.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT &&
breakPointCount == 0)
{
// Update the context so our code can be executed
DWORD mem, i, data;
SIZE_T numberOfBytesCopied;
mem = (DWORD)procMem + CodeDataStackSize;
// Child process memory layout (inside the procMem[] buffer):
//
// higher
// addresses
// .
// . UD2 instruction (causes #UD, indicator of successful
// . completion of FunctionAddress())
// .
// . last on-stack parameter for FunctionAddress()
// . ...
// . first on-stack parameter for FunctionAddress()
// .
// . address of UD2 instruction (as if "call FunctionAddress"
// . executed just before it and is going to return to UD2)
// . (ESP will point here)
// .
// . FunctionAddress()'s stack
// .
// lower
// addresses
mem -= 2;
data = 0x0B0F; // 0x0F, 0x0B = UD2 instruction
if (!WriteProcessMemory(processInfo.hProcess,
(PVOID)mem,
&data,
2,
&numberOfBytesCopied))
{
ErrWriteMem1:
printf("WriteProcessMemory() failed with error 0x%08X\n",
err = GetLastError());
goto Cleanup;
}
else if (numberOfBytesCopied != 2)
{
ErrWriteMem2:
printf("WriteProcessMemory() failed with error 0x%08X\n",
err = ERROR_BAD_LENGTH);
goto Cleanup;
}
// Copy function parameters.
mem &= 0xFFFFFFFC; // align the address for the stack
for (i = 0; i < DwordParamsCount; i++)
{
if (CallConvention == CallConvFastCall && i < 2)
{
ecxEdxParams[i] = va_arg(ap, DWORD);
}
else
{
data = va_arg(ap, DWORD);
if (!WriteProcessMemory(processInfo.hProcess,
(DWORD*)mem - DwordParamsCount + i,
&data,
sizeof(data),
&numberOfBytesCopied))
{
goto ErrWriteMem1;
}
else if (numberOfBytesCopied != sizeof(data))
{
goto ErrWriteMem2;
}
}
}
// Adjust what will become ESP according to the number of on-stack parameters.
for (i = 0; i < DwordParamsCount; i++)
{
if (CallConvention != CallConvFastCall || i >= 2)
{
mem -= 4;
}
}
// Store the function return address.
mem -= 4;
data = (DWORD)procMem + CodeDataStackSize - 2; // address of UD2
if (!WriteProcessMemory(processInfo.hProcess,
(PVOID)mem,
&data,
sizeof(data),
&numberOfBytesCopied))
{
goto ErrWriteMem1;
}
else if (numberOfBytesCopied != sizeof(data))
{
goto ErrWriteMem2;
}
// Last-minute preparations for execution...
// Set up the registers (ECX, EDX, EFLAGS, EIP, ESP).
if (CallConvention == CallConvFastCall)
{
if (DwordParamsCount >= 1) ctx.Ecx = ecxEdxParams[0];
if (DwordParamsCount >= 2) ctx.Edx = ecxEdxParams[1];
}
ctx.EFlags &= ~(1 << 10); // clear DF for string instructions
ctx.Eip = FunctionAddress + imageBase * !!FunctionAddressIsRelative;
ctx.Esp = mem;
if (!SetThreadContext(processInfo.hThread, &ctx))
{
printf("SetThreadContext() failed with error 0x%08X\n",
err = GetLastError());
goto Cleanup;
}
printf("Copied code/data to the process\n");
#if 0
for (i = esp; i < (DWORD)procMem + CodeDataStackSize; i++)
{
data = 0;
ReadProcessMemory(processInfo.hProcess,
(void*)i,
&data,
1,
&numberOfBytesCopied);
printf("E[SI]P = 0x%08X: 0x%02X\n", i, data);
}
#endif
breakPointCount++;
dwContinueStatus = DBG_CONTINUE; // continue execution of our code
}
else if (dbgEvt.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION &&
breakPointCount == 1 &&
ctx.Eip == (DWORD)procMem + CodeDataStackSize - 2/*UD2 size*/)
{
// The code has finished execution as expected.
// Collect the results.
*ResultEdxEax = ((ULONG64)ctx.Edx << 32) | ctx.Eax;
printf("Copied code/data from the process\n");
dwContinueStatus = 0; // stop debugging
}
else
{
// Unexpected event. Do not continue execution.
printf(" EAX=0x%08X EBX=0x%08X ECX=0x%08X EDX=0x%08X EFLAGS=0x%08X\n"
" ESI=0x%08X EDI=0x%08X EBP=0x%08X ESP=0x%08X EIP=0x%08X\n",
ctx.Eax, ctx.Ebx, ctx.Ecx, ctx.Edx, ctx.EFlags,
ctx.Esi, ctx.Edi, ctx.Ebp, ctx.Esp, ctx.Eip);
err = dbgEvt.u.Exception.ExceptionRecord.ExceptionCode;
goto Cleanup;
}
break; // case EXCEPTION_DEBUG_EVENT:
case CREATE_PROCESS_DEBUG_EVENT:
// As needed, examine or change the registers of the
// process's initial thread with the GetThreadContext and
// SetThreadContext functions; read from and write to the
// process's virtual memory with the ReadProcessMemory and
// WriteProcessMemory functions; and suspend and resume
// thread execution with the SuspendThread and ResumeThread
// functions. Be sure to close the handle to the process image
// file with CloseHandle.
printf("Process 0x%08X (0x%08X) "
"created, base = 0x%08X,\n"
" Thread 0x%08X (0x%08X) created, start = 0x%08X\n",
dbgEvt.dwProcessId,
dbgEvt.u.CreateProcessInfo.hProcess,
dbgEvt.u.CreateProcessInfo.lpBaseOfImage,
dbgEvt.dwThreadId,
dbgEvt.u.CreateProcessInfo.hThread,
dbgEvt.u.CreateProcessInfo.lpStartAddress);
// Found image base!
imageBase = (DWORD)dbgEvt.u.CreateProcessInfo.lpBaseOfImage;
dwContinueStatus = DBG_CONTINUE;
break;
case EXIT_PROCESS_DEBUG_EVENT:
// Display the process's exit code.
printf("Process 0x%08X exited, exit code = 0x%08X\n",
dbgEvt.dwProcessId,
dbgEvt.u.ExitProcess.dwExitCode);
// Unexpected event. Do not continue execution.
err = ERROR_PROC_NOT_FOUND;
goto Cleanup;
case CREATE_THREAD_DEBUG_EVENT:
case EXIT_THREAD_DEBUG_EVENT:
case LOAD_DLL_DEBUG_EVENT:
case UNLOAD_DLL_DEBUG_EVENT:
case OUTPUT_DEBUG_STRING_EVENT:
dwContinueStatus = DBG_CONTINUE;
break;
case RIP_EVENT:
printf("RIP: Error = 0x%08X, Type = 0x%08X\n",
dbgEvt.u.RipInfo.dwError,
dbgEvt.u.RipInfo.dwType);
// Unexpected event. Do not continue execution.
err = dbgEvt.u.RipInfo.dwError;
goto Cleanup;
} // end of switch (dbgEvt.dwDebugEventCode)
// Resume executing the thread that reported the debugging event.
if (dwContinueStatus)
{
if (!ContinueDebugEvent(dbgEvt.dwProcessId,
dbgEvt.dwThreadId,
dwContinueStatus))
{
printf("ContinueDebugEvent() failed with error 0x%08X\n",
err = GetLastError());
goto Cleanup;
}
}
} // end of while (dwContinueStatus)
err = ERROR_SUCCESS;
Cleanup:
if (processInfo.hProcess != NULL)
{
if (procMem != NULL)
{
VirtualFreeEx(processInfo.hProcess, procMem, 0, MEM_RELEASE);
}
TerminateProcess(processInfo.hProcess, 0);
CloseHandle(processInfo.hThread);
CloseHandle(processInfo.hProcess);
}
va_end(ap);
return err;
}
int __cdecl FunctionCdecl(int x, int y, int z)
{
return x + y + z;
}
int __stdcall FunctionStdCall(int x, int y, int z)
{
return x * y * z;
}
ULONG64 __fastcall FunctionFastCall(DWORD x, DWORD y, DWORD z)
{
return (ULONG64)x * y + z;
}
int main(int argc, char** argv)
{
DWORD err;
ULONG64 resultEdxEax;
err = Execute32bitFunctionFromExe(argv[0]/*ExeName*/,
1/*FunctionAddressIsRelative*/,
(DWORD)&FunctionCdecl -
(DWORD)GetModuleHandle(NULL),
CallConvCdecl,
4096/*CodeDataStackSize*/,
&resultEdxEax,
3/*DwordParamsCount*/,
2, 3, 4);
if (err == ERROR_SUCCESS)
printf("2 + 3 + 4 = %d\n", (int)resultEdxEax);
err = Execute32bitFunctionFromExe(argv[0]/*ExeName*/,
1/*FunctionAddressIsRelative*/,
(DWORD)&FunctionStdCall -
(DWORD)GetModuleHandle(NULL),
CallConvStdCall,
4096/*CodeDataStackSize*/,
&resultEdxEax,
3/*DwordParamsCount*/,
-2, 3, 4);
if (err == ERROR_SUCCESS)
printf("-2 * 3 * 4 = %d\n", (int)resultEdxEax);
err = Execute32bitFunctionFromExe(argv[0]/*ExeName*/,
1/*FunctionAddressIsRelative*/,
(DWORD)&FunctionFastCall -
(DWORD)GetModuleHandle(NULL),
CallConvFastCall,
4096/*CodeDataStackSize*/,
&resultEdxEax,
3/*DwordParamsCount*/,
-1, -1, -1);
if (err == ERROR_SUCCESS)
printf("0xFFFFFFFF * 0xFFFFFFFF + 0xFFFFFFFF = 0x%llX\n",
(unsigned long long)resultEdxEax);
return 0;
}
输出:
Process 0x00001514 (0x00000040) "C:\MinGW\msys\1.0\home\Alex\unexported.exe" cre
ated,
Thread 0x00000CB0 (0x0000003C) created
Allocated RWX memory in process 0x00001514 (0x00000040) at address 0x002B0000
Process 0x00001514 (0x00000044) created, base = 0x00400000,
Thread 0x00000CB0 (0x00000048) created, start = 0x0040126C
First Chance (continuable) Exception in process 0x00001514, thread 0x00000CB0
Exc. Code = 0x80000003 (EXCEPTION_BREAKPOINT), Instr. Address = 0x77090FAB
Copied code/data to the process
First Chance (continuable) Exception in process 0x00001514, thread 0x00000CB0
Exc. Code = 0xC000001D (EXCEPTION_ILLEGAL_INSTRUCTION), Instr. Address = 0x002
B0FFE
Copied code/data from the process
2 + 3 + 4 = 9
Process 0x00001828 (0x0000003C) "C:\MinGW\msys\1.0\home\Alex\unexported.exe" cre
ated,
Thread 0x00001690 (0x00000040) created
Allocated RWX memory in process 0x00001828 (0x0000003C) at address 0x002B0000
Process 0x00001828 (0x0000006C) created, base = 0x00400000,
Thread 0x00001690 (0x00000074) created, start = 0x0040126C
First Chance (continuable) Exception in process 0x00001828, thread 0x00001690
Exc. Code = 0x80000003 (EXCEPTION_BREAKPOINT), Instr. Address = 0x77090FAB
Copied code/data to the process
First Chance (continuable) Exception in process 0x00001828, thread 0x00001690
Exc. Code = 0xC000001D (EXCEPTION_ILLEGAL_INSTRUCTION), Instr. Address = 0x002
B0FFE
Copied code/data from the process
-2 * 3 * 4 = -24
Process 0x00001388 (0x00000040) "C:\MinGW\msys\1.0\home\Alex\unexported.exe" cre
ated,
Thread 0x00001098 (0x0000003C) created
Allocated RWX memory in process 0x00001388 (0x00000040) at address 0x002B0000
Process 0x00001388 (0x0000008C) created, base = 0x00400000,
Thread 0x00001098 (0x00000090) created, start = 0x0040126C
First Chance (continuable) Exception in process 0x00001388, thread 0x00001098
Exc. Code = 0x80000003 (EXCEPTION_BREAKPOINT), Instr. Address = 0x77090FAB
Copied code/data to the process
First Chance (continuable) Exception in process 0x00001388, thread 0x00001098
Exc. Code = 0xC000001D (EXCEPTION_ILLEGAL_INSTRUCTION), Instr. Address = 0x002
B0FFE
Copied code/data from the process
0xFFFFFFFF * 0xFFFFFFFF + 0xFFFFFFFF = 0xFFFFFFFF00000000
除了将 EXE 加载到您的进程中之外,还有两种更好的(IMO)方法:
1)使用调试API(或类似PyDbg)在调试器下启动目标,然后在堆栈中设置参数,将EIP设置为必要的地址,在返回地址设置断点,然后继续。
2)用一些IPC制作一个小DLL来与你的程序通信,将它注入目标(有几种方法可以做到,最简单的可能是键盘挂钩)并让它调用必要的代码。或者您也可以使用现有的也可以做到这一点,例如Intel 的 PIN。