-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathberlin.c
More file actions
445 lines (386 loc) · 11.8 KB
/
berlin.c
File metadata and controls
445 lines (386 loc) · 11.8 KB
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
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
/* ============================================================
* berlin.c - Berlin VM detection implementation
*
* Freestanding (no CRT) in release builds.
* DEBUG builds require libc (printf).
* ============================================================ */
#include "berlin.h"
#include <stdint.h>
/* ---- Debug output ----------------------------------------- */
#ifdef DEBUG
# include <stdio.h>
# define debugPrint(fmt, ...) \
printf("[%s] " fmt "\n", __func__, ##__VA_ARGS__)
#else
# define debugPrint(fmt, ...) ((void)0)
#endif
/* ---- Compiler intrinsics ----------------------------------- */
#if defined(_MSC_VER)
# include <intrin.h> /* __cpuid, __rdtsc, __rdtscp, _mm_lfence, _xgetbv */
#endif
static void berlin_memcpy(void *dst, const void *src, unsigned int n)
{
unsigned char *d = (unsigned char *)dst;
const unsigned char *s = (const unsigned char *)src;
while (n--) *d++ = *s++;
}
static int berlin_memcmp(const void *a, const void *b, unsigned int n)
{
const unsigned char *pa = (const unsigned char *)a;
const unsigned char *pb = (const unsigned char *)b;
while (n--) {
if (*pa != *pb) return (int)*pa - (int)*pb;
pa++; pb++;
}
return 0;
}
/* ============================================================
* Low-level CPU helpers
* ============================================================ */
static inline void cpuid(uint32_t leaf,
uint32_t *eax, uint32_t *ebx,
uint32_t *ecx, uint32_t *edx)
{
#if defined(_MSC_VER)
int regs[4];
__cpuid(regs, (int)leaf);
*eax = (uint32_t)regs[0]; *ebx = (uint32_t)regs[1];
*ecx = (uint32_t)regs[2]; *edx = (uint32_t)regs[3];
#else
__asm__ volatile("cpuid"
: "=a"(*eax), "=b"(*ebx), "=c"(*ecx), "=d"(*edx)
: "a"(leaf));
#endif
}
static inline uint64_t rdtsc(void)
{
#if defined(_MSC_VER)
return __rdtsc();
#else
uint32_t lo, hi;
__asm__ volatile("rdtsc" : "=a"(lo), "=d"(hi));
return ((uint64_t)hi << 32) | lo;
#endif
}
static inline uint64_t rdtscp(uint32_t *aux)
{
#if defined(_MSC_VER)
return __rdtscp(aux);
#else
uint32_t lo, hi, a;
__asm__ volatile("rdtscp" : "=a"(lo), "=d"(hi), "=c"(a));
if (aux) *aux = a;
return ((uint64_t)hi << 32) | lo;
#endif
}
/* ============================================================
* Timing primitives
* ============================================================ */
/*
* LFENCE (a load fence) serialises the instruction stream, preventing
* the CPU from reordering the two reads past each other.
*/
static uint64_t rdtsc_diff(void)
{
#if defined(_MSC_VER)
_mm_lfence();
uint64_t start = __rdtsc();
_mm_lfence();
uint64_t end = __rdtsc();
#else
__asm__ volatile("lfence" ::: "memory");
uint64_t start = rdtsc();
__asm__ volatile("lfence" ::: "memory");
uint64_t end = rdtsc();
#endif
return end - start;
}
/*
* CPUID is a serialising instruction and forces a VM-exit on most
* hypervisors, making the measured round-trip measurably slower.
*/
static uint64_t rdtsc_diff_vmexit(void)
{
uint32_t eax, ebx, ecx, edx;
uint64_t start = rdtsc();
cpuid(0, &eax, &ebx, &ecx, &edx);
uint64_t end = rdtsc();
return end - start;
}
static uint64_t rdtscp_diff(void)
{
uint32_t aux;
uint64_t start = rdtscp(&aux);
uint64_t end = rdtscp(&aux);
return end - start;
}
/* ============================================================
* Vendor / brand helpers
* ============================================================ */
static void cpu_write_vendor(char vendor[13])
{
uint32_t eax, ebx, ecx, edx;
cpuid(0, &eax, &ebx, &ecx, &edx);
/* CPU vendor string layout for CPUID leaf 0: EBX, EDX, ECX */
berlin_memcpy(vendor + 0, &ebx, 4);
berlin_memcpy(vendor + 4, &edx, 4);
berlin_memcpy(vendor + 8, &ecx, 4);
vendor[12] = '\0';
}
static void cpu_write_hv_vendor(char vendor[13])
{
uint32_t eax, ebx, ecx, edx;
cpuid(0x40000000, &eax, &ebx, &ecx, &edx);
/* Hypervisor vendor string layout: EBX, ECX, EDX */
berlin_memcpy(vendor + 0, &ebx, 4);
berlin_memcpy(vendor + 4, &ecx, 4);
berlin_memcpy(vendor + 8, &edx, 4);
vendor[12] = '\0';
}
static void cpu_write_brand(char brand[49])
{
uint32_t eax, ebx, ecx, edx;
cpuid(0x80000000, &eax, &ebx, &ecx, &edx);
if (eax < 0x80000004) { brand[0] = '\0'; return; }
/* x86 guarantees the brand string dwords are contiguous and
* naturally aligned when accessed via CPUID output registers.
* Casting char* to uint32_t* is safe on this target. */
uint32_t *p = (uint32_t *)brand;
cpuid(0x80000002, &p[0], &p[1], &p[2], &p[3]);
cpuid(0x80000003, &p[4], &p[5], &p[6], &p[7]);
cpuid(0x80000004, &p[8], &p[9], &p[10], &p[11]);
brand[48] = '\0';
}
static int cpuid_hypervisor_present(void)
{
uint32_t eax, ebx, ecx, edx;
cpuid(1, &eax, &ebx, &ecx, &edx);
return (ecx >> 31) & 1; /* CPUID.1:ECX[31] - Hypervisor Present */
}
/* ============================================================
* XGETBV helper
* ============================================================ */
static inline uint64_t xgetbv(uint32_t index)
{
#if defined(_MSC_VER)
return _xgetbv(index);
#else
uint32_t eax, edx;
__asm__ volatile(".byte 0x0f, 0x01, 0xd0"
: "=a"(eax), "=d"(edx)
: "c"(index));
return ((uint64_t)edx << 32) | eax;
#endif
}
/* ============================================================
* SIDT helper
* ============================================================ */
#pragma pack(push, 1)
typedef struct {
uint16_t limit;
uintptr_t base;
} idtr_t;
#pragma pack(pop)
static inline void sidt(idtr_t *idtr)
{
#if defined(_MSC_VER)
__sidt(idtr);
#else
__asm__ volatile("sidt %0" : "=m"(*idtr));
#endif
}
/* ============================================================
* Detection sub-scores
* ============================================================ */
static int rdtscp_jitter_score(void)
{
const int iterations = 20;
uint64_t min_val = (uint64_t)-1;
uint64_t max_val = 0;
for (int i = 0; i < iterations; i++) {
uint64_t d = rdtscp_diff();
if (d < min_val) min_val = d;
if (d > max_val) max_val = d;
}
return ((max_val - min_val) > 500) ? 25 : 0;
}
static int cpu_has_xsave(void)
{
uint32_t eax, ebx, ecx, edx;
cpuid(1, &eax, &ebx, &ecx, &edx);
return (ecx >> 27) & 1;
}
static uint64_t xgetbv_diff(void)
{
#if defined(_MSC_VER)
_mm_lfence();
uint64_t start = __rdtsc();
(void)xgetbv(0);
_mm_lfence();
uint64_t end = __rdtsc();
#else
__asm__ volatile("lfence" ::: "memory");
uint64_t start = rdtsc();
(void)xgetbv(0);
__asm__ volatile("lfence" ::: "memory");
uint64_t end = rdtsc();
#endif
return end - start;
}
static uint64_t sidt_diff(void)
{
idtr_t idtr;
uint64_t start = rdtsc();
sidt(&idtr);
uint64_t end = rdtsc();
return end - start;
}
static int xgetbv_score(void)
{
if (!cpu_has_xsave()) return 0;
int score = 0;
uint64_t xcr0 = xgetbv(0);
if (!(xcr0 & 0x1))
{
score += 25;
}
if ((xcr0 & (1ULL << 2)) && !(xcr0 & (1ULL << 1)))
{
score += 25;
}
uint32_t eax, ebx, ecx, edx;
cpuid(0xD, &eax, &ebx, &ecx, &edx);
uint64_t supported_mask = ((uint64_t)edx << 32) | eax;
if (xcr0 & ~supported_mask)
{
score += 25;
}
uint64_t avg = 0;
for (int i = 0; i < 10; i++)
{
avg += xgetbv_diff();
}
avg /= 10;
if (avg > 1500 || avg == 0)
{
score += 25;
}
return score;
}
static int sidt_score(void)
{
int score = 0;
idtr_t idtr;
sidt(&idtr);
#if defined(__x86_64__) || defined(_M_X64)
/* Low-confidence signals - contribute to score, not standalone proof.
* Non-canonical high bits or suspiciously high base can indicate
* hypervisor IDTR relocation. Needs further empirical refinement. */
if (idtr.base >> 48)
score += 10;
if (idtr.base > 0xFFFF800000000000ULL)
score += 10;
/* Both unconditionally fire on every x86_64 Linux system, bare metal or not.
Linux intentionally maps the IDT at 0xfffffe0000000000
(the CPU Entry Area/a fixed read-only mapping) specifically to prevent
sidt-based kernel ASLR leaks. Useful only on Windows and MacOS*/
#endif
uint64_t avg = 0;
for (int i = 0; i < 10; i++)
avg += sidt_diff();
avg /= 10;
if (avg > 4000 || avg == 0)
score += 25;
return score;
}
static int cpuid_fuzz_score(void)
{
uint32_t eax, ebx, ecx, edx;
int score = 0;
uint64_t start, end, diff;
cpuid(0, &eax, &ebx, &ecx, &edx);
start = rdtsc();
cpuid(eax + 1, &eax, &ebx, &ecx, &edx);
end = rdtsc();
diff = end - start;
if (diff > 2000 || diff == 0) score += 25;
cpuid(0x80000000, &eax, &ebx, &ecx, &edx);
start = rdtsc();
cpuid(eax + 1, &eax, &ebx, &ecx, &edx);
end = rdtsc();
diff = end - start;
if (diff > 2000 || diff == 0) score += 25;
return score;
}
/* ============================================================
* Core detection
* ============================================================ */
int vmScore(void)
{
int score = 0;
uint64_t avg = 0;
uint64_t avg_vmexit = 0;
char vendor[13];
char brand[49];
cpu_write_vendor(vendor);
cpu_write_brand(brand);
debugPrint("CPU Vendor : %s", vendor);
debugPrint("CPU Brand : %s", brand[0] ? brand : "Unknown");
/* --- 1. RDTSC overhead (serialised) --- */
for (int i = 0; i < 10; i++) avg += rdtsc_diff();
avg /= 10;
if (avg > 750 || avg == 0) {
score += 15;
debugPrint("rdtsc_diff anomaly: avg=%llu", (unsigned long long)avg);
}
/* --- 2. CPUID VM-exit timing --- */
for (int i = 0; i < 10; i++) avg_vmexit += rdtsc_diff_vmexit();
avg_vmexit /= 10;
if (avg_vmexit > 1000 || avg_vmexit == 0) {
score += 100;
debugPrint("vmexit anomaly: avg=%llu", (unsigned long long)avg_vmexit);
}
/* --- 3. Hypervisor Present bit --- */
if (cpuid_hypervisor_present()) {
score += 100;
debugPrint("Hypervisor Present bit set (CPUID.1:ECX[31])");
}
/* --- 4. Known hypervisor vendor string ---
* Zero-padded to prevent UB on memcmp
*/
static const char known[6][12] = {
{'K','V','M','K','V','M','K','V','M', 0, 0, 0 }, /* KVM */
{'M','i','c','r','o','s','o','f','t',' ','H','v'}, /* Hyper-V */
{'V','M','w','a','r','e','V','M','w','a','r','e'}, /* VMware */
{'X','e','n','V','M','M','X','e','n','V','M','M'}, /* Xen */
{'p','r','l',' ','h','y','p','e','r','v',' ',' '}, /* Parallels */
{'V','B','o','x','V','B','o','x','V','B','o','x'} /* VirtualBox */
};
char hv_vendor[13];
cpu_write_hv_vendor(hv_vendor);
debugPrint("HV Vendor : %.12s", hv_vendor);
for (int i = 0; i < 6; i++) {
if (berlin_memcmp(hv_vendor, known[i], 12) == 0) {
score += 100;
debugPrint("Matched known hypervisor vendor at index %d", i);
break;
}
}
debugPrint("Score after basic checks : %d", score);
score += rdtscp_jitter_score();
debugPrint("Score after RDTSCP jitter : %d", score);
score += sidt_score();
debugPrint("Score after SIDT : %d", score);
score += cpuid_fuzz_score();
debugPrint("Score after CPUID fuzz : %d", score);
score += xgetbv_score();
debugPrint("Score after XGETBV : %d", score);
return score;
}
int isVM(int threshold)
{
int score = vmScore();
debugPrint("Total score: %d threshold: %d result: %s",
score, threshold, score >= threshold ? "VM" : "bare metal");
return (score >= threshold) ? 1 : 0;
}