icoa-cli 2.19.75 → 2.19.77
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/repl.js +63 -1
- package/package.json +1 -1
package/dist/repl.js
CHANGED
|
@@ -17,6 +17,55 @@ import { startLogSync, stopLogSync } from './lib/log-sync.js';
|
|
|
17
17
|
import { existsSync, mkdirSync } from 'node:fs';
|
|
18
18
|
import { join } from 'node:path';
|
|
19
19
|
import { homedir } from 'node:os';
|
|
20
|
+
// Pre-import commonly-used CTF modules on interactive Python startup.
|
|
21
|
+
// Failures are silent — selectors import only what's available so a partial
|
|
22
|
+
// exam setup doesn't crash the shell. Creates ~/.icoa/python-startup.py once.
|
|
23
|
+
const PYTHON_STARTUP_CONTENT = `# ICOA exam interactive startup — auto-loaded by PYTHONSTARTUP
|
|
24
|
+
import base64, struct, hashlib, re, json, os, sys, binascii
|
|
25
|
+
try: import requests
|
|
26
|
+
except ImportError: pass
|
|
27
|
+
try: from Crypto.Cipher import AES
|
|
28
|
+
except ImportError: pass
|
|
29
|
+
try: from Crypto.Util.Padding import pad, unpad
|
|
30
|
+
except ImportError: pass
|
|
31
|
+
try: from pwn import xor, p32, u32, p64, u64
|
|
32
|
+
except ImportError: pass
|
|
33
|
+
try: import bs4
|
|
34
|
+
except ImportError: pass
|
|
35
|
+
try: import numpy as np
|
|
36
|
+
except ImportError: pass
|
|
37
|
+
`;
|
|
38
|
+
function ensurePythonStartup() {
|
|
39
|
+
const icoaDir = join(homedir(), '.icoa');
|
|
40
|
+
if (!existsSync(icoaDir))
|
|
41
|
+
mkdirSync(icoaDir, { recursive: true });
|
|
42
|
+
const path = join(icoaDir, 'python-startup.py');
|
|
43
|
+
if (!existsSync(path)) {
|
|
44
|
+
const { writeFileSync } = require('node:fs');
|
|
45
|
+
writeFileSync(path, PYTHON_STARTUP_CONTENT);
|
|
46
|
+
}
|
|
47
|
+
return path;
|
|
48
|
+
}
|
|
49
|
+
function printPythonBanner() {
|
|
50
|
+
console.log();
|
|
51
|
+
console.log(chalk.cyan(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
52
|
+
console.log(chalk.bold.white(' Python ready — ICOA exam toolkit pre-loaded'));
|
|
53
|
+
console.log(chalk.cyan(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
54
|
+
console.log();
|
|
55
|
+
console.log(chalk.white(' Already imported: ') + chalk.gray('base64, struct, hashlib, re, json, binascii'));
|
|
56
|
+
console.log(chalk.white(' Also available: ') + chalk.gray('requests, bs4, numpy, AES, pad/unpad, xor, p32/u32/p64/u64'));
|
|
57
|
+
console.log();
|
|
58
|
+
console.log(chalk.yellow(' Quick examples:'));
|
|
59
|
+
console.log(chalk.gray(' base64.b64decode("aGVsbG8=") ') + chalk.gray('# decode base64'));
|
|
60
|
+
console.log(chalk.gray(' bytes.fromhex("48656c6c6f") ') + chalk.gray('# hex → bytes'));
|
|
61
|
+
console.log(chalk.gray(' "ICOA{x}".encode() ') + chalk.gray('# str → bytes'));
|
|
62
|
+
console.log(chalk.gray(' [chr(c) for c in [73,67,79,65]] ') + chalk.gray('# ASCII codes'));
|
|
63
|
+
console.log(chalk.gray(' xor(bytes.fromhex("0a2b"), b"IC") ') + chalk.gray('# pwntools XOR'));
|
|
64
|
+
console.log();
|
|
65
|
+
console.log(chalk.gray(' Exit: ') + chalk.white('exit()') + chalk.gray(' or Ctrl-D'));
|
|
66
|
+
console.log(chalk.cyan(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
67
|
+
console.log();
|
|
68
|
+
}
|
|
20
69
|
// Compute the REPL prompt based on current state.
|
|
21
70
|
// Real exam wins over demo (matches getExamState() priority). Chat modes have
|
|
22
71
|
// their own prompts and are handled separately.
|
|
@@ -706,8 +755,11 @@ export async function startRepl(program, resumeMode) {
|
|
|
706
755
|
const organizerCommands = ['join', 'exam', 'demo', 'retry', 'next', 'prev', 'logout', 'setup', 'lang', 'ref', 'ctf', 'mark', 'unmark', 'review', 'submit'];
|
|
707
756
|
// Selection mode allows shell commands prefixed with ! — contestants
|
|
708
757
|
// need !python3, !cat, !base64 etc. for practical questions (Q31-Q40).
|
|
758
|
+
// Also allow bare `python` / `python3` / `python3.12` since contestants
|
|
759
|
+
// naturally type that when they want interactive Python.
|
|
709
760
|
// BLOCKED_COMMANDS check downstream still rejects dangerous ones.
|
|
710
|
-
const
|
|
761
|
+
const isPythonCmd = /^python3?(\.\d+)?$/.test(cmd);
|
|
762
|
+
const isShellCommand = input.startsWith('!') || cmd.startsWith('!') || isPythonCmd;
|
|
711
763
|
if (mode === 'selection' && !isShellCommand && !selectionCommands.includes(cmd)) {
|
|
712
764
|
console.log(chalk.gray(' Not available in Selection mode.'));
|
|
713
765
|
if (examState) {
|
|
@@ -792,6 +844,16 @@ export async function startRepl(program, resumeMode) {
|
|
|
792
844
|
}
|
|
793
845
|
// Ensure workspace directory
|
|
794
846
|
const cwd = ensureWorkspace();
|
|
847
|
+
// Interactive Python (no script args, no -c): print welcome banner and
|
|
848
|
+
// pre-import common CTF modules via a startup file. Makes entry painless
|
|
849
|
+
// for contestants — they land in a shell with base64/struct/hashlib/pwn
|
|
850
|
+
// already imported and see a 4-line cheat sheet of common one-liners.
|
|
851
|
+
const isInteractivePython = /^(\S*python3?(\.\d+)?)\s*$/.test(resolvedInput);
|
|
852
|
+
if (isInteractivePython) {
|
|
853
|
+
const startupPath = ensurePythonStartup();
|
|
854
|
+
resolvedInput = `PYTHONSTARTUP="${startupPath}" ${resolvedInput}`;
|
|
855
|
+
printPythonBanner();
|
|
856
|
+
}
|
|
795
857
|
// Route to Docker sandbox if available, otherwise system shell (in workspace)
|
|
796
858
|
processing = true;
|
|
797
859
|
try {
|