目录

C#中利用Syscall免杀 v1.0

在ring3利用直接系统调用来绕过杀软对敏感函数的Hook

背景

  1. 在x64中使用syscall从ring3进入ring0
  2. 我们可以通过C#自己重写ring3函数进行直接系统调用
  3. C#中调用非托管内存需要进行一定的转换

了解syscall

window的大多用户活动常常发生在ring3,与win系统的api交互常常往返于ring0于ring3之间,例如创建文件的apiCreateFile
我们在本地进行测试,代码如下

1
CreateFile(L"1789456.txt", GENERIC_READ, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);

使用Procmon64.exe可以监控到代码从ring3走到ring0的过程,如下图:

/images/createfilestack.png
CreateFile的调用过程
在第十行从用户模式进入内核模式,最后一个调用的函数为NtCreateFile,我们可以看看NtCreateFile的汇编代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
0:008> u NtCreateFile l12
ntdll!NtCreateFile:
00007ffa`fd4c41e0 4c8bd1          mov     r10,rcx
00007ffa`fd4c41e3 b855000000      mov     eax,55h
00007ffa`fd4c41e8 f604250803fe7f01 test    byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1
00007ffa`fd4c41f0 7503            jne     ntdll!NtCreateFile+0x15 (00007ffa`fd4c41f5)
00007ffa`fd4c41f2 0f05            syscall
00007ffa`fd4c41f4 c3              ret
00007ffa`fd4c41f5 cd2e            int     2Eh
00007ffa`fd4c41f7 c3              ret
00007ffa`fd4c41f8 0f1f840000000000 nop     dword ptr [rax+rax]
ntdll!NtQueryEvent:
00007ffa`fd4c4200 4c8bd1          mov     r10,rcx
00007ffa`fd4c4203 b856000000      mov     eax,56h
00007ffa`fd4c4208 f604250803fe7f01 test    byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1
00007ffa`fd4c4210 7503            jne     ntdll!NtQueryEvent+0x15 (00007ffa`fd4c4215)
00007ffa`fd4c4212 0f05            syscall
00007ffa`fd4c4214 c3              ret
00007ffa`fd4c4215 cd2e            int     2Eh
00007ffa`fd4c4217 c3              ret
00007ffa`fd4c4218 0f1f840000000000 nop     dword ptr [rax+rax]

其中最基本的汇编代码如下

1
2
3
4
mov r10,rcx
mov eax,55h
syscall
ret

上面的55hNtCreateFile在win10中的系统调用号,同理56hNtQueryEvent的系统调用号,这个编号在不同系统中是不同的,可以查看x64系统调用表

C#中使用pinvoke调用CreateFile

在C#中调用native代码,常常需要通过pinvoke,下面的代码是在C#中调用native的CreateFile函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern SafeFileHandle CreateFile(
    string fileName,
    [MarshalAs(UnmanagedType.U4)] FileAccess fileAccess,
    [MarshalAs(UnmanagedType.U4)] FileShare fileShare,
    IntPtr securityAttributes,
    [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
    int flags,
    IntPtr template);
static void Main(string[] args)
{
    string FileName = "123456789.txt";
    SafeFileHandle drive = CreateFile(fileName: FileName,
              fileAccess: FileAccess.Read,
              fileShare: FileShare.Write | FileShare.Read | FileShare.Delete,
              securityAttributes: IntPtr.Zero,
              creationDisposition: FileMode.CreateNew,
              flags: 4, //with this also an enum can be used. (as described above as EFileAttributes)
              template: IntPtr.Zero);
}

下一步使用dinvoke调用NtCreateFile

C#中dinvoke

0. 步骤

  1. 首先定义所需结构体和枚举类型
  2. 保存syscall字节码
  3. 申请非托管内存,并设定内存属性为可读可写可执行(演示功能,实战免杀不可用),将字节码拷贝到内存中
  4. 定义NtCreateFile类型委托
  5. 利用Marshal.GetDelegateForFunctionPointerNtCreateFile委托实例化
  6. 初始化调用函数所需变量并调用委托

1. 定义所需的结构体和枚举类型

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
public enum AllocationProtect : uint
{
    PAGE_EXECUTE = 0x00000010,
    PAGE_EXECUTE_READ = 0x00000020,
    PAGE_EXECUTE_READWRITE = 0x00000040,
    PAGE_EXECUTE_WRITECOPY = 0x00000080,
    PAGE_NOACCESS = 0x00000001,
    PAGE_READONLY = 0x00000002,
    PAGE_READWRITE = 0x00000004,
    PAGE_WRITECOPY = 0x00000008,
    PAGE_GUARD = 0x00000100,
    PAGE_NOCACHE = 0x00000200,
    PAGE_WRITECOMBINE = 0x00000400
}
public enum FileAttributes : uint
{
    ReadOnly = 0x00000001,
    Hidden = 0x00000002,
    System = 0x00000004,
    Directory = 0x00000010,
    Archive = 0x00000020,
    Device = 0x00000040,
    Normal = 0x00000080,
    Temporary = 0x00000100,
    SparseFile = 0x00000200,
    ReparsePoint = 0x00000400,
    Compressed = 0x00000800,
    Offline = 0x00001000,
    NotContentIndexed = 0x00002000,
    Encrypted = 0x00004000,
    Write_Through = 0x80000000,
    Overlapped = 0x40000000,
    NoBuffering = 0x20000000,
    RandomAccess = 0x10000000,
    SequentialScan = 0x08000000,
    DeleteOnClose = 0x04000000,
    BackupSemantics = 0x02000000,
    PosixSemantics = 0x01000000,
    OpenReparsePoint = 0x00200000,
    OpenNoRecall = 0x00100000,
    FirstPipeInstance = 0x00080000
}
[Flags]
public enum FileShare : uint
{
    None = 0x00000000,
    Read = 0x00000001,
    Write = 0x00000002,
    Delete = 0x00000004
}
[Flags]
internal enum CreationDisposition : uint
{
    FILE_SUPERSEDE = 0,
    FILE_OPEN = 1,
    FILE_CREATE = 2,
    FILE_OPEN_IF = 3,
    FILE_OVERWRITE = 4,
    FILE_OVERWRITE_IF = 5
}
[Flags]
public enum CreateOption : uint
{
    FILE_WRITE_THROUGH = 0x00000002,
    FILE_SEQUENTIAL_ONLY = 0x00000004,
    FILE_NO_INTERMEDIATE_BUFFERING = 0x00000008,
    FILE_SYNCHRONOUS_IO_NONALERT = 0x00000020,
    FILE_RANDOM_ACCESS = 0x00000800
}
[StructLayout(LayoutKind.Sequential, Pack = 0)]
public struct IO_STATUS_BLOCK
{
    public uint status;
    public IntPtr information;
}

[StructLayout(LayoutKind.Sequential, Pack = 0)]
public struct OBJECT_ATTRIBUTES
{
    public Int32 Length;
    public IntPtr RootDirectory;
    public IntPtr ObjectName;
    public uint Attributes;
    public IntPtr SecurityDescriptor;
    public IntPtr SecurityQualityOfService;

}
[Flags]
public enum FileAccess : uint
{
    AccessSystemSecurity = 0x1000000,
    MaximumAllowed = 0x2000000,

    Delete = 0x10000,
    ReadControl = 0x20000,
    WriteDAC = 0x40000,
    WriteOwner = 0x80000,
    Synchronize = 0x100000,

    StandardRightsRequired = 0xF0000,
    StandardRightsRead = ReadControl,
    StandardRightsWrite = ReadControl,
    StandardRightsExecute = ReadControl,
    StandardRightsAll = 0x1F0000,
    SpecificRightsAll = 0xFFFF,

    FILE_READ_DATA = 0x0001,
    FILE_LIST_DIRECTORY = 0x0001,
    FILE_WRITE_DATA = 0x0002,
    FILE_ADD_FILE = 0x0002,
    FILE_APPEND_DATA = 0x0004,
    FILE_ADD_SUBDIRECTORY = 0x0004,
    FILE_CREATE_PIPE_INSTANCE = 0x0004,
    FILE_READ_EA = 0x0008,
    FILE_WRITE_EA = 0x0010,
    FILE_EXECUTE = 0x0020,
    FILE_TRAVERSE = 0x0020,
    FILE_DELETE_CHILD = 0x0040,
    FILE_READ_ATTRIBUTES = 0x0080,
    FILE_WRITE_ATTRIBUTES = 0x0100,

    //
    // Generic Section
    //

    GenericRead = 0x80000000,
    GenericWrite = 0x40000000,
    GenericExecute = 0x20000000,
    GenericAll = 0x10000000,

    SPECIFIC_RIGHTS_ALL = 0x00FFFF,
    FILE_ALL_ACCESS =
    StandardRightsRequired |
    Synchronize |
    0x1FF,

    FILE_GENERIC_READ = StandardRightsRead |
        FILE_READ_DATA |
        FILE_READ_ATTRIBUTES |
        FILE_READ_EA |
        Synchronize,

    FILE_GENERIC_WRITE = StandardRightsWrite |
        FILE_WRITE_DATA |
        FILE_WRITE_ATTRIBUTES |
        FILE_WRITE_EA |
        FILE_APPEND_DATA |
        Synchronize,

    FILE_GENERIC_EXECUTE = StandardRightsExecute |
      FILE_READ_ATTRIBUTES |
      FILE_EXECUTE |
      Synchronize
}

2. 保存syscall字节码

汇编代码在上面已经展示出来,下面将汇编代码保存到C#中,由于C#不支持内联汇编,将以字节的形式保存

1
2
3
4
5
6
byte[] NtCreateFileAsm = {
    0x4c,0x8b,0xd1,             //mov r10,rcx
    0xb8,0x55,0x00,0x00,0x00,   //mov eax,55h
    0x0f,0x05,                  //syscall
    0xc3                        //ret
};

3. 将字节码拷贝到非托管内存

1
2
3
4
5
6
7
8
9
//申请非托管内存,大小为字节码长度
IntPtr ptrAsm = Marshal.AllocHGlobal(NtCreateFileAsm.Length);
//将申请的内存转为可读可写可执行
if (!VirtualProtect(ptrAsm, (UIntPtr)NtCreateFileAsm.Length, (uint)AllocationProtect.PAGE_EXECUTE_READWRITE, out uint lpflOldProtect))
{
    return;
}
//将托管内存复制到非托管内存
Marshal.Copy(NtCreateFileAsm, 0, ptrAsm, NtCreateFileAsm.Length);

4. 定义NtCreateFile委托类型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate int NtCreateFile(
        out Microsoft.Win32.SafeHandles.SafeFileHandle FileHandle,
        FileAccess DesiredAcces,
        ref OBJECT_ATTRIBUTES ObjectAttributes,
        ref IO_STATUS_BLOCK IoStatusBlock,
        ref long AllocationSize,
        FileAttributes FileAttributes,
        FileShare ShareAccess,
        CreationDisposition CreateDisposition,
        CreateOption CreateOptions,
        IntPtr EaBuffer,
        uint EaLength
        );

5. 实例化NtCreateFile委托

1
NtCreateFile NtCreateFileFunc = (NtCreateFile)Marshal.GetDelegateForFunctionPointer(ptrAsm, typeof(NtCreateFile));

6. 初始化变量并调用委托

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 参数初始化
Microsoft.Win32.SafeHandles.SafeFileHandle hFile;
UNICODE_STRING unicodeStringFilename = new UNICODE_STRING();
RtlInitUnicodeString(ref unicodeStringFilename, @"\??\C:\Users\pw.log");
IntPtr objectName = Marshal.AllocHGlobal(Marshal.SizeOf(unicodeStringFilename));
Marshal.StructureToPtr(unicodeStringFilename, objectName, true);
OBJECT_ATTRIBUTES objAttributes = new OBJECT_ATTRIBUTES { 
    Length = (int)Marshal.SizeOf(typeof(OBJECT_ATTRIBUTES)),
    RootDirectory = IntPtr.Zero,
    ObjectName = objectName,
    Attributes = 0x00000040,
    SecurityDescriptor = IntPtr.Zero,
    SecurityQualityOfService = IntPtr.Zero
};
IO_STATUS_BLOCK ioStatusBlock = new IO_STATUS_BLOCK();
long allocSize = 0;
// 调用委托
NtCreateFileFunc(out hFile,
  FileAccess.FILE_GENERIC_WRITE,
  ref objAttributes,
  ref ioStatusBlock,
  ref allocSize,
  FileAttributes.Normal,
  FileShare.Write,
  CreationDisposition.FILE_OVERWRITE_IF,
  CreateOption.FILE_SYNCHRONOUS_IO_NONALERT,
  IntPtr.Zero,
  0);

实现效果

/images/createfilestack1.png
NtCreateFile的调用过程
可以看到在第9行用户态不经过ntdll直接调用NtCreateFile进入了内核态

代码

查看代码

总结

由于C#运行在托管内存中,调用非托管内存需要使用Marshal类中的功能,包括:

1
2
3
Marshal.AllocHGlobal // 通过使用指定的字节数,从进程的非托管内存中分配内存
Marshal.Copy          //将数据从一维托管单精度浮点数数组复制到非托管内存指针
Marshal.GetDelegateForFunctionPointer //将非托管函数指针转换为委托