| 11-23-2007, 10:31 PM | #1 |
I am researching doing the things grimoire does in a language less nightmarish than C++. I am using GHC 6.8.1 and nasm 0.98.39. Injectable DLLs First you need a stub DllMain Code:
// dllmain.c
// compile with ghc -c dllmain.c
#include <windows.h>
#include <Rts.h>
#include <stdio.h>
extern void __stginit_ListFile(void);
static char* args[] = { "ghcDll", NULL };
extern int hsMain(void);
FILE *out;
DWORD __stdcall WorkThread(void* param)
{
return hsMain();
}
BOOL __stdcall DllMain(HMODULE module, DWORD reason, void* reserved)
{
if (reason == DLL_PROCESS_ATTACH) {
DisableThreadLibraryCalls(module);
startupHaskell(1, args, __stginit_ListFile);
CloseHandle(CreateThread(NULL,16384,WorkThread,NULL,0,NULL));
}
return TRUE;
}Each module in the haskell code into which you call directly needs a __stginit_ModuleName. For testing this, the executable you inject into needs some life time since we are in non deterministic multithreading land. In order to not block you have to createthread from the start gate. Your haskell code needs the foreign function interface. The callin listed in dllmain.c is hsMain. Code:
-- listfile.hs
-- compile with del listfile.o; ghc -shared -o listfile.dll listfile.hs dllmain.o
{-# LANGUAGE ForeignFunctionInterface #-}
foreign export ccall hsMain :: IO Int
hsMain :: IO Int
hsMain = return 0 -- your main hereMarshalling values Grimoire has lots of assembly snippets for patching warcraft. Ideally we would write these in haskell with a library like Harpy, but Harpy isn't very stable and creates MB of executable. So we continue to use nasm. To import a C function: Code:
foreign import ccall unsafe "LFPatch" lfpatch :: Addr Code:
foreign export ccall get_sFileOpenFileEx :: IO Addr get_sFileOpenFileEx :: IO Addr get_sFileOpenFileEx = readIORef glob_sFileOpenFileEx Since we can't construct foreign exports by currying, we need global variables to fake it. We do this with a horrible hack Code:
{-# NOINLINE genglob #-}
genglob :: a -> IORef a
genglob = unsafePerformIO . newIORefPointers We need to fiddle around with warcraft bits. We have Code:
peek :: Storable a => Ptr a -> IO a poke :: Storable a => Ptr a -> a -> IO () pokeElemOff :: Storable a => Ptr a -> Int -> a -> IO () peekElemOff :: Storable a => Ptr a -> Int -> IO a Code:
patchbytes :: Addr -> Addr -> DWORD -> IO () patchbytes dest src len = do old <- virtualProtect dest len pAGE_READWRITE moveMemory dest src len virtualProtect dest len old Structs We want instances of Storable for weird structs. Unfortunately ghc can't derive them automatically, although maybe a tool like c2hs can? For now I use hsc2hs. Code:
//foo.h
#pragma pack(1)
typedef struct {
int x;
char c;
int y;
} Foo;
#pragma pack()
void print_foo(const Foo *);Code:
//foo.c
#include "foo.h"
#include <stdio.h>
#include <stdlib.h>
void print_foo(const Foo *f)
{
printf("%s %d\n",__FUNCTION__,sizeof(Foo));
printf("f->x: %d\n",f->x);
printf("f->c: %d\n",f->c);
printf("f->y: %d\n",f->y);
}
Foo *makefoo()
{
Foo *f = (Foo *)malloc(sizeof(Foo));
f->x = 10;
f->c = 15;
f->y = 20;
}Code:
-- struct.hsc
-- compile with hsc2hs struct.hsc && ghc --make struct.hs foo.c
{-# LANGUAGE ForeignFunctionInterface #-}
module Main where
import Foreign
import Foreign.C.Types
#include "foo.h"
data Bar = Bar { x :: Int, c :: Word8, y :: Int } -- haskell record for Foo
foreign import ccall "static foo.h print_foo" f_print_foo :: Ptr Bar -> IO () -- static does compiletime type check presumably
foreign import ccall "makefoo" f_makefoo :: IO (Ptr Bar)
instance Storable Bar where
sizeOf _ = (#size Foo)
alignment _ = alignment (undefined :: Word8)
peek ptr = do
x' <- (#peek Foo, x) ptr
c' <- (#peek Foo, c) ptr
y' <- (#peek Foo, y) ptr
return Bar { x=x', c=c', y=y' }
poke ptr (Bar x' c' y') = do
(#poke Foo, x) ptr x'
(#poke Foo, c) ptr c'
(#poke Foo, y) ptr y'
printFoo b = with b f_print_foo
foo_setx :: Ptr Bar -> Int -> IO ()
foo_setx foo x' = (#poke Foo, x) foo x'
main = do
x <- f_makefoo -- fetch Ptr Bar from C land
b <- peek x -- read Ptr to construct a haskell Bar
printFoo b -- allocate temporary to send a ptr back to C
poke x $ Bar 20 30 40 -- write out a new bar to old Ptr location
foo_setx x 15 -- call C function on old Ptr
f_print_foo x -- and again!
-- there may be a better technique using ByteString and Data.Binary
-- Data.Binary can automatically derive instances, apparently.Sample DLL As a particularly simple example I translated the listfile generator to haskell. This does not demonstrate struct marshalling. Code:
--listfile.hs
{-# LANGUAGE ForeignFunctionInterface #-}
{-
- Implementation of listfile dump in haskell
-}
module ListFile where
import System.IO
import System.Win32.Types
import Foreign
import Foreign.C.String
import Data.IORef
import Control.Exception as E
import Hacklib
-- C imports
foreign import ccall unsafe "LFPatch" lfpatch :: Addr
foreign import ccall unsafe "LFPatchEnd" lfpatchend :: Addr
lfpatchsize = (castPtrToUINT lfpatchend) - (castPtrToUINT lfpatch)
-- Global variables
glob_out = openglobf "logs\\listfile.txt"
glob_sFileOpenFileEx = nullglob
-- actions
hsMain_ :: IO Int
hsMain_ = do
inithacklib "logs\\listfile_log.txt"
readIORef glob_out >>= hFlush -- force early initialization
lib <- waitformodule "storm.dll"
sFileOpenFileEx <- getProc lib "sFileOpenFileEx" $ castUINTToPtr 268
writeIORef glob_sFileOpenFileEx sFileOpenFileEx
sFileGetArchiveName <- getProc lib "sFileGetArchiveName" $ castUINTToPtr 275
patchbytes sFileOpenFileEx lfpatch lfpatchsize
logmsg "Listfile loading complete."
return 1
-- exports
-- callbacks embedded into warcraft
foreign export ccall get_sFileOpenFileEx :: IO Addr
get_sFileOpenFileEx :: IO Addr
get_sFileOpenFileEx = readIORef glob_sFileOpenFileEx
foreign export ccall lfHandler :: Int -> CString -> IO ()
lfHandler :: Int -> CString -> IO ()
lfHandler mpq fn = do
s <- peekCString fn
out <- readIORef glob_out
hPutStrLn out s
-- for dllMain
foreign export ccall hsMain :: IO Int
hsMain :: IO Int
hsMain = E.catch hsMain_ (\e -> logmsg (show e) >> return 0)Code:
--hacklib.hs
{-
- Common functions for hacking
-}
module Hacklib(inithacklib,openglobf,nullglob,waitformodule,patchbytes,getProc,logmsg) where
import System.IO
import System.IO.Unsafe
import Data.IORef
import Foreign.Ptr
import System.Win32.Mem
import System.Win32.Process
import System.Win32.DLL
import System.Win32.Types
-- Global variable construction. Unholy evil.
{-# NOINLINE genglob #-}
genglob :: a -> IORef a
genglob = unsafePerformIO . newIORef
{-# NOINLINE openglobf #-}
openglobf :: String -> IORef Handle
openglobf fn = genglob . unsafePerformIO . openf
openf :: String -> IO Handle
openf fn = do
f <- openFile fn WriteMode
hSetBuffering f LineBuffering
return f
nullglob :: IORef (Ptr a)
nullglob = genglob nullPtr
-- Miscellaneous Actions
waitformodule :: String -> IO HMODULE
waitformodule x = do
hmodule <- getModuleHandle (Just x)
if hmodule == nullPtr then sleep 1 >> waitformodule x else return hmodule
patchbytes :: Addr -> Addr -> DWORD -> IO ()
patchbytes dest src len = do
old <- virtualProtect dest len pAGE_READWRITE
moveMemory dest src len
old2 <- virtualProtect dest len old
return ()
erronnull :: Ptr a -> String -> IO ()
erronnull p n = if p == nullPtr then error ("couldn't open "++n) else return ()
getProc :: HMODULE -> String -> LPCSTR -> IO Addr
getProc lib print_name get_name = do
addr <- c_GetProcAddress lib get_name
erronnull addr print_name
logmsg $ print_name++": "++show addr
return addr
-- Logging engine
glob_log :: IORef Handle
glob_log = genglob stdout
logmsg :: String -> IO ()
logmsg s = do
f <- readIORef glob_log
hPutStrLn f s
inithacklib :: String -> IO ()
inithacklib fn = do
f <- openf fn
writeIORef glob_log fCode:
;lfasm.asm [bits 32] global _LFPatch global _LFPatchEnd extern _get_sFileOpenFileEx extern _lfHandler ;;cdecl of two args section .text _LFPatch: mov eax, _XLFPatch ret align 16 _LFPatchEnd: mov eax, _XLFPatchEnd ret align 16 _XLFPatch: ;;either way it's 7 bytes mov eax,_LFHook jmp eax nop ;;make it 9 bytes nop _XLFPatchEnd: align 16 _LFHook: mov eax,[esp+8] push eax mov eax,[esp+8] push eax call _lfHandler add esp,8 push ebp ;1B ;do overwritten work mov ebp,esp ;2B sub esp,0x10c ;6B call _get_sFileOpenFileEx ;mov eax,[_SFileOpenFileEx] ;return to overwritten function lea eax,[eax+_XLFPatchEnd-_XLFPatch] jmp eax _LFHookEnd: align 16 Code:
#Makefile all: listfile.dll listfile.dll: hacklib.o listfile.hs dllmain.o lfasm.o del listfile.o ghc -shared -o listfile.dll listfile.hs hacklib.o dllmain.o lfasm.o -package Win32 -package mtl hacklib.o: hacklib.hs ghc -c hacklib.hs dllmain.o: dllmain.c ghc -c dllmain.c -Wall lfasm.o: lfasm.asm nasmw -o lfasm.o -f win32 lfasm.asm clean: del *.o listfile.dll *.a listfile_stub.* This provides the IO monad skin for writing more complex hacks. |
| 11-29-2007, 08:56 PM | #2 |
Ah haskell, I only ever used that for compiling the whitespace compiler. Keep up the good work with Grimoire. |
| 12-03-2007, 01:35 AM | #3 |
Hmm, nightmarish? C++ = Good Windows GUI && C++ = Nightmare * 2349238423487929263428934623864367 I see your point. (my opinion, just stick with the console) |
