# Weather## Analysis
Decompiled C code:
```c__int64 __fastcall main(int a1, char **a2, char **a3, double a4){  int v5; // [rsp+0h] [rbp-40h] BYREF  const char *v6; // [rsp+8h] [rbp-38h]  const char *v7; // [rsp+10h] [rbp-30h] BYREF  int v8; // [rsp+18h] [rbp-28h]  const char *v9; // [rsp+20h] [rbp-20h]  int v10; // [rsp+30h] [rbp-10h] BYREF  const char *v11; // [rsp+38h] [rbp-8h]
  puts("Welcome to our global weather database!");  puts("What city are you interested in?");  __isoc99_scanf("%100s", s2);  if ( !strcmp("London", s2) )  {    v10 = 5;    v11 = "W";    v7 = "rain";    v8 = 1337;    v9 = "mm";    v5 = 10;    v6 = "°C";  }  else if ( !strcmp("Moscow", s2) )  {    v10 = 7;    v11 = "N";    v7 = "snow";    v8 = 250;    v9 = "cm";    v5 = -30;    v6 = "°C";  }  else if ( !strcmp("Miami", s2) )  {    v10 = 1;    v11 = "NE";    v7 = "sweat";    v8 = 100;    v9 = "ml";    v5 = 31337;    v6 = "°F";  }  else  {    v10 = 10;    v11 = "SW";    v7 = "nothing";    v8 = 0;    v9 = (const char *)&unk_30BE;    v5 = 15;    v6 = "°C";  }  puts("Weather for today:");  printf("Precipitation: %P\n", &v7, a4);  printf("Wind: %W\n", &v10);  printf("Temperature: %T\n", &v5;;  printf("Flag: %F\n", a4);  return 0LL;}```
Well, the main function seems to have nothing to do with the flag...Nevertheless, several special specifiers, such as `%P`, `%W`, `%T`, and `%F`, are presented in the format string. This implies that the generation of the flag may have something to do with customized specifiers.
After some investigation, I end up in a function located at `0x2328`, which contains several lines of code regarding `register_printf_function`.
```cint register(){  register_printf_function('W', (printf_function *)sub_2190, (printf_arginfo_function *)arginfo);  register_printf_function('P', (printf_function *)sub_21E0, (printf_arginfo_function *)arginfo);  register_printf_function('T', (printf_function *)sub_225A, (printf_arginfo_function *)arginfo);  register_printf_function('F', (printf_function *)sub_22AA, (printf_arginfo_function *)arginfo);  register_printf_function('C', (printf_function *)sub_2097, (printf_arginfo_function *)arginfo2);  register_printf_function('M', (printf_function *)sub_1185, (printf_arginfo_function *)arginfo2);  register_printf_function('S', (printf_function *)sub_12DC, (printf_arginfo_function *)arginfo2);  register_printf_function('O', (printf_function *)sub_143B, (printf_arginfo_function *)arginfo2);  register_printf_function('X', (printf_function *)sub_159A, (printf_arginfo_function *)arginfo2);  register_printf_function('V', (printf_function *)sub_16FA, (printf_arginfo_function *)arginfo2);  register_printf_function('N', (printf_function *)sub_185A, (printf_arginfo_function *)arginfo2);  register_printf_function('L', (printf_function *)sub_19B8, (printf_arginfo_function *)arginfo2);  register_printf_function('R', (printf_function *)sub_1B19, (printf_arginfo_function *)arginfo2);  register_printf_function('E', (printf_function *)sub_1C7A, (printf_arginfo_function *)arginfo2);  register_printf_function('I', (printf_function *)sub_1DD9, (printf_arginfo_function *)arginfo2);  return register_printf_function('U', (printf_function *)sub_1F38, (printf_arginfo_function *)arginfo2);}```
Well, we have already seen `%P`, `%W`, `%T`, and `%F` specifiers. After some brief reconnaissance, the remaining specifiers as well as their corresponding `print_function`s seem to insinuate a *virtual machine* written in format string.
Now, let's analyze the specifiers. We can inspect each `arginfo` structure with gdb. Take `%C` as an example:
```pwndbg> p *(printf_arginfo_function *)0x5555555562f0$1 = {int (const struct printf_info *, size_t, int *)} 0x5555555562f0pwndbg> p *(struct printf_info *)0x5555555562f0$2 = {  prec = -443987883,  width = -125990584,  spec = -260732600 L'\xf0758948',  is_long_double = 0,  is_short = 0,  is_long = 0,  alt = 1,  space = 0,  left = 0,  showsign = 1,  group = 0,  extra = 1,  is_char = 0,  wide = 0,  i18n = 1,  is_binary128 = 0,  __pad = 4,  user = 59477,  pad = 184 L'¸'}``````c__int64 __fastcall sub_2097(FILE *stream, struct printf_info *info, const void *const *args){  int prec; // [rsp+24h] [rbp-Ch]  _BOOL4 v5; // [rsp+2Ch] [rbp-4h]
  prec = info->prec;  if ( (*((_BYTE *)info + 12) & 0x20) != 0 )    // left space  {    v5 = dword_70A0[prec] < 0;  }  else if ( (*((_BYTE *)info + 12) & 0x40) != 0 ) // show sign  {    v5 = dword_70A0[prec] > 0;  }  else  {    v5 = info->pad != '0' || dword_70A0[prec] == 0; // pad field  }  if ( v5 )    fprintf(stream, &a52cS[info->width]);  return 0LL;}```So basically the handling function uses `left space`, `show sign`, and `pad field` to determine the value of `v5`. If `v5` is true, then it will call `fprintf` (Which means that the virtual machine will jump to `info->width` address).
Take the handling function of `%S` as another example:
```c__int64 __fastcall sub_12DC(FILE *stream, const struct printf_info *info, const void *const *args){  int prec; // [rsp+24h] [rbp-14h]  int width; // [rsp+28h] [rbp-10h]  int v6; // [rsp+2Ch] [rbp-Ch]  char *v7; // [rsp+30h] [rbp-8h]
  width = info->width;  prec = info->prec;  if ( (*((_BYTE *)info + 12) & 0x20) != 0 )    // left (%-)  {    v7 = &a52cS[width];  }  else if ( (*((_BYTE *)info + 12) & 0x40) != 0 )// show_sign (%+)  {    v7 = &a52cS[dword_70A0[width]];  }  else  {    v7 = (char *)&dword_70A0[width];  }  v6 = 0;  if ( (*((_BYTE *)info + 13) & 2) != 0 )       // char (%hh)  {    v6 = *(_DWORD *)&a52cS[prec];  }  else if ( (*((_BYTE *)info + 12) & 2) != 0 )  // short (%h)  {    v6 = *(_DWORD *)&a52cS[dword_70A0[prec]];  }  else if ( (*((_BYTE *)info + 12) & 1) != 0 )  // long double (%ll)  {    v6 = info->prec;  }  else if ( (*((_BYTE *)info + 12) & 4) != 0 )  // long (%l and %11, but %11 enters previous else if)  {    v6 = dword_70A0[prec];  }  *(_DWORD *)v7 += v6;  return 0LL;}```
The function uses `left` and `showsign` to calculate an address `v7`. Similarly, it uses either `char` or `short` or `long double` or `long` to calculate the value `v6`. Finally, it adds `v6` to `[v7]`. All of the other specifiers perform similar calculation. Below is a breif summary of the operations done by each handling function:
* `%C`: conditional branching* `%M`: =* `%S`: +* `%O`: -* `%X`: ** `%V`: /* `%N`: %* `%L`: <<* `%R`: >>* `%E`: ^* `%I`: &* `%U`: |
## `printf` assembly code
We can observer several suspicious format strings via `strings` command.
```bash...%52C%s%3.1hM%3.0lE%+1.3lM%1.4llS%3.1lM%3.2lO%-7.3C%0.4096hhM%0.255llI%1.0lM%1.8llL%0.1lU%1.0lM%1.16llL%0.1lU%1.200llM%2.1788llM%7C%-6144.1701736302llM%0.200hhM%0.255llI%0.37llO%0200.0C...```
Hmmm. `%52C%s` looks familiar. If we examine the handling function of specifier `%F`:
```c__int64 __fastcall sub_22AA(FILE *stream, const struct printf_info *info, const void *const *args, const char *a4){  return (unsigned int)fprintf(stream, "%52C%s", **(_QWORD **)args, a4);}```
We can clearly see that the string `%52C%s` is provided as the second parameter of `fprintf`. Correspondingly, it is to appropriate to postulate that `%52C%s` is the *entry point* of the printf virtual machine.
## DisassemblerHence, it is clear that our first goal is to come up with a printf virtual machine disassembler.
```pythonimport reimport sys
regex = re.compile('([+-0])?([0-9]*)\.?([0-9]*)?([a-z]*)([A-Za-z])')counter = 0
with open('assembly', 'r') as f:    fmt_str = f.read()    fmt_strs = fmt_str.split('\n')    for fmt_str in fmt_strs:        fmt_specs = fmt_str.split('%')        for fmt in fmt_specs:            if fmt == '':                continue            print('{:08x} '.format(counter), end = '')            counter += len(fmt) + 1
            g = regex.match(fmt).groups(0)            if g[4] == 'C':                if g[0] == 0:                    print('branch {:#x}'.format(int(g[1])))                elif g[0] == '0':                    print('if (reg{} == 0) branch {:#x}'.format(g[2], int(g[1])))                elif g[0] == '+':                    print('if (reg{} > 0) branch {:#x}'.format(g[2], int(g[1])))                elif g[0] == '-':                    print('if (reg{} < 0) branch {:#x}'.format(g[2], int(g[1])))
            # Probably used to print the flag            # It's ok to temporarily ignore it            elif g[4] == 's':                print('nop')                continue            else:                if g[0] == '0' and g[1] == '':                    g = (0, '0', g[2], g[3], g[4])                if g[0] == 0:                    print('reg{}'.format(g[1]), end = '')                elif g[0] == '+':                    print('[reg{}]'.format(g[1]), end = '')                elif g[0] == '-':                    print('[{:#x}]'.format(int(g[1])), end = '')
                if g[4] == 'M':                    print(' = ', end = '')                elif g[4] == 'S':                    print(' += ', end = '')                elif g[4] == 'O':                    print(' -= ', end = '')                elif g[4] == 'X':                    print(' *= ', end = '')                elif g[4] == 'V':                    print(' /= ', end = '')                elif g[4] == 'N':                    print(' %= ', end = '')                elif g[4] == 'L':                    print(' <<= ', end = '')                elif g[4] == 'R':                    print(' >>= ', end = '')                elif g[4] == 'E':                    print(' ^= ', end = '')                elif g[4] == 'I':                    print(' &= ', end = '')                elif g[4] == 'U':                    print(' |= ', end = '')
                if g[3] == 'hh':                    print('[{:#x}]'.format(int(g[2])))                elif g[3] == 'h':                    print('[reg{}]'.format(g[2]))                elif g[3] == 'll':                    print('{:#x}'.format(int(g[2])))                elif g[3] == 'l':                    print('reg{}'.format(g[2]))
        print('{:08x} '.format(counter), end = '')        counter += 1        print('return\n')```
## Analyze disassembly### Part 1Disassembling the two suspicious format strings yields the following pseudo assembly code:
```00000000 branch 0x3400000004 nop00000006 return
00000007 reg3 = [reg1]0000000d reg3 ^= reg000000013 [reg1] = reg30000001a reg1 += 0x400000021 reg3 = reg100000027 reg3 -= reg20000002d if (reg3 < 0) branch 0x700000033 return
00000034 reg0 = [0x1000]0000003e reg0 &= 0xff00000047 reg1 = reg00000004d reg1 <<= 0x800000054 reg0 |= reg10000005a reg1 = reg000000060 reg1 <<= 0x1000000068 reg0 |= reg10000006e reg1 = 0xc800000077 reg2 = 0x6fc00000081 branch 0x700000084 [0x1800] = 0x656e6f6e00000098 reg0 = [0xc8]000000a1 reg0 &= 0xff000000aa reg0 -= 0x25000000b2 if (reg0 == 0) branch 0xc8000000ba return```
According to the assembly, the purpose of function `0x7` is to xor the byte from address `reg1` to address `reg2` with character `reg0`. Function `0x34` jumps to function `0x7` with argument (0x25 ('%'), 0xc8, 0x6fc) and check whether the character at 0xc8 is 0x25. If the condition is true, the main function will jumps to 0xc8.
This part of the assembly code has nothing to do with the generation of the flag. Hence, let's grab the bytes between 0xc8 & 0x6fc and xor these bytes with '%'.
```python3from pwn import ELFelf = ELF('./weather')fmt_xor = elf.read(0x5148, 0x634)xor_char = fmt_xor[0] ^ ord('%')fmt_str = bytes(x ^ xor_char for x in fmt_xor)fmt_str = fmt_str.replace(b'\0', b'\n')fmt_str = fmt_str.decode()print(fmt_str)```
Result:
```%4.5000llM%0.13200llM%337C%0.0llM%500C%1262C%0653.0C%1.0llM%3.0lM%3.2lN%0253.3C%2.1llS%3.2lM%3.3lX%3.0lO%3.1llO%-261.3C%+4.0lM%4.2llS%1.1llM%2.2llM%261C%+322.1C%0.1llS%1.13600llM%1.0lO%+337.1C%0.0llM%0.2llV%0.3llX%0.1llS%1.0lM%1.2llN%0405.1C%+413.1C%470C%0.1llS%1.0lM%1.1llO%0397.1C%+428.1C%2.0lM%2.4096llS%4.2hM%4.255llI%+540.4C%2.0lM%2.2llX%2.5000llS%2.2hM%2.255llI%4.2lE%0.1llS%2.0lM%470C%4.0lS%4.255llI%0.2lM%2.1llO%2.4500llS%+2.4lM%500C%0.123456789llM%1.0llM%1.4096llS%1.1hM%0.1lE%2.0llM%2.846786818llS%2.0lE%1.0llM%1.6144llS%+1.2lM%1.4llM%1.4096llS%1.1hM%0.1lE%2.0llM%2.1443538759llS%2.0lE%1.4llM%1.6144llS%+1.2lM%1.8llM%1.4096llS%1.1hM%0.1lE%2.0llM%2.1047515510llS%2.0lE%1.8llM%1.6144llS%+1.2lM%1.12llM%1.4096llS%1.1hM%0.1lE%2.0llM%2.359499514llS%2.1724461856llS%2.0lE%1.12llM%1.6144llS%+1.2lM%1.16llM%1.4096llS%1.1hM%0.1lE%2.0llM%2.241024035llS%2.0lE%1.16llM%1.6144llS%+1.2lM%1.20llM%1.4096llS%1.1hM%0.1lE%2.0llM%2.222267724llS%2.0lE%1.20llM%1.6144llS%+1.2lM%1.24llM%1.4096llS%1.1hM%0.1lE%2.0llM%2.844096018llS%2.0lE%1.24llM%1.6144llS%+1.2lM%0.0llM%1.0llM%1.4500llS%1.1hM%2.0llM%2.1374542625llS%2.1686915720llS%2.1129686860llS%1.2lE%0.1lU%1.4llM%1.4500llS%1.1hM%2.0llM%2.842217029llS%2.1483902564llS%1.2lE%0.1lU%1.8llM%1.4500llS%1.1hM%2.0llM%2.1868013731llS%1.2lE%0.1lU%1.12llM%1.4500llS%1.1hM%2.0llM%2.584694732llS%2.1453312700llS%1.2lE%0.1lU%1.16llM%1.4500llS%1.1hM%2.0llM%2.223548744llS%1.2lE%0.1lU%1.20llM%1.4500llS%1.1hM%2.0llM%2.1958883726llS%2.1916008099llS%1.2lE%0.1lU%1.24llM%1.4500llS%1.1hM%2.0llM%2.1829937605llS%2.1815356086llS%2.253836698llS%1.2lE%0.1lU```
### Part 2Now, let's disassemble each format string and analyze it one by one.
First, let's focus on the function at 0xc8:
```000000c8 reg4 = 0x1388000000d2 reg0 = 0x3390000000dd branch 0x151000000e2 reg0 = 0x0000000e9 branch 0x1f4000000ee branch 0x4ee000000f4 if (reg0 == 0) branch 0x28d000000fc return```
It looks like this function set reg4 to 0x1338 and reg0 to 0x3390, tries to call several functions related to flag generation, checks if the result is 0, and jumps to 0x28d if the condition is met. Since these interal functions are essential to our flag generation, it is natural for us to investigate them in details.
### Part 3: function 0x151Now, let's examine function 0x151 and its accompanied sub functions:
```000000fd reg1 = 0x000000104 return
00000105 reg3 = reg00000010b reg3 %= reg200000111 if (reg3 == 0) branch 0xfd00000119 reg2 += 0x100000120 reg3 = reg200000126 reg3 *= reg30000012c reg3 -= reg000000132 reg3 -= 0x100000139 if (reg3 < 0) branch 0x10500000141 return
00000142 [reg4] = reg000000149 reg4 += 0x200000150 return
00000151 reg1 = 0x100000158 reg2 = 0x20000015f branch 0x10500000164 if (reg1 > 0) branch 0x1420000016c reg0 += 0x100000173 reg1 = 0x35200000017e reg1 -= reg000000184 if (reg1 > 0) branch 0x1510000018c return```
So basically the function first set reg1 to 1 and reg2 to 2 and jumps to 0x105 first. Then, it gradually perform `reg3 % reg2` (a.k.a reg0 % reg2), starting from 2 and stopping when `reg3 * reg3 >= reg0`. from 0x1111 we can see that if `reg3 % reg2 == 0` (which means that reg2 is a factor of reg3), then the function will call 0xfd and set reg1 to 0. 
Sounds familiar, right? It appears that function 0x105 is a prime checker!
Now, let's go back to function 0x151. after calling 0x105, if reg1 is greater that 0, which means that reg0 is a prime number, the function will jump to 0x142, which stores reg0 into reg4. After that, reg4 will increment itself by 2, and the loop continues until 0x3520.
In conclusion, what this function tries to do is to calculate all primes within 0x3390 and 0x3520, then store all of these primes in 0x1388 as an int16_t array.
### Part 4: function 0x1f4
```0000018d reg0 = 0x000000194 return
00000195 reg0 /= 0x20000019c return
0000019d reg0 *= 0x3000001a4 reg0 += 0x1000001ab return
000001ac reg1 = reg0000001b2 reg1 %= 0x2000001b9 if (reg1 == 0) branch 0x195000001c1 if (reg1 > 0) branch 0x19d000001c9 branch 0x1d6000001ce reg0 += 0x1000001d5 return
000001d6 reg1 = reg0000001dc reg1 -= 0x1000001e3 if (reg1 == 0) branch 0x18d000001eb if (reg1 > 0) branch 0x1ac000001f3 return
000001f4 reg2 = reg0000001fa reg2 += 0x100000000204 reg4 = [reg2]0000020a reg4 &= 0xff00000213 if (reg4 > 0) branch 0x21c0000021b return
0000021c reg2 = reg000000222 reg2 *= 0x200000229 reg2 += 0x138800000233 reg2 = [reg2]00000239 reg2 &= 0xff00000242 reg4 ^= reg200000248 reg0 += 0x10000024f reg2 = reg000000255 branch 0x1d60000025a reg4 += reg000000260 reg4 &= 0xff00000269 reg0 = reg20000026f reg2 -= 0x100000276 reg2 += 0x119400000280 [reg2] = reg400000287 branch 0x1f40000028c return```
Well, after spotting function 0x195 and function 0x19d, I immediately thought of the Collatz conjecture (3n+1 problem)!
I won't go into details here since the assembly are quite lengthy.
In conclusion, what the function is trying to do is calculate `((primes[i] & 0xff)) + Collatz(i + 1)) & 0xff` and store these values in 0x1194 as a byte array.
### Part 5: function 0x4ee and function 0x28d
```000004ee reg0 = 0x0000004f5 reg1 = 0x0000004fc reg1 += 0x119400000506 reg1 = [reg1]0000050c reg2 = 0x000000513 reg2 += 0x51eddb2100000523 reg2 += 0x648c4a8800000533 reg2 += 0x4355a74c00000543 reg1 ^= reg200000549 reg0 |= reg10000054f reg1 = 0x400000556 reg1 += 0x119400000560 reg1 = [reg1]00000566 reg2 = 0x00000056d reg2 += 0x323336450000057c reg2 += 0x58728e640000058c reg1 ^= reg200000592 reg0 |= reg100000598 reg1 = 0x80000059f reg1 += 0x1194000005a9 reg1 = [reg1]000005af reg2 = 0x0000005b6 reg2 += 0x6f57a0a3000005c6 reg1 ^= reg2000005cc reg0 |= reg1000005d2 reg1 = 0xc000005da reg1 += 0x1194000005e4 reg1 = [reg1]000005ea reg2 = 0x0000005f1 reg2 += 0x22d9bbcc00000600 reg2 += 0x569fcabc00000610 reg1 ^= reg200000616 reg0 |= reg10000061c reg1 = 0x1000000624 reg1 += 0x11940000062e reg1 = [reg1]00000634 reg2 = 0x00000063b reg2 += 0xd5315480000064a reg1 ^= reg200000650 reg0 |= reg100000656 reg1 = 0x140000065e reg1 += 0x119400000668 reg1 = [reg1]0000066e reg2 = 0x000000675 reg2 += 0x74c2318e00000685 reg2 += 0x7233f6a300000695 reg1 ^= reg20000069b reg0 |= reg1000006a1 reg1 = 0x18000006a9 reg1 += 0x1194000006b3 reg1 = [reg1]000006b9 reg2 = 0x0000006c0 reg2 += 0x6d12a1c5000006d0 reg2 += 0x6c3422b6000006e0 reg2 += 0xf213d9a000006ef reg1 ^= reg2000006f5 reg0 |= reg1000006fb return```
```0000028d reg0 = 0x75bcd150000029c reg1 = 0x0000002a3 reg1 += 0x1000000002ad reg1 = [reg1]000002b3 reg0 ^= reg1000002b9 reg2 = 0x0000002c0 reg2 += 0x3278f102000002cf reg2 ^= reg0000002d5 reg1 = 0x0000002dc reg1 += 0x1800000002e6 [reg1] = reg2000002ed reg1 = 0x4000002f4 reg1 += 0x1000000002fe reg1 = [reg1]00000304 reg0 ^= reg10000030a reg2 = 0x000000311 reg2 += 0x560aa74700000321 reg2 ^= reg000000327 reg1 = 0x40000032e reg1 += 0x180000000338 [reg1] = reg20000033f reg1 = 0x800000346 reg1 += 0x100000000350 reg1 = [reg1]00000356 reg0 ^= reg10000035c reg2 = 0x000000363 reg2 += 0x3e6fd17600000373 reg2 ^= reg000000379 reg1 = 0x800000380 reg1 += 0x18000000038a [reg1] = reg200000391 reg1 = 0xc00000399 reg1 += 0x1000000003a3 reg1 = [reg1]000003a9 reg0 ^= reg1000003af reg2 = 0x0000003b6 reg2 += 0x156d86fa000003c5 reg2 += 0x66c93320000003d5 reg2 ^= reg0000003db reg1 = 0xc000003e3 reg1 += 0x1800000003ed [reg1] = reg2000003f4 reg1 = 0x10000003fc reg1 += 0x100000000406 reg1 = [reg1]0000040c reg0 ^= reg100000412 reg2 = 0x000000419 reg2 += 0xe5dbc2300000428 reg2 ^= reg00000042e reg1 = 0x1000000436 reg1 += 0x180000000440 [reg1] = reg200000447 reg1 = 0x140000044f reg1 += 0x100000000459 reg1 = [reg1]0000045f reg0 ^= reg100000465 reg2 = 0x00000046c reg2 += 0xd3f894c0000047b reg2 ^= reg000000481 reg1 = 0x1400000489 reg1 += 0x180000000493 [reg1] = reg20000049a reg1 = 0x18000004a2 reg1 += 0x1000000004ac reg1 = [reg1]000004b2 reg0 ^= reg1000004b8 reg2 = 0x0000004bf reg2 += 0x324fe212000004ce reg2 ^= reg0000004d4 reg1 = 0x18000004dc reg1 += 0x1800000004e6 [reg1] = reg2000004ed return```
These 2 functions look like flag generation code!
Again, I won't go into much detail here. In short, these two functions perform a series of operations on your input, and then check whether the values obtained after these operations are equal to some array of values. This kind of operation involving sophisticated xor operations byte by byte are often seen in CTF. Since the flag generation function operates byte by byte, it is possible to brute force each character one by one and obtain the flag.
The full flag generation script can be found below.
## Flag generation
```pythonprimes = [13217, 13219, 13229, 13241, 13249, 13259, 13267, 13291, 13297, 13309, 13313, 13327, 13331, 13337, 13339, 13367, 13381, 13397, 13399, 13411, 13417, 13421, 13441, 13451, 13457, 13463, 13469, 13477, 13487, 13499, 13513, 13523, 13537, 13553, 13567, 13577, 13591, 13597]magic = 123456789secrets = [846786818, 1443538759, 1047515510, 359499514 + 1724461856, 241024035, 222267724, 844096018]sanity = [1374542625 + 1686915720 + 1129686860, 842217029 + 1483902564, 1868013731, 584694732 + 1453312700, 223548744, 1958883726 + 1916008099, 1829937605 + 1815356086 + 253836698]
sanity_bytes = b''.join(i.to_bytes(4, 'little') for i in sanity)
def three_n_plus_one(n):    if n == 1:        return 0    else:        if n % 2 == 0:            n //= 2        else:            n = 3 * n + 1        t = three_n_plus_one(n)        return t + 1
chars = []
for i in range(28):    for c in range(255):        if ((c ^ (primes[i] & 0xff)) + three_n_plus_one(i + 1)) & 0xff == sanity_bytes[i]:            chars.append(c)
flag = b''for i in range(7):    char_int = int.from_bytes(bytes(chars[i * 4: (i + 1) * 4]), 'little')    magic ^= char_int    flag_part = magic ^ secrets[i]    flag += flag_part.to_bytes(4, 'little')
print(flag)```
## Flag```CTF{curs3d_r3curs1ve_pr1ntf}```