Skip to content

Instantly share code, notes, and snippets.

@Konctantin
Created July 19, 2016 13:56
Show Gist options
  • Save Konctantin/31c67f1cbc6ce232265c286b784d4cc0 to your computer and use it in GitHub Desktop.
Save Konctantin/31c67f1cbc6ce232265c286b784d4cc0 to your computer and use it in GitHub Desktop.
Process memory class for x64
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace CallFsEB
{
public class ProcessMemory
{
#region Asm save/restore flags and registers
byte[] pushafq = {
// save registers
0x40, 0x50, // push rax
0x40, 0x54, // push rsp << 1
0x40, 0x54, // push rsp << 2
0x40, 0x51, // push rcx
0x40, 0x52, // push rdx
0x40, 0x53, // push rbx
0x40, 0x55, // push rbp
0x40, 0x56, // push rsi
0x40, 0x57, // push rdi
0x41, 0x50, // push r8
0x41, 0x51, // push r9
0x41, 0x52, // push r10
0x41, 0x53, // push r11
0x41, 0x54, // push r12
0x41, 0x55, // push r13
0x41, 0x56, // push r14
0x41, 0x57, // push r15
0x9C, // pushfq
};
byte[] popafq = {
// restore flags
0x9D, // popfq
// restore registers
0x41, 0x5F, // pop r15
0x41, 0x5E, // pop r14
0x41, 0x5D, // pop r13
0x41, 0x5C, // pop r12
0x41, 0x5B, // pop r11
0x41, 0x5A, // pop r10
0x41, 0x59, // pop r9
0x41, 0x58, // pop r8
0x40, 0x5F, // pop rdi
0x40, 0x5E, // pop rsi
0x40, 0x5D, // pop rbp
0x40, 0x5B, // pop rbx
0x40, 0x5A, // pop rdx
0x40, 0x59, // pop rcx
0x40, 0x5C, // pop rsp >> 2
0x40, 0x5C, // pop rsp >> 1
0x40, 0x58, // pop rax
};
#endregion
#region API
[DllImport("kernel32", SetLastError = true, ExactSpelling = true)]
public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, int dwSize, AllocationType flAllocationType, MemoryProtection flProtect);
[DllImport("kernel32", SetLastError = true)]
public static extern IntPtr OpenThread(ThreadAccess DesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwThreadId);
[DllImport("kernel32", SetLastError = true, ExactSpelling = true)]
public static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, int dwSize, FreeType dwFreeType);
[DllImport("kernel32", SetLastError = true)]
public static extern bool VirtualProtectEx(IntPtr hProcess, IntPtr lpAddress, int dwSize, MemoryProtection flNewProtect, out MemoryProtection lpflOldProtect);
[DllImport("kernel32", SetLastError = true)]
public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, IntPtr lpNumberOfBytesWritten);
[DllImport("kernel32", SetLastError = true)]
public static unsafe extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, void* lpBuffer, int nSize, IntPtr lpNumberOfBytesWritten);
[DllImport("kernel32", SetLastError = true)]
public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int dwSize, IntPtr lpNumberOfBytesRead);
[DllImport("kernel32", SetLastError = true)]
public static extern uint SuspendThread(IntPtr thandle);
[DllImport("kernel32", SetLastError = true)]
public static extern uint ResumeThread(IntPtr thandle);
[DllImport("user32", SetLastError = true)]
public static extern IntPtr GetForegroundWindow();
[DllImport("kernel32", SetLastError = true)]
public static extern bool FlushInstructionCache(IntPtr hProcess, IntPtr lpBaseAddress, IntPtr dwSize);
[DllImport("ntdll.dll", SetLastError = true)]
public static extern IntPtr NtSuspendProcess(IntPtr ProcessHandle);
[DllImport("ntdll.dll", SetLastError = true)]
public static extern IntPtr NtResumeProcess(IntPtr ProcessHandle);
#endregion
/// <summary>
/// Возвращает текущий процесс.
/// </summary>
public Process Process { get; private set; }
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="Yanitta.ProcessMemory"/>.
/// </summary>
/// <param name="process"></param>
public ProcessMemory(Process process)
{
Process = process;
}
/// <summary>
/// Выделяет в процессе участок памяти.
/// </summary>
/// <param name="size">Размер выделяемой памяти.</param>
/// <param name="allocType">Тип выделяемой памяти.</param>
/// <param name="memProtect">Тип защиты памяти.</param>
/// <returns>Указатель на выделенный участок памяти.</returns>
public IntPtr Alloc(int size, AllocationType allocType = AllocationType.Commit, MemoryProtection memProtect = MemoryProtection.ExecuteReadWrite)
{
if (size <= 0)
throw new ArgumentNullException("size");
var address = VirtualAllocEx(Process.Handle, IntPtr.Zero, size, allocType, memProtect);
if (address == IntPtr.Zero)
throw new Win32Exception();
return address;
}
/// <summary>
/// Осводождает ранее выделенный участок памяти.
/// </summary>
/// <param name="address">Указатель на выделенный участок памяти.</param>
/// <param name="freeType">Тип осводождения памяти.</param>
public void Free(IntPtr address, FreeType freeType = FreeType.Release)
{
if (address == IntPtr.Zero)
throw new ArgumentNullException("address");
if (!VirtualFreeEx(Process.Handle, address, 0, freeType))
throw new Win32Exception();
}
/// <summary>
/// Считывает массив байт из текущего процесса.
/// </summary>
/// <param name="address">Указатель на участок памяти с которого надо начать считывание.</param>
/// <param name="count">Размер считываемого массива.</param>
/// <returns>Считанный из процесса масив.</returns>
public byte[] ReadBytes(IntPtr address, int count)
{
var bytes = new byte[count];
if(!ReadProcessMemory(Process.Handle, address, bytes, count, IntPtr.Zero))
throw new Win32Exception();
return bytes;
}
/// <summary>
/// Считывает из процесса значение указанного типа.
/// </summary>
/// <typeparam name="T">Тип считываемого значения.</typeparam>
/// <param name="address">Указатель на участок памяти от куда надо считать значение.</param>
/// <returns>Значение указанного типа.</returns>
public T Read<T>(IntPtr address) where T : struct
{
var result = new byte[Marshal.SizeOf(typeof(T))];
ReadProcessMemory(Process.Handle, address, result, result.Length, IntPtr.Zero);
var handle = GCHandle.Alloc(result, GCHandleType.Pinned);
T returnObject = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
handle.Free();
return returnObject;
}
/// <summary>
/// Считывает из процесса строку заканчивающуюся 0 в кодировке utf-8.
/// </summary>
/// <param name="addess">Указатель на участок памяти от куда надо считать значение.</param>
/// <param name="length">Длинна строки (ограничение).</param>
/// <returns>Считанная строка</returns>
public string ReadString(IntPtr addess, int length = 100)
{
var result = new byte[length];
if (!ReadProcessMemory(Process.Handle, addess, result, length, IntPtr.Zero))
throw new Win32Exception();
return Encoding.UTF8.GetString(result.TakeWhile(ret => ret != 0).ToArray());
}
/// <summary>
/// Записывает в память процесса значение указанного типа.
/// </summary>
/// <typeparam name="T">Тип записываемого значения.</typeparam>
/// <param name="value">Значение, которое надо записать в память процесса.</param>
/// <returns>Указатель на участок памяти куда записано значение.</returns>
public IntPtr Write<T>(T value) where T : struct
{
var buffer = new byte[Marshal.SizeOf(value)];
var hObj = Marshal.AllocHGlobal(buffer.Length);
var address = Alloc(buffer.Length);
if (address == IntPtr.Zero)
throw new Win32Exception();
try
{
Marshal.StructureToPtr(value, hObj, false);
Marshal.Copy(hObj, buffer, 0, buffer.Length);
if (!WriteProcessMemory(Process.Handle, address, buffer, buffer.Length, IntPtr.Zero))
throw new Win32Exception();
}
catch
{
Free(address);
}
finally
{
Marshal.FreeHGlobal(hObj);
}
return address;
}
/// <summary>
/// Записывает в память процесса значение указанного типа.
/// </summary>
/// <typeparam name="T">Тип записываемого значения.</typeparam>
/// <param name="address">Указатель на участок памяти куда надо записать значение.</param>
/// <param name="value">Значение, которое надо записать в память процесса.</param>
public void Write<T>(IntPtr address, T value) where T : struct
{
var buffer = new byte[Marshal.SizeOf(value)];
var hObj = Marshal.AllocHGlobal(buffer.Length);
try
{
Marshal.StructureToPtr(value, hObj, false);
Marshal.Copy(hObj, buffer, 0, buffer.Length);
if (!WriteProcessMemory(Process.Handle, address, buffer, buffer.Length, IntPtr.Zero))
throw new Win32Exception();
}
finally
{
Marshal.FreeHGlobal(hObj);
}
}
/// <summary>
/// Затисывает массив байт в память процесса.
/// </summary>
/// <param name="buffer">Массив байт.</param>
/// <returns>Указатель на участок памяти куда записан массив.</returns>
public IntPtr Write(byte[] buffer)
{
var addr = Alloc(buffer.Length);
if (addr == IntPtr.Zero)
throw new Win32Exception();
Write(addr, buffer);
return addr;
}
/// <summary>
/// Затисывает массив байт в память процесса.
/// </summary>
/// <param name="address">Указатель на участок памяти куда надо записать массив.</param>
/// <param name="buffer">Массив байт.</param>
public void Write(IntPtr address, byte[] buffer)
{
if (!WriteProcessMemory(Process.Handle, address, buffer, buffer.Length, IntPtr.Zero))
throw new Win32Exception();
}
public IntPtr WriteArray<T>(T[] array) where T : struct, IConvertible
{
var elementSize = Marshal.SizeOf(typeof(T));
var arraySize = array.Length * elementSize;
var ptr = Alloc(arraySize, AllocationType.Reserve | AllocationType.Commit);
for (int offset = 0, i = 0; offset < arraySize; offset += elementSize, ++i)
{
Write(IntPtr.Add(ptr, offset), array[i]);
}
return ptr;
}
/// <summary>
/// Записывает в память процесса строку по указанному аддрессу в кодировке utf-8.
/// </summary>
/// <param name="address">Указатель на участок памяти куда надо записать строку.</param>
/// <param name="str">Записываемая строка.</param>
public void WriteCString(IntPtr address, string str)
{
var buffer = Encoding.UTF8.GetBytes(str + '\0');
if (!WriteProcessMemory(Process.Handle, address, buffer, buffer.Length, IntPtr.Zero))
throw new Win32Exception();
}
/// <summary>
/// Записывает в память процесса указанную строку.
/// </summary>
/// <param name="str">Строка для записи в память.</param>
/// <returns>Указатель на строку в памяти.</returns>
public IntPtr WriteCString(string str)
{
var buffer = Encoding.UTF8.GetBytes(str + '\0');
var address = Alloc(buffer.Length);
if (!WriteProcessMemory(Process.Handle, address, buffer, buffer.Length, IntPtr.Zero))
throw new Win32Exception();
return address;
}
/// <summary>
/// Выполняет функцию по указанному адрессу с указанным списком аргуметов.
/// </summary>
/// <param name="injAddress">Относительный адресс выполняемой функции.</param>
/// <param name="detourAddress">Относительный адресс выполняемой функции.</param>
/// <param name="funcAddress">Относительный адресс выполняемой функции.</param>
/// <param name="funcArgs">
/// Параметры функции.
/// Параметрами могут выступать как и значения так и указатели на значения.
/// </param>
public void Call(IntPtr injAddress, IntPtr detourAddress, IntPtr funcAddress, params long[] funcArgs)
{
var threadId = Process.Threads[0].Id;
var tHandle = OpenThread(ThreadAccess.All, false, threadId);
if (NtSuspendProcess(Process.Handle) != IntPtr.Zero)
throw new Win32Exception();
var code = new List<byte>();
var checkAddr = Write<uint>(1);
var old_code_cave = ReadBytes(detourAddress, 14); //todo: fix count aligned to instructions length >= 14
byte minStackSize = 0x20;
byte reservStack = (byte)Math.Max(funcArgs.Length * IntPtr.Size, minStackSize);
#region ASM
// save flags and registers
code.AddRange(pushafq);
// mov rax, checkAddr
// cmp dword[rax], 0
// je @exit
code.Add(0x48, 0xB8).Add(checkAddr)
.Add(0x83, 0x38, 0x00)
.Add(0x74, 0x00); // todo: write offset
var je_index = (byte)(code.Count - 1);
// align stack ???? mabe
// and rsp, not 0x10
//bytes.Add(0x48, 0x83, 0xE4, 0xEF);
// sub rsp, reservStack
code.Add(0x48, 0x83, 0xEC, reservStack);
#region Function arguments
// 4 first argument of the function is stored in the registers
if (funcArgs.Length > 0)
{
// mov rcx, funcArgs[0]
// lea rcx, [rcx]
code.Add(0x48, 0xB9).Add(funcArgs[0])
.Add(0x48, 0x8D, 0x09);
}
if (funcArgs.Length > 1)
{
// mov rdx, funcArgs[1]
// lea rdx, [rdx]
code.Add(0x48, 0xBA).Add(funcArgs[1])
.Add(0x48, 0x8D, 0x12);
}
if (funcArgs.Length > 2)
{
// mov r8, funcArgs[2]
// lea r8, [r8]
code.Add(0x49, 0xB8).Add(funcArgs[2])
.Add(0x4D, 0x8D, 0x00);
}
if (funcArgs.Length > 3)
{
// mov r9, funcArgs[3]
// lea r9, [r9]
code.Add(0x49, 0xB9).Add(funcArgs[3])
.Add(0x4D, 0x8D, 0x09);
}
// other arguments on the stack
byte displacement = minStackSize;
for (int i = 4; i < funcArgs.Length; ++i)
{
// mov rax, param
// lea rax, [rax]
// mov qword ptr [rsp+i*8], rax
code.Add(0x48, 0xB8).Add(funcArgs[i])
.Add(0x48, 0x8D, 0x00)
.Add(0x48, 0x89, 0x44, 0x24, displacement);
displacement += (byte)IntPtr.Size;
}
#endregion
// mov rax, funcPtr
code.Add(0x48, 0xB8).Add(funcAddress);
// lea rax, rax
code.Add(0x48, 0x8D, 0x00);
// call rax
code.Add(0xFF, 0xD0);
// mov rax, checkAddr
code.Add(0x48, 0xB8).Add(checkAddr);
// mov dword[rax], 0
code.Add(0xC7, 0x00, 0x00, 0x00, 0x00, 0x00);
// add rsp, reservStack
code.Add(0x48, 0x83, 0xC4, reservStack);
// @exit:
// write je offset
code[je_index] = (byte)(code.Count - je_index - 1);// todo: fix me
//restore registers and flags
code.AddRange(popafq);
// replaced instruction (old code)
code.AddRange(old_code_cave);
// jmp [orig_address]
// orig_address dq rip
code.Add(0xFF, 0x25).Add(0u)
.Add(injAddress.ToInt64() + old_code_cave.Length);
// retn
//bytes.Add(0xC3);
#endregion
// Save original code and disable protect
var old_code_shell = ReadBytes(injAddress, code.Count);
var oldProtect = MemoryProtection.ReadOnly;
if (!VirtualProtectEx(Process.Handle, injAddress, code.Count, MemoryProtection.ExecuteReadWrite, out oldProtect))
throw new Win32Exception();
Debug.WriteLine("Shell code size: {0}", code.Count);
// write shell code
Write(injAddress, code.ToArray());
#region Code cave
// jmp [detour_address]
// detour_address dq rip
var cave = new List<byte> { 0xFF, 0x25, 0x0, 0x0, 0x0, 0x0 }.Add(injAddress);
// write to execute
Write(detourAddress, cave.ToArray());
#endregion
if (NtResumeProcess(Process.Handle) != IntPtr.Zero)
throw new Win32Exception();
for (int i = 0; i < 0x100; ++i)
{
System.Threading.Thread.Sleep(15);
if (Read<uint>(checkAddr) == 0)
{
Debug.WriteLine("iter: " + i);
break;
}
}
Free(checkAddr);
Write(detourAddress, old_code_cave);
// original code
Write(injAddress, old_code_shell);
if (!FlushInstructionCache(Process.Handle, injAddress, (IntPtr)old_code_shell.Length))
throw new Win32Exception();
// restore protection
if (!VirtualProtectEx(Process.Handle, injAddress, code.Count, oldProtect, out oldProtect))
throw new Win32Exception();
}
/// <summary>
/// Возвращает абсолютный аддресс в процессе.
/// </summary>
/// <param name="offset">Относительный аддресс.</param>
/// <returns>абсолютный аддресс в процессе.</returns>
public IntPtr Rebase(long offset) => new IntPtr(offset + Process.MainModule.BaseAddress.ToInt64());
}
#region Enums
/// <summary>
/// Allocation type.
/// </summary>
[Flags]
public enum AllocationType : uint
{
Commit = 0x00001000,
Reserve = 0x00002000,
Decommit = 0x00004000,
Release = 0x00008000,
Reset = 0x00080000,
TopDown = 0x00100000,
WriteWatch = 0x00200000,
Physical = 0x00400000,
LargePages = 0x20000000,
}
/// <summary>
/// Memory protection type.
/// </summary>
[Flags]
public enum MemoryProtection : uint
{
NoAccess = 0x001,
ReadOnly = 0x002,
ReadWrite = 0x004,
WriteCopy = 0x008,
Execute = 0x010,
ExecuteRead = 0x020,
ExecuteReadWrite = 0x040,
ExecuteWriteCopy = 0x080,
GuardModifierflag = 0x100,
NoCacheModifierflag = 0x200,
WriteCombineModifierflag = 0x400,
}
/// <summary>
/// Free memory type.
/// </summary>
[Flags]
public enum FreeType : uint
{
Decommit = 0x4000,
Release = 0x8000,
}
/// <summary>
/// Thread acess type.
/// </summary>
[Flags]
public enum ThreadAccess : uint
{
Terminate = 0x00001,
SuspendResume = 0x00002,
GetContext = 0x00008,
SetContext = 0x00010,
SetInformation = 0x00020,
QueryInformation = 0x00040,
SetThreadToken = 0x00080,
Impersonate = 0x00100,
DirectImpersonation = 0x00200,
All = 0x1FFFFF
}
#endregion
static class mExtensions
{
public static List<byte> Add(this List<byte> list, params byte[] args)
{
list.AddRange(args);
return list;
}
public static List<byte> Add(this List<byte> list, int value)
{
list.AddRange(BitConverter.GetBytes(value));
return list;
}
public static List<byte> Add(this List<byte> list, uint value)
{
list.AddRange(BitConverter.GetBytes(value));
return list;
}
public static List<byte> Add(this List<byte> list, long value)
{
list.AddRange(BitConverter.GetBytes(value));
return list;
}
public static List<byte> Add(this List<byte> list, IntPtr value)
{
list.AddRange(BitConverter.GetBytes(value.ToInt64()));
return list;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment