/* shell-observer: watches processes and IPv4 TCP/UDP endpoints on Windows through documented OS calls, and writes an append-only, SHA-256 hash-chained session log. Commands: watch [-n TICKS] [-i MS] | selftest | verify FILE | help */ int GetStdHandle(int which); unsigned int WriteFile(int h, char *buf, unsigned int n, unsigned int *done, int ov); unsigned int ReadFile(int h, char *buf, unsigned int n, unsigned int *done, int ov); int CreateFileA(char *name, unsigned int access, unsigned int share, char *sec, unsigned int disp, unsigned int flags, int templ); unsigned int CloseHandle(int h); unsigned int CreateDirectoryA(char *name, char *sec); unsigned int GetLastError(void); char *GetCommandLineA(void); unsigned int GetCurrentProcessId(void); int GetTickCount64(void); void Sleep(unsigned int ms); void GetLocalTime(char *st); int CreateToolhelp32Snapshot(unsigned int flags, unsigned int pid); unsigned int Process32FirstW(int snap, char *entry); unsigned int Process32NextW(int snap, char *entry); unsigned int GetExtendedTcpTable(char *table, unsigned int *size, int order, unsigned int af, unsigned int cls, unsigned int reserved); unsigned int GetExtendedUdpTable(char *table, unsigned int *size, int order, unsigned int af, unsigned int cls, unsigned int reserved); unsigned int CreateProcessA(char *app, char *cmd, char *pa, char *ta, unsigned int inherit, unsigned int flags, char *env, char *dir, char *si, char *pi); unsigned int TerminateProcess(int h, unsigned int code); unsigned int WaitForSingleObject(int h, unsigned int ms); int VirtualAlloc(int addr, int size, unsigned int type, unsigned int prot); unsigned int GetFileSize(int h, unsigned int *high); /* ---------- output ---------- */ static int slen(char *s) { int n; n = 0; while (s[n]) n = n + 1; return n; } static void out_n(char *s, int n) { unsigned int w; w = 0; WriteFile(GetStdHandle(-11), s, n, &w, 0); } static void out(char *s) { out_n(s, slen(s)); } static char numbuf[32]; static char *dec(int v) { int i; int neg; i = 31; numbuf[i] = 0; neg = v < 0; if (v == 0) { i = i - 1; numbuf[i] = 48; } while (v != 0) { int d; d = v % 10; if (d < 0) d = 0 - d; i = i - 1; numbuf[i] = 48 + d; v = v / 10; } if (neg) { i = i - 1; numbuf[i] = 45; } return numbuf + i; } /* a line builder used for both the console and the log */ static char lb[4096]; static int ll; static void lb_reset(void) { ll = 0; lb[0] = 0; } static void lb_s(char *s) { int i; i = 0; while (s[i] && ll < 4000) { lb[ll] = s[i]; ll = ll + 1; i = i + 1; } lb[ll] = 0; } static void lb_d(int v) { lb_s(dec(v)); } static void lb_ip(int a) { lb_d(a & 255); lb_s("."); lb_d((a >> 8) & 255); lb_s("."); lb_d((a >> 16) & 255); lb_s("."); lb_d((a >> 24) & 255); } static void lb_pad2(int v) { if (v < 10) lb_s("0"); lb_d(v); } /* UTF-16LE (NUL terminated) to UTF-8 */ static void lb_w(char *w) { int i; i = 0; while (ll < 3990) { int c; c = (w[2 * i] & 255) | ((w[2 * i + 1] & 255) << 8); if (c == 0) break; i = i + 1; if (c >= 55296 && c < 56320) { int c2; c2 = (w[2 * i] & 255) | ((w[2 * i + 1] & 255) << 8); if (c2 >= 56320 && c2 < 57344) { i = i + 1; c = 65536 + ((c - 55296) << 10) + (c2 - 56320); } else c = 65533; } else if (c >= 56320 && c < 57344) c = 65533; if (c < 32 || c == 124) { lb[ll] = 63; ll = ll + 1; } else if (c < 128) { lb[ll] = c; ll = ll + 1; } else if (c < 2048) { lb[ll] = 192 | (c >> 6); lb[ll + 1] = 128 | (c & 63); ll = ll + 2; } else if (c < 65536) { lb[ll] = 224 | (c >> 12); lb[ll + 1] = 128 | ((c >> 6) & 63); lb[ll + 2] = 128 | (c & 63); ll = ll + 3; } else { lb[ll] = 240 | (c >> 18); lb[ll + 1] = 128 | ((c >> 12) & 63); lb[ll + 2] = 128 | ((c >> 6) & 63); lb[ll + 3] = 128 | (c & 63); ll = ll + 4; } } lb[ll] = 0; } /* ---------- SHA-256 (FIPS 180-4) ---------- */ int K[64] = {0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174, 0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967, 0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070, 0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2}; int H[8]; int W[64]; char blk[64]; int blen; int total; static int ror(int x, int n) { return ((x >> n) | (x << (32 - n))) & 0xFFFFFFFF; } static void sha_init(void) { H[0] = 0x6a09e667; H[1] = 0xbb67ae85; H[2] = 0x3c6ef372; H[3] = 0xa54ff53a; H[4] = 0x510e527f; H[5] = 0x9b05688c; H[6] = 0x1f83d9ab; H[7] = 0x5be0cd19; blen = 0; total = 0; } static void sha_block(void) { int i; int a; int b; int c; int d; int e; int f; int g; int h; i = 0; while (i < 16) { W[i] = ((blk[4 * i] & 255) << 24) | ((blk[4 * i + 1] & 255) << 16) | ((blk[4 * i + 2] & 255) << 8) | (blk[4 * i + 3] & 255); i = i + 1; } while (i < 64) { int s0; int s1; s0 = ror(W[i - 15], 7) ^ ror(W[i - 15], 18) ^ (W[i - 15] >> 3); s1 = ror(W[i - 2], 17) ^ ror(W[i - 2], 19) ^ (W[i - 2] >> 10); W[i] = (W[i - 16] + s0 + W[i - 7] + s1) & 0xFFFFFFFF; i = i + 1; } a = H[0]; b = H[1]; c = H[2]; d = H[3]; e = H[4]; f = H[5]; g = H[6]; h = H[7]; i = 0; while (i < 64) { int t1; int t2; t1 = (h + (ror(e, 6) ^ ror(e, 11) ^ ror(e, 25)) + ((e & f) ^ ((e ^ 0xFFFFFFFF) & g)) + K[i] + W[i]) & 0xFFFFFFFF; t2 = ((ror(a, 2) ^ ror(a, 13) ^ ror(a, 22)) + ((a & b) ^ (a & c) ^ (b & c))) & 0xFFFFFFFF; h = g; g = f; f = e; e = (d + t1) & 0xFFFFFFFF; d = c; c = b; b = a; a = (t1 + t2) & 0xFFFFFFFF; i = i + 1; } H[0] = (H[0] + a) & 0xFFFFFFFF; H[1] = (H[1] + b) & 0xFFFFFFFF; H[2] = (H[2] + c) & 0xFFFFFFFF; H[3] = (H[3] + d) & 0xFFFFFFFF; H[4] = (H[4] + e) & 0xFFFFFFFF; H[5] = (H[5] + f) & 0xFFFFFFFF; H[6] = (H[6] + g) & 0xFFFFFFFF; H[7] = (H[7] + h) & 0xFFFFFFFF; } static void sha_byte(int c) { blk[blen] = c; blen = blen + 1; total = total + 1; if (blen == 64) { sha_block(); blen = 0; } } static void sha_data(char *p, int n) { int i; i = 0; while (i < n) { sha_byte(p[i]); i = i + 1; } } static char hexout[65]; static char *sha_hex(void) { int bits; int i; bits = total * 8; sha_byte(128); while (blen != 56) sha_byte(0); i = 7; while (i >= 0) { blk[blen] = (bits >> (8 * i)) & 255; blen = blen + 1; i = i - 1; } sha_block(); i = 0; while (i < 64) { int v; v = (H[i / 8] >> (28 - 4 * (i % 8))) & 15; if (v < 10) hexout[i] = 48 + v; else hexout[i] = 87 + v; i = i + 1; } hexout[64] = 0; return hexout; } static int streq(char *a, char *b) { int i; i = 0; while (a[i] && a[i] == b[i]) i = i + 1; return a[i] == b[i]; } /* ---------- hash-chained log ---------- */ static int logh = -1; static int logseq; static char prevhash[65]; static void log_record(char *rec) { /* record line: rec + " h=" + sha256(prevhash + "|" + rec) */ char line[4200]; int n; int i; unsigned int w; char *hx; sha_init(); sha_data(prevhash, 64); sha_byte(124); sha_data(rec, slen(rec)); hx = sha_hex(); n = 0; i = 0; while (rec[i]) { line[n] = rec[i]; n = n + 1; i = i + 1; } line[n] = 32; line[n + 1] = 104; line[n + 2] = 61; n = n + 3; i = 0; while (i < 64) { line[n] = hx[i]; prevhash[i] = hx[i]; n = n + 1; i = i + 1; } line[n] = 13; line[n + 1] = 10; n = n + 2; if (logh != -1) { w = 0; WriteFile(logh, line, n, &w, 0); } } static void genesis(void) { int i; i = 0; while (i < 64) { prevhash[i] = 48; i = i + 1; } prevhash[64] = 0; logseq = 0; } /* event: prints the line on the console and appends it to the log */ static int t0; static void emit_event(void) { char rec[4200]; int i; int n; n = 0; { char *s; s = dec(logseq); i = 0; while (s[i]) { rec[n] = s[i]; n = n + 1; i = i + 1; } } rec[n] = 32; n = n + 1; { char *s; s = dec(GetTickCount64() - t0); i = 0; while (s[i]) { rec[n] = s[i]; n = n + 1; i = i + 1; } } rec[n] = 32; n = n + 1; i = 0; while (lb[i]) { rec[n] = lb[i]; n = n + 1; i = i + 1; } rec[n] = 0; out_n(rec, n); out("\r\n"); log_record(rec); logseq = logseq + 1; } static char sessdir[200]; static char logpath[260]; static int open_session(char *base) { char st[16]; int i; int n; char *s; CreateDirectoryA(base, 0); GetLocalTime(st); lb_reset(); lb_s(base); lb_s("\\session-"); lb_d((st[0] & 255) | ((st[1] & 255) << 8)); lb_pad2(st[2] & 255); lb_pad2(st[6] & 255); lb_s("-"); lb_pad2(st[8] & 255); lb_pad2(st[10] & 255); lb_pad2(st[12] & 255); lb_s("-p"); lb_d(GetCurrentProcessId()); i = 0; while (lb[i]) { sessdir[i] = lb[i]; i = i + 1; } sessdir[i] = 0; if (CreateDirectoryA(sessdir, 0) == 0) { out("cannot create a new session folder "); out(sessdir); out(" (error "); out(dec(GetLastError())); out(")\r\n"); return 0; } lb_s("\\log.txt"); i = 0; while (lb[i]) { logpath[i] = lb[i]; i = i + 1; } logpath[i] = 0; logh = CreateFileA(logpath, 1073741824, 1, 0, 1, 128, 0); if (logh == -1) { out("cannot create log file (error "); out(dec(GetLastError())); out(")\r\n"); return 0; } out("log: "); out(logpath); out("\r\n"); return 1; } /* ---------- processes ---------- */ static char pe[1024]; static int *npid; static int *nppid; static int *nhash; static char nname[8192][64]; static int ncount; static int *opid; static int *oppid; static int *ohash; static char oname[8192][64]; static int ocount; static int *shellpid; static int nshell; static int whash(char *w) { int h; int i; h = 5381; i = 0; while (i < 520) { int c; c = (w[i] & 255) | ((w[i + 1] & 255) << 8); if (c == 0) break; h = (h * 33 + c) & 0xFFFFFFFF; i = i + 2; } return h; } static int lowname_is(char *u, char *want) { int i; i = 0; while (want[i]) { int c; c = u[i] & 255; if (c >= 65 && c <= 90) c = c + 32; if (c != want[i]) return 0; i = i + 1; } return u[i] == 0; } static int is_shell(char *u) { return lowname_is(u, "cmd.exe") || lowname_is(u, "powershell.exe") || lowname_is(u, "pwsh.exe") || lowname_is(u, "bash.exe") || lowname_is(u, "wsl.exe") || lowname_is(u, "wslhost.exe") || lowname_is(u, "sh.exe"); } static int snap_procs(void) { int s; int ok; s = CreateToolhelp32Snapshot(2, 0); if (s == -1) return -1; ncount = 0; *(unsigned int *)pe = 568; ok = Process32FirstW(s, pe); while (ok && ncount < 8192) { int i; npid[ncount] = *(unsigned int *)(pe + 8); nppid[ncount] = *(unsigned int *)(pe + 32); nhash[ncount] = whash(pe + 44); lb_reset(); lb_w(pe + 44); i = 0; while (i < 63 && lb[i]) { nname[ncount][i] = lb[i]; i = i + 1; } nname[ncount][i] = 0; ncount = ncount + 1; ok = Process32NextW(s, pe); } CloseHandle(s); return ncount; } static int find_proc_new(int pid, int h) { int i; i = 0; while (i < ncount) { if (npid[i] == pid && nhash[i] == h) return i; i = i + 1; } return -1; } static int find_proc_old(int pid, int h) { int i; i = 0; while (i < ocount) { if (opid[i] == pid && ohash[i] == h) return i; i = i + 1; } return -1; } static void keep_procs(void) { int i; int j; i = 0; nshell = 0; while (i < ncount) { opid[i] = npid[i]; oppid[i] = nppid[i]; ohash[i] = nhash[i]; j = 0; while (j < 64) { oname[i][j] = nname[i][j]; j = j + 1; } if (is_shell(nname[i])) { shellpid[nshell] = npid[i]; nshell = nshell + 1; } i = i + 1; } ocount = ncount; } static int pid_is_shell(int pid) { int i; i = 0; while (i < nshell) { if (shellpid[i] == pid) return 1; i = i + 1; } return 0; } static char *name_of(int pid) { int i; i = 0; while (i < ncount) { if (npid[i] == pid) return nname[i]; i = i + 1; } return "?"; } static void diff_procs(void) { int i; i = 0; while (i < ncount) { if (find_proc_old(npid[i], nhash[i]) < 0) { lb_reset(); if (is_shell(nname[i])) lb_s("shell-start"); else lb_s("proc-start"); lb_s(" pid="); lb_d(npid[i]); lb_s(" ppid="); lb_d(nppid[i]); lb_s(" parent="); lb_s(name_of(nppid[i])); lb_s(" name="); lb_s(nname[i]); emit_event(); } i = i + 1; } i = 0; while (i < ocount) { if (find_proc_new(opid[i], ohash[i]) < 0) { lb_reset(); if (is_shell(oname[i])) lb_s("shell-exit"); else lb_s("proc-exit"); lb_s(" pid="); lb_d(opid[i]); lb_s(" name="); lb_s(oname[i]); emit_event(); } i = i + 1; } } /* ---------- IPv4 TCP and UDP endpoints ---------- */ static char *tbl; static int *nk1; static int *nk2; static int *nk3; static int *nst; static int *nown; static int *nproto; static int nconn; static int *ok1; static int *ok2; static int *ok3; static int *ost; static int *oown; static int *oproto; static int oconn; static int last_tcp_error; static int last_udp_error; static int port16(int p) { return ((p & 255) << 8) | ((p >> 8) & 255); } static int snap_conns(void) { unsigned int size; unsigned int r; int n; int i; char *row; nconn = 0; size = 1048576; r = GetExtendedTcpTable(tbl, &size, 1, 2, 5, 0); last_tcp_error = r; if (r == 0) { n = *(unsigned int *)tbl; i = 0; while (i < n && nconn < 16384) { row = tbl + 4 + 24 * i; nst[nconn] = *(unsigned int *)row; nk1[nconn] = *(unsigned int *)(row + 4); nk2[nconn] = (port16(*(unsigned int *)(row + 8)) << 16) | port16(*(unsigned int *)(row + 16)); nk3[nconn] = *(unsigned int *)(row + 12); nown[nconn] = *(unsigned int *)(row + 20); nproto[nconn] = 6; nconn = nconn + 1; i = i + 1; } } size = 1048576; r = GetExtendedUdpTable(tbl, &size, 1, 2, 1, 0); last_udp_error = r; if (r == 0) { n = *(unsigned int *)tbl; i = 0; while (i < n && nconn < 16384) { row = tbl + 4 + 12 * i; nst[nconn] = 0; nk1[nconn] = *(unsigned int *)row; nk2[nconn] = port16(*(unsigned int *)(row + 4)) << 16; nk3[nconn] = 0; nown[nconn] = *(unsigned int *)(row + 8); nproto[nconn] = 17; nconn = nconn + 1; i = i + 1; } } return nconn; } static char *tcp_state(int s) { if (s == 1) return "closed"; if (s == 2) return "listen"; if (s == 3) return "syn-sent"; if (s == 4) return "syn-rcvd"; if (s == 5) return "established"; if (s == 6) return "fin-wait1"; if (s == 7) return "fin-wait2"; if (s == 8) return "close-wait"; if (s == 9) return "closing"; if (s == 10) return "last-ack"; if (s == 11) return "time-wait"; if (s == 12) return "delete-tcb"; return "unknown"; } static void conn_line(char *kind, int proto, int a, int ports, int b, int st, int own) { lb_reset(); if (pid_is_shell(own)) lb_s("shell-"); lb_s(kind); if (proto == 6) lb_s(" tcp "); else lb_s(" udp "); lb_ip(a); lb_s(":"); lb_d((ports >> 16) & 65535); if (proto == 6) { lb_s(" -> "); lb_ip(b); lb_s(":"); lb_d(ports & 65535); lb_s(" state="); lb_s(tcp_state(st)); } lb_s(" pid="); lb_d(own); lb_s(" name="); lb_s(name_of(own)); emit_event(); } static void diff_conns(void) { int i; int j; int f; i = 0; while (i < nconn) { f = 0; j = 0; while (j < oconn) { if (ok1[j] == nk1[i] && ok2[j] == nk2[i] && ok3[j] == nk3[i] && oproto[j] == nproto[i] && oown[j] == nown[i]) { f = 1; if (ost[j] != nst[i]) { conn_line("conn-state", nproto[i], nk1[i], nk2[i], nk3[i], nst[i], nown[i]); } break; } j = j + 1; } if (!f) conn_line("conn-open", nproto[i], nk1[i], nk2[i], nk3[i], nst[i], nown[i]); i = i + 1; } i = 0; while (i < oconn) { f = 0; j = 0; while (j < nconn) { if (ok1[i] == nk1[j] && ok2[i] == nk2[j] && ok3[i] == nk3[j] && oproto[i] == nproto[j] && oown[i] == nown[j]) { f = 1; break; } j = j + 1; } if (!f) conn_line("conn-gone", oproto[i], ok1[i], ok2[i], ok3[i], ost[i], oown[i]); i = i + 1; } } static void keep_conns(void) { int i; i = 0; while (i < nconn) { ok1[i] = nk1[i]; ok2[i] = nk2[i]; ok3[i] = nk3[i]; ost[i] = nst[i]; oown[i] = nown[i]; oproto[i] = nproto[i]; i = i + 1; } oconn = nconn; } /* ---------- commands ---------- */ int WSAStartup(unsigned int version, char *data); int socket(int af, int type, int proto); int bind(int s, char *addr, int len); int listen(int s, int backlog); int getsockname(int s, char *addr, unsigned int *len); int closesocket(int s); static char wsa[1024]; static int own_listener_check(void) { char a[16]; unsigned int len; int s; int i; int port; int found; int me; if (WSAStartup(514, wsa) != 0) return 0; i = 0; while (i < 16) { a[i] = 0; i = i + 1; } a[0] = 2; a[4] = 127; a[7] = 1; s = socket(2, 1, 6); if (s == -1) return 0; if (bind(s, a, 16) != 0 || listen(s, 4) != 0) { closesocket(s); return 0; } len = 16; getsockname(s, a, &len); port = ((a[2] & 255) << 8) | (a[3] & 255); snap_conns(); me = GetCurrentProcessId(); found = 0; i = 0; while (i < nconn) { if (nproto[i] == 6 && nown[i] == me && ((nk2[i] >> 16) & 65535) == port && nst[i] == 2) found = 1; i = i + 1; } closesocket(s); return found; } static char *args[16]; static int argc; static char argbuf[4096]; static void split_args(void) { char *c; int i; int n; int q; c = GetCommandLineA(); i = 0; n = 0; argc = 0; while (c[i] && argc < 16 && n < 4000) { while (c[i] == 32 || c[i] == 9) i = i + 1; if (!c[i]) break; args[argc] = argbuf + n; argc = argc + 1; q = 0; while (c[i] && (q || (c[i] != 32 && c[i] != 9)) && n < 4000) { if (c[i] == 34) q = !q; else { argbuf[n] = c[i]; n = n + 1; } i = i + 1; } argbuf[n] = 0; n = n + 1; } } static int atoi_s(char *s) { int v; int i; v = 0; i = 0; while (s[i] >= 48 && s[i] <= 57) { v = v * 10 + s[i] - 48; i = i + 1; } if (i == 0 || s[i]) return -1; return v; } static void header(char *mode) { lb_reset(); lb_s("session-start mode="); lb_s(mode); lb_s(" pid="); lb_d(GetCurrentProcessId()); lb_s(" source=Toolhelp32,GetExtendedTcpTable,GetExtendedUdpTable ipv4-only polling"); emit_event(); } static int cmd_watch(int ticks, int interval) { int k; genesis(); t0 = GetTickCount64(); if (!open_session("shell-observer-logs")) return 2; header("watch"); if (snap_procs() < 0) { lb_reset(); lb_s("error process snapshot failed code="); lb_d(GetLastError()); emit_event(); return 3; } snap_conns(); keep_procs(); keep_conns(); lb_reset(); lb_s("baseline processes="); lb_d(ncount); lb_s(" endpoints="); lb_d(nconn); lb_s(" shells="); lb_d(nshell); lb_s(" tcp-call="); lb_d(last_tcp_error); lb_s(" udp-call="); lb_d(last_udp_error); emit_event(); k = 0; while (ticks < 0 || k < ticks) { Sleep(interval); if (snap_procs() >= 0) { snap_conns(); diff_procs(); diff_conns(); keep_procs(); keep_conns(); } k = k + 1; } lb_reset(); lb_s("session-end ticks="); lb_d(k); emit_event(); CloseHandle(logh); return 0; } /* verify: recompute every record's chain hash; report the first record that does not match */ static char *vbuf; static int vbad; static int verify_file(char *path, int quiet) { int h; unsigned int got; unsigned int hi; int size; int i; int start; int rec; char ph[65]; int bad; h = CreateFileA(path, 2147483648, 1, 0, 3, 128, 0); if (h == -1) { out("verify: cannot open "); out(path); out("\r\n"); return 2; } hi = 0; size = GetFileSize(h, &hi); if (size > 16777000 || hi != 0) { out("verify: file too large for this version\r\n"); CloseHandle(h); return 2; } got = 0; ReadFile(h, vbuf, size, &got, 0); CloseHandle(h); if ((int)got != size) { out("verify: short read\r\n"); return 2; } i = 0; while (i < 64) { ph[i] = 48; i = i + 1; } ph[64] = 0; rec = 0; start = 0; bad = -1; i = 0; while (i < size) { if (vbuf[i] == 10) { int e; int m; int j; char *hx; e = i; if (e > start && vbuf[e - 1] == 13) e = e - 1; m = e - 67; if (m < start || vbuf[m] != 32 || vbuf[m + 1] != 104 || vbuf[m + 2] != 61) { bad = rec; break; } sha_init(); sha_data(ph, 64); sha_byte(124); sha_data(vbuf + start, m - start); hx = sha_hex(); j = 0; while (j < 64) { if (hx[j] != vbuf[m + 3 + j]) break; j = j + 1; } if (j < 64) { bad = rec; break; } j = 0; while (j < 64) { ph[j] = hx[j]; j = j + 1; } rec = rec + 1; start = i + 1; } i = i + 1; } if (bad < 0 && start != size) bad = rec; vbad = bad; if (bad >= 0) { out("verify: FAILED at record "); out(dec(bad)); out(" (line "); out(dec(bad + 1)); out(")\r\n"); return 1; } if (!quiet) { out("verify: OK, "); out(dec(rec)); out(" records, last hash "); out_n(ph, 64); out("\r\n"); } return 0; } /* selftest: every check computes a real value and compares it to an expected value */ static int fails; static int checks; static void check(char *name, int okv) { checks = checks + 1; out(okv ? "ok " : "FAIL "); out(name); out("\r\n"); if (!okv) fails = fails + 1; } static char abuf[1000]; static int cmd_selftest(void) { int i; char si[104]; char pi[24]; int child; int idx; int found; int s1; char copy[260]; unsigned int got; int h; int w; unsigned int ww; int n; fails = 0; checks = 0; sha_init(); check("sha256 empty", streq(sha_hex(), "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")); sha_init(); sha_data("abc", 3); check("sha256 abc", streq(sha_hex(), "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad")); sha_init(); sha_data("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", 56); check("sha256 448-bit", streq(sha_hex(), "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1")); i = 0; while (i < 1000) { abuf[i] = 97; i = i + 1; } sha_init(); i = 0; while (i < 1000) { sha_data(abuf, 1000); i = i + 1; } check("sha256 million a", streq(sha_hex(), "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0")); sha_init(); sha_data("abd", 3); check("sha256 bit flip changes digest", !streq(sha_hex(), "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad")); s1 = snap_procs(); check("process snapshot", s1 > 0); found = 0; i = 0; while (i < ncount) { if (npid[i] == (int)GetCurrentProcessId()) found = 1; i = i + 1; } check("own process in snapshot", found); keep_procs(); i = 0; while (i < 104) { si[i] = 0; i = i + 1; } *(unsigned int *)si = 104; i = 0; while (i < 24) { pi[i] = 0; i = i + 1; } child = CreateProcessA(0, "cmd.exe /c exit 0", 0, 0, 0, 4, 0, 0, si, pi) != 0; check("start suspended child cmd.exe", child); if (child) { int cpid; cpid = *(unsigned int *)(pi + 16); snap_procs(); idx = -1; i = 0; while (i < ncount) { if (npid[i] == cpid) idx = i; i = i + 1; } check("child appears in next snapshot with its pid", idx >= 0 && find_proc_old(cpid, nhash[idx]) < 0); check("child is classed as a shell", idx >= 0 && is_shell(nname[idx])); TerminateProcess(*(int *)pi, 0); WaitForSingleObject(*(int *)pi, 5000); CloseHandle(*(int *)pi); CloseHandle(*(int *)(pi + 8)); keep_procs(); snap_procs(); found = 0; i = 0; while (i < ncount) { if (npid[i] == cpid) found = 1; i = i + 1; } check("child gone after it ends", !found); } n = snap_conns(); check("tcp table call returns 0", last_tcp_error == 0); check("udp table call returns 0", last_udp_error == 0); check("own 127.0.0.1 listener appears with own pid", own_listener_check()); /* log chain: write a session log, verify it, then write a copy with one byte changed and verify that it fails */ genesis(); t0 = GetTickCount64(); check("open new session log", open_session("shell-observer-selftest")); header("selftest"); lb_reset(); lb_s("selftest-record one"); emit_event(); lb_reset(); lb_s("selftest-record two"); emit_event(); lb_reset(); lb_s("selftest-record three"); emit_event(); CloseHandle(logh); logh = -1; check("saved log verifies", verify_file(logpath, 1) == 0); h = CreateFileA(logpath, 2147483648, 1, 0, 3, 128, 0); got = 0; ReadFile(h, vbuf, 16777000, &got, 0); CloseHandle(h); i = 0; w = 0; while (i < (int)got && w < 2) { if (vbuf[i] == 10) w = w + 1; i = i + 1; } vbuf[i + 2] = vbuf[i + 2] ^ 1; lb_reset(); lb_s(sessdir); lb_s("\\log-one-byte-changed.txt"); i = 0; while (lb[i]) { copy[i] = lb[i]; i = i + 1; } copy[i] = 0; h = CreateFileA(copy, 1073741824, 1, 0, 1, 128, 0); ww = 0; WriteFile(h, vbuf, got, &ww, 0); CloseHandle(h); out("expect a FAILED line for record 2 next:\r\n"); check("changed copy fails at record 2", verify_file(copy, 1) == 1 && vbad == 2); out(fails == 0 ? "SELFTEST PASS " : "SELFTEST FAILED "); out(dec(checks - fails)); out("/"); out(dec(checks)); out("\r\n"); return fails == 0 ? 0 : 1; } static void usage(void) { out("shell-observer: watches processes and IPv4 TCP/UDP endpoints through documented Windows calls.\r\n"); out(" shell-observer.exe watch until the window is closed (1000 ms polling)\r\n"); out(" shell-observer.exe watch -n 30 -i 500 watch 30 polls, 500 ms apart\r\n"); out(" shell-observer.exe selftest run the built-in checks\r\n"); out(" shell-observer.exe verify FILE check a saved log's hash chain\r\n"); out("Logs go to a new folder per session under shell-observer-logs. Nothing is ever overwritten or deleted.\r\n"); out("Limits: polling can miss processes and connections shorter than the interval; IPv4 only; no command lines of other processes; no keystrokes.\r\n"); } int main(void) { int ticks; int interval; int i; { char *m; int o; m = (char *)VirtualAlloc(0, 20971520, 12288, 4); if (m == 0) { out("cannot reserve working memory\r\n"); return 3; } o = 0; tbl = m; o = 1048576; vbuf = m + o; o = o + 16777216; npid = (int *)(m + o); o = o + 65536; nppid = (int *)(m + o); o = o + 65536; nhash = (int *)(m + o); o = o + 65536; opid = (int *)(m + o); o = o + 65536; oppid = (int *)(m + o); o = o + 65536; ohash = (int *)(m + o); o = o + 65536; shellpid = (int *)(m + o); o = o + 65536; nk1 = (int *)(m + o); o = o + 131072; nk2 = (int *)(m + o); o = o + 131072; nk3 = (int *)(m + o); o = o + 131072; nst = (int *)(m + o); o = o + 131072; nown = (int *)(m + o); o = o + 131072; nproto = (int *)(m + o); o = o + 131072; ok1 = (int *)(m + o); o = o + 131072; ok2 = (int *)(m + o); o = o + 131072; ok3 = (int *)(m + o); o = o + 131072; ost = (int *)(m + o); o = o + 131072; oown = (int *)(m + o); o = o + 131072; oproto = (int *)(m + o); o = o + 131072; } split_args(); if (argc >= 2 && streq(args[1], "selftest")) return cmd_selftest(); if (argc >= 3 && streq(args[1], "verify")) return verify_file(args[2], 0); if (argc >= 2 && (streq(args[1], "help") || streq(args[1], "-h") || streq(args[1], "/?"))) { usage(); return 0; } ticks = -1; interval = 1000; i = 1; if (argc >= 2 && streq(args[1], "watch")) i = 2; else if (argc >= 2) { usage(); return 2; } while (i < argc) { if (streq(args[i], "-n") && i + 1 < argc) { ticks = atoi_s(args[i + 1]); i = i + 2; } else if (streq(args[i], "-i") && i + 1 < argc) { interval = atoi_s(args[i + 1]); i = i + 2; } else { usage(); return 2; } } if (ticks < -1 || interval < 50 || interval > 3600000) { usage(); return 2; } return cmd_watch(ticks, interval); }