Skip to content

Latest commit

 

History

History
1076 lines (956 loc) · 39.9 KB

WRITEUP.md

File metadata and controls

1076 lines (956 loc) · 39.9 KB

Late Stage Capitalism: Write-up

Посмотрим, что приложено к заданию.

Во-первых, есть сайт, где можно купить UCUCUGA PRO EDITION за целый 1 BTC. При этом указан адрес, на который надо перевести деньги. Адреса Bitcoin содержат в себе контрольную сумму; у данного адреса контрольная сумма не сходится, поэтому по-честному купить уцуцугу не получится.

Также есть кнопка для получения уцуцуги по ключу. В коде страницы видим, что эта кнопка обрабатывается следующим образом:

async function have() {
    const key = prompt("Введите ключ активации:");
    alert(await (await fetch(`get-flag/${key}`)).text());
}

Таким образом, чтобы получить флаг, нам просто нужно узнать ключ активации. Запомним это.


Теперь посмотрим на программу keygen. После запуска и ввода токена видим:

UCUCUGA PRO KEYGEN by xXx_HACKERNAME_xXx
Enter token: maziserywq8vln9u
You need to obtain a key from me to use this keygen.
Please mail [email protected]. We will agree on the payment.
Enter key from purplesyringa: 

Ага, понятно. Чтобы получить ключ к уцуцуге, нужно сначала получить ключ к кейгену. На это указывает и фраза «Другое дело, что хакерам тоже хочется кушать» из условия задачи.


Будем реверсить keygen. Воспользуемся для этого IDA Free (ближе к концу соревнования она была приложена к заданию; до этого можно было использовать свою копию, если она была в образе виртуальной машины, либо найти альтернативный способ её скачать). Задание также можно решить с помощью Ghidra.

Декомпилируем main (нажатием F5):

// local variable allocation has failed, the output may be wrong!
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
  char **v3; // rdx

  __shedskin__::__init(*(__shedskin__ **)&argc);
  __sys__::__init((__sys__ *)(unsigned int)argc, (int)argv, v3);
  __shedskin__::__start((__shedskin__ *)__keygen__::__init, (void (*)(void))argv);
}

Сразу натыкаемся на интересное название пространства имён __shedskin__. Загуглим shedskin. Оказывается, что это транслятор Python в C++, который затем был скомпилирован в бинарный код, к счастью, с символами.

Судя по всему, __init инициализирует рантайм, так что заглянем внутри функции __start. Она не содержит ничего особо интересного, но вызывает первый аргумент:

void __fastcall __noreturn __shedskin__::__start(__shedskin__ *this, void (*a2)(void))
{
  std::set_terminate((void (*)(void))__shedskin__::terminate_handler);
  ((void (__fastcall *)(void (__fastcall __noreturn *)(__shedskin__ *__hidden), void (*)(void)))this)(
    __shedskin__::terminate_handler,
    a2);
  exit(0);
}

Значит, заглянем в __keygen__::__init__:

__int64 __fastcall __keygen__::__init(__keygen__ *this)
{
  __shedskin__::str *v1; // rax
  __shedskin__::str *v2; // rbx
  __shedskin__::str *v3; // rax
  __shedskin__ *v4; // rbx
  __shedskin__::str *v5; // rax
  __int64 v6; // rbx
  __shedskin__::str *v7; // rax
  __int64 v8; // rbx
  __shedskin__::str *v9; // rax
  __int64 v10; // rbx
  __shedskin__::str *v11; // rax
  __int64 v12; // rbx
  __shedskin__::str *v13; // rax
  __int64 v14; // rbx
  __shedskin__::str *v15; // rax
  __int64 v16; // rbx
  __shedskin__::str *v17; // rax
  __int64 v18; // rbx
  __shedskin__::str *v19; // rax
  __shedskin__::str *v20; // rbx
  __shedskin__::str *v21; // rax
  __int64 v22; // rbx
  __shedskin__::str *v23; // rax
  __int64 v24; // rbx
  __keygen__ *v25; // rdi

  v1 = (__shedskin__::str *)GC_malloc(0x40uLL);
  v2 = v1;
  if ( !v1 )
    GC_throw_bad_alloc();
  __shedskin__::str::str(v1, "0123456789abcdef");
  __keygen__::const_0 = v2;
  v3 = (__shedskin__::str *)GC_malloc(0x40uLL);
  v4 = v3;
  if ( !v3 )
    GC_throw_bad_alloc();
  __shedskin__::str::str(v3, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-");
  __keygen__::const_1 = v4;
  v5 = (__shedskin__::str *)GC_malloc(0x40uLL);
  v6 = (__int64)v5;
  if ( !v5 )
    GC_throw_bad_alloc();
  __shedskin__::str::str(v5, &path);
  __keygen__::const_2 = v6;
  v7 = (__shedskin__::str *)GC_malloc(0x40uLL);
  v8 = (__int64)v7;
  if ( !v7 )
    GC_throw_bad_alloc();
  __shedskin__::str::str(v7, "Invalid hex format");
  __keygen__::const_3 = v8;
  v9 = (__shedskin__::str *)GC_malloc(0x40uLL);
  v10 = (__int64)v9;
  if ( !v9 )
    GC_throw_bad_alloc();
  __shedskin__::str::str(v9, "UCUCUGA PRO KEYGEN by xXx_HACKERNAME_xXx");
  __keygen__::const_4 = v10;
  v11 = (__shedskin__::str *)GC_malloc(0x40uLL);
  v12 = (__int64)v11;
  if ( !v11 )
    GC_throw_bad_alloc();
  __shedskin__::str::str(v11, "Enter token: ");
  __keygen__::const_5 = v12;
  v13 = (__shedskin__::str *)GC_malloc(0x40uLL);
  v14 = (__int64)v13;
  if ( !v13 )
    GC_throw_bad_alloc();
  __shedskin__::str::str(v13, "You need to obtain a key from me to use this keygen.");
  __keygen__::const_6 = v14;
  v15 = (__shedskin__::str *)GC_malloc(0x40uLL);
  v16 = (__int64)v15;
  if ( !v15 )
    GC_throw_bad_alloc();
  __shedskin__::str::str(v15, "Please mail [email protected]. We will agree on the payment.");
  __keygen__::const_7 = v16;
  v17 = (__shedskin__::str *)GC_malloc(0x40uLL);
  v18 = (__int64)v17;
  if ( !v17 )
    GC_throw_bad_alloc();
  __shedskin__::str::str(v17, "Enter key from purplesyringa: ");
  __keygen__::const_8 = v18;
  v19 = (__shedskin__::str *)GC_malloc(0x40uLL);
  v20 = v19;
  if ( !v19 )
    GC_throw_bad_alloc();
  __shedskin__::str::str(v19, "UCUCUGA Pro key: ");
  __keygen__::const_9 = v20;
  v21 = (__shedskin__::str *)GC_malloc(0x40uLL);
  v22 = (__int64)v21;
  if ( !v21 )
    GC_throw_bad_alloc();
  __shedskin__::str::str(v21, "The key is invalid.");
  __keygen__::const_10 = v22;
  v23 = (__shedskin__::str *)GC_malloc(0x40uLL);
  v24 = (__int64)v23;
  if ( !v23 )
    GC_throw_bad_alloc();
  v25 = v23;
  __shedskin__::str::str(v23, "__main__");
  __keygen__::__name__ = v24;
  return __keygen__::__ss_main(v25);
}

Здесь, по-видимому, преаллоцируются строки, а затем запускается функция __ss_main. Откроем её:

__int64 __fastcall __keygen__::__ss_main(__keygen__ *this)
{
  __shedskin__ *v1; // rbp
  __keygen__ *v2; // rax
  __int64 *v3; // r12
  _QWORD *v4; // rax
  __int64 v5; // rcx
  _QWORD *v6; // rbx
  __int64 v7; // rax
  __int64 v8; // rax
  __int64 (__fastcall *v9)(); // rax
  __int64 v10; // rax
  __int64 v11; // rdx
  __int64 v12; // rdx
  __int64 v13; // rcx
  char v14; // al
  __int64 v15; // r8
  __shedskin__::str *ucucuga_key; // rax
  __int64 v18; // [rsp+8h] [rbp-20h]

  __shedskin__::print<__shedskin__::str *>((unsigned __int8)__shedskin__::False, 0LL, 0LL, 0LL, __keygen__::const_4);
  __shedskin__::print<__shedskin__::str *>(
    (unsigned __int8)__shedskin__::False,
    0LL,
    __keygen__::const_2,
    0LL,
    __keygen__::const_5);
  (*(void (__fastcall **)(__int64))(*(_QWORD *)__sys__::__ss_stdout + 168LL))(__sys__::__ss_stdout);
  v1 = (__shedskin__ *)__shedskin__::input(0LL, 0LL);
  __shedskin__::print<__shedskin__::str *>((unsigned __int8)__shedskin__::False, 0LL, 0LL, 0LL, __keygen__::const_6);
  __shedskin__::print<__shedskin__::str *>((unsigned __int8)__shedskin__::False, 0LL, 0LL, 0LL, __keygen__::const_7);
  __shedskin__::print<__shedskin__::str *>(
    (unsigned __int8)__shedskin__::False,
    0LL,
    __keygen__::const_2,
    0LL,
    __keygen__::const_8);
  (*(void (__fastcall **)(__int64))(*(_QWORD *)__sys__::__ss_stdout + 168LL))(__sys__::__ss_stdout);
  v2 = (__keygen__ *)__shedskin__::input(0LL, 0LL);
  v3 = (__int64 *)__keygen__::parse_hex(v2, 0LL);
  v4 = GC_malloc(0x28uLL);
  v6 = v4;
  if ( !v4 )
    GC_throw_bad_alloc();
  v4[2] = 0LL;
  *v4 = &off_5468E0;
  v7 = __shedskin__::cl_list;
  v6[3] = 0LL;
  v6[1] = v7;
  v8 = *v3;
  v6[4] = 0LL;
  v9 = *(__int64 (__fastcall **)())(v8 + 96);
  if ( v9 == __shedskin__::list<int>::__len__ )
    v10 = (v3[3] - v3[2]) >> 2;
  else
    LODWORD(v10) = ((__int64 (__fastcall *)(__int64 *))v9)(v3);
  v11 = (int)v10;
  if ( (int)v10 > 0 || (v5 = 0LL, (_DWORD)v10) )
  {
    v18 = (int)v10;
    std::vector<int,gc_allocator<int>>::_M_default_append(v6 + 2, (int)v10, (int)v10, v5);
    v5 = v6[2];
    v11 = 4 * v18;
  }
  v13 = _memcpy_fwd(v5, v3[2], v11);
  if ( (unsigned int)((v6[3] - v13) >> 2) == 16 )
    v14 = __keygen__::validate_purplesyringa_key(v1);
  else
    v14 = __shedskin__::False;
  v15 = __keygen__::const_10;
  if ( v14 )
  {
    ucucuga_key = (__shedskin__::str *)__keygen__::generate_ucucuga_key(v1, v3, v12, v13, __keygen__::const_10);
    v15 = __shedskin__::str::__add__(__keygen__::const_9, ucucuga_key);
  }
  __shedskin__::print<__shedskin__::str *>((unsigned __int8)__shedskin__::False, 0LL, 0LL, 0LL, v15);
  return 0LL;
}

Постараемся проигнорировать весь мусор и предположим по вызовам функций, как это выглядело бы в коде на Python. Единственная нетривиальная часть — догадаться по девиртуализованному вызову list::__len__, что значения по индексам 2 и 3 у списка содержат адресы начала и конца массива соответственно.

def main():
    print(???)
    print(???)
    sys.stdout.???()
    v1 = input()
    print(???)
    print(???)
    print(???)
    sys.stdout.???()
    v2 = input()
    v3 = parse_hex(v2)
    v6 = list(v3)
    if len(v6) == 16:
        v14 = validate_purplesyringa_key(v1)
    else:
        v14 = False
    v15 = ???
    if v14:
        ucucuga_key = generate_ucucuga_key(v1, v3);
        v15 = ??? + ucucuga_key
    print(v15)

Итак, сначала токен вводится в v1, затем ключ от кейгена считывается как hex в v3, проверяется, что он занимает 16 байт и validate_purplesyringa_key выдает True, и в случае успеха вызывается generate_ucucuga_key. Единственная странность заключается в том, что validate_purplesyringa_key принимает на вход только токен, а не только что считанный в v6 ключ.

Ну ничего, откроем validate_purplesyringa_key:

__int64 __fastcall __keygen__::validate_purplesyringa_key(__shedskin__ *this, __int64 a2)
{
  __int64 v2; // r12
  __shedskin__ *v3; // rbp
  __int64 v5; // rcx
  __int64 v6; // rsi
  __int64 v7; // rdx
  int *v8; // rax
  int v9; // r13d
  __int64 v10; // r14
  __int64 i; // r13
  __int64 v12; // r14
  int v13; // eax
  bool v14; // sf
  int v15; // r12d
  int v16; // eax
  int v17; // ecx
  int v18; // r15d
  int j; // r14d
  signed int v20; // eax
  signed int v21; // r12d
  int v22; // r13d
  __int64 v23; // r12
  int v24; // ecx
  __int64 v25; // r14
  int v26; // r15d
  __int64 (__fastcall *v27)(__shedskin__::str *__hidden); // rax

  v2 = 0LL;
  v3 = this;
  v5 = *(_QWORD *)(a2 + 16);
  v6 = *(_QWORD *)(a2 + 24);
  v7 = v5;
  do
  {
    v10 = 4 * v2;
    if ( (int)v2 >= (int)((v6 - v5) >> 2) )
      __shedskin__::__throw_index_out_of_range(this);
    v8 = (int *)(v5 + v10);
    v9 = *(_DWORD *)(v5 + 4 * v2);
    if ( !v9 )
    {
      v9 = 256;
      v7 = v5;
      v8 = (int *)(v5 + v10);
    }
    ++v2;
    *v8 = v9;
  }
  while ( v2 != 16 );
  for ( i = 0LL; i != 15; ++i )
  {
    v17 = (v6 - v7) >> 2;
    if ( v17 <= (int)i + 1 )
      __shedskin__::__throw_index_out_of_range(this);
    v12 = 4 * i;
    if ( v17 <= (int)i )
      __shedskin__::__throw_index_out_of_range(this);
    v13 = *(_DWORD *)(v7 + 4 * i) * *(_DWORD *)(v7 + 4 * i + 4);
    this = (__shedskin__ *)(unsigned int)(257 * (v13 / 257));
    v13 %= 257;
    v14 = v13 < 0;
    v15 = v13;
    v16 = v13 + 257;
    if ( v14 )
      v15 = v16;
    if ( (int)i + 1 >= v17 )
      __shedskin__::__throw_index_out_of_range(this);
    *(_DWORD *)(v7 + v12 + 4) = v15;
  }
  v18 = 433;
  for ( j = 743893; j != 743893000; j += 743893 )
  {
    v22 = (j >> 16) & 0xF;
    v23 = (v18 >> 4) & 0xF;
    if ( v22 != (_DWORD)v23 )
    {
      v24 = (v6 - v7) >> 2;
      if ( v22 >= v24 )
        __shedskin__::__throw_index_out_of_range(this);
      this = (__shedskin__ *)v22;
      if ( (int)v23 >= v24 )
        __shedskin__::__throw_index_out_of_range((__shedskin__ *)v22);
      v20 = (unsigned __int8)(((unsigned int)((*(_DWORD *)(v7 + 4 * v23) + *(_DWORD *)(v7 + 4LL * v22)) >> 31) >> 24)
                            + *(_BYTE *)(v7 + 4 * v23)
                            + *(_DWORD *)(v7 + 4LL * v22))
          - ((unsigned int)((*(_DWORD *)(v7 + 4 * v23) + *(_DWORD *)(v7 + 4LL * v22)) >> 31) >> 24);
      v21 = v20 + 256;
      if ( v20 >= 0 )
        v21 = v20;
      if ( v22 >= v24 )
        __shedskin__::__throw_index_out_of_range((__shedskin__ *)v22);
      *(_DWORD *)(v7 + 4LL * v22) = v21;
    }
    v18 += 433;
  }
  v25 = 0LL;
  while ( 1 )
  {
    if ( (int)v25 >= (int)((v6 - v7) >> 2) )
      __shedskin__::__throw_index_out_of_range(this);
    v26 = *(_DWORD *)(v7 + 4 * v25);
    v27 = *(__int64 (__fastcall **)(__shedskin__::str *__hidden))(*(_QWORD *)v3 + 96LL);
    if ( v27 == __shedskin__::str::__len__ )
    {
      if ( (int)v25 >= *((_DWORD *)v3 + 6) )
        goto LABEL_38;
    }
    else
    {
      this = v3;
      if ( (int)v25 >= (int)v27(v3) )
LABEL_38:
        __shedskin__::__throw_index_out_of_range(this);
    }
    this = *(__shedskin__ **)(__shedskin__::__char_cache + 8LL * *(unsigned __int8 *)(*((_QWORD *)v3 + 2) + v25));
    if ( *((_QWORD *)this + 3) != 1LL )
      __keygen__::validate_purplesyringa_key();
    if ( *(unsigned __int8 *)__shedskin__::str::c_str(this) != v26 )
      return (unsigned __int8)__shedskin__::False;
    if ( ++v25 == 16 )
      return (unsigned __int8)__shedskin__::True;
    v6 = *(_QWORD *)(a2 + 24);
    v7 = *(_QWORD *)(a2 + 16);
  }
}

Пролистаем код. Бросается в глаза запись в this. По-видимому, это на самом деле не this, и IDA неправильно предположила, что функция — метод класса. На самом деле, если посмотреть на mangled имя функции, мы увидим:

_ZN10__keygen__26validate_purplesyringa_keyEPN12__shedskin__3strEPNS0_4listIiEE.part.0

То есть функция принимает __shedskin__::str * и __shedskin__::list<int> *, а не те типы, которые вывела IDA. Переименуем this в token, a2 — в purplesyringa_key. Опираясь на знание о том, что (v6 - v5) >> 2) — скорее всего, вычисление размера массива, создадим из списка структуру с полями-указателями int *start, *end;.

После этого код становится более читабельным, и можно немного переименовать переменные:

__int64 __fastcall __keygen__::validate_purplesyringa_key(__shedskin__::str *token, list_int *purplesyringa_key)
{
  __int64 i_1; // r12
  struct_v3 *v3; // rbp
  int *start1; // rcx
  int *end1; // rsi
  int *start; // rdx
  int *v8; // rax
  int v9; // r13d
  __int64 offset; // r14
  __int64 i; // r13
  __int64 v12; // r14
  int x; // eax
  bool is_negative; // sf
  int v15; // r12d
  int v16; // eax
  int length; // ecx
  int v18; // r15d
  int j; // r14d
  signed int x_1; // eax
  signed int v21; // r12d
  int i_2; // r13d
  __int64 j_1; // r12
  int length_1; // ecx
  __int64 i_3; // r14
  int v26; // r15d
  __int64 (__fastcall *length_getter)(__shedskin__::str *__hidden); // rax

  i_1 = 0LL;
  v3 = (struct_v3 *)token;
  start1 = purplesyringa_key->start;
  end1 = purplesyringa_key->end;
  start = start1;
  do
  {
    offset = i_1;
    if ( (int)i_1 >= (int)(end1 - start1) )
      __shedskin__::__throw_index_out_of_range(token);
    v8 = &start1[offset];
    v9 = start1[i_1];
    if ( !v9 )
    {
      v9 = 256;
      start = start1;
      v8 = &start1[offset];
    }
    ++i_1;
    *v8 = v9;
  }
  while ( i_1 != 16 );
  for ( i = 0LL; i != 15; ++i )
  {
    length = end1 - start;
    if ( length <= (int)i + 1 )
      __shedskin__::__throw_index_out_of_range(token);
    v12 = i;
    if ( length <= (int)i )
      __shedskin__::__throw_index_out_of_range(token);
    x = start[i] * start[i + 1];
    token = (__shedskin__::str *)(unsigned int)(257 * (x / 257));
    x %= 257;
    is_negative = x < 0;
    v15 = x;
    v16 = x + 257;
    if ( is_negative )
      v15 = v16;
    if ( (int)i + 1 >= length )
      __shedskin__::__throw_index_out_of_range(token);
    start[v12 + 1] = v15;
  }
  v18 = 433;
  for ( j = 743893; j != 743893000; j += 743893 )
  {
    i_2 = (j >> 16) & 0xF;
    j_1 = (v18 >> 4) & 0xF;
    if ( i_2 != (_DWORD)j_1 )
    {
      length_1 = end1 - start;
      if ( i_2 >= length_1 )
        __shedskin__::__throw_index_out_of_range(token);
      token = (__shedskin__::str *)i_2;
      if ( (int)j_1 >= length_1 )
        __shedskin__::__throw_index_out_of_range((__shedskin__ *)i_2);
      x_1 = (unsigned __int8)(((unsigned int)((start[j_1] + start[i_2]) >> 31) >> 24) + LOBYTE(start[j_1]) + start[i_2])
          - ((unsigned int)((start[j_1] + start[i_2]) >> 31) >> 24);
      v21 = x_1 + 256;
      if ( x_1 >= 0 )
        v21 = x_1;
      if ( i_2 >= length_1 )
        __shedskin__::__throw_index_out_of_range((__shedskin__ *)i_2);
      start[i_2] = v21;
    }
    v18 += 433;
  }
  i_3 = 0LL;
  while ( 1 )
  {
    if ( (int)i_3 >= (int)(end1 - start) )
      __shedskin__::__throw_index_out_of_range(token);
    v26 = start[i_3];
    length_getter = *(__int64 (__fastcall **)(__shedskin__::str *__hidden))(v3->qword0 + 96LL);
    if ( length_getter == __shedskin__::str::__len__ )
    {
      if ( (int)i_3 >= v3->length )
        goto LABEL_38;
    }
    else
    {
      token = (__shedskin__::str *)v3;
      if ( (int)i_3 >= (int)length_getter((__shedskin__::str *)v3) )
LABEL_38:
        __shedskin__::__throw_index_out_of_range(token);
    }
    token = *(__shedskin__::str **)(__shedskin__::__char_cache + 8LL * *(unsigned __int8 *)(v3->qword10 + i_3));
    if ( *((_QWORD *)token + 3) != 1LL )
      __keygen__::validate_purplesyringa_key();
    if ( *(unsigned __int8 *)__shedskin__::str::c_str(token) != v26 )
      return (unsigned __int8)__shedskin__::False;
    if ( ++i_3 == 16 )
      return (unsigned __int8)__shedskin__::True;
    end1 = purplesyringa_key->end;
    start = purplesyringa_key->start;
  }
}

Можно упростить его дальше, заметив, что __throw_index_out_of_range принимает какой-то странный аргумент, не похожий ни на индекс, ни на тип какого-то значения; по-видимому, IDA опять неправильно вывела типы. Уберем аргумент из сигнатуры __throw_index_out_of_range и посмотрим, станет ли код проще:

__int64 __fastcall __keygen__::validate_purplesyringa_key(__shedskin__::str *token, list_int *purplesyringa_key)
{
  __int64 i_1; // r12
  int *start1; // rcx
  int *end1; // rsi
  int *start; // rdx
  int *v8; // rax
  int v9; // r13d
  __int64 offset; // r14
  __int64 i; // r13
  __int64 v12; // r14
  int v13; // r12d
  int v14; // r12d
  int length; // ecx
  int v16; // r15d
  int j; // r14d
  signed int x_1; // eax
  signed int v19; // r12d
  int i_2; // r13d
  __int64 j_1; // r12
  int length_1; // ecx
  __int64 i_3; // r14
  __shedskin__::str *v24; // rdi
  int v25; // r15d
  __int64 (__fastcall *length_getter)(__shedskin__::str *__hidden); // rax

  i_1 = 0LL;
  start1 = purplesyringa_key->start;
  end1 = purplesyringa_key->end;
  start = start1;
  do
  {
    offset = i_1;
    if ( (int)i_1 >= (int)(end1 - start1) )
      __shedskin__::__throw_index_out_of_range();
    v8 = &start1[offset];
    v9 = start1[i_1];
    if ( !v9 )
    {
      v9 = 256;
      start = start1;
      v8 = &start1[offset];
    }
    ++i_1;
    *v8 = v9;
  }
  while ( i_1 != 16 );
  for ( i = 0LL; i != 15; ++i )
  {
    length = end1 - start;
    if ( length <= (int)i + 1 )
      __shedskin__::__throw_index_out_of_range();
    v12 = i;
    if ( length <= (int)i )
      __shedskin__::__throw_index_out_of_range();
    v13 = start[i] * start[i + 1] % 257;
    v14 = v13 + (v13 < 0 ? 257 : 0);
    if ( (int)i + 1 >= length )
      __shedskin__::__throw_index_out_of_range();
    start[v12 + 1] = v14;
  }
  v16 = 433;
  for ( j = 743893; j != 743893000; j += 743893 )
  {
    i_2 = (j >> 16) & 0xF;
    j_1 = (v16 >> 4) & 0xF;
    if ( i_2 != (_DWORD)j_1 )
    {
      length_1 = end1 - start;
      if ( i_2 >= length_1 )
        __shedskin__::__throw_index_out_of_range();
      if ( (int)j_1 >= length_1 )
        __shedskin__::__throw_index_out_of_range();
      x_1 = (unsigned __int8)(((unsigned int)((start[j_1] + start[i_2]) >> 31) >> 24) + LOBYTE(start[j_1]) + start[i_2])
          - ((unsigned int)((start[j_1] + start[i_2]) >> 31) >> 24);
      v19 = x_1 + 256;
      if ( x_1 >= 0 )
        v19 = x_1;
      if ( i_2 >= length_1 )
        __shedskin__::__throw_index_out_of_range();
      start[i_2] = v19;
    }
    v16 += 433;
  }
  i_3 = 0LL;
  while ( 1 )
  {
    if ( (int)i_3 >= (int)(end1 - start) )
      __shedskin__::__throw_index_out_of_range();
    v25 = start[i_3];
    length_getter = *(__int64 (__fastcall **)(__shedskin__::str *__hidden))(*(_QWORD *)token + 96LL);
    if ( length_getter == __shedskin__::str::__len__ )
    {
      if ( (int)i_3 >= *((_DWORD *)token + 6) )
        goto LABEL_36;
    }
    else if ( (int)i_3 >= (int)length_getter(token) )
    {
LABEL_36:
      __shedskin__::__throw_index_out_of_range();
    }
    v24 = *(__shedskin__::str **)(__shedskin__::__char_cache + 8LL * *(unsigned __int8 *)(*((_QWORD *)token + 2) + i_3));
    if ( *((_QWORD *)v24 + 3) != 1LL )
      __keygen__::validate_purplesyringa_key();
    if ( *(unsigned __int8 *)__shedskin__::str::c_str(v24) != v25 )
      return (unsigned __int8)__shedskin__::False;
    if ( ++i_3 == 16 )
      return (unsigned __int8)__shedskin__::True;
    end1 = purplesyringa_key->end;
    start = purplesyringa_key->start;
  }
}

Действительно, стало покороче. Теперь посмотрим на код сверху вниз по частям.


i_1 = 0LL;
start1 = purplesyringa_key->start;
end1 = purplesyringa_key->end;
start = start1;
do
{
  offset = i_1;
  if ( (int)i_1 >= (int)(end1 - start1) )
    __shedskin__::__throw_index_out_of_range();
  v8 = &start1[offset];
  v9 = start1[i_1];
  if ( !v9 )
  {
    v9 = 256;
    start = start1;
    v8 = &start1[offset];
  }
  ++i_1;
  *v8 = v9;
}
while ( i_1 != 16 );

На Python это можно переписать как:

for i_1 in range(16):
    v9 = purplesyringa_key[i_1]
    if not v9:
        v9 = 256
    purplesyringa_key[i_1] = v9

Таким образом, в ключе все значения 0 заменяются на 256.


for ( i = 0LL; i != 15; ++i )
{
  length = end1 - start;
  if ( length <= (int)i + 1 )
    __shedskin__::__throw_index_out_of_range();
  v12 = i;
  if ( length <= (int)i )
    __shedskin__::__throw_index_out_of_range();
  v13 = start[i] * start[i + 1] % 257;
  v14 = v13 + (v13 < 0 ? 257 : 0);
  if ( (int)i + 1 >= length )
    __shedskin__::__throw_index_out_of_range();
  start[v12 + 1] = v14;
}

На Python это можно переписать как:

for i in range(15):
    v14 = purplesyringa_key[i] * purplesyringa_key[i + 1] % 257
    purplesyringa_key[i + 1] = v14

Таким образом, каждый элемент слева направо домножается на предыдущий по модулю 257.


v16 = 433;
for ( j = 743893; j != 743893000; j += 743893 )
{
  i_2 = (j >> 16) & 0xF;
  j_1 = (v16 >> 4) & 0xF;
  if ( i_2 != (_DWORD)j_1 )
  {
    length_1 = end1 - start;
    if ( i_2 >= length_1 )
      __shedskin__::__throw_index_out_of_range();
    if ( (int)j_1 >= length_1 )
      __shedskin__::__throw_index_out_of_range();
    x_1 = (unsigned __int8)(((unsigned int)((start[j_1] + start[i_2]) >> 31) >> 24) + LOBYTE(start[j_1]) + start[i_2])
        - ((unsigned int)((start[j_1] + start[i_2]) >> 31) >> 24);
    v19 = x_1 + 256;
    if ( x_1 >= 0 )
      v19 = x_1;
    if ( i_2 >= length_1 )
      __shedskin__::__throw_index_out_of_range();
    start[i_2] = v19;
  }
  v16 += 433;
}

На Python это можно переписать как:

v16 = 433
for j in range(743893, 743893000, 743893):
    i_2 = (j >> 16) & 0xf
    j_1 = (v16 >> 4) & 0xf
    if i_2 != j_1:
        x_1 = (unsigned __int8)(((unsigned int)((purplesyringa_key[j_1] + purplesyringa_key[i_2]) >> 31) >> 24) + LOBYTE(purplesyringa_key[j_1]) + purplesyringa_key[i_2]) - ((unsigned int)((purplesyringa_key[j_1] + purplesyringa_key[i_2]) >> 31) >> 24)  # ???
        v19 = x_1 + 256
        if x_1 >= 0:
            v19 = x_1
        purplesyringa_key[i_2] = v19
    v16 += 433

Правда, тут не особо понятно, что делает строка с x_1 = .... Обозначим a = purplesyringa_key[j_1], b = purplesyringa_key[i_2] и перепишем эту строчку как:

x_1 = (unsigned __int8)(((unsigned int)((a + b) >> 31) >> 24) + LOBYTE(a) + b) - ((unsigned int)((a + b) >> 31) >> 24)  // ???

После предыдущего шага с взятием по модулю все значения в массиве точно между 0 и 256. Значит, (a + b) >> 31 — это всегда 0. Скорректируем с учетом этого формулу и получим просто:

x_1 = (__int8)(a + b)

С учетом этого код значительно упростится:

v16 = 433
for j in range(743893, 743893000, 743893):
    i_2 = (j >> 16) & 0xf
    j_1 = (v16 >> 4) & 0xf
    if i_2 != j_1:
        purplesyringa_key[i_2] = (purplesyringa_key[j_1] + purplesyringa_key[i_2]) % 256
    v16 += 433

Можно пойти дальше и еще чуть его переписать, заметив, что v16 и j — вообще говоря, равнозначные переменные, поскольку обе на каждой итерации инкрементируются на фиксированный шаг:

for j in range(1, 1000):
    i_2 = ((j * 743893) >> 16) & 0xf
    j_1 = ((j * 433) >> 4) & 0xf
    if i_2 != j_1:
        purplesyringa_key[i_2] = (purplesyringa_key[j_1] + purplesyringa_key[i_2]) % 256

i_3 = 0LL;
while ( 1 )
{
  if ( (int)i_3 >= (int)(end1 - start) )
    __shedskin__::__throw_index_out_of_range();
  v25 = start[i_3];
  length_getter = *(__int64 (__fastcall **)(__shedskin__::str *__hidden))(*(_QWORD *)token + 96LL);
  if ( length_getter == __shedskin__::str::__len__ )
  {
    if ( (int)i_3 >= *((_DWORD *)token + 6) )
      goto LABEL_36;
  }
  else if ( (int)i_3 >= (int)length_getter(token) )
  {
LABEL_36:
    __shedskin__::__throw_index_out_of_range();
  }
  v24 = *(__shedskin__::str **)(__shedskin__::__char_cache + 8LL * *(unsigned __int8 *)(*((_QWORD *)token + 2) + i_3));
  if ( *((_QWORD *)v24 + 3) != 1LL )
    __keygen__::validate_purplesyringa_key();
  if ( *(unsigned __int8 *)__shedskin__::str::c_str(v24) != v25 )
    return (unsigned __int8)__shedskin__::False;
  if ( ++i_3 == 16 )
    return (unsigned __int8)__shedskin__::True;
  end1 = purplesyringa_key->end;
  start = purplesyringa_key->start;
}

Эта часть читается хуже всего, поскольку здесь мы впервые сталкиваемся со строкой token. Попробуем сделать из token структуру и назвать в ней поля по аналогии с list:

i_3 = 0LL;
while ( 1 )
{
  if ( (int)i_3 >= (int)(end1 - start) )
    __shedskin__::__throw_index_out_of_range();
  v25 = start[i_3];
  length_getter = *(__int64 (__fastcall **)(__shedskin__::str *__hidden))(token->object + 96LL);
  if ( length_getter == __shedskin__::str::__len__ )
  {
    if ( (int)i_3 >= token->length )
      goto LABEL_36;
  }
  else if ( (int)i_3 >= (int)length_getter((__shedskin__::str *)token) )
  {
LABEL_36:
    __shedskin__::__throw_index_out_of_range();
  }
  v24 = *(__shedskin__::str **)(__shedskin__::__char_cache + 8LL * (unsigned __int8)token->start[i_3]);
  if ( *((_QWORD *)v24 + 3) != 1LL )
    __keygen__::validate_purplesyringa_key();
  if ( *(unsigned __int8 *)__shedskin__::str::c_str(v24) != v25 )
    return (unsigned __int8)__shedskin__::False;
  if ( ++i_3 == 16 )
    return (unsigned __int8)__shedskin__::True;
  end1 = purplesyringa_key->end;
  start = purplesyringa_key->start;
}

Стало капельку получше. Видим цикл с 16 итерациями, на каждой из которых вычитывается очередной элемент из purplesyringa_key в v25, и из token в, по-видимому, v24.

__char_cache намекает на то, что Shed Skin компилирует обращение к индексу строки, которое в Python также возвращает строку длины 1, в получение строки из заранее подготовленной таблицы по индексу символа. Проверка с != 1LL — по-видимому, проверка длины этой закешированной строки. Мы ожидаем, что длина всегда равна 1, поэтому опустим это условие. *c_str(v24) вычитывает из строки первый символ — как раз token[i_3] — и сравнивает его с v25, то есть purplesyringa_key[i_3]. При несовпадении на какой-либо из итераций выдается False. Таким образом, код можно переписать на Python как:

for i_3 in range(16):
    if purplesyringa_key[i] != ord(token[i]):
        return False
return True

Соберем все воедино и капельку причешем:

# (1)
for i in range(16):
    purplesyringa_key[i] = purplesyringa_key[i] or 256

# (2)
for i in range(1, 16):
    purplesyringa_key[i] = purplesyringa_key[i] * purplesyringa_key[i - 1] % 257

# (3)
for i in range(1, 1000):
    a = ((i * 743893) >> 16) & 0xf
    b = ((i * 433) >> 4) & 0xf
    if a != b:
        purplesyringa_key[a] = (purplesyringa_key[a] + purplesyringa_key[b]) % 256

# (4)
return all(purplesyringa_key[i] == ord(token[i]) for i in range(16))

Теперь можно приступить к написанию кейгена для кейгена. Нужно подобрать такую бинарную строку purplesyringa_key длины 16, чтобы проверка (4) прошла.


После шага (3) purplesyringa_key должен совпадать с токеном. Посмотрим, возможно ли обратить действия шага (3), чтобы понять, чему purplesyringa_key должен был быть равен перед этим шагом.

На последней итерации при i == 999 происходит следующее:

i = 999
a = ((i * 743893) >> 16) & 0xf
b = ((i * 433) >> 4) & 0xf
if a != b:
    purplesyringa_key[a] = (purplesyringa_key[a] + purplesyringa_key[b]) % 256

a и b на этом шаге нам известны и их можно посчитать. Если они равны, то ничего обращать и не нужно; если равны, то чтобы обратить действие

purplesyringa_key[a] = (purplesyringa_key[a] + purplesyringa_key[b]) % 256

нужно сделать

purplesyringa_key[a] = (purplesyringa_key[a] - purplesyringa_key[b]) % 256

— прямо как если бы взятия по модулю не было. Это известное свойство арифметики по модулю.

После этого нужно обратить 998-ю итерацию, 997-ю и так далее — тем же методом. Запишем это кодом:

for i in range(999, 0, -1):
    a = ((i * 743893) >> 16) & 0xf
    b = ((i * 433) >> 4) & 0xf
    if a != b:
        purplesyringa_key[a] = (purplesyringa_key[a] - purplesyringa_key[b]) % 256

После запуска этого куска мы узнаем, чему должен был быть равен purplesyringa_key перед шагом (3).


На шаге (2) происходит следующее:

for i in range(1, 16):
    purplesyringa_key[i] = purplesyringa_key[i] * purplesyringa_key[i - 1] % 257

Для удобства анализа глазами раскроем цикл:

purplesyringa_key[1 ] = purplesyringa_key[1 ] * purplesyringa_key[0 ] % 257
purplesyringa_key[2 ] = purplesyringa_key[2 ] * purplesyringa_key[1 ] % 257
purplesyringa_key[3 ] = purplesyringa_key[3 ] * purplesyringa_key[2 ] % 257
purplesyringa_key[4 ] = purplesyringa_key[4 ] * purplesyringa_key[3 ] % 257
purplesyringa_key[5 ] = purplesyringa_key[5 ] * purplesyringa_key[4 ] % 257
purplesyringa_key[6 ] = purplesyringa_key[6 ] * purplesyringa_key[5 ] % 257
purplesyringa_key[7 ] = purplesyringa_key[7 ] * purplesyringa_key[6 ] % 257
purplesyringa_key[8 ] = purplesyringa_key[8 ] * purplesyringa_key[7 ] % 257
purplesyringa_key[9 ] = purplesyringa_key[9 ] * purplesyringa_key[8 ] % 257
purplesyringa_key[10] = purplesyringa_key[10] * purplesyringa_key[9 ] % 257
purplesyringa_key[11] = purplesyringa_key[11] * purplesyringa_key[10] % 257
purplesyringa_key[12] = purplesyringa_key[12] * purplesyringa_key[11] % 257
purplesyringa_key[13] = purplesyringa_key[13] * purplesyringa_key[12] % 257
purplesyringa_key[14] = purplesyringa_key[14] * purplesyringa_key[13] % 257
purplesyringa_key[15] = purplesyringa_key[15] * purplesyringa_key[14] % 257

Чтобы обратить последнее действие, нужно, зная a и c, узнать такое b, что a == b * c % 257. Если бы взятия по модулю не было, мы бы сказали, что это просто деление: b = a / c. Но модуль тут есть, так что придется либо применить знания математики, либо погуглить и узнать, что умножение по модулю тоже можно обращать. Более того, это действие встроено в Python и может быть записано как:

b = a * pow(c, -1, 257) % 257

Соответственно, обратить все 15 итераций можно так:

for i in range(15, 0, -1):
    purplesyringa_key[i] = purplesyringa_key[i] * pow(purplesyringa_key[i - 1], -1, 257) % 257

Единственная проблема заключается в том, что... purplesyringa_key[i - 1] может быть нулём! А делить на ноль нельзя даже по модулю. Что же делать, если шаг (3) требует, чтобы какой-то элемент был равен 0?

На самом деле, шаг (3) ничего такого не требует. Он может требовать, чтобы элемент был равен нулю по модулю 256. То есть он может спокойно быть равен −256, 0, 256, 512 и так далее. Получается, если нам кажется, что если какой-то элемент должен быть равен нулю, можно просто заменить этот нуль на 256, и шаг (3) продолжит работать успешно:

for i in range(16):
    purplesyringa_key[i] = purplesyringa_key[i] or 256
for i in range(15, 0, -1):
    purplesyringa_key[i] = purplesyringa_key[i] * pow(purplesyringa_key[i - 1], -1, 257) % 257

Теперь проблем с делением на 0 быть не должно.


Наконец, нужно обратить первый шаг:

for i in range(16):
    purplesyringa_key[i] = purplesyringa_key[i] or 256

Здесь все просто. Можно оставить все элементы как есть. Но если какой-то элемент после шага (1) должен быть равен 256, можно его заменить на 0 (оставить его как есть со значением 256 нельзя, потому что байт не может быть равен числу 256, а вот нулю вполне может).

Таким образом, обращение этого шага можно записать так:

for i in range(16):
    purplesyringa_key[i] = purplesyringa_key[i] % 256

Отметим, кстати, одну неочевидную деталь. На прошлом шаге (2) мы в частности заменяли последний элемент массива на 256, если он был равен 0. При этом формально шаг (2) такой замены не требует, ведь на последний элемент массива мы никогда не делим. Однако, если бы мы не произвели эту замену, могло бы оказаться так, что перед шагом (2) должно выполняться purplesyringa_key[15] == 0, но после шага (1) элемент никогда не может быть равен нулю!


Объединим куски кейгена для кейгена воедино и добавим ввод-вывод:

token = input("Token: ")
assert len(token) == 16
purplesyringa_key = list(token.encode())

for i in range(999, 0, -1):
    a = ((i * 743893) >> 16) & 0xf
    b = ((i * 433) >> 4) & 0xf
    if a != b:
        purplesyringa_key[a] = (purplesyringa_key[a] - purplesyringa_key[b]) % 256

for i in range(15, 0, -1):
    purplesyringa_key[i] = purplesyringa_key[i] * pow(purplesyringa_key[i - 1] or 256, -1, 257) % 257

for i in range(16):
    purplesyringa_key[i] = purplesyringa_key[i] % 256

print(bytes(purplesyringa_key).hex())

Запустим:

Token: maziserywq8vln9u
f3282ca56dd4d755d65731a947cec876

Теперь подставим этот ключ в кейген:

UCUCUGA PRO KEYGEN by xXx_HACKERNAME_xXx
Enter token: maziserywq8vln9u
You need to obtain a key from me to use this keygen.
Please mail [email protected]. We will agree on the payment.
Enter key from purplesyringa: f3282ca56dd4d755d65731a947cec876
UCUCUGA Pro key: egDNw8_x1gQtDy8_

Наконец можно скачать уцуцугу!

Флаг: ugra_was_is_worth_it_ff6x8jwrzr6t