🐺WolvCTF 2024

Writeups for challenges I solved in team Project Sekai

Beginner

Web: The Gauntlet (319 solves)

Problem:

Can you survive the gauntlet?

10 mini web challenges are all that stand between you and the flag.

Note: Automated tools like sqlmap and dirbuster are not allowed (and will not be helpful anyway).

https://gauntlet-okntin33tq-ul.a.run.app

Solution:

OSINT: Redditor (416 solves)

Problem:

Someone told me WolvSec has a Reddit account. I wonder if they left a flag there...

Solution:

Forensics: Hidden Data (418 solves)

Problem:

WOLPHV sent me this file. Not sure what to comment about it

Solution:

Rev: babyre (348 solves)

Problem:

Just a wee-little baby re challenge.

Solution:

Problem:

We encoded a flag, and to make sure that pesky interlopers couldn't reverse it, we shredded the encoding code.

Note: The encoder was written in C. The code is written with good style, but all indents have been removed.

Solution:

The shredded files are vertical columns of shredder.c, we can manually piece them together since we know the file starts with #include<stdio.h>, so we grep for each desired character and find which shredded file has that letter on line 1, then do the same for the longer lines afterwards.

def load_and_concatenate_shreds(shred_indices):
    shreds = []
    for index in shred_indices:
        with open(f"shred{index}.txt", 'r') as file:
            # Read the content and split into lines
            content = file.readlines()
            # Remove newline characters and add to the list
            shreds.append([line.strip() for line in content])

    # Initialize a list to hold the concatenated lines
    concatenated_lines = []

    # Assume all shreds have the same number of lines for simplicity
    for line_index in range(len(shreds[0])):
        concatenated_line = ''.join(shreds[shred_index][line_index] for shred_index in range(len(shreds)))
        concatenated_lines.append(concatenated_line)

    # Return the concatenated lines as a single string
    return '\n'.join(concatenated_lines)

# Specify the indices of the shreds you want to concatenate
shred_indices = [2, 4, 18, 31, 19, 21, 13, 5, 12, 30, 27, 28, 25, 9, 16, 6, 26, 24, 17, 29, 11, 14, 1, 3, 15, 7, 32, 0, 20, 23, 10, 8, 22]
concatenated_content = load_and_concatenate_shreds(shred_indices)

print(concatenated_content)
#include<stdio.h>
#include<string.h>
intmain(){
charflag[]="REDACTED";
charinter[51];
intlen=strlen(flag);
for(inti=0;i<len;i++){
inter[i]=flag[i];
}
for(inti=len;i<50;i++){
inter[i]=inter[(i*2)%len];
}
inter[50]='\0';
chara;
for(inti=0;i<50;i++){
a=inter[i];
inter[i]=inter[((i+7)*15)%50];
inter[((i+7)*15)%50]=a;
}
for(inti=0;i<50;i++){
a=inter[i];
inter[i]=inter[((i+3)*7)%50];
inter[((i+3)*7)%50]=a;
}
for(inti=0;i<50;i++){
inter[i]=inter[i]^0x20;
inter[i]=inter[i]^0x5;
}
for(inti=0;i<50;i++){
a=inter[i];
inter[i]=inter[((i+83)*12)%50];
inter[((i+83)*12)%50]=a;
}
for(inti=0;i<50;i++){
printf("\\x%X",inter[i]);
}
return0;
}
// Cleaned up, added 50 characters to encrypt to know mapping
#include <stdio.h>
#include <string.h>

int main() {
    char flag[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwx";
    char inter[51];
    int len = strlen(flag);

    // Copy flag to inter
    for (int i = 0; i < len; i++) {
        inter[i] = flag[i];
    }

    // Extend inter to 50 characters
    for (int i = len; i < 50; i++) {
        inter[i] = inter[(i * 2) % len];
    }
    inter[50] = '\0';

    char a;

    // First shuffle
    for (int i = 0; i < 50; i++) {
        a = inter[i];
        inter[i] = inter[((i + 7) * 15) % 50];
        inter[((i + 7) * 15) % 50] = a;
    }

    // Second shuffle
    for (int i = 0; i < 50; i++) {
        a = inter[i];
        inter[i] = inter[((i + 3) * 7) % 50];
        inter[((i + 3) * 7) % 50] = a;
    }

    // Bitwise XOR operations
    for (int i = 0; i < 50; i++) {
        inter[i] = inter[i] ^ 0x20;
        inter[i] = inter[i] ^ 0x5;
    }

    // Third shuffle
    for (int i = 0; i < 50; i++) {
        a = inter[i];
        inter[i] = inter[((i + 83) * 12) % 50];
        inter[((i + 83) * 12) % 50] = a;
    }

    // Print the encrypted string
    for (int i = 0; i < 50; i++) {
        printf("\\x%X", inter[i]);
    }

    return 0;
}
// \x54\x53\x7F\x50\x76\x61\x67\x47\x63\x41\x6D\x5D\x68\x48\x4C\x69\x7D\x52\x46\x71\x43\x55\x42\x4F\x64\x6C\x4E\x6B\x62\x60\x6E\x6A\x57\x40\x6F\x73\x72\x4B\x77\x75\x74\x51\x70\x66\x7C\x56\x49\x4D\x44\x4A
// Reverse XOR for ciphertext, output is flag scrambled
#include <stdio.h>

int main() {
    // Ciphertext
    unsigned char encrypted[] = {0x14, 0x5D, 0x14, 0x57, 0x16, 0x43, 0x46, 0x7A, 0x56, 0x16, 0x57, 0x17, 0x4B, 0x16, 0x52, 0x4C, 0x61, 0x1C, 0x1C, 0x7A, 0x1D, 0x7A, 0x11, 0x51, 0x52, 0x16, 0x5E, 0x62, 0x6D, 0x5E, 0x61, 0x7A, 0x16, 0x17, 0x61, 0x16, 0x6B, 0x61, 0x4E, 0x69, 0x14, 0x6B, 0x6D, 0x51, 0x57, 0x6D, 0x6D, 0x58, 0x5D, 0x4B};
    int len = sizeof(encrypted) / sizeof(encrypted[0]);

    for(int i = 0; i < len; i++){
        encrypted[i] = encrypted[i] ^ 0x5;
        encrypted[i] = encrypted[i] ^ 0x20;
        printf("%c", encrypted[i]);
    }

    return 0;
}
// 1x1r3fc_s3r2n3wiD99_8_4tw3{GH{D_32D3NDkL1NHtrHH}xn
# Final decryption with the above pieces
def reverse_xor(encrypted_bytes):
    # Reverse the XOR operations applied during encryption
    return bytes([b ^ 0x5 ^ 0x20 for b in encrypted_bytes])

# The hex representation of the shuffled data
shuffled_hex = "54537F507661674763416D5D68484C697D5246714355424F646C4E6B62606E6A57406F73724B7775745170667C56494D444A"
shuffled_bytes = bytes.fromhex(shuffled_hex)

# Reverse XOR to get the decrypted output in bytes
decrypted_bytes = reverse_xor(shuffled_bytes)

# Convert decrypted bytes to a string (decrypted output)
decrypted_output = decrypted_bytes.decode('latin1')

# Original input sequence for mapping
original_sequence = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwx"

# Establish the mapping based on original and decrypted outputs
positions = [decrypted_output.index(char) for char in original_sequence]

# Encrypted message to be decrypted (final adjustment as per your instruction)
encrypted_message = "1x1r3fc_s3r2n3wiD99_8_4tw3{GH{D_32D3NDkL1NHtrHH}xn"

# Decrypt the encrypted message using the established positions
unshuffled_message = "".join(encrypted_message[positions[i]] for i in range(50))

# Output the decrypted message
print("Decrypted message:", unshuffled_message)

# wctf{sHr3DDinG_L1k3_H3NDr1x_93284}wt{H3Dn_13HNrx92

Misc

Made Sense (250 solves)

Problem:

i couldn't log in to my server so my friend kindly spun up a server to let me test makefiles. at least, they thought i couldn't log in :P

https://madesense-okntin33tq-ul.a.run.app

Solution:

Almost no filtering, we can run commands if we put them in the makefile directly.

Made Functional (128 solves)

Problem:

the second makejail

https://madefunctional-okntin33tq-ul.a.run.app

Solution:

Don't have path, can't use cat. Still have source.

Made Harder (68 solves)

Problem:

the third makejail

https://madeharder-okntin33tq-ul.a.run.app

Solution:

Payload needs to be made of specific characters now.

We have access to cat again though since this is diff'd from sense. We get cat from the target and the filename from the dependency.

Made With Love (57 solves)

Problem:

the final makejail

https://madewithlove-okntin33tq-ul.a.run.app

Solution:

Payload needs to be special characters, no cat.

UnholyFile (10 solves)

Problem:

It's an unholy file cast out from a holy land.

Maybe try out UnholyEXE first.

Solution:

We're given a binary file which seems like all FF in a hex viewer, although after searching through it, there are block of zeros in the middle. The file size is 3145745 which factors to 2^20 * 3. Based on these two things, my initial thought already is this is a 1024x1024 RGB image with the flag written in black on a white background. The simplest image header for raw image data is PPM, P6 to specify color and then width, height, and component depth.

We can see the flag so we know this is the right direction, but it's hard to read and it seems like there's some repetition. After playing around with the width and changing it to grayscale PGM with P5 instead of P6, we get the flag.

Rev

Problem:

The notorious WOLPHV group has re-emerged and doubledelete is now ransoming us for our flags! Can you help us so we don't have to pay them?

Solution:

// Ghidra decompiled output of encryption binary
undefined8 main(int param_1,undefined8 *param_2)

{
  uint uVar1;
  undefined8 uVar2;
  FILE *pFVar3;
  uint *puVar4;
  long in_FS_OFFSET;
  int local_64;
  uint local_48 [14];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  if (param_1 == 3) {
    pFVar3 = fopen((char *)param_2[1],"r");
    fread(local_48,1,0x30,pFVar3);
    for (local_64 = 0; local_64 < 0xc; local_64 = local_64 + 1) {
      puVar4 = (uint *)((long)local_48 + (long)(local_64 << 2));
      uVar1 = *puVar4;
      *puVar4 = uVar1 << 0xd | uVar1 >> 0x13;
    }
    pFVar3 = fopen((char *)param_2[2],"wb");
    fwrite(local_48,1,0x30,pFVar3);
    uVar2 = 0;
  }
  else {
    printf("[wolphvlog] usage: %s <infile> <ofile>",*param_2);
    uVar2 = 1;
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    // WARNING: Subroutine does not return
    __stack_chk_fail();
  }
  return uVar2;
}

The decompiled code shows the encryption works by first opening a file, reads 0x30 (48) bytes, and then for every 4 bytes (32 bits), it concatenates two copies of it, one shifted to the left 13 and one shifted to the right 19 (which basically equals the value rotated 13 to the left), and then writes it to a file. Simple enough to decrypt.

# Rotate bits to the right
def ror(value, bits, bit_size=32):
    return ((value >> bits) | (value << (bit_size - bits))) & ((1 << bit_size) - 1)

def decode_file(input_file_path):
    with open(input_file_path, "rb") as encrypted_file:
        encrypted_data = encrypted_file.read()

    decoded_data = bytearray()
    for i in range(0, len(encrypted_data), 4):
        segment = int.from_bytes(encrypted_data[i:i+4], byteorder='little')
        decoded_segment = ror(segment, 13)
        decoded_data.extend(decoded_segment.to_bytes(4, byteorder='little'))
    
    return decoded_data.decode('utf-8')

input_file_path = "./flag.txt.enc"
decrypted_content = decode_file(input_file_path)
print(decrypted_content)

# wctf{i_th1nk_y0u_m1sund3rst00d_h0w_r0t13_w0rk5}

Palworld (2 solves)

Problem:

We have Palworld at home. Palworld at home:

Solution:

We're given a PLD file defining digital logic. Displaying below for convenience, trimmed out repetitive parts.

Name PALWORLD ;
Partno 00 ;
Date Mar 2024;
Revision 01;
Designer HCADAM;
Company WCTF;
Assembly None;
Location ;
Device ;

/* WE HAVE PALWORLD AT HOME. PALWORLD AT HOME: */

PIN    = clk;
PIN    = reset;
PIN    = flag_in0;
PIN    = flag_in1;
PIN    = flag_in2;
PIN    = flag_in3;
PIN    = flag_in4;
PIN    = flag_in5;
PIN    = flag_in6;
PIN    = flag_in7;
PIN    = flag_ok;

PINNODE      = JTCN294;
PINNODE      = JTCN295;
...
PINNODE      = JTCN368;
PINNODE      = JTCN369;

PINNODE      = AGEB0;
PINNODE      = AGEB1;
PINNODE      = AGEB2;
PINNODE      = AGEB3;
PINNODE      = AGEB4;

PINNODE      = SDFK0;
PINNODE      = SDFK1;
...
PINNODE      = SDFK254;
PINNODE      = SDFK255;

PINNODE      = SWTE0;
PINNODE      = SWTE1;
...
PINNODE      = SWTE14;
PINNODE      = SWTE15;

flag_ok = ( ( ( ( ( JTCN294 & ! ( AGEB1 ) ) & ( ( ! ( SDFK2 ) & ! ( SDFK4
  ) ) & ( ! ( SDFK8 ) & ! ( SDFK9 ) ) ) ) & ( ( ( ( ! ( SDFK15 ) & ! (
  SDFK17 ) ) & ( ! ( SDFK18 ) & ! ( SDFK20 ) ) ) & ( ( ! ( SDFK10
  ) & ! ( SDFK11 ) ) & ( ! ( SDFK13 ) & ! ( SDFK14 ) ) ) ) & ( ( ( ! (
  SDFK32 ) & ! ( SDFK34 ) ) & ( ! ( SDFK36 ) & ! ( SDFK37 ) ) ) &
  ( ( ! ( SDFK24 ) & ! ( SDFK28 ) ) & ( ! ( SDFK29 ) & ! ( SDFK31
  ) ) ) ) ) ) & ( ( ( ( ( ( ! ( SDFK76 ) & ! ( SDFK77 ) ) & ( ! (
  SDFK79 ) & ! ( SDFK83 ) ) ) & ( ( ! ( SDFK66 ) & ! ( SDFK67 ) )
  & ( ! ( SDFK68 ) & ! ( SDFK69 ) ) ) ) & ( ( ( ! ( SDFK88 ) & ! (
  SDFK89 ) ) & ( ! ( SDFK90 ) & ! ( SDFK91 ) ) ) & ( ( ! ( SDFK84
  ) & ! ( SDFK85 ) ) & ( ! ( SDFK86 ) & ! ( SDFK87 ) ) ) ) ) & ( ( ( (
  ! ( SDFK49 ) & ! ( SDFK50 ) ) & ( ! ( SDFK51 ) & ! ( SDFK52 ) )
  ) & ( ( ! ( SDFK43 ) & ! ( SDFK45 ) ) & ( ! ( SDFK47 ) & ! (
  SDFK48 ) ) ) ) & ( ( ( ! ( SDFK59 ) & ! ( SDFK60 ) ) & ( ! (
  SDFK63 ) & ! ( SDFK64 ) ) ) & ( ( ! ( SDFK53 ) & ! ( SDFK54 ) )
  & ( ! ( SDFK55 ) & ! ( SDFK57 ) ) ) ) ) ) & ( ( ( ( ( ! ( SDFK143 )
  & ! ( SDFK144 ) ) & ( ! ( SDFK145 ) & ! ( SDFK148 ) ) ) & ( ( ! (
  SDFK129 ) & ! ( SDFK133 ) ) & ( ! ( SDFK135 ) & ! ( SDFK140 ) )
  ) ) & ( ( ( ! ( SDFK156 ) & ! ( SDFK158 ) ) & ( ! ( SDFK159 ) & ! (
  SDFK160 ) ) ) & ( ( ! ( SDFK150 ) & ! ( SDFK151 ) ) & ( ! (
  SDFK153 ) & ! ( SDFK155 ) ) ) ) ) & ( ( ( ( ! ( SDFK99 ) & ! (
  SDFK101 ) ) & ( ! ( SDFK102 ) & ! ( SDFK110 ) ) ) & ( ( ! (
  SDFK92 ) & ! ( SDFK93 ) ) & ( ! ( SDFK95 ) & ! ( SDFK96 ) ) ) )
  & ( ( ( ! ( SDFK124 ) & ! ( SDFK125 ) ) & ( ! ( SDFK126 ) & ! (
  SDFK128 ) ) ) & ( ( ! ( SDFK112 ) & ! ( SDFK120 ) ) & ( ! (
  SDFK121 ) & ! ( SDFK123 ) ) ) ) ) ) ) ) & ( ( ( ( ( ( SDFK229 &
  SDFK231 ) & ( SDFK232 & SDFK234 ) ) & ( ( SDFK224 & SDFK225
  ) & ( SDFK226 & SDFK228 ) ) ) & ( ( SDFK1 & ( SDFK255 &
  SDFK0 ) ) & ( ( SDFK238 & SDFK243 ) & ( SDFK245 & SDFK246 )
  ) ) ) & ( ( ( ( ( SDFK205 & SDFK208 ) & ( SDFK209 & SDFK210 ) )
  & ( ( SDFK189 & SDFK196 ) & ( SDFK201 & SDFK203 ) ) ) & ( ( (
  SDFK215 & SDFK216 ) & ( SDFK220 & SDFK223 ) ) & ( ( SDFK211
  & SDFK212 ) & ( SDFK213 & SDFK214 ) ) ) ) & ( ( ( ( SDFK169 &
  SDFK172 ) & ( SDFK173 & SDFK177 ) ) & ( ( SDFK157 & SDFK161
  ) & ( SDFK165 & SDFK168 ) ) ) & ( ( ( SDFK184 & SDFK185 ) & (
  SDFK186 & SDFK188 ) ) & ( ( SDFK179 & SDFK180 ) & ( SDFK181
  & SDFK182 ) ) ) ) ) ) & ( ( ( ( ( ( ( SDFK72 & SDFK73 ) & (
  SDFK74 & SDFK75 ) ) & ( ( SDFK62 & SDFK65 ) & ( SDFK70 &
  SDFK71 ) ) ) & ( ( ( SDFK94 & SDFK97 ) & ( SDFK98 & SDFK100
  ) ) & ( ( SDFK78 & SDFK80 ) & ( SDFK81 & SDFK82 ) ) ) ) & ( ( (
  ( SDFK33 & SDFK35 ) & ( SDFK38 & SDFK39 ) ) & ( ( SDFK25 &
  SDFK26 ) & ( SDFK27 & SDFK30 ) ) ) & ( ( ( SDFK46 & SDFK56 )
  & ( SDFK58 & SDFK61 ) ) & ( ( SDFK40 & SDFK41 ) & ( SDFK42 &
  SDFK44 ) ) ) ) ) & ( ( ( ( ( SDFK134 & SDFK136 ) & ( SDFK137 &
  SDFK138 ) ) & ( ( SDFK127 & SDFK130 ) & ( SDFK131 & SDFK132
  ) ) ) & ( ( ( SDFK147 & SDFK149 ) & ( SDFK152 & SDFK154 ) ) & (
  ( SDFK139 & SDFK141 ) & ( SDFK142 & SDFK146 ) ) ) ) & ( ( ( (
  SDFK107 & SDFK108 ) & ( SDFK109 & SDFK111 ) ) & ( ( SDFK103
  & SDFK104 ) & ( SDFK105 & SDFK106 ) ) ) & ( ( ( SDFK117 &
  SDFK118 ) & ( SDFK119 & SDFK122 ) ) & ( ( SDFK113 & SDFK114
  ) & ( SDFK115 & SDFK116 ) ) ) ) ) ) & ( ( ( ( ( ( ! ( SDFK198 ) & !
  ( SDFK199 ) ) & ( ! ( SDFK200 ) & ! ( SDFK202 ) ) ) & ( ( ! (
  SDFK193 ) & ! ( SDFK194 ) ) & ( ! ( SDFK195 ) & ! ( SDFK197 ) )
  ) ) & ( ( ( ! ( SDFK218 ) & ! ( SDFK219 ) ) & ( ! ( SDFK221 ) & ! (
  SDFK222 ) ) ) & ( ( ! ( SDFK204 ) & ! ( SDFK206 ) ) & ( ! (
  SDFK207 ) & ! ( SDFK217 ) ) ) ) ) & ( ( ( ( ! ( SDFK167 ) & ! (
  SDFK170 ) ) & ( ! ( SDFK171 ) & ! ( SDFK174 ) ) ) & ( ( ! (
  SDFK162 ) & ! ( SDFK163 ) ) & ( ! ( SDFK164 ) & ! ( SDFK166 ) )
  ) ) & ( ( ( ! ( SDFK187 ) & ! ( SDFK190 ) ) & ( ! ( SDFK191 ) & ! (
  SDFK192 ) ) ) & ( ( ! ( SDFK175 ) & ! ( SDFK176 ) ) & ( ! (
  SDFK178 ) & ! ( SDFK183 ) ) ) ) ) ) & ( ( ( ( ( ! ( AGEB3 ) & ! ( AGEB4 )
  ) & ( SDFK3 & SDFK5 ) ) & ( ( ! ( SDFK252 ) & ! ( SDFK253 ) ) &
  ( ! ( SDFK254 ) & ! ( AGEB2 ) ) ) ) & ( ( ( SDFK19 & SDFK21 ) & (
  SDFK22 & SDFK23 ) ) & ( ( SDFK6 & SDFK7 ) & ( SDFK12 &
  SDFK16 ) ) ) ) & ( ( ( ( ! ( SDFK236 ) & ! ( SDFK237 ) ) & ( ! (
  SDFK239 ) & ! ( SDFK240 ) ) ) & ( ( ! ( SDFK227 ) & ! ( SDFK230
  ) ) & ( ! ( SDFK233 ) & ! ( SDFK235 ) ) ) ) & ( ( ( ! ( SDFK248 ) &
  ! ( SDFK249 ) ) & ( ! ( SDFK250 ) & ! ( SDFK251 ) ) ) & ( ( ! (
  SDFK241 ) & ! ( SDFK242 ) ) & ( ! ( SDFK244 ) & ! ( SDFK247 ) )
  ) ) ) ) ) ) ) );

JTCN294 = ! ( AGEB0 );
JTCN295 = ! ( JTCN296 );
JTCN296 = ( JTCN297 & JTCN325 );
JTCN297 = ! ( JTCN298 );
JTCN298 = ( JTCN299 & JTCN323 );
JTCN299 = ( JTCN300 $ JTCN315 );
JTCN300 = ! ( JTCN301 );
JTCN301 = ( JTCN302 & JTCN314 );
JTCN302 = ( JTCN303 $ JTCN313 );
JTCN303 = ( JTCN304 $ JTCN312 );
JTCN304 = ( JTCN305 $ JTCN311 );
JTCN305 = ( JTCN306 $ JTCN310 );
JTCN306 = ( ( JTCN307 $ JTCN308 ) $ JTCN309 );
JTCN307 = ( flag_in0 $ SWTE0 );
JTCN308 = ( flag_in1 $ SWTE1 );
JTCN309 = ( SWTE2 $ flag_in2 );
JTCN310 = ( SWTE3 $ flag_in3 );
JTCN311 = ( SWTE4 $ flag_in4 );
JTCN312 = ( flag_in5 $ SWTE5 );
JTCN313 = ( flag_in6 $ SWTE6 );
JTCN314 = ( flag_in7 $ SWTE7 );
JTCN315 = ( JTCN316 $ JTCN317 );
JTCN316 = ( JTCN303 & JTCN313 );
JTCN317 = ( ! ( JTCN318 ) $ ( ! ( JTCN321 ) & ! ( JTCN322 ) ) );
JTCN318 = ( JTCN319 $ JTCN320 );
JTCN319 = ( JTCN306 & JTCN310 );
JTCN320 = ( ! ( ( ! ( JTCN307 ) & ! ( JTCN308 ) ) ) & ! ( ( ! ( ( JTCN307 &
  JTCN308 ) ) & ! ( JTCN309 ) ) ) );
JTCN321 = ( JTCN305 & JTCN311 );
JTCN322 = ( JTCN304 & JTCN312 );
JTCN323 = ( JTCN324 $ JTCN314 );
JTCN324 = ( JTCN303 $ ! ( JTCN313 ) );
JTCN325 = ! ( ( JTCN315 & JTCN326 ) );
JTCN326 = ( JTCN302 $ JTCN314 );
JTCN327 = ( JTCN328 & JTCN329 );
JTCN328 = ! ( ( JTCN309 & JTCN323 ) );
JTCN329 = ! ( ( JTCN310 & JTCN326 ) );
JTCN330 = ( JTCN297 & JTCN331 );
JTCN331 = ( ! ( ( JTCN332 & ! ( JTCN333 ) ) ) & ! ( JTCN334 ) );
JTCN332 = ( JTCN301 & JTCN317 );
JTCN333 = ( ! ( ( JTCN322 & JTCN318 ) ) & ( ! ( ( JTCN319 & JTCN320 ) ) & ! ( (
  JTCN321 & JTCN320 ) ) ) );
JTCN334 = ( JTCN335 & ( ! ( ( JTCN316 & JTCN317 ) ) & JTCN333 ) );
JTCN335 = ! ( JTCN332 );
JTCN336 = ( JTCN337 & JTCN338 );
JTCN337 = ! ( ( JTCN311 & JTCN323 ) );
JTCN338 = ! ( ( JTCN312 & JTCN326 ) );
JTCN339 = ! ( JTCN331 );
JTCN340 = ( ! ( ( JTCN341 & JTCN343 ) ) & ! ( ( JTCN299 & JTCN345 ) ) );
JTCN341 = ( JTCN335 & ! ( JTCN342 ) );
JTCN342 = ( JTCN300 & ( ! ( JTCN316 ) $ JTCN317 ) );
JTCN343 = ! ( JTCN344 );
JTCN344 = ( JTCN307 & JTCN323 );
JTCN345 = ( JTCN328 & JTCN346 );
JTCN346 = ! ( ( JTCN308 & JTCN326 ) );
JTCN347 = ( JTCN297 $ JTCN331 );
JTCN348 = ( JTCN295 & ! ( JTCN349 ) );
JTCN349 = ( ! ( ( JTCN324 & JTCN314 ) ) & JTCN350 );
JTCN350 = ! ( ( JTCN313 & JTCN323 ) );
JTCN351 = ( ! ( ( JTCN296 & JTCN349 ) ) & ! ( ( JTCN295 & JTCN336 ) ) );
JTCN352 = ( JTCN329 & JTCN337 );
JTCN353 = ! ( JTCN347 );
JTCN354 = ( ! ( ( JTCN300 & JTCN296 ) ) & ! ( ( JTCN295 & JTCN355 ) ) );
JTCN355 = ( JTCN356 & ! ( JTCN357 ) );
JTCN356 = ! ( ( JTCN312 & JTCN323 ) );
JTCN357 = ( JTCN313 & JTCN326 );
JTCN358 = ! ( ( JTCN308 & JTCN323 ) );
JTCN359 = ! ( ( JTCN309 & JTCN326 ) );
JTCN360 = ( JTCN361 & JTCN362 );
JTCN361 = ! ( ( JTCN310 & JTCN323 ) );
JTCN362 = ! ( ( JTCN311 & JTCN326 ) );
JTCN363 = ( JTCN299 & ! ( JTCN364 ) );
JTCN364 = ( JTCN358 & ! ( ( JTCN307 & JTCN326 ) ) );
JTCN365 = ( ! ( ( JTCN341 & JTCN364 ) ) & ! ( ( JTCN299 & JTCN366 ) ) );
JTCN366 = ( JTCN359 & JTCN361 );
JTCN367 = ( JTCN356 & JTCN362 );
JTCN368 = ( AGEB2 & JTCN369 );
JTCN369 = ( AGEB0 & AGEB1 );

AGEB0.AP = 'b'0;
AGEB0.AR = reset;
AGEB0.CK = clk;
AGEB0.D = JTCN294;
AGEB1.AP = 'b'0;
AGEB1.AR = reset;
AGEB1.CK = clk;
AGEB1.D = ( AGEB0 $ AGEB1 );
AGEB2.AP = 'b'0;
AGEB2.AR = reset;
AGEB2.CK = clk;
AGEB2.D = ( AGEB2 $ JTCN369 );
AGEB3.AP = 'b'0;
AGEB3.AR = reset;
AGEB3.CK = clk;
AGEB3.D = ( AGEB3 $ JTCN368 );
AGEB4.AP = 'b'0;
AGEB4.AR = reset;
AGEB4.CK = clk;
AGEB4.D = ( AGEB4 $ ( AGEB3 & JTCN368 ) );

SDFK0.AP = 'b'0;
SDFK0.AR = reset;
SDFK0.CK = clk;
SDFK0.D = ! ( ( ! ( ( JTCN347 & JTCN351 ) ) & ! ( ( JTCN353 & ( ! ( (
  JTCN296 & JTCN327 ) ) & ! ( ( JTCN295 & ( JTCN346 & JTCN343 ) ) ) ) ) ) ) );
SDFK1.AP = 'b'0;
SDFK1.AR = reset;
SDFK1.CK = clk;
SDFK1.D = ! ( ( ! ( ( JTCN347 & JTCN354 ) ) & ( ! ( ( ! ( ( JTCN295 & (
  JTCN358 & JTCN359 ) ) ) & ( JTCN330 & ! ( ( JTCN325 & JTCN360 ) ) ) ) ) & ! ( (
  JTCN339 & JTCN363 ) ) ) ) );
SDFK2.AP = 'b'0;
SDFK2.AR = reset;
SDFK2.CK = clk;
SDFK2.D = ! ( ( ! ( ( ! ( ( JTCN295 & JTCN327 ) ) & ( JTCN330 & ! ( (
  JTCN296 & JTCN336 ) ) ) ) ) & ( ! ( ( JTCN339 & JTCN340 ) ) & ! ( ( JTCN347 &
  JTCN348 ) ) ) ) );
SDFK3.AP = 'b'0;
SDFK3.AR = reset;
SDFK3.CK = clk;
SDFK3.D = ! ( ( ! ( ( JTCN339 & JTCN365 ) ) & ! ( ( ! ( ( JTCN335 & JTCN347
  ) ) & ( ! ( ( JTCN353 & ( JTCN295 & JTCN360 ) ) ) & ( ! ( ( JTCN298 & JTCN339 )
  ) & ! ( ( JTCN296 & JTCN355 ) ) ) ) ) ) ) );
SDFK4.AP = 'b'0;
SDFK4.AR = reset;
SDFK4.CK = clk;
SDFK4.D = ! ( ( ! ( ( JTCN330 & JTCN351 ) ) & ! ( ( ! ( ( JTCN341 & JTCN345
  ) ) & ( ! ( ( JTCN339 & ( JTCN299 & JTCN352 ) ) ) & ! ( ( JTCN331 & ! ( (
  JTCN299 & JTCN344 ) ) ) ) ) ) ) ) );
SDFK5.AP = 'b'0;
SDFK5.AR = reset;
SDFK5.CK = clk;
SDFK5.D = ! ( ( ! ( ( JTCN330 & JTCN354 ) ) & ! ( ( ! ( ( JTCN339 & ! ( ( !
  ( ( JTCN341 & JTCN366 ) ) & ! ( ( JTCN299 & JTCN367 ) ) ) ) ) ) & ! ( ( JTCN331
  & ! ( JTCN363 ) ) ) ) ) ) );
SDFK6.AP = 'b'0;
SDFK6.AR = reset;
SDFK6.CK = clk;
SDFK6.D = ! ( ( ! ( ( JTCN331 & JTCN340 ) ) & ( ! ( ( JTCN330 & JTCN348 ) )
  & ! ( ( JTCN339 & ( ! ( ( JTCN299 & ( JTCN338 & JTCN350 ) ) ) & ! ( ( JTCN341 &
  JTCN352 ) ) ) ) ) ) ) );
SDFK7.AP = 'b'0;
SDFK7.AR = reset;
SDFK7.CK = clk;
SDFK7.D = ( ! ( ( JTCN331 & ! ( JTCN365 ) ) ) & ! ( ( ! ( ( JTCN341 & ! (
  JTCN367 ) ) ) & ( JTCN334 & ! ( ( JTCN342 & JTCN357 ) ) ) ) ) );

SDFK8.AP = 'b'0;
SDFK8.AR = reset;
SDFK8.CK = clk;
SDFK8.D = SDFK0;
...
SDFK255.AP = 'b'0;
SDFK255.AR = reset;
SDFK255.CK = clk;
SDFK255.D = SDFK247;

SWTE0.AP = reset;
SWTE0.AR = 'b'0;
SWTE0.CK = clk;
SWTE0.D = SWTE15;
SWTE1.AP = reset;
SWTE1.AR = 'b'0;
SWTE1.CK = clk;
SWTE1.D = SWTE0;
SWTE2.AP = reset;
SWTE2.AR = 'b'0;
SWTE2.CK = clk;
SWTE2.D = SWTE1;
SWTE3.AP = 'b'0;
SWTE3.AR = reset;
SWTE3.CK = clk;
SWTE3.D = ( SWTE2 $ SWTE15 );
SWTE4.AP = reset;
SWTE4.AR = 'b'0;
SWTE4.CK = clk;
SWTE4.D = ( SWTE15 $ SWTE3 );
SWTE5.AP = reset;
SWTE5.AR = 'b'0;
SWTE5.CK = clk;
SWTE5.D = ( SWTE15 $ SWTE4 );
SWTE6.AP = 'b'0;
SWTE6.AR = reset;
SWTE6.CK = clk;
SWTE6.D = SWTE5;
SWTE7.AP = 'b'0;
SWTE7.AR = reset;
SWTE7.CK = clk;
SWTE7.D = SWTE6;
SWTE8.AP = reset;
SWTE8.AR = 'b'0;
SWTE8.CK = clk;
SWTE8.D = SWTE7;
SWTE9.AP = reset;
SWTE9.AR = 'b'0;
SWTE9.CK = clk;
SWTE9.D = SWTE8;
SWTE10.AP = 'b'0;
SWTE10.AR = reset;
SWTE10.CK = clk;
SWTE10.D = SWTE9;
SWTE11.AP = 'b'0;
SWTE11.AR = reset;
SWTE11.CK = clk;
SWTE11.D = SWTE10;
SWTE12.AP = reset;
SWTE12.AR = 'b'0;
SWTE12.CK = clk;
SWTE12.D = SWTE11;
SWTE13.AP = 'b'0;
SWTE13.AR = reset;
SWTE13.CK = clk;
SWTE13.D = SWTE12;
SWTE14.AP = 'b'0;
SWTE14.AR = reset;
SWTE14.CK = clk;
SWTE14.D = SWTE13;
SWTE15.AP = 'b'0;
SWTE15.AR = reset;
SWTE15.CK = clk;
SWTE15.D = SWTE14;

There's 8 bits of flag input (flag_in), many intermediate signals (JTCN294-369), and a bunch of flip flops. AGEB are basically a check to know when 32 characters are red, SDFK store 32 characters of output, and SWTE scramble the input for added obfuscation.

Some background info: during the competition, I didn't notice that some of the reset lines for SWTE go to AP (async preset) instead of AR (async reset), and since SWTE feeds into itself, I thought it was always zero and didn't affect the logic. It is actually initialized with some ones which make it XOR the flag inputs for JTCN307-314. After adding that, my simulation code was functional.

Since JTCN isn't registered (clocked), all of the interconnecting logic happens at once every clock cycle whereas the other signals (AGEB/SDFK/SWTE) update once per clock via the flip flop. Therefore, there needs to be a way to simulate the logic propagation until the JTCN signals reach steady state. During debugging, I built two different ways to do this, the first is just doing the same update procedure as clocked signals, but keep clocking until the output doesn't change, and the second is to recursively expand all the pin logic so each JTCN signal is related directly to flag_in.

Method 1:

    while changes:
        JTCN_new[294] = not ( AGEB[0] )
        JTCN_new[295] = not ( JTCN_current[296] )
        JTCN_new[296] = ( JTCN_current[297] and JTCN_current[325] )
        JTCN_new[297] = not ( JTCN_current[298] )
        JTCN_new[298] = ( JTCN_current[299] and JTCN_current[323] )
        JTCN_new[299] = ( JTCN_current[300] ^ JTCN_current[315] )
        JTCN_new[300] = not ( JTCN_current[301] )
        JTCN_new[301] = ( JTCN_current[302] and JTCN_current[314] )
        JTCN_new[302] = ( JTCN_current[303] ^ JTCN_current[313] )
        JTCN_new[303] = ( JTCN_current[304] ^ JTCN_current[312] )
        JTCN_new[304] = ( JTCN_current[305] ^ JTCN_current[311] )
        JTCN_new[305] = ( JTCN_current[306] ^ JTCN_current[310] )
        JTCN_new[306] = ( ( JTCN_current[307] ^ JTCN_current[308] ) ^ JTCN_current[309] )
        JTCN_new[307] = ( flag_in[0] ^ SWTE[0])
        JTCN_new[308] = ( flag_in[1] ^ SWTE[1])
        JTCN_new[309] = ( flag_in[2] ^ SWTE[2])
        JTCN_new[310] = ( flag_in[3] ^ SWTE[3])
        JTCN_new[311] = ( flag_in[4] ^ SWTE[4])
        JTCN_new[312] = ( flag_in[5] ^ SWTE[5])
        JTCN_new[313] = ( flag_in[6] ^ SWTE[6])
        JTCN_new[314] = ( flag_in[7] ^ SWTE[7])
        JTCN_new[315] = ( JTCN_current[316] ^ JTCN_current[317] )
        JTCN_new[316] = ( JTCN_current[303] and JTCN_current[313] )
        JTCN_new[317] = ( not ( JTCN_current[318] ) ^ ( not ( JTCN_current[321] ) and not ( JTCN_current[322] ) ) )
        JTCN_new[318] = ( JTCN_current[319] ^ JTCN_current[320] )
        JTCN_new[319] = ( JTCN_current[306] and JTCN_current[310] )
        JTCN_new[320] = ( not ( ( not ( JTCN_current[307] ) and not ( JTCN_current[308] ) ) ) and not ( ( not ( ( JTCN_current[307] and JTCN_current[308] ) ) and not ( JTCN_current[309] ) ) ) )
        JTCN_new[321] = ( JTCN_current[305] and JTCN_current[311] )
        JTCN_new[322] = ( JTCN_current[304] and JTCN_current[312] )
        JTCN_new[323] = ( JTCN_current[324] ^ JTCN_current[314] )
        JTCN_new[324] = ( JTCN_current[303] ^ (not ( JTCN_current[313] ) ) )
        JTCN_new[325] = not ( ( JTCN_current[315] and JTCN_current[326] ) )
        JTCN_new[326] = ( JTCN_current[302] ^ JTCN_current[314] )
        JTCN_new[327] = ( JTCN_current[328] and JTCN_current[329] )
        JTCN_new[328] = not ( ( JTCN_current[309] and JTCN_current[323] ) )
        JTCN_new[329] = not ( ( JTCN_current[310] and JTCN_current[326] ) )
        JTCN_new[330] = ( JTCN_current[297] and JTCN_current[331] )
        JTCN_new[331] = ( not ( ( JTCN_current[332] and not ( JTCN_current[333] ) ) ) and not ( JTCN_current[334] ) )
        JTCN_new[332] = ( JTCN_current[301] and JTCN_current[317] )
        JTCN_new[333] = ( not ( ( JTCN_current[322] and JTCN_current[318] ) ) and ( not ( ( JTCN_current[319] and JTCN_current[320] ) ) and not ( ( JTCN_current[321] and JTCN_current[320] ) ) ) )
        JTCN_new[334] = ( JTCN_current[335] and ( not ( ( JTCN_current[316] and JTCN_current[317] ) ) and JTCN_current[333] ) )
        JTCN_new[335] = not ( JTCN_current[332] )
        JTCN_new[336] = ( JTCN_current[337] and JTCN_current[338] )
        JTCN_new[337] = not ( ( JTCN_current[311] and JTCN_current[323] ) )
        JTCN_new[338] = not ( ( JTCN_current[312] and JTCN_current[326] ) )
        JTCN_new[339] = not ( JTCN_current[331] )
        JTCN_new[340] = ( not ( ( JTCN_current[341] and JTCN_current[343] ) ) and not ( ( JTCN_current[299] and JTCN_current[345] ) ) )
        JTCN_new[341] = ( JTCN_current[335] and not ( JTCN_current[342] ) )
        JTCN_new[342] = ( JTCN_current[300] and ( not ( JTCN_current[316] ) ^ JTCN_current[317] ) )
        JTCN_new[343] = not ( JTCN_current[344] )
        JTCN_new[344] = ( JTCN_current[307] and JTCN_current[323] )
        JTCN_new[345] = ( JTCN_current[328] and JTCN_current[346] )
        JTCN_new[346] = not ( ( JTCN_current[308] and JTCN_current[326] ) )
        JTCN_new[347] = ( JTCN_current[297] ^ JTCN_current[331] )
        JTCN_new[348] = ( JTCN_current[295] and not ( JTCN_current[349] ) )
        JTCN_new[349] = ( not ( ( JTCN_current[324] and JTCN_current[314] ) ) and JTCN_current[350] )
        JTCN_new[350] = not ( ( JTCN_current[313] and JTCN_current[323] ) )
        JTCN_new[351] = ( not ( ( JTCN_current[296] and JTCN_current[349] ) ) and not ( ( JTCN_current[295] and JTCN_current[336] ) ) )
        JTCN_new[352] = ( JTCN_current[329] and JTCN_current[337] )
        JTCN_new[353] = not ( JTCN_current[347] )
        JTCN_new[354] = ( not ( ( JTCN_current[300] and JTCN_current[296] ) ) and not ( ( JTCN_current[295] and JTCN_current[355] ) ) )
        JTCN_new[355] = ( JTCN_current[356] and not ( JTCN_current[357] ) )
        JTCN_new[356] = not ( ( JTCN_current[312] and JTCN_current[323] ) )
        JTCN_new[357] = ( JTCN_current[313] and JTCN_current[326] )
        JTCN_new[358] = not ( ( JTCN_current[308] and JTCN_current[323] ) )
        JTCN_new[359] = not ( ( JTCN_current[309] and JTCN_current[326] ) )
        JTCN_new[360] = ( JTCN_current[361] and JTCN_current[362] )
        JTCN_new[361] = not ( ( JTCN_current[310] and JTCN_current[323] ) )
        JTCN_new[362] = not ( ( JTCN_current[311] and JTCN_current[326] ) )
        JTCN_new[363] = ( JTCN_current[299] and not ( JTCN_current[364] ) )
        JTCN_new[364] = ( JTCN_current[358] and not ( ( JTCN_current[307] and JTCN_current[326] ) ) )
        JTCN_new[365] = ( not ( ( JTCN_current[341] and JTCN_current[364] ) ) and not ( ( JTCN_current[299] and JTCN_current[366] ) ) )
        JTCN_new[366] = ( JTCN_current[359] and JTCN_current[361] )
        JTCN_new[367] = ( JTCN_current[356] and JTCN_current[362] )
        JTCN_new[368] = ( AGEB[2] and JTCN_current[369] )
        JTCN_new[369] = ( AGEB[0] and AGEB[1] )

        for i in range(400):
            JTCN_new[i] = int(JTCN_new[i])
        # Check for changes from the current state
        if JTCN_new == JTCN_current:
            #print("JTCN Stabilized, leaving update loop")
            changes = False
        else:
            if counter < lim:
                #print("Still unstable, looping")
                JTCN_current = JTCN_new.copy()
                counter+=1
            else:
                #print("Timeout, leaving update loop")
                changes = False

I added a configurable limit to the number of update loops because I thought maybe the reason it wasn't working initially was that the challenge was simulated to not leave enough time for the signals to stabilize per clock cycle, although this wouldn't really work anyway since different signals have different numbers of gates between them and the flag_in.

The way this works is the loop breaks once JTCN_current and JTCN_new are equal which represents all changes being propagated throughout the circuit. This often takes 10+ cycles for the logic.

The code itself was created via find and replace with the original PAL, replacing & with and, $ with ^, and not with !.

Method 2:

def calculate_sn(a, b, c, d, e, f, g, h):
    jn = [0]*400
    sn = [0]*8
    jn[295] = not ( ( not ( ( ( not ( ( ( ( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) ^ f ) ^ g ) and h ) ) ^ ( ( ( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) ^ f ) and g ) ^ ( not ( ( (( ( ( a ^ b ) ^ c ) and d )) ^ (( not ( ( not ( a ) and not ( b ) ) ) and not ( ( not ( ( a and b ) ) and not ( c ) ) ) )) ) ) ^ ( not ( (( ( ( ( a ^ b ) ^ c ) ^ d ) and e )) ) and not ( (( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) and f )) ) ) ) ) ) and (( (( ( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) ^ f ) ^ (not ( g ) ) )) ^ h )) ) ) and (not ( ( ( ( ( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) ^ f ) and g ) ^ ( not ( ( (( ( ( a ^ b ) ^ c ) and d )) ^ (( not ( ( not ( a ) and not ( b ) ) ) and not ( ( not ( ( a and b ) ) and not ( c ) ) ) )) ) ) ^ ( not ( (( ( ( ( a ^ b ) ^ c ) ^ d ) and e )) ) and not ( (( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) and f )) ) ) ) ) and (( ( ( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) ^ f ) ^ g ) ^ h )) ) )) ) )
    jn[296] = ( not ( ( ( not ( ( ( ( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) ^ f ) ^ g ) and h ) ) ^ ( ( ( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) ^ f ) and g ) ^ ( not ( ( (( ( ( a ^ b ) ^ c ) and d )) ^ (( not ( ( not ( a ) and not ( b ) ) ) and not ( ( not ( ( a and b ) ) and not ( c ) ) ) )) ) ) ^ ( not ( (( ( ( ( a ^ b ) ^ c ) ^ d ) and e )) ) and not ( (( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) and f )) ) ) ) ) ) and (( (( ( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) ^ f ) ^ (not ( g ) ) )) ^ h )) ) ) and (not ( ( ( ( ( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) ^ f ) and g ) ^ ( not ( ( (( ( ( a ^ b ) ^ c ) and d )) ^ (( not ( ( not ( a ) and not ( b ) ) ) and not ( ( not ( ( a and b ) ) and not ( c ) ) ) )) ) ) ^ ( not ( (( ( ( ( a ^ b ) ^ c ) ^ d ) and e )) ) and not ( (( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) and f )) ) ) ) ) and (( ( ( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) ^ f ) ^ g ) ^ h )) ) )) )
    jn[297] = not ( ( ( not ( ( ( ( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) ^ f ) ^ g ) and h ) ) ^ ( ( ( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) ^ f ) and g ) ^ ( not ( ( (( ( ( a ^ b ) ^ c ) and d )) ^ (( not ( ( not ( a ) and not ( b ) ) ) and not ( ( not ( ( a and b ) ) and not ( c ) ) ) )) ) ) ^ ( not ( (( ( ( ( a ^ b ) ^ c ) ^ d ) and e )) ) and not ( (( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) and f )) ) ) ) ) ) and (( (( ( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) ^ f ) ^ (not ( g ) ) )) ^ h )) ) )
    ...
    jn[365] = ( not ( ( (( (not ( (( ( ( ( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) ^ f ) ^ g ) and h ) and ( not ( ( (( ( ( a ^ b ) ^ c ) and d )) ^ (( not ( ( not ( a ) and not ( b ) ) ) and not ( ( not ( ( a and b ) ) and not ( c ) ) ) )) ) ) ^ ( not ( (( ( ( ( a ^ b ) ^ c ) ^ d ) and e )) ) and not ( (( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) and f )) ) ) ) )) )) and not ( (( (not ( ( ( ( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) ^ f ) ^ g ) and h ) )) and ( not ( ( ( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) ^ f ) and g ) ) ^ ( not ( ( (( ( ( a ^ b ) ^ c ) and d )) ^ (( not ( ( not ( a ) and not ( b ) ) ) and not ( ( not ( ( a and b ) ) and not ( c ) ) ) )) ) ) ^ ( not ( (( ( ( ( a ^ b ) ^ c ) ^ d ) and e )) ) and not ( (( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) and f )) ) ) ) ) )) ) )) and (( (not ( ( b and (( (( ( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) ^ f ) ^ (not ( g ) ) )) ^ h )) ) )) and not ( ( a and (( ( ( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) ^ f ) ^ g ) ^ h )) ) ) )) ) ) and not ( ( ( (not ( ( ( ( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) ^ f ) ^ g ) and h ) )) ^ ( ( ( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) ^ f ) and g ) ^ ( not ( ( (( ( ( a ^ b ) ^ c ) and d )) ^ (( not ( ( not ( a ) and not ( b ) ) ) and not ( ( not ( ( a and b ) ) and not ( c ) ) ) )) ) ) ^ ( not ( (( ( ( ( a ^ b ) ^ c ) ^ d ) and e )) ) and not ( (( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) and f )) ) ) ) ) ) and (( (not ( ( c and (( ( ( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) ^ f ) ^ g ) ^ h )) ) )) and (not ( ( d and (( (( ( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) ^ f ) ^ (not ( g ) ) )) ^ h )) ) )) )) ) ) )
    jn[366] = ( (not ( ( c and (( ( ( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) ^ f ) ^ g ) ^ h )) ) )) and (not ( ( d and (( (( ( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) ^ f ) ^ (not ( g ) ) )) ^ h )) ) )) )
    jn[367] = ( (not ( ( f and (( (( ( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) ^ f ) ^ (not ( g ) ) )) ^ h )) ) )) and (not ( ( e and (( ( ( ( ( ( ( a ^ b ) ^ c ) ^ d ) ^ e ) ^ f ) ^ g ) ^ h )) ) )) )

    sn[0] = not ( ( not ( ( jn[347] and jn[351] ) ) and not ( ( jn[353] and ( not ( ( jn[296] and jn[327] ) ) and not ( ( jn[295] and ( jn[346] and jn[343] ) ) ) ) ) ) ) )
    sn[1] = not ( ( not ( ( jn[347] and jn[354] ) ) and ( not ( ( not ( ( jn[295] and ( jn[358] and jn[359] ) ) ) and ( jn[330] and not ( ( jn[325] and jn[360] ) ) ) ) ) and not ( ( jn[339] and jn[363] ) ) ) ) )
    sn[2] = not ( ( not ( ( not ( ( jn[295] and jn[327] ) ) and ( jn[330] and not ( ( jn[296] and jn[336] ) ) ) ) ) and ( not ( ( jn[339] and jn[340] ) ) and not ( ( jn[347] and jn[348] ) ) ) ) )
    sn[3] = not ( ( not ( ( jn[339] and jn[365] ) ) and not ( ( not ( ( jn[335] and jn[347]) ) and ( not ( ( jn[353] and ( jn[295] and jn[360] ) ) ) and ( not ( ( jn[298] and jn[339] )) and not ( ( jn[296] and jn[355] ) ) ) ) ) ) ) )
    sn[4] = not ( ( not ( ( jn[330] and jn[351] ) ) and not ( ( not ( ( jn[341] and jn[345]) ) and ( not ( ( jn[339] and ( jn[299] and jn[352] ) ) ) and not ( ( jn[331] and not ( ( jn[299] and jn[344] ) ) ) ) ) ) ) ) )
    sn[5] = not ( ( not ( ( jn[330] and jn[354] ) ) and not ( ( not ( ( jn[339] and not ( ( not ( ( jn[341] and jn[366] ) ) and not ( ( jn[299] and jn[367] ) ) ) ) ) ) and not ( ( jn[331] and not ( jn[363] ) ) ) ) ) ) )
    sn[6] = not ( ( not ( ( jn[331] and jn[340] ) ) and ( not ( ( jn[330] and jn[348] ) ) and not ( ( jn[339] and ( not ( ( jn[299] and ( jn[338] and jn[350] ) ) ) and not ( ( jn[341] and jn[352] ) ) ) ) ) ) ) )
    sn[7] = ( not ( ( jn[331] and not ( jn[365] ) ) ) and not ( ( not ( ( jn[341] and not ( jn[367] ) ) ) and ( jn[334] and not ( ( jn[342] and jn[357] ) ) ) ) ) )
    return [sn[0], sn[1], sn[2], sn[3], sn[4], sn[5], sn[6], sn[7]]

This second method recursively expands each JTCN signal so the logic can be calculated on one loop. Not really ideal but it is easier to understand and I confirmed it results in the same output as method 1.

Next is figuring out what our goal is. The flag_ok signal conveniently has a ! in front of the SDFK signals that should be 0 and not in front of the ones that should be 1, so with some more find and replace and some sorting, we can get the desired bits from 0-255:

1101011100001000100101110111001001010011111010100000000010100110010000111111001011100000000000100110100111111101011111110010000100111010111101100011010010100100010001001100110001011110111011000000100001010100111111111000100111101101101000100001011000000001
# How SDFK works
SDFK0 -> SDFK8 -> SDFK16 -> ... -> SDFK248
SDFK1 -> SDFK9 -> SDFK17 -> ... -> SDFK249
...
SDFK7 -> SDFK15 -> SDFK23 -> ... -> SDFK255

The first input goes to SDFK0-7 which eventually becomes SDFK248-255. The typical convention is flag_in 7 downto 0 represents most to least significant bits, so SDFK255 is the most significant bit of the first input at the end. Looking at the end of the binary string, the first input should therefore result in SDFK255-248 being 10000000, which is 128 in decimal. The rest in decimal:

[128, 104, 69, 183, 145, 255, 42, 16, 55, 122, 51, 34, 37, 44, 111, 92, 132, 254, 191, 150, 64, 7, 79, 194, 101, 0, 87, 202, 78, 233, 16, 235]

At this point, we can just run the simulation for all the possible flag_in combinations from 32-127, and note what values give us the desired values one character at a time by reading SDFK0-7. Full solution script below.

def find_correct_ascii(AGEB_initial, SWTE_initial, JTCN_initial, ascii_values, desired):
    for index in range(len(ascii_values)):
        for ascii_val in range(32, 127):  # Iterates through printable ASCII range
            ascii_values[index] = ascii_val
            out = simulate_with_input(AGEB_initial, SWTE_initial, JTCN_initial, ascii_values[:index + 1])
            if out[index] == desired[index]:
                print(f"Match found for position {index + 1}: ASCII {ascii_val} ('{chr(ascii_val)}') produces {out[index]}")
                break  # Break the loop if the correct ASCII value is found
        else:
            print(f"No match found for position {index + 1}, keeping current value: ASCII {ascii_values[index]} ('{chr(ascii_values[index])}')")
    return ascii_values

def simulate_with_input(AGEB_initial, SWTE_initial, JTCN_initial, ascii_values):
    out = []
    AGEB = AGEB_initial.copy()
    SWTE = SWTE_initial.copy()
    JTCN_current = JTCN_initial.copy()
    for ascii_value in ascii_values:
        flag_in = ascii_to_binary_list(ascii_value)
        # Simulate
        SDFK_new, AGEB, SWTE, JTCN_current = simulator(flag_in, AGEB, SWTE, JTCN_current)
        # Convert SDFK_new from boolean to integer
        SDFK_as_int = boolean_list_to_int(SDFK_new)
        out.append(SDFK_as_int)
    return out

def simulator(flag_in, AGEB, SWTE, JTCN_current):
    # Flipping the bits for consistency with defined convention
    flag_in = flag_in[::-1]

    JTCN_new = [0]*400
    changes = True
    lim=100
    counter=0

    while changes:
        JTCN_new[294] = not ( AGEB[0] )
        JTCN_new[295] = not ( JTCN_current[296] )
        JTCN_new[296] = ( JTCN_current[297] and JTCN_current[325] )
        JTCN_new[297] = not ( JTCN_current[298] )
        JTCN_new[298] = ( JTCN_current[299] and JTCN_current[323] )
        JTCN_new[299] = ( JTCN_current[300] ^ JTCN_current[315] )
        JTCN_new[300] = not ( JTCN_current[301] )
        JTCN_new[301] = ( JTCN_current[302] and JTCN_current[314] )
        JTCN_new[302] = ( JTCN_current[303] ^ JTCN_current[313] )
        JTCN_new[303] = ( JTCN_current[304] ^ JTCN_current[312] )
        JTCN_new[304] = ( JTCN_current[305] ^ JTCN_current[311] )
        JTCN_new[305] = ( JTCN_current[306] ^ JTCN_current[310] )
        JTCN_new[306] = ( ( JTCN_current[307] ^ JTCN_current[308] ) ^ JTCN_current[309] )
        JTCN_new[307] = ( flag_in[0] ^ SWTE[0])
        JTCN_new[308] = ( flag_in[1] ^ SWTE[1])
        JTCN_new[309] = ( flag_in[2] ^ SWTE[2])
        JTCN_new[310] = ( flag_in[3] ^ SWTE[3])
        JTCN_new[311] = ( flag_in[4] ^ SWTE[4])
        JTCN_new[312] = ( flag_in[5] ^ SWTE[5])
        JTCN_new[313] = ( flag_in[6] ^ SWTE[6])
        JTCN_new[314] = ( flag_in[7] ^ SWTE[7])
        JTCN_new[315] = ( JTCN_current[316] ^ JTCN_current[317] )
        JTCN_new[316] = ( JTCN_current[303] and JTCN_current[313] )
        JTCN_new[317] = ( not ( JTCN_current[318] ) ^ ( not ( JTCN_current[321] ) and not ( JTCN_current[322] ) ) )
        JTCN_new[318] = ( JTCN_current[319] ^ JTCN_current[320] )
        JTCN_new[319] = ( JTCN_current[306] and JTCN_current[310] )
        JTCN_new[320] = ( not ( ( not ( JTCN_current[307] ) and not ( JTCN_current[308] ) ) ) and not ( ( not ( ( JTCN_current[307] and JTCN_current[308] ) ) and not ( JTCN_current[309] ) ) ) )
        JTCN_new[321] = ( JTCN_current[305] and JTCN_current[311] )
        JTCN_new[322] = ( JTCN_current[304] and JTCN_current[312] )
        JTCN_new[323] = ( JTCN_current[324] ^ JTCN_current[314] )
        JTCN_new[324] = ( JTCN_current[303] ^ (not ( JTCN_current[313] ) ) )
        JTCN_new[325] = not ( ( JTCN_current[315] and JTCN_current[326] ) )
        JTCN_new[326] = ( JTCN_current[302] ^ JTCN_current[314] )
        JTCN_new[327] = ( JTCN_current[328] and JTCN_current[329] )
        JTCN_new[328] = not ( ( JTCN_current[309] and JTCN_current[323] ) )
        JTCN_new[329] = not ( ( JTCN_current[310] and JTCN_current[326] ) )
        JTCN_new[330] = ( JTCN_current[297] and JTCN_current[331] )
        JTCN_new[331] = ( not ( ( JTCN_current[332] and not ( JTCN_current[333] ) ) ) and not ( JTCN_current[334] ) )
        JTCN_new[332] = ( JTCN_current[301] and JTCN_current[317] )
        JTCN_new[333] = ( not ( ( JTCN_current[322] and JTCN_current[318] ) ) and ( not ( ( JTCN_current[319] and JTCN_current[320] ) ) and not ( ( JTCN_current[321] and JTCN_current[320] ) ) ) )
        JTCN_new[334] = ( JTCN_current[335] and ( not ( ( JTCN_current[316] and JTCN_current[317] ) ) and JTCN_current[333] ) )
        JTCN_new[335] = not ( JTCN_current[332] )
        JTCN_new[336] = ( JTCN_current[337] and JTCN_current[338] )
        JTCN_new[337] = not ( ( JTCN_current[311] and JTCN_current[323] ) )
        JTCN_new[338] = not ( ( JTCN_current[312] and JTCN_current[326] ) )
        JTCN_new[339] = not ( JTCN_current[331] )
        JTCN_new[340] = ( not ( ( JTCN_current[341] and JTCN_current[343] ) ) and not ( ( JTCN_current[299] and JTCN_current[345] ) ) )
        JTCN_new[341] = ( JTCN_current[335] and not ( JTCN_current[342] ) )
        JTCN_new[342] = ( JTCN_current[300] and ( not ( JTCN_current[316] ) ^ JTCN_current[317] ) )
        JTCN_new[343] = not ( JTCN_current[344] )
        JTCN_new[344] = ( JTCN_current[307] and JTCN_current[323] )
        JTCN_new[345] = ( JTCN_current[328] and JTCN_current[346] )
        JTCN_new[346] = not ( ( JTCN_current[308] and JTCN_current[326] ) )
        JTCN_new[347] = ( JTCN_current[297] ^ JTCN_current[331] )
        JTCN_new[348] = ( JTCN_current[295] and not ( JTCN_current[349] ) )
        JTCN_new[349] = ( not ( ( JTCN_current[324] and JTCN_current[314] ) ) and JTCN_current[350] )
        JTCN_new[350] = not ( ( JTCN_current[313] and JTCN_current[323] ) )
        JTCN_new[351] = ( not ( ( JTCN_current[296] and JTCN_current[349] ) ) and not ( ( JTCN_current[295] and JTCN_current[336] ) ) )
        JTCN_new[352] = ( JTCN_current[329] and JTCN_current[337] )
        JTCN_new[353] = not ( JTCN_current[347] )
        JTCN_new[354] = ( not ( ( JTCN_current[300] and JTCN_current[296] ) ) and not ( ( JTCN_current[295] and JTCN_current[355] ) ) )
        JTCN_new[355] = ( JTCN_current[356] and not ( JTCN_current[357] ) )
        JTCN_new[356] = not ( ( JTCN_current[312] and JTCN_current[323] ) )
        JTCN_new[357] = ( JTCN_current[313] and JTCN_current[326] )
        JTCN_new[358] = not ( ( JTCN_current[308] and JTCN_current[323] ) )
        JTCN_new[359] = not ( ( JTCN_current[309] and JTCN_current[326] ) )
        JTCN_new[360] = ( JTCN_current[361] and JTCN_current[362] )
        JTCN_new[361] = not ( ( JTCN_current[310] and JTCN_current[323] ) )
        JTCN_new[362] = not ( ( JTCN_current[311] and JTCN_current[326] ) )
        JTCN_new[363] = ( JTCN_current[299] and not ( JTCN_current[364] ) )
        JTCN_new[364] = ( JTCN_current[358] and not ( ( JTCN_current[307] and JTCN_current[326] ) ) )
        JTCN_new[365] = ( not ( ( JTCN_current[341] and JTCN_current[364] ) ) and not ( ( JTCN_current[299] and JTCN_current[366] ) ) )
        JTCN_new[366] = ( JTCN_current[359] and JTCN_current[361] )
        JTCN_new[367] = ( JTCN_current[356] and JTCN_current[362] )
        JTCN_new[368] = ( AGEB[2] and JTCN_current[369] )
        JTCN_new[369] = ( AGEB[0] and AGEB[1] )

        for i in range(400):
            JTCN_new[i] = int(JTCN_new[i])
        # Check for changes from the current state
        if JTCN_new == JTCN_current:
            #print("JTCN Stabilized, leaving update loop")
            changes = False
        else:
            if counter < lim:
                #print("Still unstable, looping")
                JTCN_current = JTCN_new.copy()
                counter+=1
            else:
                #print("Timeout, leaving update loop")
                changes = False

    # Convert JTCN_new from boolean to integer
    for i in range(400):
        JTCN_new[i] = int(JTCN_new[i])

    AGEB_new = [0] * 5
    AGEB_new[0] = JTCN_new[294]
    AGEB_new[1] = AGEB[0] ^ AGEB[1]
    AGEB_new[2] = AGEB[2] ^ JTCN_new[369]
    AGEB_new[3] = AGEB[3] ^ JTCN_new[368]
    AGEB_new[4] = AGEB[4] ^ (AGEB[3] & JTCN_new[368])

    SWTE_new = [0] * 16
    SWTE_new[0] = SWTE[15]
    SWTE_new[1] = SWTE[0]
    SWTE_new[2] = SWTE[1]
    SWTE_new[3] = SWTE[2] ^ SWTE[15]
    SWTE_new[4] = SWTE[3] ^ SWTE[15]
    SWTE_new[5] = SWTE[4] ^ SWTE[15]
    SWTE_new[6] = SWTE[5]
    SWTE_new[7] = SWTE[6]
    SWTE_new[8] = SWTE[7]
    SWTE_new[9] = SWTE[8]
    SWTE_new[10] = SWTE[9]
    SWTE_new[11] = SWTE[10]
    SWTE_new[12] = SWTE[11]
    SWTE_new[13] = SWTE[12]
    SWTE_new[14] = SWTE[13]
    SWTE_new[15] = SWTE[14]

    SDFK_new = [0] * 8
    SDFK_new[0] = not ( ( not ( ( JTCN_new[347] and JTCN_new[351] ) ) and not ( ( JTCN_new[353] and ( not ( ( JTCN_new[296] and JTCN_new[327] ) ) and not ( ( JTCN_new[295] and ( JTCN_new[346] and JTCN_new[343] ) ) ) ) ) ) ) )
    SDFK_new[1] = not ( ( not ( ( JTCN_new[347] and JTCN_new[354] ) ) and ( not ( ( not ( ( JTCN_new[295] and ( JTCN_new[358] and JTCN_new[359] ) ) ) and ( JTCN_new[330] and not ( ( JTCN_new[325] and JTCN_new[360] ) ) ) ) ) and not ( ( JTCN_new[339] and JTCN_new[363] ) ) ) ) )
    SDFK_new[2] = not ( ( not ( ( not ( ( JTCN_new[295] and JTCN_new[327] ) ) and ( JTCN_new[330] and not ( ( JTCN_new[296] and JTCN_new[336] ) ) ) ) ) and ( not ( ( JTCN_new[339] and JTCN_new[340] ) ) and not ( ( JTCN_new[347] and JTCN_new[348] ) ) ) ) )
    SDFK_new[3] = not ( ( not ( ( JTCN_new[339] and JTCN_new[365] ) ) and not ( ( not ( ( JTCN_new[335] and JTCN_new[347]) ) and ( not ( ( JTCN_new[353] and ( JTCN_new[295] and JTCN_new[360] ) ) ) and ( not ( ( JTCN_new[298] and JTCN_new[339] )) and not ( ( JTCN_new[296] and JTCN_new[355] ) ) ) ) ) ) ) )
    SDFK_new[4] = not ( ( not ( ( JTCN_new[330] and JTCN_new[351] ) ) and not ( ( not ( ( JTCN_new[341] and JTCN_new[345]) ) and ( not ( ( JTCN_new[339] and ( JTCN_new[299] and JTCN_new[352] ) ) ) and not ( ( JTCN_new[331] and not ( ( JTCN_new[299] and JTCN_new[344] ) ) ) ) ) ) ) ) )
    SDFK_new[5] = not ( ( not ( ( JTCN_new[330] and JTCN_new[354] ) ) and not ( ( not ( ( JTCN_new[339] and not ( ( not ( ( JTCN_new[341] and JTCN_new[366] ) ) and not ( ( JTCN_new[299] and JTCN_new[367] ) ) ) ) ) ) and not ( ( JTCN_new[331] and not ( JTCN_new[363] ) ) ) ) ) ) )
    SDFK_new[6] = not ( ( not ( ( JTCN_new[331] and JTCN_new[340] ) ) and ( not ( ( JTCN_new[330] and JTCN_new[348] ) ) and not ( ( JTCN_new[339] and ( not ( ( JTCN_new[299] and ( JTCN_new[338] and JTCN_new[350] ) ) ) and not ( ( JTCN_new[341] and JTCN_new[352] ) ) ) ) ) ) ) )
    SDFK_new[7] = ( not ( ( JTCN_new[331] and not ( JTCN_new[365] ) ) ) and not ( ( not ( ( JTCN_new[341] and not ( JTCN_new[367] ) ) ) and ( JTCN_new[334] and not ( ( JTCN_new[342] and JTCN_new[357] ) ) ) ) ) )

    # Convert SDFK_new from boolean to integer for output
    for i in range(8):
        SDFK_new[i] = int(SDFK_new[i])

    return (SDFK_new, AGEB_new, SWTE_new, JTCN_new)


def boolean_list_to_int(boolean_list):
    return sum(1<<i for i, b in enumerate(boolean_list) if b)

def ascii_to_binary_list(ascii_value):
    return [int(x) for x in format(ascii_value, '08b')]