summaryrefslogtreecommitdiff
path: root/src/syscall.h
blob: 001b3af9761133e2a522391ddb8e7ec384b62678 (plain)
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
//|_   _   _.     _  ._  |_   _.  _ |
//| | (/_ (_| \/ (/_ | | | | (_| (_ |<

#pragma once
#include "x86.h"
#include "fnv.h"
#include "asmutil.h"
#include "conout.h"
#include "ntutil.h"
#include "winintern.h"

struct SYSCALL_ENTRY {
  STR<64>     name;
  FNV1A       hash;
  U64         base;
  I32         idx;
};


inline U32 syscall_wrapper_size( const U8* fn, U16* out_ret_bytes = 0 ) {
  for( U32 off = 0; off < 0x18; ++off ) {
    // x64
    {
      if( *(U16*)(&fn[off]) == 0x050f     /* syscall */
       && *(U16*)(&fn[off + 3]) == 0x2ecd /* int 2e */
      ) {
        if( out_ret_bytes )
          *out_ret_bytes = 0;
        return off + 5;
      }
    }

    // x86
    {
      if( fn[off] == 0xc3 /* retn */ ) {
        if( out_ret_bytes )
          *out_ret_bytes = 0;

       return off + 1;
      }
      if( fn[off] == 0xc2 /* retn imm16 */ ) {
        if( out_ret_bytes )
          *out_ret_bytes = *(U16*)( &fn[off + 1] );

        return off + 3;
      }
    }
  }

  return 0;
}

inline U8 syscall_is_syscall( const U8* fn, U32 fn_size ) {
  // x64
  if( fn[0] == 0x4c && fn[1] == 0x8b && fn[2] == 0xd1 ) { // mov r10, rcx
    return ( fn_size > 0 && fn_size < 0x20 ) ? 2 : 0;
  }
  
  // x86
  {
    if( fn[0] != x86_encode_mov_imm32( X86Regs_t::eax ) )
      return false;

    for( U32 off = 0; off < fn_size; ++off ) {
      if( *(U16*)( &fn[off] ) == 0xd2ff /* call edx */ )
        return 1;
    }
  }
    
  return 0;
}

inline U32 syscall_get_index( const U8* fn, U32 size, U16* out_ret_bytes = 0 ) {
  U16 ret_bytes{};
  U8  syscall_type = syscall_is_syscall( fn, size );

  if( !syscall_type )
    return 0;
  
  if( out_ret_bytes )
    *out_ret_bytes = ret_bytes;

  // same for x86/x64
  for( U32 off = 0; off < size; ++off ) {
    if( *( fn + off ) == x86_encode_mov_imm32( X86Regs_t::eax ) )
      return *(U32*)( fn + off + 1 );
  }

  return 0;
}

inline VECTOR< SYSCALL_ENTRY > syscall_dump() {
  static VECTOR< SYSCALL_ENTRY > ret;
  static VECTOR< MODULE_EXPORT > nt_exports;

  // ntdll can't change during runtime
  if( !ret.empty() )
    return ret;
  
  void* nt = nt_get_address();
  if( nt_exports.empty() )
    nt_exports = module_get_exports( nt );

  for( auto exp : nt_exports ) {
    U32 size = syscall_wrapper_size( (U8*)exp.base );
    if( !size )
      continue;
    
    if( !syscall_is_syscall( (U8*)exp.base, size ) )
      continue;

    U32 idx = syscall_get_index( (U8*)exp.base, size );

    SYSCALL_ENTRY e;
    e.base = (U64)exp.base;
    e.name = exp.name;
    e.hash = fnv1a( e.name );
    e.idx = idx;
    
    ret.push_back( e );
  }

  return ret;
}

inline VECTOR< SYSCALL_ENTRY > syscall_dump64() {
  static VECTOR< SYSCALL_ENTRY >   ret{};
  static VECTOR< MODULE_EXPORT64 > nt_exports{};

  if( !ret.empty() )
    return ret;

  U64 nt64 = nt_get_address64();
  if( nt_exports.empty() ) 
    nt_exports = module_get_exports64( nt64 );

  U8* syscall_wrapper = (U8*)VirtualAlloc(
    0,
    4096,
    MEM_COMMIT | MEM_RESERVE,
    PAGE_READWRITE
  );
  
  for( auto exp : nt_exports ) {
    nt_read_vm64( (HANDLE)-1, exp.base, syscall_wrapper, 0x30 );

    U32 size = syscall_wrapper_size( syscall_wrapper );
    if( !size )
      continue;

    if( !syscall_is_syscall( syscall_wrapper, size ) )
      continue;

    U32 idx = syscall_get_index( syscall_wrapper, size );

    SYSCALL_ENTRY e;
    e.base = exp.base;
    e.name = exp.name.data;
    e.hash = fnv1a( exp.name );
    e.idx = idx;

    ret.push_back( e );
  }

  VirtualFree( syscall_wrapper, 4096, MEM_FREE );
  return ret;
}

inline SYSCALL_ENTRY syscall_find_syscall( FNV1A syscall_hash ) {
  static VECTOR< SYSCALL_ENTRY > syscalls = syscall_dump();
  
  for( auto& syscall : syscalls ) {
    if( syscall.hash == syscall_hash )
      return syscall;
  }

  return {};
}

inline SYSCALL_ENTRY syscall_find_syscall64( FNV1A syscall_hash ) {
  VECTOR< SYSCALL_ENTRY > syscalls = syscall_dump64();

  for( auto& syscall : syscalls ) {
    if( syscall.hash == syscall_hash )
      return syscall;
  }

  return {}; 
}


template < typename ... argt >
NTSTATUS64 __cdecl syscall_execute( U32 idx, argt ... args ) {
  REG64 args64[] = { (REG64)(args)... };
  
  U64   argc = sizeof...( argt );
  U16   arg  = 0;
  REG64 _rcx = arg < argc? (REG64)args64[arg++] : 0;
  REG64 _rdx = arg < argc? (REG64)args64[arg++] : 0;
  REG64 _r8  = arg < argc? (REG64)args64[arg++] : 0;
  REG64 _r9  = arg < argc? (REG64)args64[arg++] : 0;
  REG64 _rax = {};

  REG64 argc64 = ( argc - arg );
  REG64 argp = (U64)&args64[arg];

  REG64 idx64 = (U64)idx;

  U32 _esp;
  U16 _fs;

  // backup fs and esp to make restoring simpler
  __asm {
    mov _fs, fs
    mov _esp, esp
    
    mov eax, 0x2b
    mov fs, ax

    // align stack to 16
    and esp, 0xfffffff0
  }

  heavens_gate_enter();

  // x64 syscall calling convention
  // standard fastcall, first 4 args go in registers
  __asm {
    rex_wr mov edx, _rcx.u32[0] // mov r10, a0 
    rex_w  mov edx, _rdx.u32[0] // mov edx, a1
    rex_w  mov ecx, _rcx.u32[0]
    rex_wr mov eax, _r8.u32[0]  // mov r8, a2
    rex_wr mov ecx, _r9.u32[0]  // mov r9, a3
  }

  __asm {
    rex_w  mov eax, argc64.u32[0]
           test al, 1
           jnz stack_ok
           // most syscalls are fine with the stack being aligned to 16 bytes
           // but few specific ones crash
           // adjust based on no. of args
           sub esp, 8
  }
  
stack_ok:

  __asm {
           push edi
    rex_w  mov edi, argp.u32[0] // mov rdi, argp
    rex_w  lea edi, dword ptr[edi + 8 * eax - 8]
  }

arg_loop:

  __asm {
    rex_w  test eax, eax       // test rax, rax
           jz loop_end
           push dword ptr[edi]
    rex_w  sub edi, 8          // sub rdi, 8
    rex_w  sub eax, 1          // sub rax, 1
           jmp arg_loop
  }
loop_end:

  __asm {
    rex_w  mov eax, idx64.u32[0]
    // make stack space for syscall
    rex_w  sub esp, 0x28 // this fucker cost me a night

    // do the epic
           db( 0x0f ) db( 0x05 ) // syscall
  }

  //unfuck the stack
  __asm {
    rex_w  mov ecx, argc64.u32[0]
    rex_w  lea esp, dword ptr[esp + 8 * ecx + 0x28]

    pop edi

    // store 64 bits of rax on the stack
    rex_w  mov _rax.u32[0], eax
  }

  heavens_gate_exit();

  __asm {
    // restore stack segment
    mov ax, ds
    mov ss, ax

    // restore esp and fs
    mov esp, _esp
    mov ax, _fs
    mov fs, ax
  }

  return _rax.u64;
}