// pokerproxy.cpp : Defines the entry point for the application.
//

#include "stdafx.h"
#include "pokerproxy.h"

LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);

int gimmeprivs()
{
    // enable SE_DEBUG_NAME and try again

    TOKEN_PRIVILEGES Priv, PrivOld;
    DWORD cbPriv = sizeof(PrivOld);
    HANDLE hToken;

    // get current thread token 
    if (!OpenThreadToken(GetCurrentThread(), 
                            TOKEN_QUERY|TOKEN_ADJUST_PRIVILEGES,
                            FALSE, &hToken))
    {
        if (GetLastError() != ERROR_NO_TOKEN)
            return FALSE;

        // revert to the process token, if not impersonating
        if (!OpenProcessToken(GetCurrentProcess(),
                                TOKEN_QUERY|TOKEN_ADJUST_PRIVILEGES,
                                &hToken))
            return FALSE;
    }

//    _ASSERTE(ANYSIZE_ARRAY > 0);

    Priv.PrivilegeCount = 1;
    Priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    LookupPrivilegeValue(NULL, SE_DEBUG_NAME, 
                            &Priv.Privileges[0].Luid);

    // try to enable the privilege
    if (!AdjustTokenPrivileges(hToken, FALSE, &Priv, sizeof(Priv),
                                &PrivOld, &cbPriv))
    {
        int dwError = GetLastError();
        CloseHandle(hToken);
        return SetLastError(dwError), FALSE;
    }

    if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
    {
        // the SE_DEBUG_NAME privilege is not in the caller's token
        CloseHandle(hToken);
        return SetLastError(ERROR_ACCESS_DENIED), FALSE;
    }

    return TRUE;
}

HINSTANCE hInst;

int APIENTRY _tWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
	hInst = hInstance;
	WORD wVersionRequested = MAKEWORD( 2, 2 );
	WSADATA wsaData;
	WSAStartup( wVersionRequested, &wsaData );
	DialogBox(hInstance, (LPCTSTR)IDD_ABOUTBOX, NULL, (DLGPROC)About);
	return 1; 
}

unsigned int proxy_ip[4];
unsigned int proxy_port = 8080;
HWND hStatus, hAttach;

typedef struct {
	unsigned char under_retaddr;
	DWORD connect_handle;
	unsigned char conn_to[4];
} conn_request;

bool WINAPI attachToClient(int n)
{
	char buf[1024];

	HWND wnd;

	wnd = FindWindow(NULL, "Connecting to Server...");
	if (wnd == NULL)
		wnd = FindWindow(NULL, "Not Connected");
	if (wnd == NULL)
		wnd = FindWindow("SWT_Window0", NULL);
	if (wnd == NULL) {
		MessageBox(NULL, "Cannot find the Poker.com client.  You need to run the client before attempting to attach to it.", "Cannot find client.", MB_OK);
		return false;
	}
	//RealGetWindowClass(wnd, buf, sizeof(buf));
	//MessageBox(NULL, buf, "Window class", MB_OK);

	DWORD pid;
	GetWindowThreadProcessId(wnd, &pid);
    if (!DebugActiveProcess(pid)) {
        gimmeprivs();
        if (!DebugActiveProcess(pid)) {
            sprintf(buf, "cannot attach to process %i err=%i\n", pid, GetLastError());
			SendMessage(hStatus, LB_ADDSTRING, 0, (LPARAM)buf);
            return false;
        }
    }
    DebugSetProcessKillOnExit(FALSE);
	sprintf(buf, "attached to process %i", pid);
	SendMessage(hStatus, LB_ADDSTRING, 0, (LPARAM)buf);
	EnableWindow(hAttach, FALSE);

	CONTEXT context;
	PROCESS_INFORMATION pi;
	HANDLE hThread;
    DEBUG_EVENT DebugEv;                   // debugging event information 
    DWORD dwContinueStatus = DBG_CONTINUE; // exception continuation 
    bool first = true;
    ULONG64 stepFrom = 0;
    unsigned char stop = 0xcc;
	FARPROC a_connect = GetProcAddress(GetModuleHandle("ws2_32.dll"), "connect");
	unsigned char undercc;
	std::map<FARPROC, std::map<DWORD, conn_request> > retaddrs;

    for(;;) 
    { 
 
        // Wait for a debugging event to occur. The second parameter indicates 
        // that the function does not return until a debugging event occurs. 
         
		if (!WaitForDebugEvent(&DebugEv, 10)) {
			Sleep(100);
			continue;
		}

		DWORD threadid = DebugEv.dwThreadId;
        
        hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, DebugEv.dwThreadId);
		if (hThread == NULL)
			break;
		FARPROC addr = (FARPROC)DebugEv.u.Exception.ExceptionRecord.ExceptionAddress;

        // Process the debugging event code. 
         
        switch (DebugEv.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. 
        
                switch (DebugEv.u.Exception.ExceptionRecord.ExceptionCode) 
                { 
                    case EXCEPTION_ACCESS_VIOLATION: 
                    // First chance: Pass this on to the system. 
                    // Last chance: Display an appropriate error. 
						//printf("access violation at %x\n", addr);
						TerminateProcess(pi.hProcess, 1);
						return false;
        
                    case EXCEPTION_BREAKPOINT: 
                    // First chance: Display the current 
                    // instruction and register values.
//						printf("breakpoint at addr %x\n", addr);
						if (first) {
							first = false;
                            SIZE_T nread;
                            ReadProcessMemory(pi.hProcess, (LPVOID)a_connect, &undercc, 1, &nread);
                            WriteProcessMemory(pi.hProcess, (LPVOID)a_connect, &stop, 1, &nread);
						} else {
                            SIZE_T nread, written;
							if (addr != a_connect) {
								if (retaddrs.find(addr) != retaddrs.end() &&
										retaddrs[addr].find(threadid) != retaddrs[addr].end()) {
									context.ContextFlags = CONTEXT_ALL;
									GetThreadContext(hThread, &context);
									printf("retaddr eax=%i\n", context.Eax);
									WriteProcessMemory(pi.hProcess, (LPVOID)addr, &retaddrs[addr][threadid].under_retaddr, 1, &written);
									context.Eip--;
									SetThreadContext(hThread, &context);
									if (context.Eax == 0) {
										HANDLE h;
										if (DuplicateHandle(pi.hProcess, (HANDLE)retaddrs[addr][threadid].connect_handle, GetCurrentProcess(), &h, 0, FALSE, DUPLICATE_SAME_ACCESS)) {
											printf("duplicated handle\n");
											char buf[1024];
											sprintf(buf, "CONNECT %i.%i.%i.%i:443 HTTP/1.0\n\n", retaddrs[addr][threadid].conn_to[0], retaddrs[addr][threadid].conn_to[1], retaddrs[addr][threadid].conn_to[2], retaddrs[addr][threadid].conn_to[3]);
											printf("sending %s result %i\n", buf, send((SOCKET)h, buf, strlen(buf), 0));
											int got = recv((SOCKET)h, buf, 1024, 0);
											if (got > 0 && got < 1023) {
												buf[got] = 0;
												printf("recved %i bytes: %s\n", got, buf);
											}
											sprintf(buf, "proxied connection to %i.%i.%i.%i:443", retaddrs[addr][threadid].conn_to[0], retaddrs[addr][threadid].conn_to[1], retaddrs[addr][threadid].conn_to[2], retaddrs[addr][threadid].conn_to[3]);
											SendMessage(hStatus, LB_ADDSTRING, 0, (LPARAM)buf);
										}
									}
									retaddrs[addr].erase(threadid);
									if (retaddrs[addr].size() == 0)
										retaddrs.erase(addr);
									break;
								}
								sprintf(buf, "stopped at unknown address %x\n", (DWORD)addr);
								SendMessage(hStatus, LB_ADDSTRING, 0, (LPARAM)buf);
								break;
								//TerminateProcess(pi.hProcess, 1);
								//return 1;
							}
							context.ContextFlags = CONTEXT_ALL;
							GetThreadContext(hThread, &context);
						
							unsigned char stack[100];
							DWORD *d = (DWORD*)&stack;
                            ReadProcessMemory(pi.hProcess, (LPVOID)context.Esp, stack, sizeof(stack), &nread);

#if 0
							printf("%i %s.%s(%x %x %x %x %x %x %x %x %x %x) ret=%x\n",
								time(NULL), 
								apis[addr].dll.c_str(),
								apis[addr].name.c_str(),
								d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9], d[10], d[0]);
#endif

							struct sockaddr_in sin;
							unsigned char *p = (unsigned char *)&sin.sin_addr;
							ReadProcessMemory(pi.hProcess, (LPVOID)d[2], &sin, sizeof(sin), &nread);
							printf("to %i.%i.%i.%i : %i\n", p[0], p[1], p[2], p[3], ((sin.sin_port & 0xff) << 8) | (sin.sin_port >> 8) );

#if 0
							if (p[3] == 50) {
								sin.sin_port = 0xbc01;   // 444
							}
							p[0] = 127; p[1] = 0; p[2] = 0; p[3] = 1;						
							WriteProcessMemory(pi.hProcess, (LPVOID)d[2], &sin, sizeof(sin), &nread);
#endif
							if (sin.sin_port == htons(443)) {
								FARPROC retaddr = (FARPROC)d[0];
								retaddrs[retaddr][threadid].conn_to[0] = p[0];
								retaddrs[retaddr][threadid].conn_to[1] = p[1];
								retaddrs[retaddr][threadid].conn_to[2] = p[2];
								retaddrs[retaddr][threadid].conn_to[3] = p[3];
								retaddrs[retaddr][threadid].connect_handle = d[1];
								ReadProcessMemory(pi.hProcess, (LPVOID)d[0], &retaddrs[retaddr][threadid].under_retaddr, 1, &nread);
								WriteProcessMemory(pi.hProcess, (LPVOID)d[0], &stop, 1, &nread);
								p[0] = 203; p[1] = 20; p[2] = 243; p[3] = 20;
								sin.sin_port = htons(8080);
								WriteProcessMemory(pi.hProcess, (LPVOID)d[2], &sin, sizeof(sin), &nread);
							}

							WriteProcessMemory(pi.hProcess, (LPVOID)addr, &undercc, 1, &written);
							context.Eip--;
							context.EFlags |= 256;
							SetThreadContext(hThread, &context);
							stepFrom = (ULONG64)addr;
						}
                        break;
        
                    case EXCEPTION_DATATYPE_MISALIGNMENT: 
                    // First chance: Pass this on to the system. 
                    // Last chance: Display an appropriate error. 
                        break;
        
                    case EXCEPTION_SINGLE_STEP: 
                    // First chance: Update the display of the 
                    // current instruction and register values. 
                        if (stepFrom) {
                            unsigned char stop = 0xcc;
                            SIZE_T written;
                            WriteProcessMemory(pi.hProcess, (LPVOID)stepFrom, &stop, 1, &written);
                            stepFrom = 0;
						} else {
							printf("single step to %x\n", addr);
                            context.ContextFlags = CONTEXT_ALL;
                            GetThreadContext(hThread, &context);
							context.EFlags |= 256;
							SetThreadContext(hThread, &context);
						}
                        break;
        
                    case DBG_CONTROL_C: 
                    // First chance: Pass this on to the system. 
                    // Last chance: Display an appropriate error. 
                        break;
        
                    default:
                    // Handle other exceptions. 
                        break;
                } 
                break;
        
            case CREATE_THREAD_DEBUG_EVENT: 
            // As needed, examine or change the thread's registers 
            // with the GetThreadContext and SetThreadContext functions; 
            // and suspend and resume thread execution with the 
            // SuspendThread and ResumeThread functions. 
                break;

            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.
                pi.hProcess = DebugEv.u.CreateProcessInfo.hProcess;
                break;
        
            case EXIT_THREAD_DEBUG_EVENT: 
            // Display the thread's exit code. 
                break;
        
            case EXIT_PROCESS_DEBUG_EVENT:
				sprintf(buf, "detached from process");
				SendMessage(hStatus, LB_ADDSTRING, 0, (LPARAM)buf);
				EnableWindow(hAttach, TRUE);
                return true;
            // Display the process's exit code. 
                break;
        
            case LOAD_DLL_DEBUG_EVENT: 
            // Read the debugging information included in the newly 
            // loaded DLL. Be sure to close the handle to the loaded DLL 
            // with CloseHandle.
                break;
        
            case UNLOAD_DLL_DEBUG_EVENT: 
            // Display a message that the DLL has been unloaded. 
                break;
        
            case OUTPUT_DEBUG_STRING_EVENT: 
            // Display the output debugging string. 
                break;
        
        } 
         
        // Resume executing the thread that reported the debugging event.
        ContinueDebugEvent(DebugEv.dwProcessId, DebugEv.dwThreadId, dwContinueStatus); 
     
    }

	sprintf(buf, "detached from process");
	SendMessage(hStatus, LB_ADDSTRING, 0, (LPARAM)buf);
	EnableWindow(hAttach, TRUE);

	return true;
}

void readProxyFromRegistry()
{
	proxy_ip[0] = 0;

	HKEY key;
	if (RegCreateKey(HKEY_CURRENT_USER, "SOFTWARE\\Poker.com Proxy", &key) != ERROR_SUCCESS)
		return;
	char buf[1024];
	DWORD sz = sizeof(buf);
	DWORD t = REG_SZ;
	if (RegQueryValueEx(key, "Proxy Address", 0, &t, (unsigned char *)buf, &sz) != ERROR_SUCCESS) {
		RegCloseKey(key);
		return;
	}
	sscanf(buf, "%i.%i.%i.%i", &proxy_ip[0], &proxy_ip[1], &proxy_ip[2], &proxy_ip[3]);
	sz = sizeof(buf);
	t = REG_SZ;
	if (RegQueryValueEx(key, "Proxy Port", 0, &t, (unsigned char *)buf, &sz) != ERROR_SUCCESS) {
		RegCloseKey(key);
		return;
	}
	sscanf(buf, "%i", &proxy_port);
	RegCloseKey(key);
}

void writeProxyToRegistry()
{
	HKEY key;
	if (RegCreateKey(HKEY_CURRENT_USER, "SOFTWARE\\Poker.com Proxy", &key) != ERROR_SUCCESS)
		return;
	char buf[1024];
	sprintf(buf, "%i.%i.%i.%i", proxy_ip[0], proxy_ip[1], proxy_ip[2], proxy_ip[3]);
	RegSetValueEx(key, "Proxy Address", 0, REG_SZ, (unsigned char *)buf, strlen(buf)+1);
	sprintf(buf, "%i", proxy_port);
	RegSetValueEx(key, "Proxy Port", 0, REG_SZ, (unsigned char *)buf, strlen(buf)+1);
	RegCloseKey(key);
}

// Message handler for about box.
LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	char buf[1024];
	switch (message)
	{
	case WM_INITDIALOG:
		hStatus = GetDlgItem(hDlg, IDC_STATUS);
		hAttach = GetDlgItem(hDlg, IDC_ATTACH);
		readProxyFromRegistry();
		if (proxy_ip[0])
			sprintf(buf, "%i.%i.%i.%i", proxy_ip[0], proxy_ip[1], proxy_ip[2], proxy_ip[3]);
		else
			buf[0] = 0;
		SetDlgItemText(hDlg, IDC_PROXYIP, buf);
		sprintf(buf, "%i", proxy_port);
		SetDlgItemText(hDlg, IDC_PROXYPORT, buf);
		SetClassLong(hDlg, GCL_HICONSM, (LONG)LoadIcon(hInst, (LPCTSTR)IDI_SMALL));
		return TRUE;

	case WM_COMMAND:
		if (LOWORD(wParam) == IDCANCEL) {
			EndDialog(hDlg, IDCANCEL);
			return TRUE;
		}
		if (LOWORD(wParam) == IDC_ATTACH) 
		{
			char proxy[50], port[50];
			GetDlgItemText(hDlg, IDC_PROXYIP, proxy, sizeof(proxy));
			GetDlgItemText(hDlg, IDC_PROXYPORT, port, sizeof(port));
			if (sscanf(proxy, "%i.%i.%i.%i", &proxy_ip[0], &proxy_ip[1], &proxy_ip[2], &proxy_ip[3]) != 4) {
				MessageBox(NULL, "Please enter the IP address of your SSL proxy\n", "Invalid Proxy Address", MB_OK);
				break;
			}
			if (sscanf(port, "%i", &proxy_port) != 1) {
				MessageBox(NULL, "Please enter the port of your SSL proxy\n", "Invalid Proxy Port", MB_OK);
				break;
			}
			writeProxyToRegistry();
			SendMessage(hStatus, LB_RESETCONTENT, 0, 0);
			DWORD id;
			CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)attachToClient, 0, 0, &id);
		}
		break;
	}
	return FALSE;
}
