770 likes | 952 Views
Revealing Stealth Malware UMD CMSC389M. Xeno Kovah – Jan. 2013 xkovah at gmail Subject line starting with "UMD:". All materials is licensed under a Creative Commons "Share Alike" license. http://creativecommons.org/licenses/by-sa/3.0/. Outline.
E N D
Revealing Stealth MalwareUMD CMSC389M Xeno Kovah – Jan. 2013 xkovah at gmail Subject line starting with "UMD:"
All materials is licensed under a Creative Commons "Share Alike" license. • http://creativecommons.org/licenses/by-sa/3.0/
Outline • Note to self, go back and show FindFirstFile() struct again • Type 2 – change things that shouldn't be changed • Windows Portable Executable(PE) file format background • Import Address Table (IAT) or Export Address Table (EAT) hooks • A userspace OR kernel technique
A portrait of the rootkit as a young man in the middle (CC BY-NC-SA 2.0) image by thrill kills sunday pills http://www.flickr.com/photos/27086700@N03/2994587384/in/photostream/
Normal Inter-Module Function Call WickedSweetApp.exe WickedSweetLib.dll … push 1234 call [0x40112C] add esp, 4 … Import Address Table 0x40112C:SomeFunc 0x401130:SomeJunk 0x401134:ScumDunk … … SomeFunc: mov edi, edi push ebp mov ebp, esp sub esp, 0x20 … ret 1 2
Normal Inter-Module Function Call WickedSweetApp.exe WickedSweetLib.dll WickedWickedDll.dll EvilFunc: … call SomeFunc() … ret … SomeFunc: mov edi, edi push ebp mov ebp, esp sub esp, 0x20 … ret … push 1234 call [0x40112C] add esp, 4 … Import Address Table 0x40112C:EvilFunc 0x401130:SomeJunk 0x401134:ScumDunk … 2 1 4 3
Normal Inter-Module Function Call WickedSweetApp.exe WickedSweetLib.dll WickedWickedDll.dll MySomeFunc: … call SomeFunc() … ret … SomeFunc: mov edi, edi push ebp mov ebp, esp sub esp, 0x20 … ret … push 1234 call [0x40112C] add esp, 4 … Import Address Table 0x40112C:MySomeFunc 0x401130:SomeJunk 0x401134:ScumDunk … 2 1 4 3
So what functions should you hook? • The same ones as last time! Derp!
Example use of AppInit_DLLs for DLL injection • http://www.codeproject.com/KB/vista/api-hooks.aspx • This will hook NtQuerySystemInformation(), which is what taskmgr.exe uses in order to list the currently running processes. It will replace this with HookedNtQuerySystemInformation(), which will hide calc.exe • I modified that code to use IAT hooking rather than inline (which is much simpler actually) • Steps: • Compile AppInitHookIAT.dll • Place at C:\tmp\AppInitHookIAT.dllfor simplicity • Use regedit.exe to add C:\tmp\AppInitHookIAT.dllas the value for the key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT \CurrentVersion\Windows\AppInit_DLLsand set the other values from the previous slide • Start calc.exe, start taskmgr.exe, confirm that calc.exedoesn't show up in the list of running processes. • Remove C:\tmp\AppInitHookIAT.dllfrom AppInit_DLLs and restart taskmgr.exe. • Confirm calc.exe shows up in the list of running processes. • (This is a basic "userspace rootkit" technique. Because of this, all entries in this registry key should always be looked upon with suspicion.)
But what do you do? I'm an importer!
IAT Hooks • We now need to pull in a minimal breeze-through of content from the Life of Binaries OpenSecurityTraining class. Specifically we want to know how an attacker can find the Import/Export Address Tables in order to modify them. • An alternative view of what I'm about to show is here: • http://www.openrce.org/reference_library/files/reference/Windows%20Memory%20Layout,%20User-Kernel%20Address%20Spaces.pdf • (I would use this, but I want to cut it down to only the minimal information we need to know)
On Win XP taskmgr.exe's memory space (not to scale :P) Kernel (> 0x80000000) Userspace (<= 0x7FFFFFFF) FS:[0] The kernel ensures that the FS CPU segment register points at a segment that has its base address starting at the TEB (e.g. first 4 bytes can be read with "mov eax, FS:[0]") TEB (Thread Environment Block) struct PEB (Process Environment Block) struct PEB_LDR_DATA struct LDR_DATA_TABLE_ENTRY linked list entry LDR_DATA_TABLE_ENTRY linked list entry LDR_DATA_TABLE_ENTRY linked list entry . . . taskmgr.exe (mapped PE file) ntdll.dll (mapped PE file) You learn more about segment registers in the Intermediate x86 OST class kernel32.dll (mapped PE file) user32.dll (mapped PE file) . . .
On Win XP Common shellcode to find kernel32.dll (because it has lots of useful functions) Simplified from http://www.hick.org/code/skape/papers/win32-shellcode.pdf and used in the LoB tutorial virus. Kernel (> 0x80000000) Userspace (<= 0x7FFFFFFF) //put addr of PEB into eax mov eax, fs:[0x30]; //put addr of PEB_LDR_DATA into eax mov eax, [eax + 0xC]; //put addr of first LDR_DATA_TABLE_ENTRY //in InInitializationOrderModuleList into esi mov esi, [eax + 0x1C]; //lodsd is like "mov eax, [esi]" //presumably chosen for small code size //Put addr of 2nd LDTE (abbrev) to eax lodsd; //get base address of kernel32 out of //LDR_DATA_TABLE_ENTRY struct mov eax, [eax + 0x8]; TEB (Thread Environment Block) struct PEB (Process Environment Block) struct PEB_LDR_DATA struct LDR_DATA_TABLE_ENTRY linked list entry LDR_DATA_TABLE_ENTRY linked list entry LDR_DATA_TABLE_ENTRY linked list entry . . . taskmgr.exe (mapped PE file) ntdll.dll (mapped PE file) kernel32.dll (mapped PE file) user32.dll (mapped PE file) . . .
On Win 7 x64 taskmgr.exe's memory space (not to scale :P) Kernel (> 0xFFFFF80000000000) Userspace (<= 0x00007FFFFFFFFFFF) GS:[0] The kernel ensures that the GS CPU segment register points at a segment that has its base address starting at the TEB (e.g. first 4 bytes can be read with "mov eax, GS:[0]") TEB (Thread Environment Block) struct PEB (Process Environment Block) struct PEB_LDR_DATA struct LDR_DATA_TABLE_ENTRY linked list entry LDR_DATA_TABLE_ENTRY linked list entry LDR_DATA_TABLE_ENTRY linked list entry . . . taskmgr.exe (mapped PE file) ntdll.dll (mapped PE file) You learn more about segment registers in the Intermediate x86 OST class kernel32.dll (mapped PE file) user32.dll (mapped PE file) . . .
On Win 7 x64 Example shellcode to find kernel32.dll Adapted from http://code.google.com/p/win-exec-calc-shellcode/source/browse/trunk/w64-exec-calc-shellcode.asm?r=3 Kernel (> 0xFFFFF80000000000) Userspace (<= 0x00007FFFFFFFFFFF) //put addr of PEB into rsi mov rsi, gs:[0x60]; //put addr of PEB_LDR_DATA into rsi mov rsi, [rsi + 0x18]; //put LDR_DATA_TABLE_ENTRY[0] (taskmgr) //in InLoadOrderModuleList into rsi mov rsi, [rsi + 0x10]; //lodsq is like "mov rax, [rsi]" //rax = LDR_DATA_TABLE_ENTRY[1] (ntdll) lodsq; //rsi = LDR_DATA_TABLE_ENTRY[2] (kernel32) mov rsi, [rax] //get base address of kernel32 out of //LDR_DATA_TABLE_ENTRY struct mov rdi, [rsi + 0x30]; TEB (Thread Environment Block) struct PEB (Process Environment Block) struct PEB_LDR_DATA struct LDR_DATA_TABLE_ENTRY linked list entry LDR_DATA_TABLE_ENTRY linked list entry LDR_DATA_TABLE_ENTRY linked list entry . . . taskmgr.exe (mapped PE file) ntdll.dll (mapped PE file) kernel32.dll (mapped PE file) user32.dll (mapped PE file) . . .
Let's look at some structs! (yaaaay!) • Use the "dt" (display type) command in windbg to show a structure definition • By convention Windows tends to name structs like typedefstruct _FOO{ char a[4] } FOO, *PFOO; • So you should generally look for structs assuming an underscore on the front of the name
Let's look at some structs! (yaaaay!) • Don't forget your windbg superstition, always check your File->Symbol File Path is set to the SRV*… address from the day 1 part 2 slides, and then do a ".reload" in order to make sure your symbols are loaded. Then you can do things like • dt _TEB • in which case it has to search all modules for the symbol, thankfully ntdll is the first one it searches • dtntdll!_TEB • Or to interpret some particular location as a TEB, first do !teb so it will dump the address for you and then do • dtntdll!_TEB <address>
Then what? • Well, let's go try to hack the IAT • When you get the base address of the module you're interested in, it turns out you can parse the PE headers which will still typically be mapped into memory. • PE = Portable Executable = Windows' binary format • This is where you *really* need LoB to due it justice, but you'll get the quick and dirty version
IMAGE_DOS_HEADER struct Let's say you want to: Modify the code called when taskmgr.exe tries to call functions like NtQuerySystemInfo() You need to find the IAT entries that point at that function IMAGE_NT_HEADER struct IMAGE_NT_HEADER struct IMAGE_OPTIONAL_HEADER struct OptionalHeader.DataDirectory[0] 1st entry points at imports info OptionalHeader.DataDirectory[1] OptionalHeader.DataDirectory[2] . . . IMAGE_IMPORT_DESCRIPTOR (advapi32.dll) One per module that is imported from IMAGE_IMPORT_DESCRIPTOR (kernel32.dll) . . . IMAGE_IMPORT_DESCRIPTOR (ntdll.dll) . . . Import Names Table (INT) Import Addresses Table (IAT)
You are here :D Care about this Image by Ero Carrera
Care about this Image by Ero Carrera
Care about this Image by Ero Carrera
FYI tho • The optional header is different size in 32 bit binaries vs. 64 bit ones. So the previous and next slide is 32 bit. 2 slides forward is 64 bit • Calculate offsets accordingly
From winnt.h typedef struct _IMAGE_OPTIONAL_HEADER { WORD Magic; BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; DWORD SizeOfInitializedData; DWORD SizeOfUninitializedData; DWORD AddressOfEntryPoint; DWORD BaseOfCode; DWORD BaseOfData; DWORD ImageBase; DWORD SectionAlignment; DWORD FileAlignment; WORD MajorOperatingSystemVersion; WORD MinorOperatingSystemVersion; WORD MajorImageVersion; WORD MinorImageVersion; WORD MajorSubsystemVersion; WORD MinorSubsystemVersion; DWORD Win32VersionValue; DWORD SizeOfImage; DWORD SizeOfHeaders; DWORD CheckSum; WORD Subsystem; WORD DllCharacteristics; DWORD SizeOfStackReserve; DWORD SizeOfStackCommit; DWORD SizeOfHeapReserve; DWORD SizeOfHeapCommit; DWORD LoaderFlags; DWORD NumberOfRvaAndSizes; IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
From winnt.h typedef struct _IMAGE_OPTIONAL_HEADER64 { WORD Magic; BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; DWORD SizeOfInitializedData; DWORD SizeOfUninitializedData; DWORD AddressOfEntryPoint; DWORD BaseOfCode; ULONGLONG ImageBase; DWORD SectionAlignment; DWORD FileAlignment; WORD MajorOperatingSystemVersion; WORD MinorOperatingSystemVersion; WORD MajorImageVersion; WORD MinorImageVersion; WORD MajorSubsystemVersion; WORD MinorSubsystemVersion; DWORD Win32VersionValue; DWORD SizeOfImage; DWORD SizeOfHeaders; DWORD CheckSum; WORD Subsystem; WORD DllCharacteristics; ULONGLONG SizeOfStackReserve; ULONGLONG SizeOfStackCommit; ULONGLONG SizeOfHeapReserve; ULONGLONG SizeOfHeapCommit; DWORD LoaderFlags; DWORD NumberOfRvaAndSizes; IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; } IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;
Import Descriptor(from winnt.h) I think they meant "INT" typedefstruct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; // 0 for terminating null import descriptor DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA) //Xeno Comment: In reality a PIMAGE_THUNK_DATA }; DWORD TimeDateStamp; // 0 if not bound, // -1 if bound, and real date\time stamp // in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND) // O.W. date/time stamp of DLL bound to (Old BIND) DWORD ForwarderChain; // -1 if no forwarders DWORD Name; DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses) //Xeno Comment: In reality a PIMAGE_THUNK_DATA } IMAGE_IMPORT_DESCRIPTOR; • While the things in blue are the fields filled in for the most common case, we will actually have to understand everything for this structure, because you could run into all the variations.
Review: Import data structures ON DISK Import Names Table (IMAGE_THUNK_DATA array) Import Address Table (IMAGE_THUNK_DATA array) Graphical style borrowed from the Matt Pietrek articles Array of IMAGE_IMPORT_BY_NAME Structures stored wherever in the file IMAGE_IMPORT_DESCRIPTOR Zero-filled IMAGE_IMPORT_DESCRIPTOR entry terminates the array …
Review: Import data structures IN MEMORY AFTER IMPORTS RESOLVED Import Names Table (IMAGE_THUNK_DATA array) Import Address Table (IMAGE_THUNK_DATA array) Graphical style borrowed from the Matt Pietrek articles Array of IMAGE_IMPORT_BY_NAME Structures stored wherever in the file IMAGE_IMPORT_DESCRIPTOR IAT entries now point to the full virtual addresses where the functions are found in the other modules (just ntoskrnl.exe in this case) Zero-filled IMAGE_IMPORT_DESCRIPTOR entry terminates the array …
IAT Hooking • When the IAT is fully resolved, it is basically an array of function pointers. Somewhere, in some code path, there's something which is going to take an IAT address, and use whatever's in that memory location as the destination of the code it should call. • What if the "whatever's in that memory location" gets changed after the OS loader is done? What if it points at attacker code?
IAT Hooking 2 • Well, that would mean the attacker's code would functionally be "man-in-the-middle"ingthe call to the function. He can then change parameters before forwarding the call on to the original function, and filter results that come back from the function, or simply never call the original function, and send back whatever status he pleases. • Think rootkits. Say you're calling FindNextFile(). It looks at the file name and if you're asking for a file it wants to hide, it simply returns "no file found." • But how does the attacker change the IAT entries? This is a question of assumptions about where the attacker is.
IAT Hooking 3 • In a traditional memory-corrupting exploit, the attacker is, by definition, in the memory space of the attacked process, upon successfully gaining arbitrary code execution. The attacker can now change memory such as the IAT for this process only, because remember (from OS class or Intermediate x86) each process has a separate memory space. • If the attacker wants to change the IAT on other processes, he must be in their memory spaces as well. Typically the attacker will format some of his code as a DLL and then perform "DLL Injection" in order to get his code in other process'memory space. • The ability to do something like DLL injection is generally a prerequisite in order to leverage IAT hooking across many userspace processes. In the kernel, kernel modules are generally all sharing the same memory space with the kernel, and therefore one subverted kernel module can hook the IAT of any other modules that it wants.
DLL Injection • See http://en.wikipedia.org/wiki/DLL_injection for more ways that this can be achieved on Windows/*nix • We're going to use the AppInit_DLLs way of doing this for simplicity • Note: AppInit_DLLs'behavior has changed in releases > XP, it now has to be enabled with Administrator level permissions. • Must set the DLL in question in the registry key: • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT \CurrentVersion\Windows\AppInit_DLLs • Use comma delimitation if there is an existing entry • Must also set the following key to 1 • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT \CurrentVersion\Windows\LoadAppInit_DLLs • Must also set the following key to 0 • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT \CurrentVersion\Windows\RequireSignedAppInit_DLLs
Example use of AppInit_DLLs for DLL injection • http://www.codeproject.com/KB/vista/api-hooks.aspx • This will hook NtQuerySystemInformation(), which is what taskmgr.exe uses in order to list the currently running processes. It will replace this with HookedNtQuerySystemInformation(), which will hide calc.exe • I modified that code to use IAT hooking rather than inline (which is much simpler actually) • Steps: • Compile AppInitHookIAT.dll • Place at C:\tmp\AppInitHookIAT.dllfor simplicity • Use regedit.exe to add C:\tmp\AppInitHookIAT.dllas the value for the key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT \CurrentVersion\Windows\AppInit_DLLsand set the other values from the previous slide • Start calc.exe, start taskmgr.exe, confirm that calc.exedoesn't show up in the list of running processes. • Remove C:\tmp\AppInitHookIAT.dllfrom AppInit_DLLs and restart taskmgr.exe. • Confirm calc.exe shows up in the list of running processes. • (This is a basic "userspace rootkit" technique. Because of this, all entries in this registry key should always be looked upon with suspicion.)
Process Explorer • http://technet.microsoft.com/en-us/sysinternals/bb896653.aspx • Another useful sysinternals tool for seeing what processes are running, what DLLs are loaded in those processes, and what kernel modules are loaded
This is it toggled This button right here will toggle back and forth between showing DLLs or "handles" at the bottom (when in "System" process the "DLLs" are actually the kernel modules)
Expanding on using dt in windbg & using it with CFF Explorer • You can use dt to get from the _TEB to the _PEB to the _PEB_LDR_DATA to the _LDR_DATA_TABLE_ENTRY[] by combining the topics in slides 14-17, but once you get to the module, how do you get to the IAT in windbg by combining that information with the PE structs and/or CFF Explorer data.
IMAGE_DOS_HDR IMAGE_OPTIONAL_HEADER
Value in the struct at that offset Offset into the file IMAGE_DOS_HDR Care about this
Assume you already found the base address of regedit by walking from the TEB (that's how lm does it). In this case it's 0x00000000ff350000. So now you want to make sure you're getting the right values for the right offsets. We can use our friendly friend "dt" again to "cast" this memory location to an IMAGE_DOST_HEADER, and then confirm we see the same thing as in CFF explorer from the previous slide. Decimal (0n prefix here) 0n232 = 0xE8, and they're both at the same offset into the struct, 0x3c. (FYI a fast way to convert between bases in windbgis ".formats 0n232")
The sometimes good, sometimes bad thing about CFF Explorer is that although the Optional Header is embedded in the NT Header, and the data directory is embedded in the Optional Header, it puts them as separate looking things. But the key point is that you have to go Value in the struct at that offset Offset into the file DD[0] DD[1] DD[2] … Care about this This is an RVA, a Relative Virtual Address. Meaning it's a relative offset from the base to get to the next structure
Alright let's use that 0xE8 offset from the DOS header to get to the NT header And then go to the optional header <snip> And then the data directory Well that's entry [0], like shown on the previous slide, but we want entry [1] So you see, all this is just C-style casting of data to a particular structure definition. You can do the same sort of thing in your C code. And if you add it all up, you will see that the offset to the DD[1].VirtualAddress field is 0x178 like shown on the previous slide
Disclaimer • Don't just hardcode these values. Cast to memory to structs and pull fields from structs. Because if you don't, then what might work on regedit might not work on some other app (e.g. the offset to the start of the NT header on calc.exe is 0xF0 not 0xE8). You want to write code that is maximally compatible.
Disclaimer 2 • How did I know it was _IMAGE_NT_HEADER64 instead of just _IMAGE_NT_HEADER? Well, sometimes you need to go back to the source to find the correct struct name. As previously said, the structs for PE headers are defined in winnt.h (see next slide)
typedefstruct _IMAGE_NT_HEADERS64 { DWORD Signature; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER64 OptionalHeader; } IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64; typedefstruct _IMAGE_NT_HEADERS { DWORD Signature; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER32 OptionalHeader; } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32; #ifdef _WIN64 typedef IMAGE_NT_HEADERS64 IMAGE_NT_HEADERS; typedef PIMAGE_NT_HEADERS64 PIMAGE_NT_HEADERS; #else typedef IMAGE_NT_HEADERS32 IMAGE_NT_HEADERS; typedef PIMAGE_NT_HEADERS32 PIMAGE_NT_HEADERS; #endif
SO! You've got an offset, 0x20e70 to get to the _IMAGE_IMPORT_DESCRIPTOR. Unfortunately for you, at this point you're SOL, because that struct isn't defined in any of the modules! Time to reinforce that understanding of data type sizes. If the structure looks like this (cut down from slide 28): typedefstruct _IMAGE_IMPORT_DESCRIPTOR { DWORD OriginalFirstThunk; DWORD TimeDateStamp; DWORD ForwarderChain; DWORD Name; DWORD FirstThunk; } IMAGE_IMPORT_DESCRIPTOR; Then that means the RVA to the OriginalFirstThunk (INT) table is at 0x21090. The RVA to the Name is at 0x21080. And the RVA to the FirstThunk (IAT) table is at 0x1F000. But let's check the name first because we don't even know if this is the right module yet. So, is that the right module? IDK, you tell me :P
But let's assume that's the right module, then we want to try and find the IAT entry that has the same index as a particular INT entry which points at a particular function we want to hook. So lets look at the INT. Again, that OriginalFirstThunk was an RVA, which means it's a base-relative offset to an array, each entry of which is yet another RVA (but this time 64 bit) pointing at a hint/name structure (see slides 30/31). And if we then pull out the first 64 bit RVA There are some nice hint/name structures!
Sanity checking that all against CFF Explorer… _IMAGE_IMPORT_DESCRIPTOR array Hint/name pointed to by INT Entries in the INT pointed to by the thing you're currently clicking in the IMAGE_IMPORT_DESCRIPTOR array Entries in the IAT pointed to by the thing you're currently clicking in the IMAGE_IMPORT_DESCRIPTOR array