吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2719|回复: 7
收起左侧

[其他原创] .NET ShellCode 远程注入 MessageBoxA (x86/x64)

  [复制链接]
Vvvvvoid 发表于 2021-9-1 23:11
本帖最后由 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 我们自定义的标题与内容

x86.png

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  

x64.png

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[asmPramLength / 2];
    if (asmPramLength==8)
    {
        for (int i = 0; i < reAsmCode.Length; i++)
        {
            reAsmCode[i] = 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[i] = 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

Snipaste_2021-09-02_19-47-49.png

Snipaste_2021-09-02_19-51-49.png

示例

疑问

1.有没有一个通用的类库,可以转换汇编代码到字节集,同时兼容 x86/x64
2.我那个字节码硬编码的那种一堆byte的写法有没有什么办法可以优化的好看简单些??

免费评分

参与人数 4威望 +1 吾爱币 +23 热心值 +4 收起 理由
笙若 + 1 + 1 谢谢@Thanks!
qaz003 + 1 + 1 用心讨论,共获提升!
yyb414 + 1 + 1 热心回复!
苏紫方璇 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

爱飞的猫 发表于 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, 没什么必要发..

[C#] 纯文本查看 复制代码
[DllImport("kernel32.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学到了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-25 08:27

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表