diff --git a/java/RunMe.java b/java/RunMe.java new file mode 100644 index 0000000..2de3e12 --- /dev/null +++ b/java/RunMe.java @@ -0,0 +1,603 @@ +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.time.Duration; +import java.time.Instant; +import java.util.*; +import java.util.concurrent.ConcurrentSkipListSet; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +/** + * Run this code with provided arguments. + * + * @author Georgiy Korneev (kgeorgiy@kgeorgiy.info) + */ +@SuppressWarnings("all") +public final class RunMe { + + private RunMe() { + // Utility class + } + + public static void main(final String[] args) { + final byte[] password = parseArgs(args); + + flag0(password); + System.out.println( + "The first flag was low-hanging fruit, can you find others?" + ); + System.out.println( + "Try to read, understand and modify code in flagX(...) functions" + ); + + flag1(password); + flag2(password); + flag3(password); + flag4(password); + flag5(password); + flag6(password); + flag7(password); + flag8(password); + flag9(password); + flag10(password); + flag12(password); + flag13(password); + flag14(password); + flag15(password); + flag16(password); + flag17(password); + flag18(password); + flag19(password); + flag20(password); + } + + private static void flag0(final byte[] password) { + // The result of print(...) function depends only on explicit arguments + print(0, 0, password); + } + + private static void flag1(final byte[] password) { + while ("true".length() == 4) {} + + print(1, -5204358702485720348L, password); + } + + private static void flag2(final byte[] password) { + int result = 0; + for (int i = 0; i < 300_000; i++) { + for (int j = 0; j < 300_000; j++) { + for (int k = 0; k < 300_000; k++) { + result ^= (i * 7) | (j + k); + result ^= result << 1; + } + } + } + + print(2, -3458723408232943523L, password); + } + + private static void flag3(final byte[] password) { + int result = 0; + for (int i = 0; i < 2025; i++) { + for (int j = 0; j < 2025; j++) { + for (int k = 0; k < 2025; k++) { + for (int p = 0; p < 12; p++) { + result ^= (i * 17) | ((j + k * 7) & ~p); + result ^= result << 1; + } + } + } + } + + print(3, result, password); + } + + private static void flag4(final byte[] password) { + final long target = 8504327508437503432L + getInt(password); + for (long i = 0; i < Long.MAX_VALUE; i++) { + if ((i ^ (i >>> 32)) == target) { + print(4, i, password); + } + } + } + + /* package-private */ static final long PRIME = 2025_2025_07; + + private static void flag5(final byte[] password) { + final long n = 1_000_000_000_000_000L + getInt(password); + + long result = 0; + for (long i = 0; i < n; i++) { + result = (result + i / 3 + i / 5 + i / 7 + i / 2025) % PRIME; + } + + print(5, result, password); + } + + private static void flag6(final byte[] password) { + /*** + \u002a\u002f\u0077\u0068\u0069\u006c\u0065\u0020\u0028\u0022\u0031\u0022 + \u002e\u006c\u0065\u006e\u0067\u0074\u0068\u0028\u0029\u0020\u003d\u003d + \u0020\u0031\u0029\u003b\u0020\u0020\u006c\u006f\u006e\u0067\u0020\u0009 + \u0020\u0020\u0072\u0065\u0073\u0075\u006c\u0074\u0020\u003d\u0020\u000a + \u0035\u0037\u0034\u0038\u0035\u0037\u0030\u0032\u0034\u0038\u0033\u004c + \u002b\u0070\u0061\u0073\u0073\u0077\u006f\u0072\u0064\u005b\u0035\u005d + \u002b\u0070\u0061\u0073\u0073\u0077\u006f\u0072\u0064\u005b\u0032\u005d + \u003b\u002f\u002a + ***/ + print(6, result, password); + } + + private static void flag7(final byte[] password) { + // Count the number of occurrences of the most frequent noun at the following page: + // https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html + + // The singular form of the most frequent noun + final String singular = ""; + // The plural form of the most frequent noun + final String plural = ""; + // The total number of occurrences + final int total = 0; + if (total != 0) { + print( + 7, + (singular + ":" + plural + ":" + total).hashCode(), + password + ); + } + } + + private static void flag8(final byte[] password) { + // Count the number of bluish (#5984A1) pixels of this image: + // https://dev.java/assets/images/java-affinity-logo-icode-lg.png + + final int number = 0; + if (number != 0) { + print(8, number, password); + } + } + + private static final String PATTERN = + "Reading the documentation can be surprisingly helpful!"; + private static final int SMALL_REPEAT_COUNT = 10_000_000; + + private static void flag9(final byte[] password) { + String repeated = ""; + for (int i = 0; i < SMALL_REPEAT_COUNT; i++) { + repeated += PATTERN; + } + + print(9, repeated.hashCode(), password); + } + + private static final long LARGE_REPEAT_SHIFT = 29; + private static final long LARGE_REPEAT_COUNT = 1L << LARGE_REPEAT_SHIFT; + + private static void flag10(final byte[] password) { + String repeated = ""; + for (long i = 0; i < LARGE_REPEAT_COUNT; i++) { + repeated += PATTERN; + } + + print(10, repeated.hashCode(), password); + } + + private static void flag11(final byte[] password) { + print(11, 5823470598324780581L, password); + } + + private static void flag12(final byte[] password) { + final BigInteger year = BigInteger.valueOf(-2025); + final BigInteger term = BigInteger.valueOf( + PRIME + (Math.abs(getInt(password)) % PRIME) + ); + + final long result = Stream.iterate(BigInteger.ZERO, BigInteger.ONE::add) + .filter( + i -> + year + .multiply(i) + .add(term) + .multiply(i) + .compareTo(BigInteger.TEN) > + 0 + ) + .mapToLong( + i -> i.longValue() * password[i.intValue() % password.length] + ) + .sum(); + + print(12, result, password); + } + + private static final long MAX_DEPTH = 100_000_000L; + + private static void flag13(final byte[] password) { + try { + flag13(password, 0, 0); + } catch (final StackOverflowError e) { + System.err.println("Stack overflow :(("); + } + } + + private static void flag13( + final byte[] password, + final long depth, + final long result + ) { + if (depth < MAX_DEPTH) { + flag13( + password, + depth + 1, + (result ^ PRIME) | ((result << 2) + depth * 17) + ); + } else { + print(13, result, password); + } + } + + private static void flag14(final byte[] password) { + final Instant today = Instant.parse("2025-09-09T12:00:00Z"); + final BigInteger hours = BigInteger.valueOf( + Duration.between(Instant.EPOCH, today).toHours() + + password[1] + + password[3] + ); + + final long result = Stream.iterate(BigInteger.ZERO, hours::add) + .reduce(BigInteger.ZERO, BigInteger::add) + .longValue(); + + print(14, result, password); + } + + private static void flag15(final byte[] password) { + // REDACTED + } + + private static void flag16(final byte[] password) { + byte[] a = { + (byte) (password[0] + password[3]), + (byte) (password[1] + password[4]), + (byte) (password[2] + password[5]), + }; + + for ( + long i = 1_000_000_000_000_000_000L + getInt(password); + i >= 0; + i-- + ) { + flag16Update(a); + } + + print(16, flag16Result(a), password); + } + + /* package-private */ static void flag16Update(byte[] a) { + a[0] ^= a[1]; + a[1] -= a[2] | a[0]; + a[2] *= a[0]; + } + + /* package-private */ static int flag16Result(byte[] a) { + return (a[0] + " " + a[1] + " " + a[2]).hashCode(); + } + + /** + * Original idea by Alexei Shishkin. + */ + private static void flag17(final byte[] password) { + final int n = Math.abs(getInt(password) % 2025) + 2025; + print(17, calc17(n), password); + } + + /** + * Write me + *
+     *    0: iconst_0
+     *    1: istore_1
+     *    2: iload_1
+     *    3: iload_1
+     *    4: imul
+     *    5: sipush        2025
+     *    8: idiv
+     *    9: iload_0
+     *   10: isub
+     *   11: ifge          20
+     *   14: iinc          1, 1
+     *   17: goto          2
+     *   20: iload_1
+     *   21: ireturn
+     * 
+ */ + private static int calc17(final int n) { + return n; + } + + private static void flag18(final byte[] password) { + final int n = 2025 + (getInt(password) % 2025); + // Find the number of factors of n! modulo PRIME + final int factors = 0; + if (factors != 0) { + print(18, factors, password); + } + } + + private static void flag19(final byte[] password) { + // Let n = 2025e25 + abs(getInt(password)). + // Consider the sequence of numbers (n + i) ** 2. + // Instead of each number, we write the number that is obtained from it by discarding the last 25 digits. + // How many of the first numbers of the resulting sequence will form an arithmetic progression? + final long result = 0; + if (result != 0) { + print(19, result, password); + } + } + + /** + * Original idea by Dmitrii Liapin. + */ + private static void flag20(final byte[] password) { + final Collection longs = new Random(getInt(password)) + .longs(2025_000) + .map(n -> n % 1000) + .boxed() + .collect(Collectors.toCollection(LinkedList::new)); + + // Calculate the number of objects (recursively) accessible by "longs" reference. + final int result = 0; + + if (result != 0) { + print(20, result, password); + } + } + + /** + * Original idea and implementation Igor Panasyuk. + */ + private static void flag21(final byte[] password) { + record Pair(int x, int y) {} + + final List items = new ArrayList<>( + Arrays.asList( + Byte.toUnsignedInt(password[0]), + (long) getInt(password), + new Pair(password[1], password[2]), + "Java SE 21 " + Arrays.toString(password) + ) + ); + + for (int round = 0; round < 10; round++) { + for (final Object item : List.copyOf(items)) { + // TODO: complete the switch expression using Java 21 features: + // items.add( + // case Integer i -> square of i as long + // case Long l and l is even -> l ^ 0x21L + // case Long l and l is odd -> -l + // case Pair(int x, int y) -> x << 8 ^ y + // case String s -> s.hashCode() + // default -> 0 + // ); + } + } + + long result = 0; + for (final Object item : items) { + result = result * 17 + item.toString().hashCode(); + } + + print(21, result, password); + } + + // --------------------------------------------------------------------------------------------------------------- + // You may ignore all code below this line. + // It is not required to get any of the flags. + // --------------------------------------------------------------------------------------------------------------- + + private static void print( + final int no, + long result, + final byte[] password + ) { + System.out.format( + "flag %d: https://www.kgeorgiy.info/courses/prog-intro/hw1/%s%n", + no, + flag(result, password) + ); + } + + /* package-private */ static String flag(long result, byte[] password) { + final byte[] flag = password.clone(); + for (int i = 0; i < 6; i++) { + flag[i] ^= result; + result >>>= 8; + } + + return flag(flag); + } + + /* package-private */ static String flag(final byte[] data) { + final MessageDigest messageDigest = RunMe.DIGEST.get(); + messageDigest.update(SALT); + messageDigest.update(data); + messageDigest.update(SALT); + final byte[] digest = messageDigest.digest(); + + return IntStream.range(0, 6) + .map( + i -> + (((digest[i * 2] & 255) << 8) + (digest[i * 2 + 1] & 255)) % + KEYWORDS.size() + ) + .mapToObj(KEYWORDS::get) + .collect(Collectors.joining("-")); + } + + /* package-private */ static byte[] parseArgs(final String[] args) { + if (args.length != 6) { + throw error( + "Expected 6 command line arguments, found: %d", + args.length + ); + } + + final byte[] bytes = new byte[args.length]; + for (int i = 0; i < args.length; i++) { + final Byte value = VALUES.get(args[i].toLowerCase(Locale.US)); + if (value == null) { + throw error("Expected keyword, found: %s", args[i]); + } + bytes[i] = value; + } + return bytes; + } + + private static AssertionError error( + final String format, + final Object... args + ) { + System.err.format(format, args); + System.err.println(); + System.exit(1); + throw new AssertionError(); + } + + /* package-private */ static int getInt(byte[] password) { + return IntStream.range(0, password.length) + .map(i -> password[i]) + .reduce((a, b) -> a * KEYWORDS.size() + b) + .getAsInt(); + } + + private static final ThreadLocal DIGEST = + ThreadLocal.withInitial(() -> { + try { + return MessageDigest.getInstance("SHA-256"); + } catch (final NoSuchAlgorithmException e) { + throw new AssertionError("Cannot create SHA-256 digest", e); + } + }); + + public static final byte[] SALT = + "fathdufimmonJiajFik3JeccafdaihoFrusthys9".getBytes( + StandardCharsets.US_ASCII + ); + + private static final List KEYWORDS = List.of( + "abstract", + "assert", + "boolean", + "break", + "byte", + "case", + "catch", + "char", + "class", + "const", + "new", + "package", + "private", + "protected", + "public", + "return", + "short", + "static", + "strictfp", + "super", + "for", + "goto", + "if", + "implements", + "import", + "instanceof", + "int", + "interface", + "long", + "native", + "continue", + "default", + "do", + "double", + "else", + "enum", + "extends", + "final", + "finally", + "float", + "switch", + "synchronized", + "this", + "throw", + "throws", + "transient", + "try", + "void", + "volatile", + "while", + "record", + "Error", + "AssertionError", + "OutOfMemoryError", + "StackOverflowError", + "ArrayIndexOutOfBoundsException", + "ArrayStoreException", + "AutoCloseable", + "Character", + "CharSequence", + "ClassCastException", + "Comparable", + "Exception", + "IllegalArgumentException", + "IllegalStateException", + "IndexOutOfBoundsException", + "Integer", + "Iterable", + "Math", + "Module", + "NegativeArraySizeException", + "NullPointerException", + "Number", + "NumberFormatException", + "Object", + "Override", + "RuntimeException", + "StrictMath", + "String", + "StringBuilder", + "StringIndexOutOfBoundsException", + "SuppressWarnings", + "System", + "Thread", + "Throwable", + "ArithmeticException", + "ClassLoader", + "ClassNotFoundException", + "Cloneable", + "Deprecated", + "FunctionalInterface", + "InterruptedException", + "Process", + "ProcessBuilder", + "Runnable", + "SafeVarargs", + "StackTraceElement", + "Runtime", + "ThreadLocal", + "UnsupportedOperationException" + ); + + private static final Map VALUES = IntStream.range( + 0, + KEYWORDS.size() + ) + .boxed() + .collect( + Collectors.toMap( + index -> KEYWORDS.get(index).toLowerCase(Locale.US), + Integer::byteValue + ) + ); +}