Анализ результатов взлома программы однокомандного процессора NORCPU

Итак, публикуя анонс задачи по взлому программы, работающей на однокомандном процессоре NORCPU, я наивно думал, что хотя бы до конца марта я получу какое-нибудь решение.

Замечание: все цитируемые ниже фрагменты приводятся как есть, с минимальными правками в плане форматирования на странице.

В реальности все было иначе. Буквально через пару часов после поста на Хабре пришло письмо от Vasiliy Artemev:

Secret code: 139471

Это было правильным ответом. Василий официально стал первым и получил законные 100$ призовых. Он любезно рассказал, что взломал методом грубой силы, перебором. Действительно, простейшая переборная атака заставляла программу выдать секретное слово уже на 3-х символьном пароле. Увы, моя реализация хеш-функции для пароля стала уязвимым звеном.

Но полного анализа не было, и к тому же Василий предложил спонсировать дальнейший анализ задачи в виде 50$ из начального призового фонда. В общем, анализ продолжался.

Тем временем я сделал новую версию задачи, где уже не было хеширования, а был просто зашифрованный пароль. Я думал, это усложнит взлом. Но вечером того же дня приходит письмо от Anton Bukov:

Ответ на NORCPU hackme challenge, Version 2: “R0und2 D0ne!” ?

Это правильный ответ. Я недоумевал. Как можно было так быстро разобраться?

Антон также любезно рассказал, как ему удалось получить ответ:

Мой взлом основан на строке:

mem = mem_0.slice(0);

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

wcout << calc(L"0") << endl << calc(L"0") << endl;

Я так догадываюсь для предотвращения этого и осуществлялось копирование. Боюсь это не тот способ, которого вы ждали. Я и сам удивился, когда заметил ответ в output-е. Ну а после уже разобрался из-за чего он вылез.

Антон случайно нашел баг, из-за которого программа опять сама выдавала секрет. Надо было запустить программу повторно без приведения памяти эмулятора в исходное состояния.

Корень проблемы:

...
  IS_0(flag)
  JZ okay
  EXIT(1)
okay:
  ; print the secret
...

После первого неправильного прогона и отказа в выводе секрета программа останавливалась в строке 4, и регистр комманд ip уже указывал на метку okay. Поэтому если интерпретатор просто запустить еще раз без инициализации памяти (а все регистры, включая ip находятся в памяти), он продолжит выполнение с метки okay и выведет секрет.

Досадно. Я быстро исправил проблему и выложил версию 2.1, в котором этого эффекта уже не было.

Прошло пару дней.

И вот приходит письмо от пользователя a5b с Хабра:

Хотпатчинг переходов и ответ pw: abcd resp: R0und2 D0ne!

  • пароль естественно неверный
  • инвертировал записываемые данные в (i == 27692 || i == 31712)

Формально, это решение, но нет пароля, а значит полного анализа тоже нет.

Но через час от a5b приходит довесок:

h1cKmE1fUsAn

input data:
chr conI LEET xor
CHR1 13417 13313 104 h
CHR2 39953 39968 49 1
CHR3 54302 54397 99 c
CHR4 32223 32148 75 K
CHR5 30900 30937 109 m
CHR6 27373 27304 69 E
CHR7 16420 16405 49 1
CHR8 49210 49244 102 f
CHR9 16740 16689 85 U
CHR10 50115 50096 115 s
CHR11 19308 19245 65 A
CHR12 57802 57764 110 n

static init:
CHRi 59609= 59651
CONi 59610= 59634
CNTR 59611=12
SUM 59608=0
LEET 59607 = 13313

1:
[59609]+1 -> [59609] // select next chr
[59610]+1 -> [59610] // select next con

SUM |= CHRi ^ LEET ^ CONi
LEET = LEET * 3 + 29

[59611]: if([59611] != 0) Loop 1

...
if(SUM != 0) exit
else print R0und2 D0ne // эту часть подробно не смотрел.
// Судя по модификациям кода (продвижение индекса), загружает 12 констант:
// 29528 22899 2971 9089 27542 17353 52278 25635 11626 34909 39131 51838,
// над каждой колдует и пишет в вывод

А вот это уже анализ. a5b стал первым, кто прислал алгоритм задачи номер 2.

В тот же день вечером приходит письмо от Max Filippov:

Algorithm that was used to check password correctness in the first round was the following:

bool check(const char *p)
{
   int v = 0x1040;

   for(; *p; ++p)
   {
       v ^= *p;
       for (int i = 0; i < 8; ++i)
       {
           int f = v & 1;
           v >>= 1;
           if (f)
               v ^= 0x1408;
       }
   }
   return v == 0x1c89;
}

that is, sort of CRC.

To discover it I’ve collected NORCPU execution trace and “disassembled” it.

Modified NORCPU source and disassembler are attached, and also may be found there: http://jcmvbkbc.spb.ru/git/?p=dumb/norcpu.git;a=summary

И довесок:

The method used is pretty straightforward:

  • collect execution trace
  • recognize instruction patterns and collapse sequences of primitive instructions to more complex ones
  • analyze disassembled trace

So, first I needed trace: I copied javascript text into cpp source, fixed lingual differences and inserted the following printf:

while (1) {
  int i = mem[ip];
  printf("%04x:NOR(%04x, %04x => %04x) ", i, mem[i], mem[i + 1], mem[i + 2]);
  int a = mem[i + 0];

so that I got a long line (about 8Mb) of primitive instruction execution trace.

Then I started constructing sed script that would make it readable.

First, it broke the trace line-wise, one instruction per line (288323 lines, will read it in case of insomnia). I took a look at processed trace and recorded several obvious instruction patterns into sed. Then reran script, took next look, recorded more patterns, …

This way I figured out all boolean logic commands and jumps. Then rotations left. Each time new command got recognized, new filtered processed trace was suggesting next step, e.g. 15 ROTL equals ROTR etc.

Then I looked into your article at “Модель процессора с одной командой”. And found addition pattern in disassembly. And recorded it in sed script.

After that I was able to just read the trace (which shrunk to 1035 lines). Its inner loop fit into one page, I just made some notes on a scratchpad:

[f1ba]: current in-T index (i)
[f1b4]: LEN
[f1b5]: 8

0012-0035:[f1b9] ^= (T[i] & 0xff)

006d-007b:[f1b8] = [f1b9] & 1
008a-0158:[f1b9] >>= 1
0167-10c7:[f1aa] = [f1b8] + -1, [f1ab] = !carry
10ca-10e6:jmp on carry to 1145:110d

110d-111b:[f1b9] ^= 1408

1145-1f4f:--[f1b5]
20a5-3005:[f1aa] = [f1b5] + -1, f1ab = !carry
3008-3024:jmp on carry to 006d:304b

304b-304b:++i
3fab-3fab:--LEN
4f0b-5e8a:jmp on carry to 5eb1:6

then I browsed through the repetitions of this inner loop and found the end of the outer loop.

5eb1-6e74:check 1c89

Then just translated it into C. It all took me three evenings.

Затем от Max Filippov пришло решение и второй задачи.

Ответ на второй тур – h1cKmE1fUsAn

Результат – R0und2 D0ne!

Алгоритм проверки пароля такой:

bool check(const char *p)
{
   static const int xor_array[] = {
       0x3469,
       0x9c11,
       0xd41e,
       0x7ddf,
       0x78b4,
       0x6aed,
       0x4024,
       0xc03a,
       0x4164,
       0xc3c3,
       0x4b6c,
       0xe1ca,
   };

   int v = 0;
   int x = 0x3401;

   for (int i = 0; i < 12; ++i)
   {
       int f = p[i] ^ x ^ xor_array[i];
       // printf("x: %04x, f: %04x\n", x, f);
       v |= f;
       x = (x * 3 + 0x1d) & 0xffff;
   }
   return !v;
}

Закомментированный printf выводит ключевую фразу по ходу.

Методика анализа – как и в первом туре – дизассемблирование трассы выполнения.

Первый тур был откровенно интереснее.

И, наконец, последнее полученное решение от Salo Kril для первой задачи.

Особых пояснений нет – просто исходники.

// Генерация паролей

// Brute_force(3);

WORD ks_f(char *buff, int len)
{
   int i, j;
   WORD ks = 0x1040;

   for (i = 0; i < len; i++)
   {
       ks ^= buff[i];
       for (j = 0; j < 8; j++)
       {
           if((ks & 1) == 0)
               ks = ks >> 1;
           else
               ks = (ks >> 1) ^ 0x1408;
       }
   }
   return ks;
}

void Brute_force(int n)
{
   int i;
   static char alphabet[] =
       "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2A\x2B\x2C\x2D\x2E\x2F"
       "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3A\x3B\x3C\x3D\x3E\x3F"
       "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4A\x4B\x4C\x4D\x4E\x4F"
       "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5A\x5B\x5C\x5D\x5E\x5F"
       "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6A\x6B\x6C\x6D\x6E\x6F"
       "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7A\x7B\x7C\x7D\x7E";

   if(n == 0)
   {
       if(ks_f(buff_bf, BF_N) == 0x1c89)
           printf("%s\n",buff_bf);
       return;
   }

   n--;
   for (i = 0; alphabet[i]; i++)
   {
       buff_bf[n] = alphabet[i];
       Brute_force(n);
   }
}

и реконструированный код:

#define DEST_COUNT  0xF1FE
extern WORD mem[];

/*
    Secret code: 139471
*/
void reconstructed_fn(void)
{
    int i, j;
    WORD src, dest, key, count, hash, hash_OK, key_const;

    src = mem[0xF1BA];     // input string
    count = mem[0xF1ED];   // input string length
    dest = mem[0xF1BB];    // 0xf1ff
    hash_OK = mem[0xF1BC]; // 0x1c89
    hash = mem[0xF1B9];    // 0x1040


    for(i = 0; i < count; i++)
    {
        hash ^= mem[src + i] & 0xFF;

        for (j = 0; j < 8; j++)
        {
            if ((hash & 1) == 0)
                hash = hash >> 1;
            else
                hash = (hash >> 1) ^ 0x1408;
        }
    }

    if (hash == hash_OK)
    {
        src = mem[0x6EB6];              // "Secret code: 139471"
        count = mem[0xF1C7];            // 19
        key = ((hash >> 8) ^ hash) + 1;
        key_const = mem[0x6EA8];        // 11
    }
    else
    {
        src = mem[0x5EBE];          // "Wrong password!"
        count = mem[0xF1DC];        // 15
        key = mem[0xF1BD];
        key_const = mem[0xF1BE];    // 17
    }

    mem[DEST_COUNT] = count;
    for (i = 0; i < count; i++)
    {
        mem[dest + i] = mem[src + i] ^ key;
        key = key * 3 + key_const;
    }
}

/*
--------------------------------------------------------------------------------------------
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
--------------------------------------------------------------------------------------------
*/
WORD and(WORD w1, WORD w2)
{
    return w1 & w2;
}

WORD or(WORD w1, WORD w2)
{
    return w1 | w2;
}

WORD xor(WORD w1, WORD w2)
{
    return w1 ^ w2;
}

WORD rol(WORD w1, WORD n)
{
    return (w1 << n) | (w1 >> (16 - n));
}

WORD ror(WORD w1, WORD n)
{
    return (w1 >> n) | (w1 << (16 - n));
}
WORD extend_16(WORD k1)
{
    int i, k2;

    for (i = 0, k2 = 0; i < 16; i++)
    {
        k2 = or(k2, k1);
        k1 = rol(k1, 1);
    }

    return k2;
}

WORD add(WORD *kk1, WORD a, WORD b)
{
    WORD mask_bit, aa0, aa1, tmp1, i, k1, k2;
    k1 = 0;
    k2 = 0;
    mask_bit = 1;

    for (i = 0; i < 16; i++)
    {
        k1 = and(k1, mask_bit);
        aa0 = xor(a, b);
        tmp1 = xor(aa0, k1);
        tmp1 = and(tmp1, mask_bit);
        k2 = or(tmp1, k2);
        aa1 = and(a, b);
        aa0 = and(k1, aa0);
        k1 = or(aa1, aa0);
        k1 = rol(k1, 1);
        mask_bit = rol(mask_bit, 1);
    }
    k1 = and(k1, mask_bit);
    *kk1 = extend_16(k1);

    return k2;
}

void reconstr(void)
{
    WORD k1, k2, src, dest, tmp1, key, count, tmp2, tmp3, i, ks, ks_OK, key_a;
    WORD mask_bit, aa1, aa0;

    k1 = mem[0xF1AF];    // 0x88
    k2 = mem[0xF1A3];    // 0x47
    src = mem[0xF1BA];   // input string
    count = mem[0xF1ED]; // input string length
    dest = mem[0xF1BB];  // 0xf1ff
    ks_OK = mem[0xF1BC]; // 0x1c89             "w":ks=0x0ACC   "WWW":0x0FCE   "123456789":ks=0x05E3
    ks = mem[0xF1B9];    // 0x1040


l_0006:
    ks = xor(ks, and(mem[src], 0xFF));
    i = 8;

l_006D:
    tmp3 = and(ks, 1);
    ks = ror(ks, 1);
    ks = and(ks, 0x7FFF);
    tmp2 = add(&k2, tmp3, -1);
    if(k2 != 0)
    {
        ks = xor(ks, 0x1408);
    }

l_1145:
    i = add(&k1, i, -1);
    tmp2 = add(&k2, i, -1);
    if(k2 != 0)
        goto l_006D;

l_304B:
    src = add(&k1, src, 1);
    count = add(&k1, count, -1);
    tmp2 = add(&k2, count, -1);
    if(k2 != 0)
        goto l_0006;


    src = mem[0x5EBE];    // Wrong password!
    count = mem[0xF1DC];  // 15
    key = mem[0xF1BD];
    key_a = mem[0xF1BE];
    ks_OK = xor(ks_OK, ks);
    tmp2 = add(&k2, ks_OK, -1);

    if(k2 != 0)
        goto l_8552;

l_6E9B:
    src = mem[0x6EB6];     // Secret code: 139471
    count = mem[0xF1C7];   // 19
    key = ks;
    key_a = mem[0x6EA8];

    key = ror(key, 8);
    key = and(key, 0xFF);
    key = xor(ks, key);
    key = and(key, 0x7FFF);
    key = add(&k1, key, 1);


l_8552:
    mem[DEST_COUNT] = count;
l_8558:
    mem[dest] = xor(mem[src], key);
    tmp3 = add(&k1, key, key);
    key = add(&k1, key, tmp3);
    key = add(&k1, key, key_a);
    src = add(&k1, src, 1);
    dest = add(&k1, dest, 1);
    count = add(&k1, count, -1);
    tmp2 = add(&k2, count, -1);
    if(k2 != 0)
        goto l_8558;

l_F186:
    mem[0xF1B2] = mem[0xF193]; // nc
}

Итак, как вы уже поняли, решения были полностью исчерпывающими.

Всем приславшим огромное спасибо за проявленное внимание.

Ниже привожу оригинальные исходники обоих задач. Надо просто запустить питоновский скрипт, он скомпилирует код, написанный через функции-макросы, сделает тестовый прогон и сгенерирует html-страничку (нужен файл-шаблон template.html).

Весь архив вместе c решениями-взломами доступны в виде git репозитория.

Задача 1

Файл norcpu.py (template.html):

import sys, re, time, string, binascii

verbose = False
verbose_cpu = False
scramble = True

test_wrong_crc = 0

secret_code = "Secret code: 139471"
password = "h0cKmE1fUsAn"
guess    = "123456789012"
guess    = password

# Secret code message encryption mask.
secret_coef_add = 17

message_text = "Wrong password!"
# Wrong password message encryption mask.
message_mask = 0x6301
message_coef_add = 11

# Non-standard CRC initial value (should be 0xFFFF).
crc16_initial_value = 0x1040

# Non-standard CRC constant (should be 0x8401).
crc16_constant = 0x1408

code_segment = []
data_segment = []

label_count = 0

def dump(data, length = 8):
  result = []
  for i in xrange(0, len(data), length):
    line = data[i:i + length]
    hex_line = ' '.join(["%04X" % x for x in line])
    result.append("%04X: %-*s\n" % (i, length*5, hex_line))
  return ''.join(result)

def dump_js(data, length = 8):
  result = []
  for i in xrange(0, len(data), length):
    line = data[i:i + length]
    hex_line = ' '.join(["0x%04X," % x for x in line])
    result.append("%-*s\n" % (length*5, hex_line))
  return ''.join(result)

def calc_crc16(data):
  global crc16_initial_value
  global crc16_constant

  crc16 = crc16_initial_value
  for i in range(0, len(data)):
    ch = ord(data[i]) & 0xff
    crc16 = crc16 ^ ch
    for j in range(0, 8):
      if ((crc16 & 1) != 0):
        crc16 = (crc16 >> 1) ^ crc16_constant
      else:
        crc16 = crc16 >> 1
  return crc16

crc16 = calc_crc16(password)

def encode_string(data, name, mask, coef_add):
  global mem, names
  offset = names[name]
  offset_sz = names[name + "_sz"]
  for i in range(0, len(data)):
    mem[offset + i] = ord(data[i]) ^ mask
    mask = (mask * 3 + coef_add) & 0xffff
  mem[offset_sz] = len(data)

def put_string(data, name):
  global mem, names
  offset = names[name]
  offset_sz = names[name + "_sz"]
  for i in range(0, len(data)):
    mem[offset + i] = ord(data[i])
  mem[offset_sz] = len(data)

def save_mem(name, size = -1):
  f = open(name, "w")
  if size == -1: size = len(mem)
  for i in (mem[0:size]):
    hex = "%04X" % i
    bin = binascii.a2b_hex(hex)
    f.write(bin)
  f.close()

def next_label():
  global label_count
  label_count = label_count + 1
  return "label_%04d" % label_count

def code_rem(comment):
  code_segment.append('; ' + comment)

def data_rem(comment):
  data_segment.append('; ' + comment)

def data_label(name):
  data_segment.append(name + ":")

def code_label(name):
  code_segment.append(name + ":")

def code(value):
  printed = value
  if type(value).__name__ == 'int':
    printed = "%d" % value
  code_segment.append("  dw %s" % printed)

scramble_counter = 0x27

def next_scramble_counter():
  global scramble_counter
  scramble_counter = scramble_counter * 3 + 7
  return scramble_counter & 0xff

def word(value):
  if value == -1:
    if scramble:
      value = next_scramble_counter()
    else:
      value = 0
  printed = value
  if type(value).__name__ == 'int':
    printed = "%d" % value
  data_segment.append("  dw %s" % printed)

def buffer(length, value = -1):
  for i in range(0, length):
    word(value)

def var(name, value = -1):
  data_label(name);
  word(value);

def NOR(a, b, r):
  code_rem('NOR ' + str(a) + ' ' + str(b) + ' ' + str(r))
  code(a)
  code(b)
  code(r)

def NOT(a, r):
  NOR(a, a, r);

def OR(a, b, r):
  NOR(a, b, "or_reg")
  NOT("or_reg", r)
var("or_reg")

def AND(a, b, r):
  NOT(a, "and_reg_a")
  NOT(b, "and_reg_b")
  OR("and_reg_a", "and_reg_b", "and_reg_a")
  NOT("and_reg_a", r)
var("and_reg_a")
var("and_reg_b")

def ANDi(a, imm, r):
  MOVi(imm, "and_i_reg")
  AND(a, "and_i_reg", r)
var("and_i_reg")

def XOR(a, b, r):
  NOT(a, "xor_reg_a")
  NOT(b, "xor_reg_b")
  AND(a, "xor_reg_b", "xor_reg_b")
  AND(b, "xor_reg_a", "xor_reg_a")
  OR("xor_reg_a", "xor_reg_b", r)
var("xor_reg_a")
var("xor_reg_b")

def XORi(a, imm, r):
  MOVi(imm, "xor_i_reg")
  XOR(a, "xor_i_reg", r)
var("xor_i_reg")

def MOV(a, b):
  code_rem('MOV ' + str(a) + ' ' + str(b))
  NOT(a, "move_reg")
  NOT("move_reg", b)
  code_rem('MOV END')
var("move_reg")

def JMP(a):
  code_rem('JMP ' + str(a))
  MOV(a, "ip")

def JMPi(a):
  code_rem('JMPi ' + str(a))
  label = next_label()
  JMP(label)
  code_label(label)
  code(a)

def MOVi(imm, a):
  code_rem('MOVi #' + str(imm) + ' ' + str(a))
  label_data = next_label()
  label_jump = next_label()
  MOV(label_data, a)
  JMPi(label_jump)
  code_label(label_data)
  code(imm)
  code_label(label_jump)

# [a] -> b
def PEEK(a, b):
  label1 = next_label()
  label2 = next_label()
  MOV(a, label1)
  MOV(a, label2)
  code_label(label1)  # NOT(0, 0, move_reg)
  code(0)             # <- a
  code_label(label2)  #
  code(0)             # <- a
  code("move_reg")    #
  NOT("move_reg", b)

# a -> [b]
def POKE(a, b):
  code_rem('POKE ' + str(a) + ' [' + str(b) + ']')
  label = next_label()
  MOV(b, label)
  NOT(a, "move_reg")  # +3 (three operations)
  code("move_reg")    # +4
  code("move_reg")    # +5
  code_label(label)
  code(0)             # <- b

# imm -> [a]
def POKEi(imm, a):
  MOVi(imm, "poke_i_reg")
  POKE("poke_i_reg", a)
var("poke_i_reg")

def EXIT(a):
  MOV(a, "exit_reg")

def EXITi(a):
  MOVi(a, "exit_reg")

def FADD(mask, carry, a, b, r):
  AND(a, mask, "fadd_reg_a")  # zero bits in 'a' except mask'ed
  AND(b, mask, "fadd_reg_b")  # zero bits in 'b' except mask'ed
  AND(carry, mask, carry)     # zero bits in 'carry' except mask'ed

  # SUM = (a ^ b) ^ carry
  XOR(a, b, "fadd_reg_t1")
  XOR("fadd_reg_t1", carry, "fadd_reg_bit_r")

  # Leave only 'mask'ed bit in bit_r.
  AND("fadd_reg_bit_r", mask, "fadd_reg_bit_r")

  # Add current added bit to the result.
  OR("fadd_reg_bit_r", r, r)

  # CARRY = (a & b) | (carry & (a ^ b))
  AND(a, b, "fadd_reg_t2")
  AND(carry, "fadd_reg_t1", "fadd_reg_t1")

  # CARRY is calculated, and 'shift_reg' contains the same value
  # but shifted the left by 1 bit.
  OR("fadd_reg_t2", "fadd_reg_t1", carry)

  # CARRY is shifted the left by 1 bit to be used on the next round.
  MOV("shift_reg", carry)

  # shift_reg = mask << 1
  MOV(mask, mask)
  # mask = shift (effectively "mask = mask << 1")
  MOV("shift_reg", mask)

  AND(carry, mask, carry)

var("fadd_reg_a")
var("fadd_reg_b")
var("fadd_reg_bit_r")
var("fadd_reg_t1")
var("fadd_reg_t2")

def ZERO(a):
  XOR(a, a, a)

def FADC(a, b, r):
  ZERO("fadc_reg_t")
  MOV("const_1", "fadc_reg_mask")
  for i in range(0, 16):
    FADD("fadc_reg_mask", "carry_reg", a, b, "fadc_reg_t")
  MOV("fadc_reg_t", r)

  ZERO("fadc_reg_t")

  for i in range(0, 16):
    OR("fadc_reg_t", "carry_reg", "fadc_reg_t")
    MOV("carry_reg", "carry_reg")
    MOV("shift_reg", "carry_reg")

  MOV("fadc_reg_t", "carry_reg")

var("fadc_reg_mask")
var("fadc_reg_t")

def ADD(a, b, r):
  ZERO("carry_reg")
  FADC(a, b, r)

def ADDi(a, imm, r):
  MOVi(imm, "add_i_reg")
  ADD(a, "add_i_reg", r)
var("add_i_reg")

def PUSH(a):
  ADD("stack_reg", "const_minus_1", "stack_reg")
  POKE(a, "stack_reg")

def PUSHi(imm):
  MOVi(imm, "push_i_reg")
  PUSH("push_i_reg")
var("push_i_reg")

def POP(a):
  PEEK("stack_reg", a)
  ADD("stack_reg", "const_1", "stack_reg")

def CALL(a):
  label = next_label()
  PUSHi(label)
  JMP(a)
  code_label(label)

def CALLi(a):
  label = next_label()
  PUSHi(label)
  JMPi(a)
  code_label(label)

def RET():
  POP("ip")

# Jump 'a', if cond = FFFF, and 'b' if conf = 0000
def BRANCH(a, b, cond):
  AND(a, cond, "branch_reg_a")              # reg_a = a & cond
  NOT(cond, "branch_reg_b")                 # reg_b = !cond
  AND(b, "branch_reg_b", "branch_reg_b")    # reg_b = b & reg_b = b & !cond
  OR("branch_reg_a", "branch_reg_b", "ip")  # ip = (a & cond) | (b & !cond)
var("branch_reg_a")
var("branch_reg_b")

# Jump 'a', if cond = FFFF, and 'b' if conf = 0000
def BRANCHi(a, b, cond):
  MOVi(a, "branch_i_reg_a")
  MOVi(b, "branch_i_reg_b")
  BRANCH("branch_i_reg_a", "branch_i_reg_b", cond)
var("branch_i_reg_a")
var("branch_i_reg_b")

# if a != 0 -> carry = FFFF else carry = 0000
def IS_0(a):
  ZERO("carry_reg")
  FADC(a, "const_minus_1", "is_0_reg")
  NOT("carry_reg", "zero_reg")
var("is_0_reg")
var("zero_reg")

# ip = (zero_reg == FFFF ? a : ip)
def JZi(a):
  label = next_label()
  BRANCHi(a, label, "zero_reg")
  code_label(label)

# ip = (zero_reg == FFFF ? a : ip)
def JNZi(a):
  label = next_label()
  BRANCHi(label, a, "zero_reg")
  code_label(label)

def ROL(a, b):
  MOV(a, a)            # shift_reg = a << 1
  MOV("shift_reg", b)

def ROR(a, b):
  MOV(a, "ror_reg")
  for i in range(0, 15):
    ROL("ror_reg", "ror_reg")
  MOV("ror_reg", b)
var("ror_reg")

def SHL(a, b):
  ROL(a, b)
  ANDi(b, 0x0001, b)

def SHR(a, b):
  ROR(a, b)
  ANDi(b, 0x7FFF, b)

# NORCPU code

var("ip", "start")
var("shift_reg")
var("carry_reg")
var("const_1", 1)
var("const_minus_1", 0xFFFF)
var("exit_reg")

var("stack_reg", "stack")

code_label("start")

var("i")
var("j")
var("ch")
var("mask")
var("t")

var("crc16", crc16_initial_value)

var("ptr", "password")
MOV("password_sz", "i")

crc_loop = next_label()
code_label(crc_loop)          # crc_loop
# ch = *ptr
PEEK("ptr", "ch")
# ch &= 0xFF
ANDi("ch", 0xFF, "ch")
# crc16 ^= ch
XOR("crc16", "ch", "crc16")

MOVi(8, "j")
crc_loop_j = next_label()
code_label(crc_loop_j)        # crc_loop_j

# t = crc16 & 1
ANDi("crc16", 1, "t")
# crc16 >>= 1
SHR("crc16", "crc16")
IS_0("t")
crc_loop_1 = next_label()
JZi(crc_loop_1)
# crc16 ^= crc16_constant
XORi("crc16", crc16_constant, "crc16")
code_label(crc_loop_1)        # crc_loop_1

ADD("j", "const_minus_1", "j")
IS_0("j")
JNZi(crc_loop_j)

# ptr += 1
ADD("ptr", "const_1", "ptr")
# i = i - 1
ADD("i", "const_minus_1", "i")
IS_0("i")
JNZi(crc_loop)

var("ptr2", "result")

correct_crc = crc16 + test_wrong_crc

var("correct_crc", correct_crc)

# By default we're going to decrypt 'Wrong...' message.
MOVi("message", "ptr")
MOV("message_sz", "i")

var("message_mask", message_mask)
MOV("message_mask", "mask")

var("coef_add", message_coef_add)

wrong_label = next_label()

XOR("correct_crc", "crc16", "correct_crc")
IS_0("correct_crc")
JNZi(wrong_label)

# Now we switch to descrypt the secret message.
MOVi(secret_coef_add, "coef_add")

MOVi("secret", "ptr")
MOV("secret_sz", "i")

# mask = ((crc16 & 0xff) | ((crc16 >> 8) & 0xff)) + 1
MOV("crc16", "mask")
for i in range(0, 8):
  SHR("mask", "mask")
XOR("crc16", "mask", "mask")
ANDi("mask", 0xff, "mask")

ADD("mask", "const_1", "mask")

code_label(wrong_label)

MOV("i", "result_sz")

loop = next_label()
code_label(loop)              # loop
# ch = *ptr
PEEK("ptr", "ch")
# ch ^= mask
XOR("ch", "mask", "ch")
POKE("ch", "ptr2")
# mask = mask * 3 + 11
ADD("mask", "mask", "t")
ADD("mask", "t", "mask")
ADD("mask", "coef_add", "mask")
# ptr += 1
ADD("ptr", "const_1", "ptr")
# ptr2 += 1
ADD("ptr2", "const_1", "ptr2")
# i = i - 1
ADD("i", "const_minus_1", "i")
IS_0("i")
JNZi(loop)

EXITi(0x00)

buffer(8)
data_label("stack")

var("secret_sz", len(secret_code))
data_label("secret")
buffer(len(secret_code) + 1)

var("message_sz")
data_label("message")
buffer(16)

var("password_sz")
data_label("password")
buffer(16)

# The buffer holding the result string.
var("result_sz")
data_label("result")
buffer(32)

# Compiler

text = code_segment
text.extend(data_segment)

if verbose:
  print "\n".join(text)

# Phase 1. Calculate names.

addr = 0
names = {}
for line in text:
  if line[0] == ';': continue
  if line[0] != ' ':
    name = line.partition(':')[0]
    names[name] = addr
  else:
    addr = addr + 1

if verbose:
  print names

raw_text = "\n".join(text)

# Resolve names.

for name in names:
  if verbose:
    print name, names[name], type(names[name])
  name_re = re.compile(r'dw ' + name + '$', re.M)
  value = "%d" % names[name]
  raw_text = name_re.sub('dw ' + value, raw_text)

text = raw_text.split("\n")

if verbose:
  print "\n".join(text)

# Phase 2. Compilation.

addr = 0
comment = ""
mem = []
for line in text:
  if line[0] == ';' or line[0] != ' ':
    comment = comment + line + ' '
  else:
    value = int(line.strip().partition(" ")[2])
    if verbose:
      print "%04X: %04X ; %s" % (addr, value, comment)
    mem.append(value)
    addr = addr + 1
    comment = ""

# Interpretation

ip = names["ip"]
exit_reg = names["exit_reg"]
shift_reg = names["shift_reg"]
carry_reg = names["carry_reg"]

def nor(a, b):
  r = a | b
  r = r ^ 0xFFFF
  return r & 0xFFFF

def norcpu():
  while 1:
    i = mem[ip];
    a = mem[i + 0]
    b = mem[i + 1]
    r = mem[i + 2]
    mem[ip] = i + 3
    f = nor(mem[a], mem[b])
    mem[r] = f
    mem[shift_reg] = ((f >> 15) & 1) | ((f & 0x7FFF) << 1)

    if verbose_cpu:
      print "%04X: %04X [%04X] %04X [%04X] -> %04X [%04X]" % \
            (i, a, mem[a], b, mem[b], r, mem[r])
    if r == exit_reg:
      break

print "Starting from [%04X]" % mem[ip]

# Encrypt the secret code.
secret_mask = ((crc16 & 0xff) ^ ((crc16 >> 8) & 0xff)) + 1
encode_string(secret_code, "secret", secret_mask, secret_coef_add);

# Encrypt 'Wrong...' message.
encode_string(message_text, "message", message_mask, message_coef_add);

mem_js = dump_js(mem)
save_mem("norcpu-1-before.bin")
mem_sz = len(mem)

if len(mem) >= 0x10000:
  print "Too much code (%08X, %04X)" % (len(mem), len(mem) - 0x10000)
  sys.exit()

# Inject plain password in the last moment (for testing).
put_string(guess, "password")

save_mem("norcpu-2-before-with-password.bin")

if verbose:
  print "Original memory:"
  print dump(mem)

start_time = time.time()

norcpu()

end_time = time.time()

save_mem("norcpu-3-after.bin", mem_sz)

if verbose:
  print "Memory after:"
  dump(mem)

print
print "Size: %X" % len(mem)
print "Time: %d" % (end_time - start_time)
print "Exit: %04X" % mem[exit_reg]

print("CRC : %04X (%04X)" % (crc16, correct_crc))

result = names["result"]
result_value = ""
for i in range(0, mem[names["result_sz"]]):
  result_value = result_value + chr(mem[result + i] & 0xff)

if result_value != secret_code:
  print "ERROR: [%s] != [%s]" % (secret_code, result_value)

js = string.Template(open('template.html', 'r').read())

js = js.substitute( \
  ip = names["ip"],
  exit_reg = names["exit_reg"],
  shift_reg = names["shift_reg"],
  password = names["password"],
  password_sz = names["password_sz"],
  result = names["result"],
  result_sz = names["result_sz"],
  mem_js = mem_js
)

f = open("norcpu.html", "w")
f.write(js)
f.close()

Задача 2

Файл norcpu.py (template.html).

import sys, re, time, string, binascii

verbose = False
verbose_cpu = False
scramble = True

secret_password = "h1cKmE1fUsAn"
secret_password_xor_mask = 0x3401
secret_password_add = 29

secret_code = "R0und2 D0ne!"
secret_code_xor_mask = 0x730A
secret_code_add = 37

guess = "123456789012"
guess = secret_password

code_segment = []
data_segment = []

label_count = 0

def dump(data, length = 8):
  result = []
  for i in xrange(0, len(data), length):
    line = data[i:i + length]
    hex_line = ' '.join(["%04X" % x for x in line])
    result.append("%04X: %-*s\n" % (i, length*5, hex_line))
  return ''.join(result)

def dump_js(data, length = 8):
  result = []
  for i in xrange(0, len(data), length):
    line = data[i:i + length]
    hex_line = ' '.join(["0x%04X," % x for x in line])
    result.append("%-*s\n" % (length*5, hex_line))
  return ''.join(result)

def encode_string(data, name, mask, coef_add):
  global mem, names
  offset = names[name]
  offset_sz = names[name + "_sz"]
  for i in range(0, len(data)):
    mem[offset + i] = ord(data[i]) ^ mask
    mask = (mask * 3 + coef_add) & 0xffff
  mem[offset_sz] = len(data)

def put_string(data, name):
  global mem, names
  offset = names[name]
  offset_sz = names[name + "_sz"]
  for i in range(0, len(data)):
    mem[offset + i] = ord(data[i])
  mem[offset_sz] = len(data)

def save_mem(name, size = -1):
  f = open(name, "w")
  if size == -1: size = len(mem)
  for i in (mem[0:size]):
    hex = "%04X" % i
    bin = binascii.a2b_hex(hex)
    f.write(bin)
  f.close()

def next_label():
  global label_count
  label_count = label_count + 1
  return "label_%04d" % label_count

def code_rem(comment):
  code_segment.append('; ' + comment)

def data_rem(comment):
  data_segment.append('; ' + comment)

def data_label(name):
  data_segment.append(name + ":")

def code_label(name):
  code_segment.append(name + ":")

def code(value):
  printed = value
  if type(value).__name__ == 'int':
    printed = "%d" % value
  code_segment.append("  dw %s" % printed)

scramble_counter = 0x2743

def next_scramble_counter():
  global scramble_counter
  scramble_counter = scramble_counter * 3 + 7
  return scramble_counter & 0xffff

def word(value):
  if value == -1:
    if scramble:
      value = next_scramble_counter()
    else:
      value = 0
  printed = value
  if type(value).__name__ == 'int':
    printed = "%d" % value
  data_segment.append("  dw %s" % printed)

def buffer(length, value = -1):
  for i in range(0, length):
    word(value)

def var(name, value = -1):
  data_label(name);
  word(value);

# Macros

def NOR(a, b, r):
  code_rem('NOR ' + str(a) + ' ' + str(b) + ' ' + str(r))
  code(a)
  code(b)
  code(r)

def NOT(a, r):
  NOR(a, a, r);

def OR(a, b, r):
  NOR(a, b, "or_reg")
  NOT("or_reg", r)
var("or_reg")

def AND(a, b, r):
  NOT(a, "and_reg_a")
  NOT(b, "and_reg_b")
  OR("and_reg_a", "and_reg_b", "and_reg_a")
  NOT("and_reg_a", r)
var("and_reg_a")
var("and_reg_b")

def ANDi(a, imm, r):
  MOVi(imm, "and_i_reg")
  AND(a, "and_i_reg", r)
var("and_i_reg")

def XOR(a, b, r):
  NOT(a, "xor_reg_a")
  NOT(b, "xor_reg_b")
  AND(a, "xor_reg_b", "xor_reg_b")
  AND(b, "xor_reg_a", "xor_reg_a")
  OR("xor_reg_a", "xor_reg_b", r)
var("xor_reg_a")
var("xor_reg_b")

def XORi(a, imm, r):
  MOVi(imm, "xor_i_reg")
  XOR(a, "xor_i_reg", r)
var("xor_i_reg")

def MOV(a, b):
  code_rem('MOV ' + str(a) + ' ' + str(b))
  NOT(a, "move_reg")
  NOT("move_reg", b)
  code_rem('MOV END')
var("move_reg")

def JMP(a):
  code_rem('JMP ' + str(a))
  MOV(a, "ip")

def JMPi(a):
  code_rem('JMPi ' + str(a))
  label = next_label()
  JMP(label)
  code_label(label)
  code(a)

def MOVi(imm, a):
  code_rem('MOVi #' + str(imm) + ' ' + str(a))
  label_data = next_label()
  label_jump = next_label()
  MOV(label_data, a)
  JMPi(label_jump)
  code_label(label_data)
  code(imm)
  code_label(label_jump)

# [a] -> b
def PEEK(a, b):
  label1 = next_label()
  label2 = next_label()
  MOV(a, label1)
  MOV(a, label2)
  code_label(label1)  # NOT(0, 0, move_reg)
  code(0)             # <- a
  code_label(label2)  #
  code(0)             # <- a
  code("move_reg")    #
  NOT("move_reg", b)

# a -> [b]
def POKE(a, b):
  code_rem('POKE ' + str(a) + ' [' + str(b) + ']')
  label = next_label()
  MOV(b, label)
  NOT(a, "move_reg")  # +3 (three operations)
  code("move_reg")    # +4
  code("move_reg")    # +5
  code_label(label)
  code(0)             # <- b

# imm -> [a]
def POKEi(imm, a):
  MOVi(imm, "poke_i_reg")
  POKE("poke_i_reg", a)
var("poke_i_reg")

def EXIT(a):
  MOV(a, "exit_reg")

def EXITi(a):
  MOVi(a, "exit_reg")

def FADD(mask, carry, a, b, r):
  AND(a, mask, "fadd_reg_a")  # zero bits in 'a' except mask'ed
  AND(b, mask, "fadd_reg_b")  # zero bits in 'b' except mask'ed
  AND(carry, mask, carry)     # zero bits in 'carry' except mask'ed

  # SUM = (a ^ b) ^ carry
  XOR(a, b, "fadd_reg_t1")
  XOR("fadd_reg_t1", carry, "fadd_reg_bit_r")

  # Leave only 'mask'ed bit in bit_r.
  AND("fadd_reg_bit_r", mask, "fadd_reg_bit_r")

  # Add current added bit to the result.
  OR("fadd_reg_bit_r", r, r)

  # CARRY = (a & b) | (carry & (a ^ b))
  AND(a, b, "fadd_reg_t2")
  AND(carry, "fadd_reg_t1", "fadd_reg_t1")

  # CARRY is calculated, and 'shift_reg' contains the same value
  # but shifted the left by 1 bit.
  OR("fadd_reg_t2", "fadd_reg_t1", carry)

  # CARRY is shifted the left by 1 bit to be used on the next round.
  MOV("shift_reg", carry)

  # shift_reg = mask << 1
  MOV(mask, mask)
  # mask = shift (effectively "mask = mask << 1")
  MOV("shift_reg", mask)

  AND(carry, mask, carry)

var("fadd_reg_a")
var("fadd_reg_b")
var("fadd_reg_bit_r")
var("fadd_reg_t1")
var("fadd_reg_t2")

def ZERO(a):
  XOR(a, a, a)

def FADC(a, b, r):
  ZERO("fadc_reg_t")
  MOV("const_1", "fadc_reg_mask")
  for i in range(0, 16):
    FADD("fadc_reg_mask", "carry_reg", a, b, "fadc_reg_t")
  MOV("fadc_reg_t", r)

  ZERO("fadc_reg_t")

  for i in range(0, 16):
    OR("fadc_reg_t", "carry_reg", "fadc_reg_t")
    MOV("carry_reg", "carry_reg")
    MOV("shift_reg", "carry_reg")

  MOV("fadc_reg_t", "carry_reg")

var("fadc_reg_mask")
var("fadc_reg_t")

def ADD(a, b, r):
  ZERO("carry_reg")
  FADC(a, b, r)

def ADDi(a, imm, r):
  MOVi(imm, "add_i_reg")
  ADD(a, "add_i_reg", r)
var("add_i_reg")

def PUSH(a):
  ADD("stack_reg", "const_minus_1", "stack_reg")
  POKE(a, "stack_reg")

def PUSHi(imm):
  MOVi(imm, "push_i_reg")
  PUSH("push_i_reg")
var("push_i_reg")

def POP(a):
  PEEK("stack_reg", a)
  ADD("stack_reg", "const_1", "stack_reg")

def CALL(a):
  label = next_label()
  PUSHi(label)
  JMP(a)
  code_label(label)

def CALLi(a):
  label = next_label()
  PUSHi(label)
  JMPi(a)
  code_label(label)

def RET():
  POP("ip")

# Jump 'a', if cond = FFFF, and 'b' if conf = 0000
def BRANCH(a, b, cond):
  AND(a, cond, "branch_reg_a")              # reg_a = a & cond
  NOT(cond, "branch_reg_b")                 # reg_b = !cond
  AND(b, "branch_reg_b", "branch_reg_b")    # reg_b = b & reg_b = b & !cond
  OR("branch_reg_a", "branch_reg_b", "ip")  # ip = (a & cond) | (b & !cond)
var("branch_reg_a")
var("branch_reg_b")

# Jump 'a', if cond = FFFF, and 'b' if conf = 0000
def BRANCHi(a, b, cond):
  MOVi(a, "branch_i_reg_a")
  MOVi(b, "branch_i_reg_b")
  BRANCH("branch_i_reg_a", "branch_i_reg_b", cond)
var("branch_i_reg_a")
var("branch_i_reg_b")

# if a != 0 -> carry = FFFF else carry = 0000
def IS_0(a):
  ZERO("carry_reg")
  FADC(a, "const_minus_1", "is_0_reg")
  NOT("carry_reg", "zero_reg")
var("is_0_reg")
var("zero_reg")

# ip = (zero_reg == FFFF ? a : ip)
def JZi(a):
  label = next_label()
  BRANCHi(a, label, "zero_reg")
  code_label(label)

# ip = (zero_reg == FFFF ? a : ip)
def JNZi(a):
  label = next_label()
  BRANCHi(label, a, "zero_reg")
  code_label(label)

def ROL(a, b):
  MOV(a, a)            # shift_reg = a << 1
  MOV("shift_reg", b)

def ROR(a, b):
  MOV(a, "ror_reg")
  for i in range(0, 15):
    ROL("ror_reg", "ror_reg")
  MOV("ror_reg", b)
var("ror_reg")

def SHL(a, b):
  ROL(a, b)
  ANDi(b, 0x0001, b)

def SHR(a, b):
  ROR(a, b)
  ANDi(b, 0x7FFF, b)

def MUL3(a, b):
  ADD(a, a, "mul3_reg")    # mul3_reg = a + a
  ADD("mul3_reg", a, b)    # b = mul3_reg + a
var("mul3_reg")

# NORCPU code

var("ip", "start")
var("shift_reg")
var("carry_reg")
var("const_1", 1)
var("const_minus_1", 0xFFFF)
var("exit_reg")

var("stack_reg", "stack")

code_label("start")

var("ch")
var("t")
var("xor_mask")
var("cmp_flag")

var("ptr")
var("ptr2")
var("i")

MOVi("exchange", "ptr")
MOVi("secret_password", "ptr2")
MOVi(secret_password_xor_mask, "xor_mask")
MOVi(0, "cmp_flag")
MOVi(len(secret_password), "i")

cmp_loop = next_label()
code_label(cmp_loop)               # cmp_loop:
PEEK("ptr", "ch")                                      # ch = *ptr
XOR("ch", "xor_mask", "ch")                            # ch ^= xor_mask
PEEK("ptr2", "t")                                      # t = *ptr2
XOR("ch", "t", "ch")                                   # ch = ch ^ t
OR("cmp_flag", "ch", "cmp_flag")                       # cmp_flag |= ch
ADD("ptr", "const_1", "ptr")                           # ptr += 1
ADD("ptr2", "const_1", "ptr2")                         # ptr2 += 1
MUL3("xor_mask", "xor_mask")                           # xor_mask *= 3
ADDi("xor_mask", secret_password_add, "xor_mask")      # xor_mask += add_const
ADD("i", "const_minus_1", "i")                         # i -= 1
IS_0("i")
JNZi(cmp_loop)

MOVi(0, "exchange_sz")

ok_label = next_label()
IS_0("cmp_flag")
JZi(ok_label)

exit_label = next_label()
JMPi(exit_label)

code_label(ok_label)

MOVi("secret_code", "ptr")
MOV("secret_code_sz", "i")
MOVi(secret_code_xor_mask, "xor_mask")

MOVi("exchange", "ptr2")

MOV("i", "exchange_sz")

loop = next_label()
code_label(loop)                   # loop:
PEEK("ptr", "ch")                             # ch = *ptr
XOR("ch", "xor_mask", "ch")                   # ch ^= xor_mask
POKE("ch", "ptr2")                            # *ptr2 = ch
MUL3("xor_mask", "xor_mask")                  # xor_mask *= 3
ADDi("xor_mask", secret_code_add, "xor_mask") # xor_mask += add_const
ADD("ptr", "const_1", "ptr")                  # ptr += 1
ADD("ptr2", "const_1", "ptr2")                # ptr2 += 1
ADD("i", "const_minus_1", "i")                # i = i - 1
IS_0("i")
JNZi(loop)

code_label(exit_label)             # exit_label:
EXITi(0x00)

buffer(8)
data_label("stack")

var("secret_code_sz", len(secret_code))
data_label("secret_code")
buffer(len(secret_code))

var("secret_password_sz")
data_label("secret_password")
buffer(16)

var("exchange_sz", 0)
data_label("exchange")
buffer(32)

# Compiler

text = code_segment
text.extend(data_segment)

if verbose:
  print "\n".join(text)

# Phase 1. Calculate names.

addr = 0
names = {}
for line in text:
  if line[0] == ';': continue
  if line[0] != ' ':
    name = line.partition(':')[0]
    names[name] = addr
  else:
    addr = addr + 1

if verbose:
  print names

raw_text = "\n".join(text)

# Resolve names.

for name in names:
  if verbose:
    print name, names[name], type(names[name])
  name_re = re.compile(r'dw ' + name + '$', re.M)
  value = "%d" % names[name]
  raw_text = name_re.sub('dw ' + value, raw_text)

text = raw_text.split("\n")

if verbose:
  print "\n".join(text)

# Phase 2. Compilation.

addr = 0
comment = ""
mem = []
for line in text:
  if line[0] == ';' or line[0] != ' ':
    comment = comment + line + ' '
  else:
    value = int(line.strip().partition(" ")[2])
    if verbose:
      print "%04X: %04X ; %s" % (addr, value, comment)
    mem.append(value)
    addr = addr + 1
    comment = ""

# Interpretation

ip = names["ip"]
exit_reg = names["exit_reg"]
shift_reg = names["shift_reg"]
carry_reg = names["carry_reg"]

def nor(a, b):
  r = a | b
  r = r ^ 0xFFFF
  return r & 0xFFFF

def norcpu():
  while 1:
    i = mem[ip];
    a = mem[i + 0]
    b = mem[i + 1]
    r = mem[i + 2]
    mem[ip] = i + 3
    f = nor(mem[a], mem[b])
    mem[r] = f
    mem[shift_reg] = ((f >> 15) & 1) | ((f & 0x7FFF) << 1)

    if verbose_cpu:
      print "%04X: %04X [%04X] %04X [%04X] -> %04X [%04X]" % \
            (i, a, mem[a], b, mem[b], r, mem[r])
    if r == exit_reg:
      break

print "Starting from [%04X]" % mem[ip]

encode_string(secret_code, "secret_code", secret_code_xor_mask, secret_code_add);
encode_string(secret_password, "secret_password", secret_password_xor_mask, secret_password_add);

mem_js = dump_js(mem)
save_mem("norcpu-1-before.bin")
mem_sz = len(mem)

if len(mem) >= 0x10000:
  print "Too much code (%08X, %04X)" % (len(mem), len(mem) - 0x10000)
  sys.exit()

# Inject plain password in the last moment (for testing).
put_string(guess, "exchange")

save_mem("norcpu-2-before-with-password.bin")

if verbose:
  print "Original memory:"
  print dump(mem)

start_time = time.time()

norcpu()

end_time = time.time()

save_mem("norcpu-3-after.bin", mem_sz)

if verbose:
  print "Memory after:"
  dump(mem)

print
print "Size: %X" % len(mem)
print "Time: %d" % (end_time - start_time)
print "Exit: %04X" % mem[exit_reg]

exchange = names["exchange"]
result_value = ""
for i in range(0, mem[names["exchange_sz"]]):
  result_value = result_value + chr(mem[exchange + i] & 0xff)

print "Result: [%s]" % result_value

if len(result_value) == 0:
  print "ERROR: Wrong password"

js = string.Template(open('template.html', 'r').read())

js = js.substitute( \
  ip = names["ip"],
  exit_reg = names["exit_reg"],
  shift_reg = names["shift_reg"],
  exchange = names["exchange"],
  exchange_sz = names["exchange_sz"],
  mem_js = mem_js
)

f = open("norcpu2.html", "w")
f.write(js)
f.close()

Оригинальный пост | Disclaimer

Комментарии