71104
12-12-2005, 20:45
l'altro giorno sulla mailing list del corso di Laboratorio di SO arriva un niubbo e fa: "ho fatto per esercitarmi un programma che usa la funzione fork bla bla e su Windows non compila (lol :asd: ) esiste una funzione analoga alla fork su Windows?"; dopodiché arriva un altro niubbo (un po' meno niubbo del precedente però) e fa "ah bla bla bla la fork è una prerogativa dei sistemi Unix ecc. ecc.".
allora io prendo spunto e realizzo una mini-fork per Windows :asd:
non vi immaginate che sia un granché, è molto rudimentale: altro non fa che creare il processo figlio sospeso, ricopiarci tutte le sezioni non shared e non discardable impostate con permessi di scrittura oltre che di lettura, settare opportunamente il contesto del thread interrotto, ed infine riesumarlo; una fork simile funziona solo a patto che siano verificate le seguenti condizioni:
1) il programma è single-threaded
2) il programma non chiama VirtualAlloc e HeapAlloc prima della fork (dopo volendo si, ma prima no)
3) il programma non alloca handles non ereditabili prima della fork (dopo magari si, ma prima no :p)
comunque quel poco che fa lo fa bene :)
siccome ricordo che ilsensine una volta chiese a fek di spiegargli come hanno fatto quelli di Cygwin a realizzare la fork su Windows, ora la incollo qui di seguito; BTW, per l'occasione mi sono anche letto il codice della fork di Cygwin: la loro funziona un po' meglio della mia perché hanno un vantaggio: se ho ben capito come funziona questo Cygwin, si assume che un programma usi solamente le funzioni di Cygwin, quindi loro nel realizzare la fork avevano controllo sull'heap; ma per fare un bel crash anche su Cygwin è sufficiente tenere memoria di un handle non ereditabile prima di una fork :)
#include <windows.h>
int fork();
int main() {
TCHAR pszMsg[0x100];
wsprintf(pszMsg, TEXT("questo è il processo padre, PID = %d"), GetCurrentProcessId());
MessageBox(NULL, pszMsg, TEXT("fork on Windows"), MB_ICONINFORMATION);
if (fork()) {
wsprintf(pszMsg, TEXT("questo invece è il figlio, PID = %d"), GetCurrentProcessId());
MessageBox(NULL, pszMsg, TEXT("fork on Windows"), MB_ICONINFORMATION);
}
return 0;
}
#define TESTFLAG(mask, flag) ((flag) == ((mask) & (flag)))
HANDLE hStarted = NULL;
__declspec(naked) void Stub() {
SetEvent(hStarted);
CloseHandle(hStarted);
SuspendThread(GetCurrentThread());
}
BYTE pbJmp[5] = {0xE9};
int fork() {
DWORD dwDummy;
TCHAR pszFileName[0x400];
if (!GetModuleFileName(NULL, pszFileName, 0x400)) {
return -1;
}
SECURITY_ATTRIBUTES sa = {
sizeof(SECURITY_ATTRIBUTES),
NULL,
TRUE
};
hStarted = CreateEvent(&sa, FALSE, FALSE, NULL);
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory((PVOID)&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
if (!CreateProcess(pszFileName, NULL, NULL, NULL, TRUE, GetPriorityClass(GetCurrentProcess()) |
CREATE_SUSPENDED, NULL, NULL, &si, &pi))
{
return -1;
}
WriteProcessMemory(pi.hProcess, (PVOID)&hStarted, (PVOID)&hStarted, 4, &dwDummy);
PBYTE pbInstBase = (PBYTE)GetModuleHandle(NULL);
PIMAGE_NT_HEADERS pinth = (PIMAGE_NT_HEADERS)(pbInstBase + *(PDWORD)(pbInstBase + 0x3C));
PVOID pvEntryAddress = (PVOID)(pbInstBase + pinth->OptionalHeader.AddressOfEntryPoint);
DWORD dwPrevProtect;
VirtualProtectEx(pi.hProcess, pvEntryAddress, 5, PAGE_READWRITE, &dwPrevProtect);
*(PDWORD)(pbJmp + 1) = (DWORD)&Stub - (DWORD)pvEntryAddress - 5;
WriteProcessMemory(pi.hProcess, pvEntryAddress, (PVOID)pbJmp, 5, &dwDummy);
ResumeThread(pi.hThread);
WaitForSingleObject(hStarted, INFINITE);
CloseHandle(hStarted);
WriteProcessMemory(pi.hProcess, pvEntryAddress, pvEntryAddress, 5, &dwDummy);
VirtualProtectEx(pi.hProcess, pvEntryAddress, 5, dwPrevProtect, &dwDummy);
PIMAGE_SECTION_HEADER pSecHdrs = (PIMAGE_SECTION_HEADER)((PBYTE)pinth +
sizeof(IMAGE_NT_HEADERS));
for (UINT u = 0; u < pinth->FileHeader.NumberOfSections; u++) {
DWORD dwFlags = pSecHdrs[u].Characteristics;
if (!TESTFLAG(dwFlags, IMAGE_SCN_MEM_DISCARDABLE) &&
!TESTFLAG(dwFlags, IMAGE_SCN_MEM_SHARED) &&
TESTFLAG(dwFlags, IMAGE_SCN_MEM_READ) &&
TESTFLAG(dwFlags, IMAGE_SCN_MEM_WRITE))
{
PVOID pvSecAddr = (PVOID)(pbInstBase + pSecHdrs[u].VirtualAddress);
WriteProcessMemory(pi.hProcess, pvSecAddr, pvSecAddr, pSecHdrs[u].Misc.VirtualSize,
&dwDummy);
}
}
BOOL InTheChild = FALSE;
CONTEXT c;
ZeroMemory((PVOID)&c, sizeof(CONTEXT));
c.ContextFlags = CONTEXT_FULL;
GetThreadContext(GetCurrentThread(), &c);
__asm {
call near here
here:
pop eax
inc eax
mov c.Eip,eax
mov c.Esp,esp
mov c.Ebp,ebp
}
if (InTheChild) {
return GetCurrentProcessId();
}
/*
PUSH EBP
MOV EBP,ESP
*/
DWORD dwFramePtr;
__asm {
mov dwFramePtr,ebp
}
DWORD dwKernel32 = (DWORD)LoadLibrary(TEXT("kernel32.dll"));
PIMAGE_NT_HEADERS pinthKernel32 = (PIMAGE_NT_HEADERS)(dwKernel32 +
*(PDWORD)(dwKernel32 + 0x3C));
UINT uKernel32Size = pinthKernel32->OptionalHeader.SizeOfImage;
while (dwFramePtr) {
DWORD dwRetAddress = *(((PDWORD)dwFramePtr) + 1);
if ((UINT)(dwRetAddress - dwKernel32) <= uKernel32Size) {
break;
}
dwFramePtr = *(PDWORD)dwFramePtr;
}
DWORD dwStackPtr;
__asm {
mov dwStackPtr,esp
}
DWORD dwDelta = dwFramePtr - dwStackPtr;
WriteProcessMemory(pi.hProcess, (PVOID)dwStackPtr, (PVOID)dwStackPtr, dwDelta, &dwDummy);
BOOL RemoteChildFlag = TRUE;
WriteProcessMemory(pi.hProcess, (PVOID)&InTheChild, (PVOID)&RemoteChildFlag, 4, &dwDummy);
SetThreadContext(pi.hThread, &c);
ResumeThread(pi.hThread);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return 0;
}
allora io prendo spunto e realizzo una mini-fork per Windows :asd:
non vi immaginate che sia un granché, è molto rudimentale: altro non fa che creare il processo figlio sospeso, ricopiarci tutte le sezioni non shared e non discardable impostate con permessi di scrittura oltre che di lettura, settare opportunamente il contesto del thread interrotto, ed infine riesumarlo; una fork simile funziona solo a patto che siano verificate le seguenti condizioni:
1) il programma è single-threaded
2) il programma non chiama VirtualAlloc e HeapAlloc prima della fork (dopo volendo si, ma prima no)
3) il programma non alloca handles non ereditabili prima della fork (dopo magari si, ma prima no :p)
comunque quel poco che fa lo fa bene :)
siccome ricordo che ilsensine una volta chiese a fek di spiegargli come hanno fatto quelli di Cygwin a realizzare la fork su Windows, ora la incollo qui di seguito; BTW, per l'occasione mi sono anche letto il codice della fork di Cygwin: la loro funziona un po' meglio della mia perché hanno un vantaggio: se ho ben capito come funziona questo Cygwin, si assume che un programma usi solamente le funzioni di Cygwin, quindi loro nel realizzare la fork avevano controllo sull'heap; ma per fare un bel crash anche su Cygwin è sufficiente tenere memoria di un handle non ereditabile prima di una fork :)
#include <windows.h>
int fork();
int main() {
TCHAR pszMsg[0x100];
wsprintf(pszMsg, TEXT("questo è il processo padre, PID = %d"), GetCurrentProcessId());
MessageBox(NULL, pszMsg, TEXT("fork on Windows"), MB_ICONINFORMATION);
if (fork()) {
wsprintf(pszMsg, TEXT("questo invece è il figlio, PID = %d"), GetCurrentProcessId());
MessageBox(NULL, pszMsg, TEXT("fork on Windows"), MB_ICONINFORMATION);
}
return 0;
}
#define TESTFLAG(mask, flag) ((flag) == ((mask) & (flag)))
HANDLE hStarted = NULL;
__declspec(naked) void Stub() {
SetEvent(hStarted);
CloseHandle(hStarted);
SuspendThread(GetCurrentThread());
}
BYTE pbJmp[5] = {0xE9};
int fork() {
DWORD dwDummy;
TCHAR pszFileName[0x400];
if (!GetModuleFileName(NULL, pszFileName, 0x400)) {
return -1;
}
SECURITY_ATTRIBUTES sa = {
sizeof(SECURITY_ATTRIBUTES),
NULL,
TRUE
};
hStarted = CreateEvent(&sa, FALSE, FALSE, NULL);
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory((PVOID)&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
if (!CreateProcess(pszFileName, NULL, NULL, NULL, TRUE, GetPriorityClass(GetCurrentProcess()) |
CREATE_SUSPENDED, NULL, NULL, &si, &pi))
{
return -1;
}
WriteProcessMemory(pi.hProcess, (PVOID)&hStarted, (PVOID)&hStarted, 4, &dwDummy);
PBYTE pbInstBase = (PBYTE)GetModuleHandle(NULL);
PIMAGE_NT_HEADERS pinth = (PIMAGE_NT_HEADERS)(pbInstBase + *(PDWORD)(pbInstBase + 0x3C));
PVOID pvEntryAddress = (PVOID)(pbInstBase + pinth->OptionalHeader.AddressOfEntryPoint);
DWORD dwPrevProtect;
VirtualProtectEx(pi.hProcess, pvEntryAddress, 5, PAGE_READWRITE, &dwPrevProtect);
*(PDWORD)(pbJmp + 1) = (DWORD)&Stub - (DWORD)pvEntryAddress - 5;
WriteProcessMemory(pi.hProcess, pvEntryAddress, (PVOID)pbJmp, 5, &dwDummy);
ResumeThread(pi.hThread);
WaitForSingleObject(hStarted, INFINITE);
CloseHandle(hStarted);
WriteProcessMemory(pi.hProcess, pvEntryAddress, pvEntryAddress, 5, &dwDummy);
VirtualProtectEx(pi.hProcess, pvEntryAddress, 5, dwPrevProtect, &dwDummy);
PIMAGE_SECTION_HEADER pSecHdrs = (PIMAGE_SECTION_HEADER)((PBYTE)pinth +
sizeof(IMAGE_NT_HEADERS));
for (UINT u = 0; u < pinth->FileHeader.NumberOfSections; u++) {
DWORD dwFlags = pSecHdrs[u].Characteristics;
if (!TESTFLAG(dwFlags, IMAGE_SCN_MEM_DISCARDABLE) &&
!TESTFLAG(dwFlags, IMAGE_SCN_MEM_SHARED) &&
TESTFLAG(dwFlags, IMAGE_SCN_MEM_READ) &&
TESTFLAG(dwFlags, IMAGE_SCN_MEM_WRITE))
{
PVOID pvSecAddr = (PVOID)(pbInstBase + pSecHdrs[u].VirtualAddress);
WriteProcessMemory(pi.hProcess, pvSecAddr, pvSecAddr, pSecHdrs[u].Misc.VirtualSize,
&dwDummy);
}
}
BOOL InTheChild = FALSE;
CONTEXT c;
ZeroMemory((PVOID)&c, sizeof(CONTEXT));
c.ContextFlags = CONTEXT_FULL;
GetThreadContext(GetCurrentThread(), &c);
__asm {
call near here
here:
pop eax
inc eax
mov c.Eip,eax
mov c.Esp,esp
mov c.Ebp,ebp
}
if (InTheChild) {
return GetCurrentProcessId();
}
/*
PUSH EBP
MOV EBP,ESP
*/
DWORD dwFramePtr;
__asm {
mov dwFramePtr,ebp
}
DWORD dwKernel32 = (DWORD)LoadLibrary(TEXT("kernel32.dll"));
PIMAGE_NT_HEADERS pinthKernel32 = (PIMAGE_NT_HEADERS)(dwKernel32 +
*(PDWORD)(dwKernel32 + 0x3C));
UINT uKernel32Size = pinthKernel32->OptionalHeader.SizeOfImage;
while (dwFramePtr) {
DWORD dwRetAddress = *(((PDWORD)dwFramePtr) + 1);
if ((UINT)(dwRetAddress - dwKernel32) <= uKernel32Size) {
break;
}
dwFramePtr = *(PDWORD)dwFramePtr;
}
DWORD dwStackPtr;
__asm {
mov dwStackPtr,esp
}
DWORD dwDelta = dwFramePtr - dwStackPtr;
WriteProcessMemory(pi.hProcess, (PVOID)dwStackPtr, (PVOID)dwStackPtr, dwDelta, &dwDummy);
BOOL RemoteChildFlag = TRUE;
WriteProcessMemory(pi.hProcess, (PVOID)&InTheChild, (PVOID)&RemoteChildFlag, 4, &dwDummy);
SetThreadContext(pi.hThread, &c);
ResumeThread(pi.hThread);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return 0;
}