aplikacja jako dll?

0

Witam.

Jak w temacie a dokładniej chodzi mi o wykonanie kodu aplikacji znając jej np. PID... ale najlepiej przykład:

Mamy aplikację A.exe. Program jest załadowany w pamięci i uruchomiony. Gdzieś tam w kodzie pod adresem np. 0x403000 rozpoczyna się funkcja o trzech parametrach która np. tworzy na dysku plik...

... i chciałbym stworzyć program B.exe który byłby w stanie uruchomić fragment kodu o adresie 0x403000 z aplikacji A.exe... coś w rodzaju takiego globalnego 'Calla' :)

Z odczytaniem tego fragmentu kodu nie ma problemu... ale co z uruchomieniem...?

Mam nadzieje że przykład czytelny ;-)
Pozdrawiam

0

Możesz to rozwiązać używając code injection.

Z procesu B rezerwujesz pamięć w procesie A lub poprzez DLL injection ładujesz doń swoją bibliotekę. W przypadku code injection musisz zarezerwować pamięć na wstrzyknięty kod i na małą strukturę, która będzie przechowywała parametry do funkcji, która ma być wywołana w A. Następnie Zapełniasz zarezerwowaną pamięć w A odpowiednimi danymi i wywołujesz zdalny wątek CreateRemoteThread, którego parametrem jest adres do struktury przechowującej parametry funkcji w A. Twoja wstrzyknięta funkcja w A uruchamia się jako osobny wątek i przetwarza parametry podane we wstrzykniętej strukturze, a następnie wrzuca owe parametry na stos i wywołuje Twoją funkcję o adresie 0x400.... (zapomniałem jak to tam szło). Zaraz po wywołaniu funkcji możesz dać RET aby zakończyć wątek.

[edited]
Oczywiście jeśli zechcesz zaimplementować to jako DLL injection to masz o połowę mniej roboty ;)

0

A oto i przykład:
Program ptrzebuje jakiegoś programu aby to zademonstrować, więc po uruchomieniu szuka okna o unikalnym tytule i gdy go nie znajdzie, to je tworzy i uruchamia się od nowa, a gdy znajdzie to dobiera się do procesu właściciela okna.
Dla ułatwienia onkem jest MessageBox, a w nim, w property "callme" jest adres funkcji którą należy wywołać.

Mając uchwyt okna zdobywamy PID i TID (thread id), otwieramy proces i wątek obsługujący okno.
Zatrzymujemy wątek, alokujemy w procesie stronę pamięci i wpisujemy do niej kod, który wyśle na stos parametry i skoczy do podanej funkcji, a następnie wznowi działanie wątku w przerwanym momencie.
Kod pisany w vc++6:

// zanim uruchomisz, zapoznaj się z kolejnością zamykania okien:

  1. Pierwsze okno będzie zachęcało do zainfekowania procesu.
  2. Drugie okno się pokaże zaraz po infekcji (ukryta funkcja)
  3. Trzecie okno będzie prosiło o zamknięcie drugiego okna aby zwolnić pamięć
    Ponieważ brak tu jakiejkolwiek automatycznej synchronizacji, zdalnie wywołana funkcja może się wykonywać przez nieokreślony czas (MessageBox), więc jako pierwsze zamykasz okno o tytule "brawo!"
#define _WIN32_WINNT 0xFFFF
#include <windows.h>
#include <stdio.h>
#define CAPTION "1FC90BEF-58F0-4B97-B8FF-AADEEB6B2386" // unikat

void    __stdcall TargetFunc(int,int,int);
LRESULT __stdcall SaveFuncLongCbtProc(int,WPARAM,LPARAM);
void  CallFunction(DWORD,DWORD,FARPROC,int,int,int);
extern "C" WINBASEAPI HANDLE WINAPI OpenThread(DWORD dwDesiredAccess,BOOL bInheritHandle,DWORD dwThreadId);

HHOOK g_hook;


int main()
{
	// bylejaka synchronizacja dla uprzedniego ShellExecute
	Sleep(1000);
	// okno otwarte?
	if (HWND hwnd = FindWindow("#32770", CAPTION))
	{
		// tak, wywołaj funkcję
		DWORD pid;
		DWORD thid = GetWindowThreadProcessId(hwnd, &pid);
		// adres funkcji do wykonania pobieramy z property okna
		FARPROC func = (FARPROC)GetProp(hwnd, "callMe");
		CallFunction(pid, thid, func, 3,4,5);
	}
	else
	{
		// otwórz okno i uruchom samego siebie
		char txt[MAX_PATH];
		GetModuleFileName(0, txt, MAX_PATH);
		ShellExecute(0,"open",txt,0,0,0);
		// stwórz docelowe okno
		sprintf(txt, "inject me at 0x%X", (DWORD)TargetFunc);

		// zanim otworzymy okno, uswawimy w nim property na adres funkcji TargetFunc()
		g_hook = SetWindowsHookEx(WH_CBT, SaveFuncLongCbtProc, 0, GetCurrentThreadId());
		MessageBox(0, txt, CAPTION, 0);
		UnhookWindowsHookEx(g_hook);

	}
	return 0;
}


//---------------------------------------------------------
// funkcja którą należy zdalnie wywołać
// jej adres jest wpisywany do MsgBoxa jako property "callMe"
void __stdcall TargetFunc(int q,int w,int e)
{
	char txt[64];
	sprintf(txt, "funkcja TargetFunc(%d,%d,%d)", q,w,e);
	MessageBox(0, txt, "brawo!", MB_TOPMOST);
}

//---------------------------------------------------------
// zapisuje w oknie MsgBox'a adres funkcji do wykonania
LRESULT __stdcall SaveFuncLongCbtProc(int nCode,WPARAM wParam,LPARAM lParam)
{
	if (nCode == HCBT_CREATEWND)
		SetProp((HWND)wParam, "callMe", (HANDLE)TargetFunc);

	return CallNextHookEx(g_hook, nCode, wParam, lParam);
}


//---------------------------------------------------------
//
void CallFunction(DWORD pid,DWORD thid,FARPROC func,int arg1,int arg2,int arg3)
{
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, pid);
	HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, false, thid);
	CONTEXT ctx;
	ctx.ContextFlags = 0x1003F;
	SuspendThread(hThread);
	GetThreadContext(hThread, &ctx);
	// zapamiętaj adres aktualnego rozkazu 
	DWORD dwCurrentEIP = ctx.Eip;
	void *remote = VirtualAllocEx(hProcess,0,4096,MEM_COMMIT,PAGE_EXECUTE_READ);

#pragma pack(1)
	struct CALLER
	{
		char x1;    // 0x68 push dword
		DWORD eip;  //      dwCurrentEIP
		char x2[2]; // 0x60,0x9C pusha;pushf;
		char pu3;   // 0x68 push dword
		int arg3;   //      arg3
		char pu2;   // 0x68 push dword
		int arg2;   //      arg2
		char pu1;   // 0x68 push dword
		int arg1;   //      arg1
		char p;     // 0xB8 mov eax,
		FARPROC fn; //      func
		char cal[2];// 0xFF,0xD0 call eax
		char pop[3];// 0x9D,0x61,0xC2 popf;popa;ret ?
	};
#pragma pack()

	CALLER code = {
		'\x68',dwCurrentEIP, // push dword dwCurrentEIP
		'\x60','\x9C',       // pusha;pushf;
		'\x68',arg3,         // push dword arg3
		'\x68',arg2,         // push dword arg2
		'\x68',arg1,         // push dword arg1
		'\xB8',func,         // mov eax,func
		'\xFF','\xD0',       // call eax
		'\x9D','\x61',       // popf/popa
		'\xC3'};             // ret

	WriteProcessMemory(hProcess,remote,&code, sizeof(CALLER), 0);
	//wznawiamy wykonywanie wątku w naszej funkcji
	ctx.Eip = (DWORD)remote;
	SetThreadContext(hThread, &ctx);
	ResumeThread(hThread);
	// ponieważ funkcja 'func' używa MessageBox, zaczekamy
	// na jej zakończenie w podobny sposób:
	MessageBox(0, "kliknij OK po zamknięciu okna 'brawo'", "", 0);
	VirtualFreeEx(hProcess,remote,4096,MEM_RELEASE);
	CloseHandle(hThread);
	CloseHandle(hProcess);
}

// edit
Mając chwilkę czasu dodałem synchronizację polegającą na automatycznym zwalnianiu pamięci bea potrzeby użycia MessageBox'a.
Użyłem obiektu synchronizacji typu Event, którego uchwyt zostaje zduplikowany dla docelowego procesu. Gdy funkcja func() powróci, event jest ustawiany a program wywołujący tę funkcję jest o tym informowany przez system, po czym odczekuje jeszcze chwilkę i zwalnia pamięć:
(zakładam że kernel32.dll jest w docelowym procesie by default)

// pid  - process id
// thid - thread id
// func - adres funkcji do wykonania w procesie pid
// arg* - 4 bajtowe parametry funkcji func

void CallFunction(DWORD pid,DWORD thid,FARPROC func,int arg1,int arg2,int arg3)
{
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, pid);
	HANDLE hThread   = OpenThread(THREAD_ALL_ACCESS, false, thid);
	// zamroź wątek
	SuspendThread(hThread);
	// pobierz rejetry wątku
	CONTEXT ctx;
	ctx.ContextFlags = 0x1003F;
	GetThreadContext(hThread, &ctx);

	// zapamiętaj adres aktualnego rozkazu bo zostanie chwilowo podmieniony
	DWORD dwCurrentEIP = ctx.Eip;
	// zaalokuj stronę pamięci na nasz kod
	void *remote = VirtualAllocEx(hProcess, 0, 4096, MEM_COMMIT, PAGE_EXECUTE_READ);
	// stwórz synchronizator
	HANDLE hEvent = CreateEvent(0, false, false, CAPTION);

#pragma pack(1)
	struct CALLER
	{
		char push1;   // push dword dwCurrentEIP <- adres powrotny do przerwanego wątku
		DWORD eip;   
		char paf[2];  // pusha;pushf;
		char push2;   // push dword arg3
		int arg1;
		char push3;   // push dword arg2
		int arg2;
		char push4;   // push dword arg1
		int arg3;
		char p;       // mov eax, func
		FARPROC fn;
		char ca1[2];  // call eax // wykonaj func(arg1,arg2,arg3);

		char push5;   // push dword hEvent // wykonaj SetEvent(hEvent);
		HANDLE hEvent;
		char x4;      // mov eax, SetEvent
		FARPROC fn2;
		char ca2[2];  // call eax

		char push6;      // push dword hEvent // wykonaj CloseHandle(hEvent);
		HANDLE hEvent2;
		char x6;      // mov eax, CloseHandle
		FARPROC fn3;
		char ca3[2];  // call eax

		char pop[3];  // popf;popa;ret
	};
#pragma pack()
	HMODULE kernel32 = GetModuleHandle("kernel32.dll");
#define API(x) GetProcAddress(kernel32, x)

	CALLER code = {
		'\x68',dwCurrentEIP, // push dword dwCurrentEIP
		'\x60','\x9C',       // pusha;pushf;
		'\x68',arg3,         // push dword arg3
		'\x68',arg2,         // push dword arg2
		'\x68',arg1,         // push dword arg1
		'\xB8',func,         // mov eax,func
		'\xFF','\xD0',       // call eax

		'\x68',hEvent,       // push dword hEvent
		'\xB8',API("SetEvent"),// mov eax,SetEvent
		'\xFF','\xD0',       // call eax

		'\x68',hEvent,       // push dword hEvent
		'\xB8',API("CloseHandle"),// mov eax,CloseHandle
		'\xFF','\xD0',       // call eax

		'\x9D','\x61',       // popf/popa
		'\xC3'};             // ret

	// zduplikuj event aby inny proces mógł go ustawić
	DuplicateHandle(GetCurrentProcess(), hEvent, hProcess, &code.hEvent,DUPLICATE_SAME_ACCESS,false,0);
	code.hEvent2 = code.hEvent;
	// skopiuj kod do procesu
	WriteProcessMemory(hProcess,remote,&code, sizeof(CALLER), 0);
	//wznawiamy wykonywanie wątku w naszej funkcji
	ctx.Eip = (DWORD)remote;
	SetThreadContext(hThread, &ctx);
	// uruchom wątek
	ResumeThread(hThread);
	// czekaj aż funkcja zakończy
	WaitForSingleObject(hEvent, INFINITE);
	// zaczekaj jeszcze chwilkę aby funkcja zdążyła powrócić do oryginalnego kodu
	Sleep(0);
	CloseHandle(hEvent);
	CloseHandle(hThread);
	VirtualFreeEx(hProcess, remote, 4096, MEM_RELEASE);
	CloseHandle(hProcess);
}

Małe wyjaśnienie celu użycia rozkazów pusha + pushf:
Funkcja SuspendThread przerywa wykonywanie się wątku i może to się zdarzyć w dowolnym momencie, np. wtedy gdy coś z czymś jest porównywane, a rejestry przechowują pointery do danych, liczniki przebiegów pętli... Jeśli nie zabezpieczymy rejestrów (pusha/popa) to program będzie narażony na wyjątki.
Jeśli nie zabezpieczymy flag procesora (pushf/popf) to wynik "przerwanej" operacji porównania, mnożenia, dodawania... zostanie zafałszowany.
Kod wstrzyknięty w taki sposób powinien spełniać te same wymogi co funkcje obsługi przerwań.

Apropos skoków do funkcji - użycie rejestru jako FARPROC zwalnia nas od obliczania offsetu +- do docelowej funkcji, wiadomo że w dzisiejszych procesorach prawie wszystkie skoki bezpośrednie są relatywne, czyli adres następnego rozkazu + 1,2,4 bajtowy offset (call'a też to dotyczy).

0
sapero napisał(a)

(zakładam że kernel32.dll jest w docelowym procesie by default)

AFAiK to kernel32.dll jest ładowany zawsze (nawet jeśli IAT o to nie prosi) do programu i w to samo miejsce w każdym odpalonym procesie. :-)

sapero napisał(a)

Apropos skoków do funkcji - użycie rejestru jako FARPROC zwalnia nas od obliczania offsetu +- do docelowej funkcji, wiadomo że w dzisiejszych procesorach prawie wszystkie skoki bezpośrednie są relatywne, czyli adres następnego rozkazu + 1,2,4 bajtowy offset (call'a też to dotyczy).

Można też

PUSH adres
RET

Wtedy nie marnujemy żadnego z rejestrów ;)

1 użytkowników online, w tym zalogowanych: 0, gości: 1