Skip to content

Commit 40e2c48

Browse files
committed
System Touch
1 parent d942d06 commit 40e2c48

4 files changed

Lines changed: 493 additions & 0 deletions

File tree

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
#include "ModuleHeuristics.h"
2+
#include <stdio.h>
3+
#include <string.h>
4+
#include <strings.h>
5+
6+
/* ── Internal helpers ──────────────────────────────────────────────────────── */
7+
8+
static void add_finding(mh_result_t *r, const char *msg)
9+
{
10+
if (r->finding_count >= MH_MAX_FINDINGS) return;
11+
strncpy(r->findings[r->finding_count++], msg, MH_FINDING_LEN - 1);
12+
}
13+
14+
static mh_type_t detect_type(const char *name, const unsigned char *data, size_t len)
15+
{
16+
/* ZIP/JAR magic bytes: PK (0x50 0x4B) */
17+
if (len >= 4 && data[0] == 0x50 && data[1] == 0x4B)
18+
{
19+
/* Heuristic: if the first 512 bytes contain "META-INF" it is a jar */
20+
size_t scan = len < 512 ? len : 512;
21+
for (size_t i = 0; i + 8 < scan; i++)
22+
if (memcmp(data + i, "META-INF", 8) == 0) return MH_TYPE_JAR;
23+
return MH_TYPE_ZIP;
24+
}
25+
26+
/* ELF magic: native shared object / executable */
27+
if (len >= 4 && data[0] == 0x7F && data[1] == 'E' && data[2] == 'L' && data[3] == 'F')
28+
return MH_TYPE_NATIVE;
29+
30+
/* Java source by extension */
31+
if (name)
32+
{
33+
size_t nlen = strlen(name);
34+
if (nlen > 5 && strcasecmp(name + nlen - 5, ".java") == 0) return MH_TYPE_JAVA;
35+
}
36+
37+
return MH_TYPE_UNKNOWN;
38+
}
39+
40+
/* ── Public API ────────────────────────────────────────────────────────────── */
41+
42+
void mh_evaluate(const char *name, const unsigned char *data, size_t length, mh_result_t *result)
43+
{
44+
memset(result, 0, sizeof(*result));
45+
46+
/* 1. Type recognised (+20) */
47+
result->type = detect_type(name, data, length);
48+
if (result->type == MH_TYPE_UNKNOWN)
49+
{
50+
add_finding(result, "FAIL unrecognised type — must be .jar, .zip, .java, or ELF");
51+
result->suitable = 0;
52+
return;
53+
}
54+
result->score += 20;
55+
switch (result->type)
56+
{
57+
case MH_TYPE_JAR: add_finding(result, "OK type: jar"); break;
58+
case MH_TYPE_ZIP: add_finding(result, "OK type: zip"); break;
59+
case MH_TYPE_JAVA: add_finding(result, "OK type: java"); break;
60+
case MH_TYPE_NATIVE: add_finding(result, "OK type: native ELF"); break;
61+
default: break;
62+
}
63+
64+
/* 2. Non-empty (+10) */
65+
if (length > 0)
66+
{
67+
result->score += 10;
68+
add_finding(result, "OK file is non-empty");
69+
}
70+
else
71+
{
72+
add_finding(result, "FAIL file is empty");
73+
result->suitable = 0;
74+
return;
75+
}
76+
77+
/* 3. Size within limit (+10) */
78+
if (length <= MH_MAX_SIZE_BYTES)
79+
{
80+
result->score += 10;
81+
add_finding(result, "OK size within 50 MB limit");
82+
}
83+
else
84+
{
85+
add_finding(result, "WARN size exceeds 50 MB — server may reject");
86+
}
87+
88+
/* 4. Type-specific checks */
89+
if (result->type == MH_TYPE_JAR || result->type == MH_TYPE_ZIP)
90+
{
91+
/* Verify ZIP local-file header signature at offset 0 */
92+
if (length >= 30 && data[0] == 0x50 && data[1] == 0x4B && data[2] == 0x03 && data[3] == 0x04)
93+
{
94+
result->score += 30;
95+
add_finding(result, "OK valid ZIP local-file header");
96+
}
97+
else
98+
{
99+
add_finding(result, "WARN ZIP local-file header not found at offset 0");
100+
}
101+
}
102+
else if (result->type == MH_TYPE_JAVA)
103+
{
104+
/* Scan first 2 KB for Java declarations */
105+
size_t scan = length < 2048 ? length : 2048;
106+
char preview[2049];
107+
memcpy(preview, data, scan);
108+
preview[scan] = '\0';
109+
110+
if (strstr(preview, "public class") || strstr(preview, "public interface"))
111+
{
112+
result->score += 20;
113+
add_finding(result, "OK public type declaration found");
114+
}
115+
else
116+
{
117+
add_finding(result, "WARN no public class/interface found");
118+
}
119+
120+
if (strstr(preview, "package "))
121+
{
122+
result->score += 10;
123+
add_finding(result, "OK package declaration present");
124+
}
125+
126+
if (strstr(preview, "Runtime.getRuntime") || strstr(preview, "ProcessBuilder"))
127+
add_finding(result, "WARN process-execution pattern detected — review before install");
128+
else
129+
{
130+
result->score += 10;
131+
add_finding(result, "OK no process-execution patterns found");
132+
}
133+
}
134+
else if (result->type == MH_TYPE_NATIVE)
135+
{
136+
/* ELF: check 64-bit class byte (offset 4: 1=32-bit, 2=64-bit) */
137+
if (length > 4 && data[4] == 2)
138+
{
139+
result->score += 20;
140+
add_finding(result, "OK ELF 64-bit class");
141+
}
142+
else if (length > 4 && data[4] == 1)
143+
{
144+
result->score += 10;
145+
add_finding(result, "INFO ELF 32-bit class");
146+
}
147+
148+
/* ELF type offset 16 (2 bytes LE): 3 = ET_DYN (shared object) */
149+
if (length > 17)
150+
{
151+
unsigned short etype = (unsigned short)(data[16] | (data[17] << 8));
152+
if (etype == 3)
153+
{
154+
result->score += 20;
155+
add_finding(result, "OK ELF type ET_DYN (shared object — loadable)");
156+
}
157+
else
158+
{
159+
add_finding(result, "WARN ELF type is not ET_DYN — may not be loadable as a module");
160+
}
161+
}
162+
}
163+
164+
/* 5. Safe name (+5) */
165+
if (name)
166+
{
167+
int safe = 1;
168+
for (const char *c = name; *c; c++)
169+
if (!((*c >= 'a' && *c <= 'z') || (*c >= 'A' && *c <= 'Z') ||
170+
(*c >= '0' && *c <= '9') || *c == '.' || *c == '_' || *c == '-'))
171+
{ safe = 0; break; }
172+
if (safe) { result->score += 5; add_finding(result, "OK module name is safe"); }
173+
else add_finding(result, "WARN module name contains special characters");
174+
}
175+
176+
if (result->score > 100) result->score = 100;
177+
result->suitable = (result->score >= MH_PASS_THRESHOLD);
178+
}
179+
180+
void mh_print_result(const mh_result_t *result)
181+
{
182+
printf("ModuleHeuristics score: %d/100 — %s\n",
183+
result->score,
184+
result->suitable ? "SUITABLE for install" : "NOT suitable");
185+
for (int i = 0; i < result->finding_count; i++)
186+
printf(" %s\n", result->findings[i]);
187+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#ifndef MODULE_HEURISTICS_H
2+
#define MODULE_HEURISTICS_H
3+
4+
#include <stddef.h>
5+
6+
#define MH_PASS_THRESHOLD 70
7+
#define MH_MAX_SIZE_BYTES (50 * 1024 * 1024)
8+
#define MH_MAX_FINDINGS 32
9+
#define MH_FINDING_LEN 128
10+
11+
typedef enum { MH_TYPE_UNKNOWN, MH_TYPE_JAR, MH_TYPE_ZIP, MH_TYPE_JAVA, MH_TYPE_NATIVE } mh_type_t;
12+
13+
typedef struct {
14+
int score; /* 0–100 */
15+
int suitable; /* 1 if score >= threshold */
16+
mh_type_t type;
17+
int finding_count;
18+
char findings[MH_MAX_FINDINGS][MH_FINDING_LEN];
19+
} mh_result_t;
20+
21+
/**
22+
* Evaluate a candidate module from a memory buffer.
23+
* name — original filename (used for extension hint)
24+
* data — raw file bytes
25+
* length — byte count
26+
* result — caller-allocated result struct to populate
27+
*/
28+
void mh_evaluate(const char *name, const unsigned char *data, size_t length, mh_result_t *result);
29+
30+
/** Print a human-readable summary of the result to stdout. */
31+
void mh_print_result(const mh_result_t *result);
32+
33+
#endif /* MODULE_HEURISTICS_H */

0 commit comments

Comments
 (0)