Vvvvvoid 发表于 2021-9-1 23:11

.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的写法有没有什么办法可以优化的好看简单些??

爱飞的猫 发表于 2021-9-2 06:28

有没有一个通用的类库,可以转换汇编代码到字节集,同时兼容 x86/x64

参考 FASM/NASM,提供汇编文本可以生成字节码。



2.我那个字节码硬编码的那种一堆byte的写法有没有什么办法可以优化的好看简单些??

写辅助类,比如:

class NativeAssembly {
NativeAssembly() {
    this.code = new List<byte>();
}
mov_eax(int value) {
    this.code.Add( ... );
}
}

EnterpriseSolu 发表于 2021-9-2 07:59

这个厉害了,如果能把notepad中的保存改为不保存,这样不就成了病毒了,完全改写了目标进程的行为,win98/2000时代有那么多病毒,是有这个原因吧,到现在很少遇到这种病毒了,windows安全已经不同往日,恨不得执行一个EXE都要用户点确认,这让坏的行为几乎无隐藏的地方

yunruifuzhu 发表于 2021-9-2 11:04

就像知道 NativeMethods 类是哪个库的,提供下nuget地址

oo789458 发表于 2021-9-2 11:10

感谢谢谢分享

Vvvvvoid 发表于 2021-9-2 11:15

yunruifuzhu 发表于 2021-9-2 11:04
就像知道 NativeMethods 类是哪个库的,提供下nuget地址

这是我自己的类, 里面全是 import DLL, 没什么必要发..


public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, IntPtr dwProcessId);   
      

Vvvvvoid 发表于 2021-9-2 19:55

jixun66 发表于 2021-9-2 06:28
有没有一个通用的类库,可以转换汇编代码到字节集,同时兼容 x86/x64

参考 FASM/NASM,提供汇编文本可以生 ...

Done !!

感谢分享

wsf5201314 发表于 2021-9-4 21:57

6666666学到了
页: [1]
查看完整版本: .NET ShellCode 远程注入 MessageBoxA (x86/x64)