.NET ShellCode 远程注入 MessageBoxA (x86/x64)
本帖最后由 Vvvvvoid 于 2021-9-2 19:54 编辑### 前言
ShellCode 通常是利用 Win32 API WriteProcessMemory 先内存中写入代码 , 之后 调用 CreateRemoteThread 来创建改进程下的一条线程来执行, 今天来试试 x86/x64 下的 Shell MessageBoxA 函数
### 打开进程
打开进程,获取 API 函数地址,以及读写内存的操作都是一样的
```
// 根据进程ID打开进程,获得进程句柄
IntPtr openProcess = NativeMethods.OpenProcess(NativeMethods.PROCESS_ALL_ACCESS, false, processId);
if (openProcess == IntPtr.Zero)
{
MessageBox.Show("OpenProcess 异常");
return false;
}
// 获取函数 MessageBoxA 的地址
IntPtr lpLLAddress = NativeMethods.GetProcAddress(NativeMethods.GetModuleHandle("user32.dll"), "MessageBoxA");
// 写入标题与内容
// title 跟 value 是string类型的, MessageBoxA 的标题与内容
var titleBytes = MemUtil.convertStr2ByteArr(title);
var titleAddress = NativeMethods.VirtualAllocEx(openProcess, (IntPtr)null, (IntPtr)titleBytes.Length, NativeMethods.Commit, NativeMethods.ExecuteReadWrite);
MemUtil.WriteBytes(openProcess,titleAddress,titleBytes,titleBytes.Length);
var valueBytes = MemUtil.convertStr2ByteArr(value);
var valueAddress = NativeMethods.VirtualAllocEx(openProcess, (IntPtr)null, (IntPtr)valueBytes.Length, NativeMethods.Commit, NativeMethods.ExecuteReadWrite);
MemUtil.WriteBytes(openProcess,valueAddress,valueBytes,valueBytes.Length);
// 写入要执行的函数代码
// 具体要写入的 汇编代码,x64/x86 差异还是挺大的, 下面会说
byte[] buffer;
if (NativeMethods.Is64BitProcess())
{
buffer = buffer64(lpLLAddress,titleAddress,valueAddress);
}
else
{
buffer = buffer32(lpLLAddress,titleAddress,valueAddress);
}
IntPtr lpAddress = NativeMethods.VirtualAllocEx(openProcess, (IntPtr)null, (IntPtr)buffer.Length, NativeMethods.Commit, NativeMethods.ExecuteReadWrite);
if (lpAddress == IntPtr.Zero)
{
MessageBox.Show("VirtualAllocEx 异常");
return false;
}
Boolean writeBytes = MemUtil.WriteBytes(openProcess,lpAddress,buffer,buffer.Length);
if (!writeBytes)
{
MessageBox.Show("WriteProcessMemory 异常");
return false;
}
// 这块可以弹出消息框暂停下,然后用 x64dbg/x32dbg 可以 dd 到 lpAddress 检查看看写的对不对
MessageBox.Show("WriteProcessMemory!!!!");
// lpLLAddress 要执行的函数地址
// lpAddress 代码注入地址
var remoteThread = NativeMethods.CreateRemoteThread(openProcess, (IntPtr)null, (IntPtr)0, lpAddress, (IntPtr)0, 0, (IntPtr)null);
if (remoteThread==(IntPtr)0)
{
MessageBox.Show("CreateRemoteThread 异常");
return false;
}
// 等待原创线程执行完毕
NativeMethods.WaitForSingleObject(remoteThread, 60 * 1000);
// 线程跑完后,可以释放清空,我们写入的数据 , Release 类型时 size 必须位0
NativeMethods.VirtualFreeEx( openProcess, lpAddress, (IntPtr)0, NativeMethods.Release );
NativeMethods.VirtualFreeEx( openProcess, titleAddress, (IntPtr)0, NativeMethods.Release );
NativeMethods.VirtualFreeEx( openProcess, valueAddress, (IntPtr)0, NativeMethods.Release );
NativeMethods.CloseHandle(remoteThread);
NativeMethods.CloseHandle(openProcess);
return true;
```
### ShellCode
#### X86
> X86 下,调用 MessageBoxA 很简单, 参数可以push 4个0
> 但是 这里, 我们要 push 我们自定义的标题与内容
```
private static byte[] buffer32(IntPtr lpLLAddress,IntPtr titleAddress,IntPtr valueAddress)
{
# region 32 位 MessageBoxA
ArrayList asmList = new ArrayList();
// 755DED60 | 8BFF | mov edi,edi | MessageBoxA 函数头
// pushad
// 055D0000 | 60 | pushad |
// 055D0001 | 6A 00 | push 0 |
// 055D0003 | B8 00003B05 | mov eax,53B0000 | 53B0000:"this is title"
// 055D0008 | 50 | push eax |
// 055D0009 | B8 00005C05 | mov eax,55C0000 | 55C0000:"this is value"
// 055D000E | 50 | push eax |
// 055D000F | 6A 00 | push 0 |
// 055D0011 | B8 70ECBD77 | mov eax,<user32.MessageBoxA> |
// 055D0016 | FFD0 | call eax |
// 055D0018 | 61 | popad |
// 055D0019 | C3 | ret |
// popad
MemUtil.appendAll(asmList,new byte[]{
0x60,0x6A, 0x00
});
MemUtil.appendAll(asmList,new byte[]{
0xB8
});
MemUtil.appendAll(asmList,MemUtil.AsmChangebytes(MemUtil.intTohex(titleAddress.ToInt32(), 8)));
MemUtil.appendAll(asmList,new byte[]{
0x50
});
MemUtil.appendAll(asmList,new byte[]{
0xB8
});
MemUtil.appendAll(asmList,MemUtil.AsmChangebytes(MemUtil.intTohex(valueAddress.ToInt32(), 8)));
MemUtil.appendAll(asmList,new byte[]{
0x50
});
MemUtil.appendAll(asmList,new byte[]{
0x6A, 0x00,
0xB8,
});
MemUtil.appendAll(asmList,MemUtil.AsmChangebytes(MemUtil.intTohex(lpLLAddress.ToInt32(), 8)));
MemUtil.appendAll(asmList,new byte[]{
0xFF, 0xD0,
0x61,
0xC3
});
# endregion
byte[] buffer = asmList.ToArray(typeof(byte)) as byte[];
return buffer;
}
```
#### X64
>X64 的MessageBoxA,好像必须按照 r9 r8 rdx rcx 顺序来push参数,不晓得为什么
> 函数内部,会将寄存器传参的值(rcx,rdx,r8,r9)保存到我们申请的预留空间中. 所以这里我们push参数直接 mov,xx,x, 就可以了
>而且还要特别注意 sub rsp,30与 add rsp,30 , 我是挨个试,才把堆栈搞平衡, 虽然不太清楚原理....
>我的猜测 : 不知对否
>push 了 4个参数, = 4x8 = 32 = 0x20 ,
>加上我们call函数用了一个 eax,32+8 = 40 =0x28
>40 + 8 (函数返回地址 ) = 48 = 0x30 , 关于这个 函数返回地址我也不太了解 也是网上找的答案..
>而且特别要注意 sub 的栈大小要16位对齐, 比如我们的 48模16=0
```
private static byte[] buffer64(IntPtr lpLLAddress,IntPtr titleAddress,IntPtr valueAddress)
{
# region 64 位 MessageBoxA
ArrayList asmList = new ArrayList();
// 这里的堆栈指针操作, 这么理解
// 而在x64汇编中,两方面都发生了变化。一是前四个参数分析通过四个寄存器传递:RCX、RDX、R8、R9,如果还有更多的参数,才通过椎栈传递。二是调用者负责椎栈空间的分配与回收。
//00007FFC6036AC30 | 48:83EC 38 | sub rsp,38 | MessageBoxA函数头
// push all
// 0000019036990019 | 48:83EC 30 | sub rsp,30 |
// 000001903699001D | 41:B9 00000000 | mov r9d,0 |
// 0000019036990023 | 49:B8 0000973690010000 | mov r8,19036970000 | 19036970000:"this is title"
// 000001903699002D | 48:BA 0000983690010000 | mov rdx,19036980000 | 19036980000:"this is value"
// 0000019036990037 | B9 00000000 | mov ecx,0 |
// 000001903699003C | 48:B8 30AC3660FC7F0000 | mov rax,<user32.MessageBoxA> |
// 0000019036990046 | FFD0 | call rax |
// 0000019036990048 | 48:83C4 30 | add rsp,30 |
// pop all
// 0000019036990065 | C3 | ret |
MemUtil.appendAll(asmList, new byte[]
{
/*push all*/
0X9C,0X54,0X50,0X51,0X52,0X53,0X55,0X56,0X57,0X41,0X50,0X41,0X51,0X41,0X52,0X41,0X53,0X41,0X54,0X41,0X55,0X41,0X56,0X41,0X57,
0X48,0X83,0XEC,0X30,
0X41,0XB9,0X00,0X00,0X00,0X00
});
MemUtil.appendAll(asmList, new byte[]
{
0x49,0xB8
});
MemUtil.appendAll(asmList, MemUtil.AsmChangebytes(MemUtil.intTohex(titleAddress.ToInt64(), 16)));
MemUtil.appendAll(asmList, new byte[]
{
0x48,0xBA
});
MemUtil.appendAll(asmList, MemUtil.AsmChangebytes(MemUtil.intTohex(valueAddress.ToInt64(), 16)));
MemUtil.appendAll(asmList, new byte[]
{
0XB9,0X00,0X00,0X00,0X00,
0x48, 0xB8
});
MemUtil.appendAll(asmList, MemUtil.AsmChangebytes(MemUtil.intTohex(lpLLAddress.ToInt64(), 16)));
MemUtil.appendAll(asmList, new byte[]
{
0xFF, 0xD0,
0X48, 0X83, 0XC4, 0X30,
/* pop all*/
0X41,0X5F,0X41,0X5E,0X41,0X5D,0X41,0X5C,0X41,0X5B,0X41,0X5A,0X41,0X59,0X41,0X58,0X5F,0X5E,0X5D,0X5B,0X5A,0X59,0X58,0X5C,0X9D,
0xC3
});
# endregion
byte[] buffer = asmList.ToArray(typeof(byte)) as byte[];
return buffer;
}
```
### 进制机器码转换
```
// 32位参数位 int32,8
// 64位参数为 int64,16
public static string intTohex(long value, int num)
{
string str1;
string str2 = "";
str1 = "0000000" + hex(value);
str1 = str1.Substring(str1.Length - num,num);
for (int i = 0; i < str1.Length/2; i++)
{
str2 = str2 + str1.Substring(str1.Length - 2 - 2 * i, 2);
}
return str2;
}
private static string hex(long address)
{
string str = address.ToString("X");
return str;
}
public static byte[] AsmChangebytes(string asmPram)
{
var asmPramLength = asmPram.Length;
byte[] reAsmCode = new byte;
if (asmPramLength==8)
{
for (int i = 0; i < reAsmCode.Length; i++)
{
reAsmCode = Convert.ToByte(Int32.Parse(asmPram.Substring(i * 2, 2), System.Globalization.NumberStyles.AllowHexSpecifier));
}
} else if (asmPramLength==16)
{
for (int i = 0; i < reAsmCode.Length; i++)
{
reAsmCode = Convert.ToByte(Int64.Parse(asmPram.Substring(i * 2, 2), System.Globalization.NumberStyles.AllowHexSpecifier));
}
}
return reAsmCode;
}
```
### 封装/继承
> 简单的做了下封装,做了 ASM 机器码生成工具类
> 这里面 AsmUtilX64 直接继承 AsmUtilX86 就可以, 因为汇编 x64下 操作低位的话 实际上机器码 与 x86下一样
> 比如 mov rax,0x1== mov eax,0x1
### 示例
![](https://attach.52pojie.cn/forum/202109/01/231119vdk5ptqqkbd5c1qw.png)
### 疑问
1.有没有一个通用的类库,可以转换汇编代码到字节集,同时兼容 x86/x64
2.我那个字节码硬编码的那种一堆byte的写法有没有什么办法可以优化的好看简单些??
有没有一个通用的类库,可以转换汇编代码到字节集,同时兼容 x86/x64
参考 FASM/NASM,提供汇编文本可以生成字节码。
2.我那个字节码硬编码的那种一堆byte的写法有没有什么办法可以优化的好看简单些??
写辅助类,比如:
class NativeAssembly {
NativeAssembly() {
this.code = new List<byte>();
}
mov_eax(int value) {
this.code.Add( ... );
}
}
这个厉害了,如果能把notepad中的保存改为不保存,这样不就成了病毒了,完全改写了目标进程的行为,win98/2000时代有那么多病毒,是有这个原因吧,到现在很少遇到这种病毒了,windows安全已经不同往日,恨不得执行一个EXE都要用户点确认,这让坏的行为几乎无隐藏的地方
就像知道 NativeMethods 类是哪个库的,提供下nuget地址 感谢谢谢分享 yunruifuzhu 发表于 2021-9-2 11:04
就像知道 NativeMethods 类是哪个库的,提供下nuget地址
这是我自己的类, 里面全是 import DLL, 没什么必要发..
public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, IntPtr dwProcessId);
jixun66 发表于 2021-9-2 06:28
有没有一个通用的类库,可以转换汇编代码到字节集,同时兼容 x86/x64
参考 FASM/NASM,提供汇编文本可以生 ...
Done !!
感谢分享 6666666学到了
页:
[1]