Process-wide API spying - an ultimate hack

政府科技管理者如何提升科技成果转化项目进场交易的备案效率?.docx 政府科技管理者如何提升科技成果转化项目进场交易的备案效率? 立即下载
By Anton Bassov

Process-wide API spying. 

 

Abstract

API hooking and spying is not uncommon practice in Windows programming. Development of system monitoring and analysis tools heavily depends upon it. Numerous articles have been written on this subject – quite a few are even available on The Code Project. To be honest, I did not find these articles to be that much informative - they all seem to describe the techniques that were presented by Matt Pietrek and Jeffrey Richter a decade ago. Don’t get me wrong – I don’t want to say anything about the quality of these articles. The only thing I am saying is that their authors don’t seem to be describing programming tips and tricks of their own design.

This article presents an absolutely universal model of process-wide API spying solution, capable of hooking all API calls in any user-mode process of our choice, i.e. our spying model is not bound to any particular API at the compile time. Our implementation is limited to logging the return values of all API functions that are called by the target module. However, our model is extensible - you can add parameter logging as well. Our spying model is particularly useful for analyzing the internal working of third-party applications when the source code is not available. In addition to the universal process-wide spying model, we also present one more way to inject the DLL into the target process.

All the programming tricks, described in this article, are 100% of my own design, although, certainly, based upon the ideas that were first expressed by Matt Pietrek.

Introduction

Process-wide API hooking relies upon the technique of modifying entries in the Import Address Table (IAT) of the target executable module. First of all, you need to understand how imported functions are invoked – at the binary level, calling an imported function is different from intra-modular call. When you make an intra-modular call, the compiler generates the direct call instruction (0xE8 on Intel CPU), because the offset of function within the module, relative to the place from which it is called, is always known - even at the compile time. However, if the function is imported, its address is unknown at the compile time, although a guess can be made. Therefore, when you call the imported function, the compiler generates indirect (0xFF, 0x15 on Intel CPU), rather than direct, call instruction. When you call an imported function, the compiled code looks like following:

call        dword ptr 
[__imp__CreateWindowExA@48]

This instruction tells CPU to call the function, the address of which is stored in __imp__CreateWindowExA@48 memory location. At the load time, the loader will write the address of CreateWindowExA() to __imp__CreateWindowExA@48 memory location, and the above instruction, when executed, will invoke CreateWindowExA(). If we write the address of our user-defined function into __imp__CreateWindowExA@48 memory location at the run time, then all calls to CreateWindowExA() within the module will invoke our user-defined function, instead of CreateWindowExA(). Our user-defined function can log or validate parameters, and then call CreateWindowExA() directly by its address. Process-wide API hooking is based upon this idea.

The API spying solution normally consists of driver DLL, which actually does all the job of hooking and spying, and controller application, which injects the driver DLL into the target process. The driver DLL normally communicates with its controller application by window messages - WM_COPYDATA message is a convenient way to pass a small amount of data from one application to another.

The addresses of all functions, imported by the module, are stored in Import Address Table (IAT), every entry of which has the internal form of __imp__xxx. Once the driver DLL has been injected into the target process, it overwrites IAT entries of the target module with the addresses of user-defined proxy functions, implemented by the driver DLL. Each IAT entry replacement normally requires a separate proxy function - a proxy function must know which particular API function it replaces so that it can invoke the original callee. However, with some certain workaround, all IAT entry replacements can be serviced by a single proxy function - we will show you how this can be done. This is an ultimate hack, but such approach makes our model absolutely universal – we can hook all API calls in any user-mode process of our choice.

Locating the Import Address Table

In order to start spying, we have to locate the Import Address Table (IAT) of the target executable module. Therefore, we need a brief introduction to Portable Executable (PE) file format, which is the file format of any executable module or DLL. MSDN CD provides a very detailed description of Portable Executable (PE) file format, so we are not going too deeply into details here - we are mostly concerned with locating the Import Address Table of the target executable module.

PE file starts with 64-byte DOS file header (IMAGE_DOS_HEADER structure), followed by tiny DOS program which, in turn, is followed by 248-byte NT file header (IMAGE_NT_HEADERS structure). The offset to NT file header from the beginning of the file is given by e_lfanew field of IMAGE_DOS_HEADER structure. First 4 bytes of NT file header are file signature, followed by 20-byte IMAGE_FILE_HEADER structure, which, in turn, is followed by 224-byte IMAGE_OPTIONAL_HEADER structure. The code below obtains a pointer to IMAGE_OPTIONAL_HEADER structure (hMod is a module handle):

IMAGE_DOS_HEADER * 
dosheader=(IMAGE_DOS_HEADER *)hMod;
IMAGE_OPTIONAL_HEADER * opthdr =
  (IMAGE_OPTIONAL_HEADER *) ((BYTE*)hMod+dosheader->e_lfanew+24);

In actuality, IMAGE_OPTIONAL_HEADER is far from being optional – the information it contains is too important to be omitted. This includes the suggested base address of the module, size and base addresses of code and data, stack and heap configuration, the address of entry point, and, what we are mostly interested in, pointer to the table of directories. PE file reserves 16 so-called data directories. The most commonly seen directories are import, export, resource and relocation. We are mostly interested in import directory, which is just an array of IMAGE_IMPORT_DESCRIPTOR structures, with one structure corresponding to each imported module. The code below obtains a pointer to the first IMAGE_IMPORT_DESCRIPTOR structure in import directory:

IMAGE_IMPORT_DESCRIPTOR 
*descriptor=
      (IMAGE_IMPORT_DESCRIPTOR *)(BYTE*) hMod +
      opthdr->DataDirectory[ IMAGE_DIRECTORY_ENTRY_IMPORT]. 
VirtualAddress;

The first field of IMAGE_IMPORT_DESCRIPTOR structure holds an offset to the hint/name table, its last field holds an offset to the import address table. These two tables are of the same length, with one entry corresponding to each imported function. The code below lists all names and addresses of IAT entries for all functions imported by the module:

while(descriptor ->FirstThunk)
{
    char*dllname=(char*)((BYTE*)hMod+ descriptor ->Name);

    IMAGE_THUNK_DATA* thunk=( IMAGE_THUNK_DATA*)((BYTE*) hMod +
                                 descriptor ->OriginalFirstThunk);

    int x=0;
    while(thunk->u1.Function)
    {
        char*functionname=(char*)((BYTE*) hMod +
                ( DWORD)thunk->u1.AddressOfData+2);

        DWORD *IATentryaddress=( DWORD *)((BYTE*) hMod +
                descriptor->FirstThunk)+x;
        x++; thunk++;
    }

    descriptor++;
}

The inner loop retrieves function names and addresses of IAT entries for the imported module from IMAGE_IMPORT_DESCRIPTOR structure that corresponds to the given module; the outer loop just proceeds to the next imported module. As you can see, Import Address Table for the imported module is nothing more than just an array of DWORDs. All we have to do in order to start spying is to fill this array with the addresses of our user-defined proxy functions. As we promised, we will show you a trick that makes it possible for all IAT entry replacements to be serviced by a single proxy function.

Implementing the spying solution

Our spying team consists of 4 members - ProxyProlog(), Prolog(), ProxyEpilog() and Epilog(). As their names suggest, ProxyProlog() and Prolog() are invoked before the actual calee takes control; ProxyEpilog() and Epilog() are invoked after the actual calee returns. ProxyProlog() and ProxyEpilog() are implemented as naked assembly routines; Prolog() and Epilog() are just regular C functions. The actual spying job is done by Prolog() and Epilog(). The only task of ProxyProlog() and ProxyEpilog() is to save and restore CPU registers and flags before and after Prolog() and Epilog() perform their tasks – if we want the target process to keep on functioning properly, the whole process of spying must leave everything intact, at least as far as the API function and its client code are concerned.

Windows uses flat memory model, which means code and data reside in the single address space, rather than in separate segments. This implies we can fill an array with the machine instructions, and call it as a function. Look at the code below:

DWORD addr=(DWORD)&retbuff[6];
retbuff[0]=0xFF; retbuff[1]=0x15;
memmove (&retbuff[2],&addr,4);
addr=(DWORD)&ProxyEpilog;
memmove (&retbuff[6],&addr,4);

This is a 6-byte indirect call instruction. The first 2 bytes are occupied by the call instruction itself, and 4 bytes that follow are occupied by the operand - they hold the address of the variable that contains the address of ProxyEpilog(). In this particular case, this variable comes immediately after the 6-byte instruction. When the instruction pointer hits retbuff, our handcrafted code is going to call ProxyEpilog(). Call instruction implicitly pushes the address, to which the invoked routine must return control, on the stack – this is how the function knows its return address. In our case, the pointer to the variable that contains the address of ProxyEpilog() (the address of retbuff[6]) is going to be on top of the stack when ProxyEpilog() starts execution.

When DllMain() is called with fdwReason set to DLL_PROCESS_ATTACH, we fill retbuff array with the machine instructions (retbuff is a global BYTE array), dynamically allocate some memory, allocate Tls index, and store the memory we have allocated in the thread local storage. Every time DllMain() is called with fdwReason set to DLL_THREAD_ATTACH, it must dynamically allocate some memory and put it aside into thread local storage.

Now let’s look at how we overwrite IAT entries, after obtaining name and address of IAT entry for the given imported function:

struct RelocatedFunction{DWORD proxyptr;
    DWORD funtioncptr;char *dllname;char *functionname;};

BYTE* ptr=(BYTE*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,32);
RelocatedFunction * reloc=(RelocatedFunction*)&ptr[6];
DWORD addr=(DWORD)&ProxyProlog;
reloc->proxyptr=addr;
reloc->funcname= functionname;
reloc->dllname=dllname;
memmove (&reloc->functionptr, IATentryaddress,4);
ptr[0]= 0xFF; ptr[1]= 0x15; memmove(&ptr[2],&reloc,4);
DWORD byteswritten;
WriteProcessMemory(GetCurrentProcess(),IATentryaddress,&ptr,4,&byteswritten);

For each IAT entry replacement, we dynamically allocate an array, first 6 bytes of which are occupied by indirect call instruction, and 16 bytes that follow are processed as RelocatedFunction structure, first member of which is set to the address of ProxyProlog() (it definitely has to be the first). The other fields are set to the address and the name of the imported function, plus to the name of the DLL, from which the given function is being imported. First 2 bytes of the array are 0xFF and 0x15, and 4 bytes that follow contain the address of RelocatedFunctin structure. We replace each IAT entry with the address of such array - each IAT entry replacement requires a separate array.

As a result, every call to the API function will, in actuality, call our handcrafted code that calls ProxyProlog(). As we said, call instruction implicitly pushes on the stack the address, to which the invoked routine must return. In our case, the pointer to RelocatedFunction structure is going to be on top of the stack, and the original return address, i.e. the address to which the API function must return control, is going to be one stack entry below at the time when ProxyProlog() starts execution. Stack entries below the original return address are going to be occupied by the API function arguments. Now let’s look at ProxyProlog() and Prolog() implementations.

__declspec(naked)void ProxyProlog()
{

_asm{
push eax
push ebx
push ecx
push edx

mov ebx,esp
pushf
add ebx,16
push ebx
call Prolog

popf
pop edx
pop ecx
pop ebx
pop eax
ret
}

}

ProxyProlog() saves registers and CPU flags, pushes the value of ESP at the time when ProxyProlog() started execution, and calls Prolog(). As we said, the pointer to RelocatedFunction structure is on top of the stack, and the address to which the API function must return control, is one stack entry below at the time when ProxyProlog() starts execution. As a result, Prolog() receives a pointer to the stack location where the pointer to RelocatedFunction structure can be found, as an argument. By incrementing its argument, Prolog() can find a pointer to the stack location where the original return address is stored.

struct Storage{DWORD retaddress;RelocatedFunction* ptr;};

void __stdcall Prolog(DWORD * relocptr)
{

    //get pointer to RelocatedFunction structure
    RelocatedFunction * reloc=(RelocatedFunction*)relocptr[0];

    // get pointer to return address
    DWORD *retaddessptr=relocptr+1;


    // save pointer to RelocatedFunction structure and return address in tls
    DWORD *nestlevelptr=(DWORD *)TlsGetValue(tlsindex);
    DWORD nestlevel=nestlevelptr[0];
    Storage*storptr=(Storage*)&nestlevelptr[1];
    storptr[nestlevel].retaddress=(*retaddessptr);
    storptr[nestlevel].ptr=reloc;
    nestlevelptr[0]++;

    //place APi function pointer on top of the stack
    relocptr[0]=reloc->funcptr;

    //replace ProxyProlog()'s return address with retbuff
    retaddessptr[0]=(DWORD)&retbuff;

}

Prolog() saves the pointer to RelocatedFunction structure and the original return address in the thread local storage, which is organized as a DWORD, followed by the array of Storage structures. We treat this array as a stack – DWORD just indicates the number of stack entries, i.e. is just a counter. Prolog() saves the pointer to RelocatedFunction structure and the return address in the topmost stack entry, and increments the counter. After performing the above tasks, Prolog() modifies the CPU stack – the address of the API function obtained from RelocatedFunction structure, replaces the pointer to RelocatedFunction structure, and the address of retbuff global array which is filled with the machine instructions in DllMain(), replaces the original return address on the stack.

After Prolog() returns, ProxyProlog() restores registers and CPU flags. Prolog() has modified the CPU stack in such way that, after ProxyProlog() returns, the program flow jumps to the original calee, i.e. to the API function, upon the return of which the program flow jumps, instead of the original return address, to our handcrafted code that calls ProxyEpilog().

Let’s look at ProxyEpilog().

__declspec(naked)void ProxyEpilog()
{

_asm{
push eax
push ebx
push ecx
push edx

mov ebx,esp
pushf
add ebx,12
push ebx
all Epilog


popf
pop edx
pop ecx
pop ebx
pop eax
ret
}

}

Implementation of ProxyEpilog() is almost identical to that of ProxyProlog(). ProxyEpilog() saves registers and CPU flags, pushes the value of ESP at the time when EAX register was on top of the stack, and calls Epilog(). As a result, Epilog() receives a pointer to the stack location where the return value of the API function can be found, as an argument. By incrementing its argument, Epilog() can find a pointer to the stack location where the address, to which ProxyEpilog() must return, is stored. Let’s look at Epilog().

void  __stdcall 
Epilog(DWORD*retvalptr)
{

    //get pointer to ProxyEpilog()’s return address
    DWORD*retaddessptr=retvalptr+1;

    //get return value
    DWORD retval=retvalptr[0];

    //get the original return address and pointer to
    //RelocatedFunction structure from the topmost Storage entry in tls
    DWORD *nestlevelptr=(DWORD *)TlsGetValue(tlsindex);
    nestlevelptr[0]--;
    DWORD nestlevel=nestlevelptr[0];
    Storage*storptr=(Storage*)&nestlevelptr[1];
    RelocatedFunction * reloc=(RelocatedFunction*)storptr[nestlevel].ptr;

    // replace ProxyEpilog()’s return address with the original one
    retaddessptr[0]=storptr[nestlevel].retaddress;

    // pack all info into the buffer and
    // send it to the controller application
    DWORD id=GetCurrentThreadId();
    char buff[256];char smallbuff[8];char secsmallbuff[8];
    strcpy(buff, "Thread ");wsprintf(smallbuff,"%d/n",id);
    strcat(buff,smallbuff);strcat(buff," -  ");
    strcat(buff,reloc->dllname);strcat(buff,"!");
    strcat(buff,reloc->funcname);
    strcat(buff," -  ");
    strcat(buff,"returns ");
    wsprintf(secsmallbuff,"%d/n", retval);
    strcat(buff,secsmallbuff);

    COPYDATASTRUCT  data;data.cbData=1+strlen(buff);
    data.lpData=buff;data.dwData=WM_COPYDATA;
    SendMessage(wnd,WM_COPYDATA,(WPARAM) secwnd,(LPARAM) &data);

}

Epilog() gets the pointer to RelocatedFunction structure and the original return address from the topmost Storage structure in the thread local storage, and decrements the counter. Then Epilog() modifies the CPU stack – it replaces the address to which ProxyEpilog() must return, with the original return address. After performing the above tasks, Epilog() informs the controller application that the API function has returned – the name of the given function, as well as of the DLL that exports it, are available from RelocatedFunction structure, pointer to which was saved in the thread local storage, and the pointer to the return value of the API function is Epilog()’s argument. Epilog() provides the controller application with all the above information by sending WM_COPYDATA message to the controller window.

After Epilog() returns, ProxyEpilog() restores registers and CPU flags. Epilog() has modified the CPU stack in such a way that, after ProxyEpilog() returns, the program flow jumps to the address, to which the API function was supposed to return control if no “espionage” was taking place. As you can see, all our “spying activity” cannot disrupt the program execution in any possible way, because it leaves CPU stack, registers and flags intact, at least as far as the API function and its client code are concerned. Our “spying team” does not care which API function to spy on - our model is absolutely universal, because our implementation is not bound to any particular API function at the compile time. Furthermore, our model is suitable for spying in multithreaded environment, because we save all necessary data in the thread local storage.

For the time being, our model is suitable only for listing all API calls and for logging the return values of API functions. If you want to add parameter logging or validation, it can easily be done - the API function arguments are just below the original return address on the CPU stack. However, you must provide our “spying team” with the argument lists of the target API functions – unfortunately, there is no way to obtain this information from the PE file. The solution to this problem lies with the enhanced communication between the controller application and the spying DLL - the controller application can always get the description of arguments of the target API function from the user, and provide the DLL with this information at run time. Apparently, RelocatedFunction structure would require one more data member, i.e. a pointer to some array that contains the description of arguments, so that Prolog() would be able to examine the arguments. We leave it for you to decide how to do it.

Warning: In case if your target executable module dynamically links to C run-time library, don’t try to hook the functions that are imported from MSVCRT.dll. Instead, you should hook the API calls that C run-time library makes, i.e. overwrite the Import Address Table of MSVCRT.dll’s module.

Therefore, we are able to hook all API calls that are made by the target executable module, i.e.outgoing calls. What about the opposite task, i.e. hooking all incoming calls to some particular DLL module (say, kernel32.dll ), made by all modules that are loaded into the address space of the target process, including system DLLs?

HOOKING ALL CALLS TO DLL MODULE, MADE BY THE TARGET PROCESS

Once we know that process-wide API hooking can be achieved by modifying IAT entries of the target executable module, the answer to this question must be obvious. All we have to do is to walk through all modules that are currently loaded into the address space of the target process, and, in each loaded module, overwrite IAT entries of all functions that are imported from kernel32.dll. As a result, we will hook all calls that are made to kernel32.dll by all modules that are currently loaded into the address space of the target process.

Unfortunately, this is only the partial solution. The problem is that any modification of IAT entries in the module affects only the given module. Hence, even if we hook all calls to kernel32.dll in all currently loaded modules, any module that is subsequently loaded into the address space of the target process is not going to be affected – all calls to kernel32.dll , made by such module, will remain unhooked.

In order to get a real solution, in addition to above mentioned overwriting of IAT entries in all currently loaded modules, we must also overwrite IMAGE_EXPORT_DIRECTORYof kernel32.dll itself. If we overwrite IMAGE_EXPORT_DIRECTORY of kernel32.dll, all future loading of DLLs into the target process will link with our proxy functions, although all currently loaded modules are not going to be affected. By combining the modification of IATs of all currently loaded modules with overwriting the IMAGE_EXPORT_DIRECTORY of kernel32.dll itself, we will hook all calls that are made to kernel32.dll by absolutely all (including yet-to-be-loaded) modules in the address space of the target process. Don’t confuse it with system-wide spying – apart from the target process, all other processes in the system will stay intact.

All information about the functions, exported by DLL module, can be found in IMAGE_EXPORT_DIRECTORY structure, which is accessible via IMAGE_OPTIONAL_HEADER structure. The code below obtains a pointer to IMAGE_EXPORT_DIRECTORY structure (hMod is kernel32.dll module's handle):

IMAGE_DOS_HEADER * dosheader=(IMAGE_DOS_HEADER *)hMod;
IMAGE_OPTIONAL_HEADER * opthdr =(IMAGE_OPTIONAL_HEADER *) 
((BYTE*)hMod+dosheader->e_lfanew+24);
IMAGE_EXPORT_DIRECTORY *exp=(IMAGE_EXPORT_DIRECTORY *)((BYTE*) hMod 
+opthdr->DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT]. VirtualAddress);

IMAGE_EXPORT_DIRECTORY contains the information about the addresses, names and ordinal values of all functions that are exported from the given DLL. The address table is an ULONG array that holds the addresses of all exported functions, name table is an ULONG array that holds the addresses of function name strings, and the ordinal table is an USHORT array that holds the difference between the real ordinal and base ordinal values. Please note that the addresses of functions and names are given as Relative Virtual Addresses (RVAs). In order to get the actual memory address of the exported function or of its string name, you must add its corresponding entry in the address or name table to the address, at which the given module is loaded. The code below lists all names and addresses of all functions that are exported by DLL module:

ULONG *addressoffunctions=(ULONG*)((BYTE*) hMod +exp->AddressOfFunctions);
ULONG * addressofnames=(ULONG*)((BYTE*) hMod +exp->AddressOfNames);

for(DWORD x=0; x < exp->NumberOfFunctions;x++)
{
char*functionname=(char*)((BYTE*) hMod +addressofnames[x]);

DWORD functionaddress=(DWORD)((BYTE*) hMod +addressoffunctions[x]);
}

As you can see, for the time being everything is more or less the same as with listing the imported functions and their names. However, things become a little bit different when it comes to patching the export address table – its entries must be overwritten not with actual memory addresses of proxy functions, but with RVAs, i.e. the differences between the actual memory addresses of proxy functions and the address, at which the given module is loaded. This means that all proxy functions must be loaded at the addresses that are higher than kernel32.dll module’s base address – RVA cannot be negative. Let’s look at how it can be done:

BYTE* writebuff=(BYTE* 
)VirtualAllocEx(GetCurrentProcess(),0,5*4096,
  MEM_RESERVE|MEM_TOP_DOWN,PAGE_EXECUTE_READWRITE);
writebuff=(BYTE* 
)VirtualAllocEx(GetCurrentProcess(),writebuff,5*4096,
  MEM_COMMIT|MEM_TOP_DOWN,PAGE_EXECUTE_READWRITE);

for(int x=1;x<=exp->NumberOfFunctions;x++)
{
//get our current position in virtual memory 
chunk
DWORD a=(x-1)/170,pos=a*16+(x-1)*24;
BYTE*currentchunk= &writebuff[pos];
DWORD offset=(DWORD)writebuff-(DWORD)hMod+pos;

//get name and address of the target 
function
char*functionname=(char*)((BYTE*) hMod +addressofnames[x-1]);
DWORD functionaddress=(DWORD)((BYTE*) hMod +addressoffunctions[x-1]);

// load virtual memory with machine instructions 
and relocation information

DWORD addr=(DWORD)&writebuff[pos+6];
currentchunk[0]=0xFF;currentchunk[1]=0x15; 
memmove(¤tchunk[2],&addr,4);
RelocatedFunction * reloc=(RelocatedFunction*)¤tchunk[6];
reloc->funcname= functionname;
reloc->funcptr=functionaddress;
reloc->proxyptr=(DWORD)&ProxyProlog;

// overwrite export address table
DWORD byteswritten;
WriteProcessMemory(GetCurrentProcess(),&addressoffunctions[x-1],
 &offset,4,&byteswritten);
}

As a first step, we allocate a chunk of virtual memory at the highest possible address. The version of kernel32.dll on my machine (it runs Windows 2000) exports 823 functions. For each function replacement, we need 6 bytes for indirect call instruction, plus 16 bytes for RelocatedFunction structure, i.e.22 bytes. If we round this number up to 24 bytes, we will be able to fit 170 function replacement chunks in one page of memory (4096 bytes on Intel CPU), and 16 bytes of every page will remain unused. Therefore, we will need the total of 5 pages of virtual memory. It is a good idea to align these function replacement chunks on the page boundary. Therefore, the address of every given function replacement chunk can be calculated as following:

DWORD a=(x-1)/170,pos=a*16+(x-1)*24;
BYTE*currentchunk=&writebuff[pos];

Hence, the RVA of every given chunk, relative to the target module’s base address, can be calculated as following:

DWORD offset=(DWORD)writebuff-(DWORD)hMod+pos;

The rest is pretty much the same as overwriting the IAT entry – we fill first 6 bytes of the current chunk with the machine instructions, process 16 bytes that follow as RelocatedFunction structure, and write RVA to export address table entry that corresponds to the given function. As a result, every DLL that is subsequently loaded into the target process, will link with our proxy “functions”, i.e. with our handcrafted code that calls ProxyProlog(). Furthermore, any call to GetProcAddress() from any module within the target process will return the address of our proxy “function”, rather than the address of the real calee, although if we call any function, exported by kernel32.dll, by its name, it will result in calling the actual function, rather than our handcrafted code (unless the call is made by the module that was loaded after we have patched the export address table of kernel32.dll) - IATs of all modules that were loaded into the target process before we had patched the export address table of kernel32.dll still contain the addresses of actual functions.

WARNING: In case if any module in your target process dynamically links to C run-time library, make sure that MSVCRT.dll is loaded into your target process’s address space before you overwrite kernel32.dll’s export table. If you try to load MSVCRT.dll into your target process’s address space after you have hooked kernel32.dll, it will fail to load properly. When it comes to hooking and spying, MSVCRT.dll turns out to be a hell of a library to work with - you remember that you should not hook the functions that are imported from MSVCRT.dll, i.e. this library always requires a special treatment.

After having modified the export address table of kernel32.dll, we must walk through all modules that are currently loaded into the address space of the target process, and, in each loaded module, overwrite IAT entries of all functions that are imported from kernel32.dll. The code below shows how it can be done ( currenthandle is a module handle of spying DLL):
void overwrite(HMODULE hMod)
{
IMAGE_DOS_HEADER * dosheader=(IMAGE_DOS_HEADER *)hMod;
IMAGE_OPTIONAL_HEADER * opthdr =(IMAGE_OPTIONAL_HEADER *) 
((BYTE*)hMod+dosheader->e_lfanew+24);
IMAGE_IMPORT_DESCRIPTOR *descriptor= (IMAGE_IMPORT_DESCRIPTOR 
*)((BYTE*)dosheader+opthdr->DataDirectory[ 
IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

HANDLE hand=GetCurrentProcess();
HMODULE ker=GetModuleHandle("kernel32.dll");

while(descriptor->FirstThunk)
{
     char*dllname=(char*)((BYTE*)hMod+descriptor->Name);
     if(lstrcmp(dllname,"KERNEL32.dll")){descriptor++;continue;}
     IMAGE_THUNK_DATA* thunk=( 
IMAGE_THUNK_DATA*)((BYTE*)dosheader+descriptor->OriginalFirstThunk);
     int x=0;
     while(thunk->u1.Function)
     {
       char*functionname=(char*)((BYTE*)dosheader+
         (unsigned)thunk->u1.AddressOfData+2);
       DWORD*IATentryaddress=(DWORD*)
             ((BYTE*)dosheader+descriptor->FirstThunk)+x;

            DWORD addr=(DWORD)GetProcAddress(ker,functionname);
            DWORD byteswritten;    
            WriteProcessMemory(hand,IATentryaddress,
               &addr,4,&byteswritten);
            x++;thunk++;
            }

    descriptor++;

}

CloseHandle(hand);
}

HANDLE snap= 
CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,GetCurrentProcessId());
MODULEENTRY32 mod;mod.dwSize=sizeof(MODULEENTRY32);
Module32First(snap,&mod);
HMODULE first=mod.hModule;
overwrite(first);
while(Module32Next(snap,&mod))
{
HMODULE next=mod.hModule;
if(next==currenthandle)continue;
overwrite(next);
}

We walk through all modules that are currently loaded into the address space of the target process (the fact that, starting from Windows 2000, Toolhelp32 functions are available on NT platform, simplifies our task greatly), and, in each loaded module, overwrite IAT entries of all functions that are imported from kernel32.dll. We don't even have to fill function replacement chunks - it has already been done when we overwrote the export address table of kernel32.dll. All we have to do is to overwrite IAT entries with the addresses that are returned by GetProcAddress() - after we have overwritten the export address table of kernel32.dll, GetProcAddress() returns the addresses of our function replacement chunks, rather than addresses of actual exported functions. It is understandable that all the code you have seen so far resides in our spying DLL.

INJECTING THE SPYING DLL INTO THE TARGET PROCESS

There is one more thing to be done – we must inject the spying DLL into the target process. The technique, described by Jeffrey Richter, uses CreateRemoteThead() API function in order to achieve this goal. Unfortunately, this technique is not going to work in our case. Why not? Because we save that original return address in the thread local storage. If we want the target process to keep on functioning properly, absolutely every thread in the process must dynamically allocate some memory and put it aside into thread local storage, i.e. DllMain() must be called by absolutely every thread in the process. DllMain()will be first called by the thread that loads the spying DLL into the target process, and, subsequently, by all threads that are created in the target process after the spying DLL has been loaded. However, in case if we use CreateRemoteThead() to inject the spying DLL, all threads that were created by the target process before we had injected the spying DLL are not going to call DllMain(). Therefore, if we want the target process to keep on functioning properly, we have only 2 options:

1. We must inject the spying DLL into its primary thread, and do it before the target process creates any additional threads, i.e. at the earliest possible stage of the target process’s lifetime

2. We must make every thread that currently runs in the target process call our spying DLL's entry point

Implementing the former option is relatively easy, compared to the latter one. Therefore, we will start from the first option, and then proceed to the second one.

INJECTING THE SPYING DLL INTO THE PROCESS THAT WE CREATE OURSELVES

First, we will inject our spying DLL into the process that we create ourselves. Let’s look at how it can be done:

void install(char* filename)
{

// get the address of target application’s entry 
point
DWORD bytes;char buff[4096];
HANDLE file=CreateFile(filename , 
GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
ReadFile(file,buff,1024,&bytes,0);
CloseHandle(file);
IMAGE_DOS_HEADER * dosheader=(IMAGE_DOS_HEADER *)buff;
IMAGE_OPTIONAL_HEADER *optionalheader=(IMAGE_OPTIONAL_HEADER 
*)((BYTE*)buff+dosheader->e_lfanew+24);
DWORD 
entryptr=optionalheader->AddressOfEntryPoint+optionalheader->ImageBase;



// create target process
STARTUPINFO startup;GetStartupInfo(&startup);PROCESS_INFORMATION procinfo;
CreateProcess(filename,0,0,0,TRUE,CREATE_SUSPENDED,0,0,&startup,&procinfo);


// allocate memory in the  target process
BYTE* writebuff=(BYTE* 
)VirtualAllocEx(procinfo.hProcess,0,4096,MEM_RESERVE,PAGE_EXECUTE_READWRITE);
writebuff=(BYTE* 
)VirtualAllocEx(procinfo.hProcess,writebuff,4096,MEM_COMMIT,
  PAGE_EXECUTE_READWRITE);

//get the adress of LoadLibraryAs
DWORD 
function=(DWORD)GetProcAddress(GetModuleHandle("kernel32.dll"),
  "LoadLibraryA");

//fill the array with the machine instructions 

DWORD stringptr=(DWORD)&writebuff[20];strcpy(&buff[20],"spydll.dll");
DWORD funcptr=(DWORD)&writebuff[16];memmove(&buff[16],&function,4);
buff[0]=0x68;
memmove(&buff[1],&stringptr,4);
buff[5]=0x68;
memmove(&buff[6],&entryptr,4);
buff[10]=0xFF;buff[11]=0x25;
memmove(&buff[12],&funcptr,4);


// copy the above array into the memory that we 
have allocated in the  target process 
WriteProcessMemory(procinfo.hProcess,writebuff,buff,4096,&bytes);



// change the execution context of the target 
process’s primary thread 
CONTEXT Context;Context.ContextFlags=CONTEXT_CONTROL;
GetThreadContext(procinfo.hThread,&Context);
Context.Eip=(DWORD)writebuff;
SetThreadContext(procinfo.hThread,&Context);
ResumeThread(procinfo.hThread);

}

As a first step, we obtain the address of entry point of the target executable module – we can get this information before even spawning the target process. Our executable file is saved on the disk in PE format, and, hence, the address of entry point is available from the IMAGE_OPTIONAL_HEADER structure - all we have to do is to add together AddressOfEntryPoint and ImageBase fields of IMAGE_OPTIONAL_HEADER structure.

Then we create a target process with the initially suspended primary thread from the .exe file, dynamically allocate a memory array in the target process’s address space, and fill this array with the machine instructions in the following form:

push  pointer_to_dllname
push address_of_entry point
jmp dword ptr [_imp_LoadLibraryA]
lang=mc+

Here we simulate the call instruction by combination of push and jmp instructions. When the instruction pointer hits the first byte of this array, the program will call LoadLibraryA() with pointer_to_dllname as an argument, and then return control to the application’s entry point.

Finally, we change the execution context of the target process’s primary thread – we set the thread’s instruction pointer to the first byte of our array with handcrafted instructions, and then let the thread run by calling ResumeThread() . As a result, the spying DLL will be loaded by the target process’s primary thread even before the target application’s entry point is called.

INJECTING THE SPYING DLL INTO THE RUNNING PROCESS

Now let' do much more complicated thing, and inject our spying DLL into the process that already runs. Let’s look at how it can be done:

void inject(DWORD threadid,BYTE*remotebuff, HMODULE hMod, DWORD 
entrypoint,HANDLE processhandle,HANDLE eventhandle);


void loadandinject(DWORD procid)
{
BYTE array[256];char buff[1024];DWORD byteswritten,dw,threadid;


//allocate memory and create thread in the target 
process
HANDLE processhandle=OpenProcess(PROCESS_ALL_ACCESS,0,procid);
BYTE* writebuff=(BYTE* 
)VirtualAllocEx(processhandle,0,4096,MEM_RESERVE,PAGE_EXECUTE_READWRITE);
writebuff=(BYTE* 
)VirtualAllocEx(processhandle,writebuff,4096,MEM_COMMIT,
  PAGE_EXECUTE_READWRITE);
DWORD 
funcptr=(DWORD)GetProcAddress(GetModuleHandle("kernel32.dll"),
  "LoadLibraryA");
strcpy(buff,"spydll.dll");
WriteProcessMemory(processhandle,writebuff,buff,256,&byteswritten);
CreateRemoteThread(processhandle,0,0,(LPTHREAD_START_ROUTINE)funcptr,
  writebuff,0,&threadid);

//get module handle and entry point of our 
dll
HANDLE snap= CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,procid);
MODULEENTRY32 mod;mod.dwSize=sizeof(MODULEENTRY32); 
Module32First(snap,&mod);
HMODULE hMod=0;

while(Module32Next(snap,&mod))
{

if(!strcmp(mod.szModule,"spydll.dll")){hMod=mod.hModule;break;}
}

CloseHandle(snap);
ReadProcessMemory(processhandle,(void*)hMod,buff,1024,&dw);
IMAGE_DOS_HEADER * dosheader=(IMAGE_DOS_HEADER *)buff;
IMAGE_OPTIONAL_HEADER * opthdr =(IMAGE_OPTIONAL_HEADER *) 
((BYTE*)buff+dosheader->e_lfanew+24);
DWORD entry=(DWORD)hMod+opthdr->AddressOfEntryPoint;

//create auto-reset event in initially unsignaled 
state
HANDLE eventhandle=CreateEvent(0,0,0,"spyevent");

//make every thread in the target process call 
entry point of our dll
snap= CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD,0);
THREADENTRY32 th;th.dwSize=sizeof(THREADENTRY32);
Thread32First(snap,&th);
while(Thread32Next(snap,&th))
{
if(th.th32OwnerProcessID==procid)
inject(th.th32ThreadID,writebuff,hMod,entry,processhandle,eventhandle);
}

CloseHandle(eventhandle);
}

As a very first step, we allocate a memory array in the address space of the target process, copy the name of our spying DLL into this array, and call CreateRemoteThread() API function with the lpStartAddress and lpParameter parameters set to respectively the address of LoadLibrary() API function and the address of the array that we have allocated, i.e. inject the spying DLL into the target process the way described by Jeffrey Richter. Then we walk through all modules that are currently loaded into the address space of the target process, until we find the module handle of our spying DLL. Then we read the memory of the target process, starting from the address that corresponds to our spying DLL's module handle. At this point we are already able to find the address of our DLL's entry point in the address space of the target process - this information is available from IMAGE_OPTIONAL_HEADER.

Then we create auto-reset event in initially unsignaled state - the meaning of this step will become obvious when you see the implementation of inject(). Finally, we enumerate all threads that currently run in the target process, and make every thread in the target process call our DLL's entry point - this is implemented by inject(), to which the above mentioned event handle is one of the parameters. Let's look at inject()'s implementation:

void inject(DWORD threadid,BYTE*remotebuff, HMODULE hMod, DWORD 
entrypoint,HANDLE processhandle,HANDLE eventhandle)
{
DWORD arg1=(DWORD)hMod,arg2=DLL_THREAD_ATTACH,arg3=0;

typedef HANDLE (__stdcall*func)(DWORD,BOOL,DWORD);

func 
OpenThread=(func)GetProcAddress(GetModuleHandle("KERNEL32.dll"),
  "OpenThread");
HANDLE threadhandle=OpenThread(THREAD_SUSPEND_RESUME|
  THREAD_GET_CONTEXT|THREAD_SET_CONTEXT,0,threadid);
SuspendThread(threadhandle);
CONTEXT Context;Context.ContextFlags=CONTEXT_CONTROL;
GetThreadContext(threadhandle,&Context);

DWORD retaddress= Context.Eip;

//we are going to do the tough job of filling the 
array with the machine codes


BYTE array[256];

//copy all necessary data into the array


DWORD *openeventptr=(DWORD *)&array[100];
openeventptr[0]=(DWORD )&OpenEvent;
openeventptr=(DWORD *)&remotebuff[100];


DWORD*seteventptr=(DWORD *)&array[104];
seteventptr[0]=(DWORD )&SetEvent;
seteventptr=(DWORD *)&remotebuff[104];

DWORD* closehandleptr=(DWORD *)&array[108];
closehandleptr[0]=(DWORD )&CloseHandle;
closehandleptr=(DWORD *)&remotebuff[108];

DWORD* entrypointptr=(DWORD *)&array[112];
entrypointptr[0]=entrypoint;
entrypointptr=(DWORD *)&remotebuff[112];

DWORD* retaddressptr=(DWORD *)&array[116];
retaddressptr[0]=retaddress;
retaddressptr=(DWORD *)&remotebuff[116];




strcpy((char*)&array[120],"spyevent");
char*eventnameptr=(char*)&remotebuff[120];

//now we are filling the array with actual machine 
instructions

//push registers and flags
array[0]=0x50;array[1]=0x53;array[2]=0x51;array[3]=0x52;array[4]=0x9C;

//push entrypoint arguments
array[5]=0x68; memmove(&array[6],&arg3,4);
array[10]=0x68;memmove(&array[11],&arg2,4);
array[15]=0x68;memmove(&array[16],&arg1,4);

//call entrypoint
array[20]=0xFF;array[21]=0x15;memmove(&array[22],&entrypointptr,4);

//push OpenEvent arguments
array[26]=0x68;memmove(&array[27],&eventnameptr,4);
array[31]=0x68;int a=0; memmove(&array[32],&a,4);
array[36]=0x68;a=EVENT_ALL_ACCESS; memmove(&array[37],&a,4);

//call OpenEvent
array[41]=0xFF;array[42]=0x15;memmove(&array[43],&openeventptr,4);

// push eax 
array[47]=0x50;

// push eax 
array[48]=0x50;

//call SetEvent 
array[49]=0xFF;array[50]=0x15;memmove(&array[51],&seteventptr,4);

//call CloseHandle
array[55]=0xFF;array[56]=0x15;memmove(&array[57],&closehandleptr,4);

//restore registers and flags
array[61]=0x9D;array[62]=0x5A;array[63]=0x59;array[64]=0x5B;array[65]=0x58;


//jmp dword ptr[retaddressptr]
array[66]=0xFF;array[67]=0x25;memmove(&array[68],&retaddressptr,4);


// we have finished filling the array, thanks God 


DWORD byteswritten;
WriteProcessMemory(processhandle,(void *)remotebuff,(void 
*)array,256,&byteswritten);
Context.Eip=(DWORD)&remotebuff[0];
SetThreadContext(threadhandle,&Context);
ResumeThread(threadhandle);

WaitForSingleObject(eventhandle,INFINITE);


CloseHandle(threadhandle);
}

The implementation of inject() does, basically, the same thing as our DLL-injecting code in the previous example- it fills the memory array with the machine codes, and changes the execution context of the target thread, i.e. makes it execute our handcrafted code that calls our DLL's entry point. However, now things become more complicated -our target thread already runs, so that all our activity must leave CPU registers and flags intact, as far as the target thread is concerned. Furthermore, for the safety reasons, we must synchronize our injections, i.e. proceed to the next target thread only after the current target thread's execution context has been restored. Therefore, we have to fill the array with the following instructions:

push eax
push ebx
push ecx
push edx
pushf
push 0
push value_of_DLL_THREAD_ATTACH
push hMod
call dword ptr[_imp_Dllentrypoint]
push eventnameptr
push 0
push value_of_EVENT_ALL_ACCESS
call dword ptr[_imp_OpenEvent]
push eax
push eax
call dword ptr[_imp_SetEvent]
call dword ptr[_imp_CloseHandle]
popf
pop edx
pop ecx
pop ebx
pop eax
jmp dword ptr[retaddressptr]

This seems to be a bit of a tough job, but, unless you are desperate to crash the target process, it has to be done. After having changed the execution context of the target thread, inject() waits until the target thread sets the synchronization event we have created, so that we cannot proceed to the next thread until the execution context of the target thread is restored. But what if the target thread is deadlocked at the time when we want it to call the entry point of our spying DLL? Then our code will get stuck - no one is going to set our synchronization event to the signaled state. This means that the above technique can be useful (with few adjustments applied) for detecting deadlocked threads in the target process - the fact that one of the worker threads in multithreaded application is deadlocked is not always obvious at the first glance.

NOTE: In case if we inject our spying DLL into the target process that we create ourselves, we can overwrite the addresses of our target functions right in DllMain()when it is called with fdwReason parameter set to DLL_PROCESS_ATTACH, because our target process has only one thread at the time when our spying DLL is injected. However, if we inject our spying DLL into the target process that already runs, we can overwrite the addresses of our target functions only after absolutely every thread in the target process has called our DLL's entry point. Otherwise, there is a good chance that the function replacement code will be called by the thread that has not yet allocated its storage, which means the target process will crash when Prolog() tries to save the return address in the storage that has not yet been allocated.

This implies that the code, which actually overwrites the addresses of our target functions, must reside in a function that is exported by our spying DLL. Then, after the code in loadandinject() is executed , we would be able to create a thread in the target process by calling CreateRemoteThread() with the lpStartAddress parameter set to the address of this function - once the function is exported, we can always get its address in the target process from the spying DLL's export address table.

In case if all this seems too complicated to you, I suggest you should create the target process yourself, rather than spy on the process that already runs - as you can see, the fact that the target process already runs at the time when we inject our spying DLL gives us quite a few things to worry about. To be honest, I would personally prefer, for the practical purposes, to create the target process myself.

科技中介服务机构如何通过智能体技术提升服务效率与专业度?.docx 科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。 立即下载

相关推荐

产业园区如何高效对接企业技术需求并推动成果转化?观点作者:科易网-国家科技成果转化(厦门)示范基地.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

Ubuntu 18.04 This page isn’t working xxx.com didn’t send any data. ERR_EMPTY_RESPONSE

Ubuntu18.04 chrome Firefox 都试了一遍 提示如下: This page isn’t working xxx.com didn’t send any data. ERR_EMPTY_RESPONSE 本地 curl xxx.com 都可以出数据,找了一天的问题,最终发现是 Network-&gt; Network Proxy 网络有代理 http:...

马金波 1万+

山区道路边坡落石隐患检测数据集VOC+YOLO格式241张1类别.md

【重要提示】本页面仅为资源介绍,不含外部下载地址 亲爱的 优快云 用户: 感谢你浏览本资源页面,在这里提前向大家说明相关情况,避免后续产生误解。本资源我设置的初衷是 0 积分免费分享文档介绍,希望可以无偿给到有需要的朋友。但 优快云 平台存在自动调控机制,系统会根据资源下载热度、文件体积、平台规则等因素,自动修改资源所需下载积分,页面显示为 1 积分、2 积分或更高积分都属于平台自动调整结果,并非我手动修改设置,我没有权限自主锁定积分数值。 如果你打开页面后,发现下载所需积分不为 0,请务必谨慎考虑是否下载。一旦你支付积分完成下载,后续遇到资源内容和描述不符、文件损坏等问题,我无法承担对应的积分损失,也没有渠道为你办理积分退还。在此强烈建议,只有页面明确显示 0 积分的时候,再进行下载操作。 同时特别说明:这份资源介绍文案里,没有附带任何外部下载地址。资源本身是通过 优快云 官方上传通道上传的附件,正常情况下直接点击页面自带的下载按钮即可获取文件。页面找不到外部下载链接属于正常情况,并非资源缺失。 如果因为平台积分显示异常,导致你被扣取积分,积分退款、积分异常相关问题,请直接联系 优快云 官方客服咨询处理。平台规则由官方制定,我无法干预积分变动,望各位理解。

Xcode8 - apploader 上传失败 - ERROR ITMS-90168: "The binary you uploaded was invalid."

背景:最近电脑升级了系统macOS Sierra 10.12.1;Xcode 也升级到了Version 8.1 (8B62)。 问题:使用Application Loader3.0 上传应用到iTunes connect 遇到报错:ERROR ITMS-90168:"The binary you upload was invalid",但是也没有其他的提示信息。 问题总是要解决的,于...

weixin_30502965的博客 295

高空作业栏杆检测数据集VOC+YOLO格式230张3类别.md

【重要提示】本页面仅为资源介绍,不含外部下载地址 亲爱的 优快云 用户: 感谢你浏览本资源页面,在这里提前向大家说明相关情况,避免后续产生误解。本资源我设置的初衷是 0 积分免费分享文档介绍,希望可以无偿给到有需要的朋友。但 优快云 平台存在自动调控机制,系统会根据资源下载热度、文件体积、平台规则等因素,自动修改资源所需下载积分,页面显示为 1 积分、2 积分或更高积分都属于平台自动调整结果,并非我手动修改设置,我没有权限自主锁定积分数值。 如果你打开页面后,发现下载所需积分不为 0,请务必谨慎考虑是否下载。一旦你支付积分完成下载,后续遇到资源内容和描述不符、文件损坏等问题,我无法承担对应的积分损失,也没有渠道为你办理积分退还。在此强烈建议,只有页面明确显示 0 积分的时候,再进行下载操作。 同时特别说明:这份资源介绍文案里,没有附带任何外部下载地址。资源本身是通过 优快云 官方上传通道上传的附件,正常情况下直接点击页面自带的下载按钮即可获取文件。页面找不到外部下载链接属于正常情况,并非资源缺失。 如果因为平台积分显示异常,导致你被扣取积分,积分退款、积分异常相关问题,请直接联系 优快云 官方客服咨询处理。平台规则由官方制定,我无法干预积分变动,望各位理解。

ti-cgt-c2000-22.6.0.LTS-windows-x64-installer

ti-cgt-c2000-22.6.0.LTS-windows-x64-installer

The page at ‘xx‘ was loaded over HTTPS, but requested an insecure frame ‘http:xxx‘. This request has

https系统使用iframe内嵌一个外部https地址,外部地址执行完毕后,会回调到一个外部http地址 导致出现如下报错,无法跳转http页面 Mixed Content: The page at 'xx' was loaded over HTTPS, but requested an insecure frame 'http:xxx'. This request has been blocked; the content must be served over HTTPS. 加这个没有起到作用 &l

eadela的博客 3411

光伏板太阳能板尘土灰尘脏污检测数据集VOC+YOLO格式1006张3类别.md

【重要提示】本页面仅为资源介绍,不含外部下载地址 亲爱的 优快云 用户: 感谢你浏览本资源页面,在这里提前向大家说明相关情况,避免后续产生误解。本资源我设置的初衷是 0 积分免费分享文档介绍,希望可以无偿给到有需要的朋友。但 优快云 平台存在自动调控机制,系统会根据资源下载热度、文件体积、平台规则等因素,自动修改资源所需下载积分,页面显示为 1 积分、2 积分或更高积分都属于平台自动调整结果,并非我手动修改设置,我没有权限自主锁定积分数值。 如果你打开页面后,发现下载所需积分不为 0,请务必谨慎考虑是否下载。一旦你支付积分完成下载,后续遇到资源内容和描述不符、文件损坏等问题,我无法承担对应的积分损失,也没有渠道为你办理积分退还。在此强烈建议,只有页面明确显示 0 积分的时候,再进行下载操作。 同时特别说明:这份资源介绍文案里,没有附带任何外部下载地址。资源本身是通过 优快云 官方上传通道上传的附件,正常情况下直接点击页面自带的下载按钮即可获取文件。页面找不到外部下载链接属于正常情况,并非资源缺失。 如果因为平台积分显示异常,导致你被扣取积分,积分退款、积分异常相关问题,请直接联系 优快云 官方客服咨询处理。平台规则由官方制定,我无法干预积分变动,望各位理解。

线程与信号

类UNIX信号以前是专为进程设计的,它比线程的出现早了很多年。当线程模型出现后,专家们试图也在线程上实现信号,这导致了一个问题:如果要在线程模型中保持原来在进程中信号语意不变,是相当困难的。 避免信号和线程一起使用是明智的选择。但是,将他们分开又是不可能或不实际的。只要有可能的话,仅仅在主线程内使用pthread_sigmask()来屏蔽信号,然后同步地在专用线程中使用sigwait()来处理信

zhangfulin_hwatop的专栏 8592

无功优化电网故障下分布式能源系统多目标优化[并网转换器(GCC)](Matlab代码&Simulink实现)

内容概要:本文详细介绍了基于Plecs或Simulink平台构建的全桥LLC谐振变换器仿真模型,重点研究了变频控制与移相混合控制策略下的闭环系统设计与参数优化方法。内容涵盖全桥LLC谐振变换器的工作原理、小信号建模技术(如扩展描述函数EDF)、PID控制器参数整定过程以及软开关实现条件,并通过仿真验证了系统在稳态输出性能、动态响应特性(如启动过程与负载阶跃)和频率调节平滑性等方面的优良表现。同时提供了完整的仿真模型、参数设计报告及说明文档,有助于读者复现结果并深入理解控制机制与建模细节。; 适合人群:具备电力电子、自动控制等相关专业知识背景,从事高效率电源设计、新能源变换系统研发的工程师及高等院校电力电子与电力传动方向的研究生。; 使用场景及目标:①掌握LLC谐振变换器的小信号建模与闭环控制系统设计方法;②学习并应用变频与移相结合控制策略提升电源转换效率;③支撑科研项目中的电力电子系统仿真验证或学位论文课题开发;④复现高水平学术论文中关于谐振变换器的控制方案与性能分析。; 阅读建议:建议结合所提供的仿真模型与参数设计资料,边运行仿真边对照文章内容理解控制逻辑与建模过程,特别关注小信号模型的建立方法与PID控制器参数整定环节,以增强对实际工程问题的分析与设计能力。

两相交错并联Boost电路仿真模型(Simulink仿真实现)

内容概要:本文围绕两相交错并联Boost电路的Simulink仿真模型展开,旨在通过仿真手段深入研究该电路在电力电子系统中的工作特性与性能优势。文章系统阐述了两相交错并联Boost电路的拓扑结构、工作原理及其在提升系统效率、减小输入电流纹波、均衡电流分配方面的显著优势。通过构建完整的Simulink仿真模型,对电路在不同负载与输入条件下的动态响应、稳态性能及控制策略进行了全面分析与验证。重点设计并实现了电压电流双闭环PI控制策略,详细论述了PI控制器的参数整定方法、占空比调节机制以及交错并联所带来的均流效果。仿真结果表明,所构建的模型能够有效提升系统的动态响应速度与稳定性,显著降低输入电流的谐波含量与纹波幅值,验证了交错并联技术在中高功率DC-DC变换应用中的优越性。; 适合人群:具备电力电子技术基础、熟悉Simulink仿真的电气工程及相关专业本科生、研究生,以及从事电源系统设计、新能源变换器研发的工程技术人员。; 使用场景及目标:①学习和掌握交错并联Boost电路的拓扑结构、工作原理与建模方法;②开展电力电子变换器的仿真分析、控制器设计与参数优化;③为实际工程项目中的高性能DC-DC变换器设计,特别是追求低输入纹波和高功率密度的应用场景,提供理论支撑与仿真验证依据。; 阅读建议:建议读者结合Simulink模型文件同步操作,深入理解主电路各模块、双闭环控制结构及PWM调制环节的参数设置与逻辑实现,重点关注PI控制器的参数整定过程与交错控制的时序配合,并可通过调整输入电压、负载大小及控制参数进行对比实验,以深化对交错并联技术优势及其控制策略的理解。

linux内核调试指南

Hunnad的专栏 * 条新通知 * 登录 * 注册 * 欢迎 * 退出 * 我的博客 * 配置 * 写文章 * 文章管理 * 博客首页 * * * * 空间 * 博客 * 好友 * 相册 * 留言 用户操作 [留言] [发消息] [加为好友] 订阅我的博客 XML聚合 F

crond123的专栏 2万+

英飞凌功率MOSFET SPICE 瞬态仿真不收敛问题解决(基于TINA)

英飞凌功率MOSFET SPICE仿真容易出现不收敛问题,这个对于IGBT和SIC的管子应该是一样的,在瞬态仿真分析中,出现如下图所示的报错,Convergence problem。spice是为线性电路设计的,例如运算放大器、线性电源等,不是专门仿真开关电路的。在瞬态仿真时,仿真器是要不断迭代求解的,开关快速的跳变,容易在迭代式出现不收敛的问题。,由于时间步长减少,会消耗更多的计算机内存,如果运行多次内存消耗过大,会报“Out of memory”.解决方法是:报错工程,重启软件,内存重新计算。

miaoxiaokeji的博客 2300

Process-wide API spying - an ultimate hack[ By Anton Bassov ][www.codeproject.com]

AbstractAPI hooking and spying is not uncommon practice in Windows programming. Development of system monitoring and analysis tools heavily depends upon it. Numerous articles have been written on th

C/C++ World 1269

基于Qwen3-14B大模型的智能UI自动化测试实践

UI自动化测试是软件工程中保障产品质量的关键环节,其核心原理是通过脚本模拟用户操作,对软件界面进行功能验证。传统方法依赖于编写和维护基于元素定位的固定规则脚本,在面对复杂交互或频繁变化的界面时,维护成本高昂。大语言模型(LLM)的出现为这一领域带来了新的技术价值,其强大的自然语言理解和代码生成能力,使得测试脚本的编写从精确编码转向了任务描述。通过将大模型作为决策大脑,结合Playwright等自动化工具作为执行器,可以构建出能够理解界面、规划操作并验证结果的智能测试系统。这种方案特别适用于处理变化频繁、逻辑

weixin_42619742的博客 304

一周涨十万星,GitHub 爆火的 REA 逆向工程到底是什么?

REA 这波热度里,我一直没太想明白的是它为什么火在 AI 圈而不是安全圈。同类的 MCP 工具早就在了,技术上也谈不上新,真正变的只有一件事:门槛。门槛一降,参与的人就从"会逆向的工程师"变成了"会打字的任何人"。这大概也是争议的根源。护城河这件事,多数报道的共识是它从"你看不到我的代码"挪到了"你拿不走我的服务能力和业务理解"。这话对不对,现在没人拿得出证据,只能等下一批真实案例。哪天有人拿 REA 把某个还在卖钱的闭源软件啃出可维护的工程,那个答案才算落地。

优快云zhizunbao的博客 1203

Process-wide API spying - an ultimate hack 摘要翻译(一)

简介      进程范围的API挂钩一般都是基于修改目标执行文件的输入函数表(IAT)。通过把目标进程中调用需要挂钩的API函数替换为用户编写的函数,达到挂钩的目的。当然一般情况下,用户的替换函数都是对API的传入参数进行记录或者验证,然后再调用挂钩的API函数。API Spy一般都是把进行Hook和Spy工作的驱动DLL文件通过一个控制程序注射到目标进程,驱动DLL文件和控制程序之间通过WM_C

1049

166、Sim2Real中的强化学习:域随机化与鲁棒策略

第二,训练时监控每个随机化维度对策略梯度的贡献,如果某个维度对loss的方差贡献极小,说明策略已经忽略它了,要么加大范围,要么删掉。第四,如果条件允许,用真实数据来校准随机化分布——采集真机上的传感器数据,拟合出参数分布,再把这个分布作为随机化的采样依据,这比拍脑袋定范围靠谱得多。动态随机化是很多人忽略的维度。域随机化的核心思想说白了就一句话:别去精确建模现实,而是把仿真环境本身变成一个随机分布的集合,让策略在“各种乱七八糟的物理参数”里都见过世面,这样到了真机上,不管现实落在哪个参数区间,策略都能兜住。

sundong_81的博客 27

Process-wide API spying - an ultimate hack 摘要翻译(三)

在已运行进程注入间谍DLL主体代码(文字说明放在代码的注释中):void inject(DWORD threadid,BYTE*remotebuff, HMODULE hMod, DWORD entrypoint,HANDLE processhandle,HANDLE eventhandle);void loadandinject(DWORD procid){    BYTE array[

1021

二、CANdelaStudio入门-版本介绍

本专栏将由浅入深的展开诊断实际开发与测试的数据库编辑,包含大量实际开发过程中的步骤、使用技巧与少量对Autosar标准的解读。希望能对大家有所帮助,与大家共同成长,早日成为一名车载诊断、通信全栈工程师。 本文介绍CANdelaStudio的各个版本,欢迎各位朋友订阅、评论,可以提出宝贵意见,以持续输出更好的作品,同时会根据评论、私信不定期更新文章内容,以及专栏目录。订阅该专栏的朋友可私信小编免费答疑解惑(^U^)ノ~YOCANdelaStudio——诊断需求定义工具,诊断协议开发软件 ①用户以模板概念为导向

诊断协议那些事儿's blog 1309

Machine Unlearning: A Survey

机器学习已经引起了广泛的关注,并发展成为一种使能技术,用于广泛的高度成功的应用,如智能计算机视觉、语音识别、医疗诊断等。然而,由于隐私、可用性和/或被遗忘权,一些特定样本的信息需要从称为机器遗忘的模型中删除,这就产生了一种特殊的需求。这项新兴技术因其创新性和实用性而引起了学术界和工业界的极大兴趣。与此同时,这个雄心勃勃的问题导致了许多旨在应对其挑战的研究工作。据我们所知,没有任何研究分析过这一复杂的主题,也没有比较过现有的遗忘解决方案在不同场景下的可行性。因此,通过这项调查,我们旨在了解遗忘技术的关键概念。

c_cpp_csharp的专栏 1159

Process-wide API spying - an ultimate hack 摘要翻译(二)

通过目标进程中挂钩所有对DLL的调用由于通过修改目标进程的输入表IAT只能对当前进程有效,所以我们即使对当前所有已加载模块都挂钩了对kernel32.dll的调用,但在这之后启动的进程将不会被挂钩。因此除了对已加载进程修改IAT以外,还需要修改kernel32.dll的输出表EAT。1、修改目标进程的IAT表对所有已加载的模块有效2、修改kernel32.dll的EAT表,所有将来加载的DLL

989

手把手教你安装 OpenClaw 小龙虾(MAC安装教程),一次成功不踩坑

本文详细介绍了在Mac上安装OpenClaw的完整流程。主要内容包括:1)准备工作(磁盘空间、网络环境、终端工具);2)安装Homebrew包管理器及常见问题解决;3)安装Node.js 22+版本(环境核心);4)Git安装与验证;5)OpenClaw项目克隆、依赖安装及运行配置;6)大模型配置方法(以MiniMax为例)。文章提供了详细的命令行操作步骤和常见错误解决方案,特别强调了网络环境要求和Apple Silicon芯片的原生支持注意事项。最后展示了验证配置成功的方法和资源获取渠道。

Chengbei11的博客 6344
上一篇: Winamp "in_cdda" 模块的调试以及SHELLCODE的编写
下一篇: 常用函数库收集
iiprogram
博客等级 码龄25年 1068粉丝 1515原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值