Первая часть.
Привет, Хабр! Сегодня я покажу, как заработать мульёны на собственной гениальной идее! сделать очередную бесполезную фигню. В рамках цикла из трёх статей я хочу поделиться опытом создания своего crackme для linux amd64. Вот две основные черты полученного crackme:
Каждая функция зашифрована своим ключом (используется алгоритм AES с режимом CBC и 256 битным ключом). При вызове функции она расшифровывается, а вызвавшая её функция обратно зашифровывается.
Для усложнения отладки используются наномиты.
Если перед прочтением статьи вы хотите попробовать решить crackme, скачать его можно с github. А я приступаю к описанию.
Whitebox AES
Стоит сказать, что заголовок несколько кликбейтный, потому что никакие из существующих реализаций whitebox алгоритмов я не изучал, так как мне хотелось попробовать придумать что‑то самостоятельно. Поэтому описанный далее алгоритм не подойдёт для практического использования. Whitebox криптография используется, когда пользователь имеет доступ к коду вашего приложения (пускай и скомпилированному), который выполняет криптографические операции (шифрование и/или расшифровывание), и вам не хочется, чтобы пользователь мог достать из него ключи. У меня, такой цели не стояло, нужно было лишь усложнить пользователю самостоятельную расшифровку функций, просто я не смог подобрать более подходящее название, чем whitebox AES.
В шифре AES ключ используется всего в одной функции — AddRoundKey, давайте её рассмотрим (код взят из репозитория tiny‑AES‑c):
#define Nb 4 // число столбцов (32-битных слов), составляющих State.
void AddRoundKey(uint8_t round, state_t* state, const uint8_t* RoundKey) {
uint8_t i,j;
for (i = 0; i < 4; ++i) {
for (j = 0; j < 4; ++j) {
(*state)[i][j] ^= RoundKey[(round * Nb * 4) + (i * Nb) + j];
}
}
}
Как можно видеть, над блоком данных и частью ключа выполняется операция исключающее или, при этом часть ключа зависит исключительно от номера раунда. Изначально у меня возникла очень тупая идея — просто развернуть цикл, получилось бы что‑то вроде такого:
void AddRoundKey(uint8_t round, state_t* _state) {
uint32_t *state = (uint32_t*)_state;
if(round == 0) {
state[0] ^= word_0_round_0;
state[1] ^= word_1_round_0;
state[2] ^= word_2_round_0;
state[3] ^= word_3_round_0;
} else if(round == 1) {
state[0] ^= word_0_round_1;
state[1] ^= word_1_round_1;
state[2] ^= word_2_round_1;
state[3] ^= word_3_round_1;
}
//...
else if(round == 14) {
state[0] ^= word_0_round_14;
state[1] ^= word_1_round_14;
state[2] ^= word_2_round_14;
state[3] ^= word_3_round_14;
}
}
Где word_n_round_k — соответствующее 32-битное слово из ключа. Таким образом ключ встраивается в код, что немного усложняет его получение. Далее я подумал, что из кода можно убрать условные операторы и, таким образом, усложнить понимание его работы. Для этого надо было придумать, как сделать так, чтобы операции текущего раунда вносили изменения в данные, а операции других раундов — нет. Я решил сдвигать операнд на 32 бита влево или вправо, чтобы исключающее или с данными не имело эффекта. В таком случае функция AddRoundKey преображается следующим образом:
// если n > 0, возвращает 1.
// если n < 0, возвращает -1.
// если n = 0, возвращает 0.
uint32_t sgn32(uint32_t n) {
return (-n>>31)-(n>>31);
}
// если n = 0, возвращает 0.
// если n != 0, возвращает 32.
uint32_t guard(uint32_t n) {
uint32_t a = sgn32(n);
return ((a & 1) | (a >> 31)) * 32;
}
void AddRoundKey(uint32_t round, state_t* _state) {
uint32_t *state = (uint32_t*)_state;
uint32_t r0 = round ^ 0; // r0 = 0, если round = 0
uint32_t r1 = round ^ 1; // r1 = 0, если round = 1
//...
uint32_t r14 = round ^ 14; // r14 = 0, если round = 14
// используем 64-битное число, так как сдвиг более, чем на 31 бит,
// является undefined behavior для 32-битных чисел.
uint64_t shift = 0;
// нулевой раунд
shift = word_0_round_0;
// guard(r0) = 32, если r0 != 0.
// guard(r0) = 0, если r0 = 0.
shift <<= guard(r0);
state[0] ^= shift;
//...
// четырнадцатый раунд
shift = word_0_round_14;
shift <<= guard(r14);
state[0] ^= shift;
}
Уже лучше, но байты ключа всё ещё лежат открытым тестом. А что если сделать так, чтобы исключающее или для нулевого раунда применялось всегда, для первого — только от первого раунда и до четырнадцатого, для второго — от второго и до четырнадцатого и т. д. Тогда в неизменном виде будут лежать только 4 слова нулевого раунда. Так будет выглядит следующая итерации функции AddRoundKey:
void AddRoundKey(uint32_t round, state_t* _state) {
uint32_t *state = (uint32_t*)_state;
uint32_t r0 = round ^ 0;
uint32_t r1 = round ^ 1;
//...
uint32_t r14 = round ^ 14;
uint64_t shift = 0;
// нулевой раунд
shift = word_0_round_0;
// guard вернёт ноль, если хотя бы одна переменная равна нулю
shift <<= guard(r0 * r1 * /*...*/ * r14);
state[0] ^= shift;
//...
// первый раунд
// (state[0] ^ word_0_round_0) ^ shift = state[0] ^ word_0_round_1
shift = word_0_round_0 ^ word_0_round_1;
// выполняем для всех раундов, кроме нулевого
shift <<= guard(r1 * r2 /*...*/ * r14);
shift[0] ^= shift;
// ...
// четырнадцатый раунд
shift = word_0_round_0 ^ word_0_round_1 ^ /*...*/ ^ word_0_round_13;
shift <<= guard(r14);
state[0] ^= shift;
}
Алгоритм можно улучшить, присваивая в shift произвольные выражения, зависимые от переменной round, которые после вычисления будут давать нужны значения. Например:
void AddRoundKey(uint32_t round, state_t* _state) {
uint32_t *state = (uint32_t*)_state;
uint32_t r0 = round ^ 0;
uint32_t r1 = round ^ 1;
//...
uint32_t r14 = round ^ 14;
uint64_t shift = 0;
// нулевой раунд
// при условии, что rotl((round + 0x12345678), 17) * 0x90801023 = word_0_round_0
shift = rotl((round + 0x12345678), 17) * 0x90801023;
shift <<= guard(r0 * r1 * /*...*/ * r14);
state[0] ^= shift;
//...
// четырнадцатый раунд
// при условии, что (rotl(round, 11) ^ 0x546321) * 0x77889910 =
// word_0_round_0 ^ word_0_round_1 ^ ... ^ word_0_round_13;
shift = (rotl(round, 11) ^ 0x546321) * 0x77889910;
shift <<= guard(r14);
state[0] ^= shift;
}
Следующее улучшение, которое пришло мне в голову — перемешать раунды местами, т.е сначала идут вычисления, например, для шестого раунда, потом для десятого и т. д. Вид функции AddRoundKey при этом не меняется, меняется лишь порядок вычислений в ней, поэтому примера кода здесь не будет (более подробно я рассмотрю этот момент при описании алгоритма генерации кода AddRoundKey для произвольного ключа). Как я писал ранее, шифроваться будут все функции, а текущий алгоритм AddRoundKey работает лишь с одним ключом. Что ж, предлагаю это исправить. Делать мы это будем тем же самым способом — добавим вычисления для всех имеющихся у нас ключей в одну функцию AddRoundKey, а чтобы нивелировать эффект нужных вычислений будем использовать битовые сдвиги, только в этот раз зависимость будет не от номера раунда, а от адреса функции. Параметр _state, который передаётся в AddRoundKey является указателем на шифруемую или расшифровываемую область памяти, то есть мы можем использовать его для задания зависимости от адреса функции. Но это будет работать не со всеми режимами AES, например, CTR использовать не выйдет, потому что там в _state передаётся указатель на временный буфер, содержащий счётчик, а не указатель на какую‑либо часть тела функции. Для начала рассмотрим вспомогательную функцию, которая будет возвращать 0, если адрес принадлежит заданному интервалу, и 33 в ином случае (можно возвращать любое число, не являющееся степенью двойки, потому что при перемножении нескольких чисел, являющихся степенями двойки, при переполнении получится 0).
int64_t sgn64(uint64_t n) {
uint64_t s = 63;
return (-n>>s)-(n>>s);
}
uint32_t address_guard(uint64_t addr, uint64_t begin, uint64_t end) {
uint64_t a = sgn64((2 + sgn64(addr - begin)) * (2 + sgn64(end - addr)) - 4);
uint64_t s = 63;
a >>= s;
return a * 33;
}
Чтобы доказать корректность работы address_guard, построим таблицу, для начала введя псевдоним для одного подвыражения: range = (2 + sgn64(addr - begin)) * (2 + sgn64(end - addr)) - 4, чтобы таблица была не слишком широкой. Прочерки в таблице означают невозможность ситуации, например, условия addr < begin и addr > end не могут выполнятся одновременно.
sgn64(addr - begin) |
sgn64(end - addr) |
range |
a |
a >> 63 |
result |
---|---|---|---|---|---|
0 (addr = begin) |
0 (addr = end) |
- |
- |
- |
- |
0 (addr = begin) |
1 (addr < end) |
2 |
1 |
0 |
0 |
0 (addr = begin) |
-1 (addr > end) |
-2 |
-1 |
1 |
33 |
1 (addr > begin) |
0 (addr = end) |
2 |
1 |
0 |
0 |
1 (addr > begin) |
1 (addr < end) |
5 |
1 |
0 |
0 |
1 (addr > begin) |
-1 (addr > end) |
-1 |
-1 |
1 |
33 |
-1 (addr < begin) |
0 (addr = end) |
- |
- |
- |
- |
-1 (addr < begin) |
1 (addr < end) |
-1 |
-1 |
1 |
33 |
-1 (addr < begin) |
-1 (addr > end) |
- |
0 |
- |
- |
Как видно из таблицы, address_guard возвращает 0, только при выполнении двух условий begin <= addr <= end, в остальных случаях она возвращает 33, что нам и было нужно. Теперь рассмотрим код для функции AddRoundKey, которая работает с двумя ключами. Первая функция находится по адресу 0x401000 и имеет размер 16 байт, вторая функция располагается по адресу 0x401020 и имеет размер 48 байт:
void AddRoundKey(uint32_t round, state_t* _state) {
uint32_t *state = (uint32_t*)_state;
uint64_t addr = (uint64_t)state;
uint32_t r0 = round ^ 0;
uint32_t r1 = round ^ 1;
//...
uint32_t r14 = round ^ 14;
uint32_t f0 = address_guard(addr, 0x401000, 0x401010);
uint32_t f1 = address_guard(addr, 0x401020, 0x401050);
uint64_t shift = 0;
// нулевой раунд первой функции
shift = rotl((round + 0x12345678), 17) * 0x90801023;
// вычисление будет выполнено для всех раундов обеих функций
shift <<= guard(r0 * r1 * /*...*/ * r14);
shift <<= guard(f0 * f1);
state[0] ^= shift;
//...
// четырнадцатый раунд первой функции
shift = (rotl(round, 11) ^ 0x546321) * 0x77889910;
// вычисление будет выполнено для четырнадцатых раундов обеих функций.
shift <<= guard(r14);
shift <<= guard(f0 * f1);
state[0] ^= shift;
// нулевой раунд второй функции
shift = (round * 0x11223344) ^ 0x78771122;
// вычисление будет выполнено для всех раундов второй функции
shift <<= guard(r0 * r1 * /*...*/ * r14);
shift <<= guard(f1);
state[0] ^= shift;
//...
// четырнадцатый раунд второй функции
shift = rotl((round * 0x98765432) + 0x988888), 12) ^ 0x77777;
// вычисление будет выполнено для четырнадцатого раунда второй функции
shift <<= guard(r14);
shift <<= guard(f1);
state[0] ^= shift;
}
На этом я заканчиваю описание алгоритма встраивания ключей в функцию AddRoundKey. В следующей главе я покажу, как написать генератор кода для функции AddRoundKey на Haskell.
Генератор функции AddRoundKey
Вспомогательные функции
Сначала опишем вспомогательные типы и функции:
-- Массив state
data AesState = AesState !Word32 !Word32 !Word32 !Word32
-- Операция исключающего или для двух массивов state
xorAesStates :: AesState -> AesState -> AesState
xorAesStates (AesState w11 w12 w13 w14) (AesState w21 w22 w23 w24) =
AesState (w11 `xor` w21) (w12 `xor` w22) (w13 `xor` w23) (w14 `xor` w24)
-- Тип, нужный чтобы передавать в генератор адрес начала функции.
newtype Address = Address (Word64) deriving newtype Read
-- Тип, который представляет ключ шифрования.
newtype Key = Key (ByteString)
-- Тип для представления номера раунда
newtype Round = Round Word32 deriving newtype (Eq, Ord, Enum)
-- Вместо 256 бит мы используем сразу расширенный до 15
-- блоков ключ.
toKey :: ByteString -> Maybe Key
toKey key
| BS.length key == 240 = Just (Key key)
| otherwise = Nothing
-- Функция для получения нужного блока ключа по номеру раунда
getKeyRoundState :: Key -> Round -> AesState
getKeyRoundState (Key key) (Round rnd) = AesState (getWord32 0)
(getWord32 1)
(getWord32 2)
(getWord32 3)
where getWord32 :: Int -> Word32
getWord32 j = (b4 `shiftL` 24) .|. (b3 `shiftL` 16) .|. (b2 `shiftL` 8) .|. b1
where b1 = fromIntegral $ BS.index key w32Idx
b2 = fromIntegral $ BS.index key (w32Idx + 1)
b3 = fromIntegral $ BS.index key (w32Idx + 2)
b4 = fromIntegral $ BS.index key (w32Idx + 3)
w32Idx = rndIdx + j * 4
rndIdx :: Int
rndIdx = fromIntegral rnd * 16
Далее создадим типы для описания выражений:
data Stmt
= Define !Type !Var !Expr
| Assign !Var !Expr
data Expr
= Var !Var
| Int32 !Word32
| Int64 !Word64
| Binop !Binop !Expr !Expr
| Guard !Expr -- вызов guard
| AddressGuard !Expr !Expr !Expr -- вызов address_guard
data Var
= VarName !Text -- Обращение к обычной переменной
| VarState !Int -- Обращение к индексу массива state
| VarAddress -- Обращение к переменной addr
| VarRound -- Обращение к переменной round
deriving stock Eq
varToText :: Var -> Text
varToText = \case
VarName name -> name
VarState idx -> "state[" <> Text.pack (show idx) <> "]"
VarAddress -> "address"
VarRound -> "round"
data Binop
= Add
| Mul
| Shl
| Shr
| Rotl
| Rotr
| Xor
| And
deriving stock (Eq, Bounded, Enum)
data Type
= Uint32
| Uint64
Интерпретатор
Далее напишем интерпретатор для наших выражений.
Начнём с монады, в которой будут проходить все вычисления:
-- Состояние интерпретатора - хэш-таблица со значениями
-- переменных
newtype State = State { stVariables :: HashMap Var Value }
-- Данная функция принимает состояние интерпретатора
-- и возвращает значения массива state, полученные
-- в результате интерпретации. Эта функция понадобится
-- при генерировании кода AddRoundKey, в интерпретации
-- она никакого участия не принимает.
lookupAesState :: State -> Either InterpretError AesState
lookupAesState (State variables) = AesState
<$> lookupStateOrError 0
<*> lookupStateOrError 1
<*> lookupStateOrError 2
<*> lookupStateOrError 3
where lookupStateOrError :: Int -> Either InterpretError Word32
lookupStateOrError stateIdx =
case HashMap.lookup var variables of
Nothing -> Left (UndefinedVariable var)
Just (Val64 _) -> Left (WrongStateType stateIdx)
Just (Val32 w) -> Right w
where var = VarState stateIdx
-- Значение может быть либо 32 либо 64-битным.
data Value
= Val32 !Word32
| Val64 !Word64
-- При интерпретации возможны две ошибки:
-- 1. мы обратились к несуществующей переменной,
-- 2. переменная state[i] имеет 64, а не 32-битное значение.
data InterpretError
= UndefinedVariable !Var
| WrongStateType !Int
interpretErrorToText :: InterpretError -> Text
interpretErrorToText = \case
UndefinedVariable var -> "Undefined variable " <> (varToText var)
WrongStateType idx -> "Wrong type: "
<> varToText (VarState idx)
<> " must be 32-bit integer"
type InterpretM a = StateT State (Except InterpretError) a
Теперь создадим две функции, которые будут запускать вычисления:
-- Данная функция принимает на вход начальное значение массива state,
-- адрес текущей функции, номер текущего раунда и список выражений
-- для интерпретации, а возвращает конечное состояние интерпретатора.
runInterpreter :: Traversable t
=> AesState
-> Address
-> Round
-> t Stmt
-> Either InterpretError State
runInterpreter (AesState w1 w2 w3 w4) (Address addr) (Round rnd) stmts = result
where result = runExcept (execStateT (runInterpreterM stmts) initState)
initState = State initVariables
initVariables = HashMap.fromList [ (VarState 0, Val32 w1)
, (VarState 1, Val32 w2)
, (VarState 2, Val32 w3)
, (VarState 3, Val32 w4)
, (VarAddress, Val64 addr)
, (VarRound, Val32 rnd)
]
-- Данная функция интерпретирует отдельное выражение.
runInterpreterExpr :: Round -> Expr -> Either InterpretError Value
runInterpreterExpr (Round rnd) expr = runExcept (evalStateT (runExpr expr) initState)
where initState = State initVariables
initVariables = HashMap.fromList [(VarRound, Val32 rnd)]
-- runStmt :: Stmt -> InterpreterM ()
runInterpreterM :: Traversable t => t Stmt -> InterpretM ()
runInterpreterM = mapM_ runStmt
Теперь напишем функции которые будут интерпретировать наши выражения:
runStmt :: Stmt -> InterpretM ()
runStmt = \case
-- При присваивании обязательно делаем приведение
-- типа результата к типу переменной.
Define typ var expr -> runExpr expr >>= setVariable var . castToType typ
Assign var expr -> do
typ <- getVarType <$> getVariable var
val <- castToType typ <$> runExpr expr
setVariable var val
where castToType :: Type -> Value -> Value
castToType Uint32 v@(Val32 _) = v
castToType Uint64 v@(Val64 _) = v
castToType Uint32 (Val64 v) = Val32 (fromIntegral v)
castToType Uint64 (Val32 v) = Val64 (fromIntegral v)
getVarType :: Value -> Type
getVarType = \case
Val32 _ -> Uint32
Val64 _ -> Uint64
runExpr :: Expr -> InterpretM Value
runExpr = \case
Var var -> getVariable var
Int32 i32 -> pure (Val32 i32)
Int64 i64 -> pure (Val64 i64)
Binop op e1 e2 -> runBinop op <$> runExpr e1 <*> runExpr e2
Guard e -> runGuard <$> runExpr e
AddressGuard addr begin end -> runAddressGuard
<$> runExpr addr
<*> runExpr begin
<*> runExpr end
where -- Тут всё очевидно, если типы совпадают, просто выполняем операцию,
-- в ином случае преобразовываем 32-битное значение в 64-битное и
-- выполняем операцию
runBinop :: Binop -> Value -> Value -> Value
runBinop op (Val32 v1) (Val32 v2) = Val32 (runBinop32 op v1 v2)
runBinop op (Val64 v1) (Val64 v2) = Val64 (runBinop64 op v1 v2)
runBinop op (Val32 v1) (Val64 v2) = Val64 (runBinop64 op (fromIntegral v1) v2)
runBinop op (Val64 v1) (Val32 v2) = Val64 (runBinop64 op v1 (fromIntegral v2))
runBinop32 :: Binop -> Word32 -> Word32 -> Word32
runBinop32 op v1 v2 = case op of
Add -> v1 + v2
Mul -> v1 * v2
Shl -> shiftL v1 (fromIntegral v2)
Shr -> shiftR v1 (fromIntegral v2)
Rotl -> rotateL v1 (fromIntegral v2)
Rotr -> rotateR v1 (fromIntegral v2)
Xor -> v1 `xor` v2
And -> v1 .&. v2
runBinop64 :: Binop -> Word64 -> Word64 -> Word64
runBinop64 op v1 v2 = case op of
Add -> v1 + v2
Mul -> v1 * v2
Shl -> shiftL v1 (fromIntegral v2)
Shr -> shiftR v1 (fromIntegral v2)
Rotl -> rotateL v1 (fromIntegral v2)
Rotr -> rotateR v1 (fromIntegral v2)
Xor -> v1 `xor` v2
And -> v1 .&. v2
-- Вместо вычисления описанного в первой главе выражения,
-- просто закодируем его семантику.
runGuard :: Value -> Value
runGuard (Val32 v) = Val32 (if v == 0 then 0 else 32)
runGuard (Val64 v) = Val32 (if v == 0 then 0 else 32)
-- Аналогично поступим с вычислением address_guard.
runAddressGuard :: Value -> Value -> Value -> Value
runAddressGuard addr begin end
| addrGeBegin && addrLeEnd = Val32 0
| otherwise = Val32 33
where addrGeBegin = case (addr, begin) of
(Val32 a32, Val32 b32) -> a32 >= b32
(Val64 a64, Val64 b64) -> a64 >= b64
(Val32 a32, Val64 b64) -> fromIntegral a32 >= b64
(Val64 a64, Val32 b32) -> a64 >= fromIntegral b32
addrLeEnd = case (addr, end) of
(Val32 a32, Val32 e32) -> a32 <= e32
(Val64 a64, Val64 e64) -> a64 <= e64
(Val32 a32, Val64 e64) -> fromIntegral a32 <= e64
(Val64 a64, Val32 e32) -> a64 <= fromIntegral e32
-- Функция для получения значения переменной
getVariable :: Var -> InterpretM Value
getVariable var = gets (\State {..} -> HashMap.lookup var stVariables) >>= \case
Nothing -> lift $ throwE $ UndefinedVariable var
Just val -> pure val
-- Функция для присваивания значения переменной
setVariable :: Var -> Value -> InterpretM ()
setVariable var val = modify' $
\s@(State {..}) -> s { stVariables = HashMap.insert var val stVariables }
Наш интерпретатор закончен, теперь перейдём к самой интересной части — генератору кода.
Генератор кода AddRoundKey
Начнём как обычно с монады и функции запуска генерации:
-- Поле funcIv содержит вектор инициализации,
-- он нужен для генерации функции get_iv, но об этом
-- я расскажу в конце главы.
data FuncInfo = FuncInfo
{ funcAddress :: !Address
, funcSize :: !Word32
, funcKey :: !Key
, funcIv :: !Iv
}
-- В ходе генерации мы будем использовать интерпретатор,
-- сама генерация не порождает ошибок, поэтому используем
-- для ошибок тип InterpreterError.
type GenM a = StateT GenState (Except InterpretError) a
data GenState = GenState
{ -- Состояние генератора случайных чисел.
stGen :: !StdGen
-- Список сгенерированных выражений.
-- Seq используется потому, что нужно добавлять
-- новые выражения в конец списка, а интерпретировать
-- получившийся список сначала.
, stStmts :: !(Seq Stmt)
-- Индекс следующей переменной, при генерации
-- мы будем создавать переменные вида var0, var1 и т.д.
, stNextVar :: !Int
-- Массив переменных, которые будут использоваться в функции guard:
-- r0 = round ^ 0;
-- r1 = round ^ 1;
-- и т.д.
, stGuardVars :: !(Array Int Expr)
-- Список переменных, в которых будут лежать результаты вызова address_guard:
-- f0 = address_guard(addr, 0x1234, 0x12345);
-- f1 = address_guard(addr, 0x5678, 0x56789);
, stAddrGuardVars :: ![Expr]
-- Переменная uint64_t shift;
, stShiftVar :: !Var
-- Данная переменная нужна для генерации get_iv.
, stSizeVar :: !Var
}
-- Данный тип объявлен в другом модуле. Он нужен, чтобы не спутать два
-- списка выражений, так как мы будем генерировать две функции:
-- AddRoundKey и get_iv.
newtype AddRoundKeyCode f = AddRoundKeyCode { addRoundKeyCode :: f Stmt }
-- Функция, которая принимает начальное состояние генератора случайных чисел,
-- список с функциями, которые мы будем шифровать и расшифровывать, и
-- возвращает код AddRoundKey.
generateAddRoundKey :: StdGen
-> [FuncInfo]
-> Either InterpretError (AddRoundKeyCode Seq, StdGen)
generateAddRoundKey = runGenerator generateAddRoundKeyM AddRoundKeyCode
-- Эта функция нужна, чтобы запускать различные генераторы.
runGenerator :: ([FuncInfo] -> GenM ())
-> (Seq Stmt -> a)
-> StdGen
-> [FuncInfo]
-> Either InterpretError (a, StdGen)
runGenerator run toRes gen funcs = case finalStateOrError of
Left err -> Left err
Right finalState -> Right (toRes $ stStmts finalState, stGen finalState)
where finalStateOrError = runExcept $ execStateT (run funcs) initState
initState = GenState { stGen = gen
, stStmts = Seq.empty
, stNextVar = 0
, stGuardVars = array (0, 0) []
, stAddrGuardVars = []
, stShiftVar = VarName "undefined"
, stSizeVar = VarName "undefined"
}
Далее рассмотрим вспомогательные функции:
-- Функция для создания переменной.
define :: Type -> Var -> Expr -> GenM ()
define typ var expr = modify' (\s@GenState{..} -> s { stStmts = stStmts |> stmt })
where stmt = Define typ var expr
-- Функция для присваивания значения существующей переменной.
assign :: Var -> Expr -> GenM ()
assign var expr = modify' (\s@GenState{..} -> s { stStmts = stStmts |> stmt })
where stmt = Assign var expr
-- Функция для получения уникального имени переменной.
getNextVar :: GenM Var
getNextVar = do
idx <- gets stNextVar
modify' (\s -> s { stNextVar = idx + 1 })
pure (VarName $ "var" <> Text.pack (show idx))
-- Функции для получения случайного значения в заданном интервале.
randomR :: Random a => (a, a) -> GenM a
randomR rng = applyGen (Random.randomR rng)
-- Функция для получения случайного значения.
random :: Random a => GenM a
random = applyGen Random.random
-- Функция для выбора случайного элемента из списка.
randomChoice :: [a] -> GenM a
randomChoice xs = randomR (0, length xs - 1) >>= \idx -> pure (xs !! idx)
-- Функция для удобного использования
-- генератора случайных значений.
applyGen :: (StdGen -> (a, StdGen)) -> GenM a
applyGen f = do
(res, newGen) <- f <$> gets stGen
modify' (\s -> s { stGen = newGen })
pure res
-- Функция для перемешивания элементов списка.
-- Код довольно большой и не относящийся к теме статьи,
-- поэтому здесь не приводится.
shuffle :: [a] -> GenM [a]
shuffle = undefined
Далее перейдём к самому алгоритму генерации кода:
rounds :: [Round]
rounds = [Round 0..Round 14]
generateAddRoundKeyM :: [FuncInfo] -> GenM ()
generateAddRoundKeyM funcs = do
-- Задаём случайный порядок функций.
shuffledFuncs <- shuffle funcs
-- Объявляем нужные переменные
defineGuardVars shuffledFuncs
defineShiftVar
-- Делаем xor массива state со случайными значениями,
-- для усложнения получения ключа.
xorInitState
-- Проходимся по всем функциям
forM_ shuffledFuncs $ \func -> do
-- Задаём случайный порядок раундов для каждой функции
roundsOrder <- shuffle rounds
-- Генерируем код в AddRoundKey, который относится к
-- текущей функции.
generateAddRoundKeyForFunction func roundsOrder
where defineGuardVars :: [FuncInfo] -> GenM ()
defineGuardVars shuffledFuncs = do
-- Объявляем переменные, которые будут использоваться для
-- устранения эффектов вычислений в зависимости от номера
-- раунда.
-- uint32_t var0 = round ^ 0;
-- uint32_t var1 = round ^ 1;
-- и т.д.
guardVars <- forM rounds $ \(Round round) -> do
guardVar <- getNextVar
define Uint32 guardVar (Binop Xor (Var VarRound) (Int32 round))
pure $ Var guardVar
-- Объявляем переменные, которые будут использоваться для
-- устранения эффектов вычислений в зависимости от адреса
-- функции.
-- uint32_t var15 = address_guard(addr, funcAddress f1, funcAddress f1 + funcSize f1)
-- uint32_t var16 = address_guard(addr, funcAddress f2, funcAddress f2 + funcSize f2)
-- и т.д.
addrGuardVars <- defineAddrGuardVars shuffledFuncs
-- Создаём массив из списка guardVars.
let guardVarsArray = array (0, length rounds - 1) (zip [0..] guardVars)
-- Обновляем состояние ранее сгенерированными переменными.
modify' $ \s -> s { stGuardVars = guardVarsArray
, stAddrGuardVars = addrGuardVars
}
-- Объявляем переменную uint64_t shift = 0.
defineShiftVar :: GenM ()
defineShiftVar = do
let shiftVar = VarName "shift"
define Uint64 shiftVar (Int64 0)
modify' (\s -> s { stShiftVar = shiftVar })
-- Делаем xor массива state со случайными значениями.
xorInitState :: GenM ()
xorInitState = forM_ [0..3] $ \stateIdx -> do
val <- Int32 <$> randomR (0x10000000, 0xffffffff)
assign (VarState stateIdx) (Binop Xor (Var $ VarState stateIdx) val)
-- Эта функция создаёт переменные, которые используются для
-- устранения эффектов вычислений в зависимости от адреса
-- функции.
defineAddrGuardVars :: [FuncInfo] -> GenM [Expr]
defineAddrGuardVars funcs = forM funcs $ \FuncInfo{..} -> do
guardVar <- getNextVar
let Address begin = funcAddress
let beginExpr = Int64 begin
let end = begin + fromIntegral funcSize - 1
let endExpr = Int64 end
-- uint32_t varn = address_guard(addr, funcAddress, funcAddress + funcSize)
define Uint32 guardVar (AddressGuard (Var VarAddress) beginExpr endExpr)
pure $ Var guardVar
Рассмотрим метод генерации кода, относящегося к конкретной функции:
-- Значение для запуска интерпретатора.
-- Для генерации кода не обязательно использовать
-- реальное тело функции, поэтому я просто взял
-- четыре случайных значения.
initAesState :: AesState
initAesState = AesState 0xa749220 0xbe93bfa6 0x852f3eee 0xde9a4ae0
-- В данной функции мы проходимся по всем 15 раундам, заданным
-- в случайном порядке, и вызываем генерацию кода для каждого из
-- них.
generateAddRoundKeyForFunction :: FuncInfo -> [Round] -> GenM ()
generateAddRoundKeyForFunction func roundsOrder = do
-- Для каждого раунда shift всегда будет
-- сдвигаться на addrGuardExpr влево или вправо.
addrGuardExpr <- getAddrGuardExpr
generateRounds roundsOrder addrGuardExpr
where generateRounds :: [Round] -> Expr -> GenM ()
generateRounds (rnd:rs) addrGuardExpr = do
generateRound func rnd rs addrGuardExpr
generateRounds rs addrGuardExpr
generateRounds _ _ = pure ()
-- Данная функция возвращает выражение для устранения эффектов
-- вычислений в зависимости от адреса функции.
-- Допустим код для функций идёт в следующем порядке f2, f4, f3, f1.
-- Сейчас мы генерируем код для f4, в таком случае список stAddrGuardVars
-- содержит переменные для f4, f3 и f1: [var15, var16, var17], где:
-- var15 = address_guard(addr, funcAddress f4, funcAddress f4 + funcSize f4);
-- var16 = address_guard(addr, funcAddress f3, funcAddress f3 + funcSize f3);
-- var17 = address_guard(addr, funcAddress f1, funcAddress f1 + funcSize f1);
-- Тогда данная функция вернёт выражение: guard(var15 * var16 * var17),
-- при двоичном сдвиге на которое вычисления будут иметь эффект только если
-- мы расшифровываем или зашифровываем одну из функций: f4, f3 или f1.
-- Также эта функция меняет список stAddrGuardVars, убирая из него первую
-- переменную, в конце он будет иметь значение: [var16, var17].
getAddrGuardExpr :: GenM Expr
getAddrGuardExpr = do
addrGuardVars <- gets stAddrGuardVars
modify' (\s -> s { stAddrGuardVars = tail addrGuardVars })
pure $ Guard $ foldl1 (\e1 e2 -> Binop Mul e1 e2) addrGuardVars
-- В данной функции мы генерируем код для одного раунда.
generateRound :: FuncInfo -> Round -> [Round] -> Expr -> GenM ()
generateRound FuncInfo{..} rnd nextRnds addrGuardExpr = do
-- Выполняем все сгенерированные ранее инструкции
-- и получаем значение массива state после выполнения.
AesState c1 c2 c3 c4 <- getCurAesState
shiftVar <- gets stShiftVar
roundGuardExpr <- getRoundGuardExpr
forM_ (zip3 [0..] [c1, c2, c3, c4] [d1, d2, d3, d4]) $ \(idx, c, d) -> do
-- Получаем значение, которое должно быть в переменной shift,
-- чтобы state[idx] ^ shift = d
let destWord = c `xor` d
-- Генерируем выражение и присваиваем его в переменную
-- shift. True означает, что в данном выражении будет использована
-- переменная round.
exprForXor <- randomExprForXor rnd True destWord
assign shiftVar exprForXor
-- Побитово сдвигаем shift.
shiftByGuard shiftVar roundGuardExpr
shiftByGuard shiftVar addrGuardExpr
-- Изменяем массив state. state[idx] = state[idx] ^ shift.
assign (VarState idx) (Binop Xor (Var $ VarState idx) (Var $ shiftVar))
where -- Значение массива state, которое должно поучится на
-- данном раунде.
AesState d1 d2 d3 d4 = initAesState `xorAesStates` getKeyRoundState funcKey rnd
getCurAesState :: GenM AesState
getCurAesState = runInterpreter funcAddress rnd >>= lookupAesState
-- Здесь выполняется аналогичная логика, что и в функции
-- getAddrGuardExpr только для раундов.
getRoundGuardExpr :: GenM Expr
getRoundGuardExpr = do
guardVars <- gets stGuardVars
let roundToExpr (Round rndIdx) = guardVars ! (fromIntegral rndIdx)
let guardExprs = map roundToExpr (rnd:nextRnds)
pure $ Guard $ foldl1 (\e1 e2 -> Binop Mul e1 e2) guardExprs
-- Функция для интерпретации всего списка выражений,
-- в качестве начального значения массива state используется
-- initAesState.
runInterpreter :: Address -> Round -> GenM Interpreter.State
runInterpreter addr rnd = do
stmts <- gets stStmts
case (Interpreter.runInterpreter initAesState addr rnd stmts) of
Left err -> lift $ throwE err
Right res -> pure res
-- Обёртка над одноимённой функцией из модуля для интерпретации
-- выражений.
lookupAesState :: Interpreter.State -> GenM AesState
lookupAesState state = case Interpreter.lookupAesState state of
Left err -> lift $ throwE err
Right aesState -> pure aesState
-- Данная функция выбирает, какой сдвиг делать - левый или правый.
-- Если мы делаем правый сдвиг, надо перед этим привести 64-битное
-- значение shift к 32 битам, на случай если shift > 0xffffffff.
-- Пример выражения при выборе левого сдвига:
-- shift = shift << guard(...);
-- Пример выражения при выборе правого сдвига:
-- shift = shift & 0xffffffff;
-- shift = shift >> guard(...);
shiftByGuard :: Var -> Expr -> GenM ()
shiftByGuard shiftVar guardExpr = do
shiftOp <- randomChoice [Shl, Shr]
when (shiftOp == Shr) $
assign shiftVar (Binop And (Var shiftVar) (Int64 0xffffffff))
assign shiftVar (Binop shiftOp (Var shiftVar) guardExpr)
Последняя функция, которую мы рассмотрим в данной главе, — randomExprForXor. Как раз в ней и генерируется выражение, которое присваивается в переменную shift для последующего изменения массива state.
randomExprForXor :: Round -> Bool -> Word32 -> GenM Expr
randomExprForXor rnd withRound destWord = do
-- Получаем случайное количество операций в выражении.
numOps <- randomR (2, 5)
-- Генерируем numOps - 1 случайных операций.
ops <- replicateM (numOps - 1) (randomChoice [Add, Mul, Rotl, Rotr, Xor])
-- Генерируем выражение, состоящее из numOps - 1 операций,
-- полученных выше.
partialExpr <- randomPartialExpr ops
-- Вычисляем значение этого выражения
partialResult <- runExpr32 rnd partialExpr
-- Генерируем последнюю операцию вместе с операндом так,
-- чтобы результат всего выражения был равен destWord.
-- Мы не используем здесь операции Rotl и Rotr, потому что
-- мало вероятно, что destWord можно будет получить циклическим
-- сдвигом partialResult.
(lastOp, lastOperand) <- randomLastOperand partialResult [Add, Mul, Xor]
-- Возвращаем выражение.
pure (Binop lastOp partialExpr lastOperand)
where randomPartialExpr :: [Binop] -> GenM Expr
randomPartialExpr ops@(op : _) = do
-- Генерируем левый операнд и передаём его в randomPartialExpr'.
(lOperand, isRoundUsed) <- randomOperand op (not withRound)
randomPartialExpr' ops isRoundUsed lOperand
randomPartialExpr _ = error "Never executed"
randomPartialExpr' :: [Binop] -> Bool -> Expr -> GenM Expr
randomPartialExpr' (op:ops@(_:_)) isRoundUsed lOperand = do
-- Если у нас больше одной операции в списке,
-- генерируем правый операнд, составляем из левого
-- и правого операндов выражение и запускаем рекурсивно
-- обработку оставшегося списка операций.
(rOperand, isRoundUsed') <- randomOperand op isRoundUsed
randomPartialExpr' ops isRoundUsed' (Binop op lOperand rOperand)
randomPartialExpr' [op] isRoundUsed lOperand
-- Если осталась одна операция, проверяем была ли использована
-- переменная round.
| isRoundUsed
-- Если да, то последний операнд - случайное число.
= Binop op lOperand . fst <$> randomOperand op isRoundUsed
| otherwise
-- Иначе - это переменная round.
= pure $ Binop op lOperand (Var VarRound)
randomPartialExpr' _ _ _ = error "Never executed"
-- Данная функция генерирует последний операнд и операцию так,
-- чтобы результат выражения был равен destWord.
randomLastOperand :: Word32 -> [Binop] -> GenM (Binop, Expr)
randomLastOperand curWord ops = randomChoice ops >>= \case
Mul
-- Если последняя операция - умножение,
-- пробуем получить множитель с помощью
-- расширенного алгоритма Евклида. Если множитель
-- нашёлся, возвращаем операцию и множитель.
| Just term <- getMultTerm curWord destWord
-> pure (Mul, Int32 term)
-- Если множитель найти не удалось, запускаем генерацию
-- заново без операции умножения.
| otherwise
-> randomLastOperand curWord (delete Mul ops)
-- Обратная операция для сложения - разность.
Add -> pure (Add, Int32 (destWord - curWord))
-- Для исключающего или - оно само же.
_ -> pure (Xor, Int32 (destWord `xor` curWord))
-- Данная функция принимает на вход бинарную операцию и
-- булево значение, свидетельствующее о том, была ли
-- использована переменная round в выражении. На выходе
-- она даёт выражение, в котором использована переданная
-- операция, и новое булево значение.
randomOperand :: Binop -> Bool -> GenM (Expr, Bool)
randomOperand op isRoundUsed = do
-- Будет ли использована переменная round в качестве
-- операнда.
useRound <- if isRoundUsed then pure False else random
if useRound
-- Если да, то возвращаем её и True,
-- таким образом в следующий раз round
-- точно не будет использован.
then pure (Var VarRound, True)
else do
-- В ином случае генерируем случайное число.
val <- case op of
-- Здесь задан интервал от 5 до 31, так как двоичный сдвиг
-- 32-битного значения больше, чем на 31 бит, является
-- UB.
Rotl -> randomR (5, 31)
Rotr -> randomR (5, 31)
-- Для остальных операций берём интервал побольше.
_ -> randomR (0x10000000, 0xffffffff)
pure (Int32 val, isRoundUsed)
-- from * getMultTerm from to = to
getMultTerm :: Word32 -> Word32 -> Maybe Word32
getMultTerm from to = (*to) <$> getReciprocal from
getReciprocal :: Word32 -> Maybe Word32
getReciprocal a
| g /= 1 = Nothing
| otherwise = Just $ fromIntegral $ (x `mod` m + m) `mod` m
where (g, x, _) = gcd (fromIntegral a) m
m = 0x100000000
gcd :: Word64 -> Word64 -> (Word64, Word64, Word64)
gcd a b
| a == 0 = (b, 0, 1)
| otherwise = (d, y - (b `div` a) * x, x)
where (d, x, y) = gcd (b `mod` a) a
-- Данные функции нужны, чтобы вычислить выражение.
runExpr32 :: Round -> Expr -> GenM Word32
runExpr32 rnd expr = runExpr rnd expr >>= \case
Val32 w32 -> pure w32
Val64 w64 -> pure (fromIntegral w64)
runExpr :: Round -> Expr -> GenM Value
runExpr rnd expr = case runInterpreterExpr rnd expr of
Left err -> lift $ throwE err
Right res -> pure res
На этом наш генератор кода для AddRoundKey завершён. В ходе генерации был использован алгоритм получения обратного элемента в кольце по модулю, для получения нужного множителя. Его возможно применить, так как умножение 32-битных чисел на самом деле является умножением по модулю 0x100000000. Сам алгоритм хорошо описан здесь. Также стоит упомянуть о функции get_iv:
uint32_t get_iv(uint8_t *_state, uint64_t addr);
Данная функция принимает буфер и адрес функции, записывает в буфер вектор инициализации, а возвращает размер функции. Описывать алгоритм её генерации я не буду, так как он построен на том же принципе, что и алгоритм для AddRoundKey. Далее остаётся только преобразовать список выражений в код на Си, так как это достаточно тривиальная задача, описывать алгоритм преобразования я не буду. Скачать готовый бинарник и посмотреть код всего проекта можно в репозитории на github.
Пример сгенерированного кода
Данный код рассчитан на одну функцию, которая располагается по адресу 0x401000 и имеет размер 48 байт.
Слабонервным не смотреть.
static inline uint32_t sgn32(uint32_t n) {
return (-n>>31)-(n>>31);
}
static inline uint32_t guard(uint32_t n) {
uint32_t a = sgn32(n);
return ((a & 1) | (a >> 31)) * 32;
}
static inline int64_t sgn64(uint64_t n) {
uint64_t s = 63;
return (-n>>s)-(n>>s);
}
static inline uint32_t address_guard(uint64_t addr, uint64_t begin, uint64_t end) {
uint64_t a = sgn64((2 + sgn64(addr - begin)) * (2 + sgn64(end - addr)) - 4);
uint64_t s = 63;
a >>= s;
return a * 33;
}
static inline uint32_t rotl(uint32_t x, unsigned int n) {
const unsigned int mask = (CHAR_BIT*sizeof(x)-1);
n &= mask;
return (x<<n) | (x>>( (-n)&mask ));
}
static inline uint32_t rotr(uint32_t x, unsigned int n) {
const unsigned int mask = CHAR_BIT * sizeof(x) - 1;
n &= mask;
return (x >> n) | (x << (-n & mask));
}
static inline uint32_t get_iv(uint8_t *_state, uint64_t addr) {
uint32_t *state = (uint32_t*)_state;
state[0] = 0xc351597c;
state[1] = 0x268cd519;
state[2] = 0x59c504c3;
state[3] = 0x24bb08f9;
uint64_t shift = 0x0;
uint32_t size = 0x7b5620c9;
uint32_t var0 = address_guard(addr,0x401000,0x80202f);
shift = (rotr((0x362cee65 ^ 0x6e2d7ea0), 0x1f) ^ 0xcb151173);
shift = (shift & 0xffffffff);
shift = (shift >> guard(var0));
size = (size ^ shift);
shift = (((0x4b93c7d3 ^ 0x31d550ee) ^ 0xb1912cd8) * 0x6a407173);
shift = (shift & 0xffffffff);
shift = (shift >> guard(var0));
state[0] = (state[0] ^ shift);
shift = (rotr(rotl(0x5, 0x9), 0x5) + 0xede949a3);
shift = (shift & 0xffffffff);
shift = (shift >> guard(var0));
state[1] = (state[1] ^ shift);
shift = (rotr((0x263e128c + 0x6d3c16e9), 0x1f) + 0x584e5296);
shift = (shift << guard(var0));
state[2] = (state[2] ^ shift);
shift = (rotr(0x1e, 0xf) ^ 0x8712c49d);
shift = (shift & 0xffffffff);
shift = (shift >> guard(var0));
state[3] = (state[3] ^ shift);
return size;
}
INLINE
static inline void AddRoundKey(state_t *_state, uint32_t round) {
uint32_t *state = (uint32_t*)_state;
uint64_t addr = (uint64_t)state;
uint32_t var0 = (round ^ 0x0);
uint32_t var1 = (round ^ 0x1);
uint32_t var2 = (round ^ 0x2);
uint32_t var3 = (round ^ 0x3);
uint32_t var4 = (round ^ 0x4);
uint32_t var5 = (round ^ 0x5);
uint32_t var6 = (round ^ 0x6);
uint32_t var7 = (round ^ 0x7);
uint32_t var8 = (round ^ 0x8);
uint32_t var9 = (round ^ 0x9);
uint32_t var10 = (round ^ 0xa);
uint32_t var11 = (round ^ 0xb);
uint32_t var12 = (round ^ 0xc);
uint32_t var13 = (round ^ 0xd);
uint32_t var14 = (round ^ 0xe);
uint32_t var15 = address_guard(addr,0x401000,0x80202f);
uint64_t shift = 0x0;
state[0] = (state[0] ^ 0x9e1039a6);
state[1] = (state[1] ^ 0xe4074a25);
state[2] = (state[2] ^ 0x3591ebee);
state[3] = (state[3] ^ 0x6c2bca0a);
shift = ((round + 0xa7fb8c08) ^ 0x7dde4f5c);
shift = (shift & 0xffffffff);
shift = (shift >> guard(((((((((((((((var6 * var11) * var10) * var4) * var9) * var3) * var8) * var12) * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[0] = (state[0] ^ shift);
shift = (((rotl(round, 0x10) + 0xe5d8467b) * 0xa6a7f63a) + 0x2b41e232);
shift = (shift << guard(((((((((((((((var6 * var11) * var10) * var4) * var9) * var3) * var8) * var12) * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift << guard(var15));
state[1] = (state[1] ^ shift);
shift = ((rotl(round, 0x1c) * 0xb567757f) ^ 0x7b8b6d31);
shift = (shift & 0xffffffff);
shift = (shift >> guard(((((((((((((((var6 * var11) * var10) * var4) * var9) * var3) * var8) * var12) * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[2] = (state[2] ^ shift);
shift = ((rotl(0x18, 0x17) + round) ^ 0x747fe9e2);
shift = (shift & 0xffffffff);
shift = (shift >> guard(((((((((((((((var6 * var11) * var10) * var4) * var9) * var3) * var8) * var12) * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[3] = (state[3] ^ shift);
shift = ((rotr(((round + 0xb9c2c963) ^ 0x505edd69), 0xf) + 0xd2dcd618) ^ 0x4736fe58);
shift = (shift << guard((((((((((((((var11 * var10) * var4) * var9) * var3) * var8) * var12) * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[0] = (state[0] ^ shift);
shift = (rotl(0x1a, round) + 0xc811a71b);
shift = (shift & 0xffffffff);
shift = (shift >> guard((((((((((((((var11 * var10) * var4) * var9) * var3) * var8) * var12) * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift << guard(var15));
state[1] = (state[1] ^ shift);
shift = (rotl(rotl(rotr((round ^ 0xcf89eae7), 0x1d), 0x16), 0x7) ^ 0xf3acad7e);
shift = (shift & 0xffffffff);
shift = (shift >> guard((((((((((((((var11 * var10) * var4) * var9) * var3) * var8) * var12) * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[2] = (state[2] ^ shift);
shift = ((rotl((0xf51c1e51 + round), 0x17) ^ 0x1ae06ec7) + 0x9de7d1f1);
shift = (shift & 0xffffffff);
shift = (shift >> guard((((((((((((((var11 * var10) * var4) * var9) * var3) * var8) * var12) * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift << guard(var15));
state[3] = (state[3] ^ shift);
shift = (((rotr(round, 0xd) ^ 0x6f744e3d) ^ 0x9bd81345) + 0x9010d480);
shift = (shift << guard(((((((((((((var10 * var4) * var9) * var3) * var8) * var12) * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift << guard(var15));
state[0] = (state[0] ^ shift);
shift = (rotr(round, 0x6) ^ 0x28a38990);
shift = (shift & 0xffffffff);
shift = (shift >> guard(((((((((((((var10 * var4) * var9) * var3) * var8) * var12) * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift << guard(var15));
state[1] = (state[1] ^ shift);
shift = ((((0x30e893c5 * round) * 0x3f47d073) ^ 0xe7e7d941) + 0x31246c85);
shift = (shift << guard(((((((((((((var10 * var4) * var9) * var3) * var8) * var12) * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift << guard(var15));
state[2] = (state[2] ^ shift);
shift = ((((0x706bcec2 + 0x3fe671ba) * round) + 0xf58e3d8f) ^ 0x4b40e278);
shift = (shift << guard(((((((((((((var10 * var4) * var9) * var3) * var8) * var12) * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[3] = (state[3] ^ shift);
shift = ((((round + 0x1d05f17d) * 0xf56254b3) * 0xfba55362) ^ 0x2a43ba5e);
shift = (shift << guard((((((((((((var4 * var9) * var3) * var8) * var12) * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[0] = (state[0] ^ shift);
shift = (rotr(rotl(round, 0x1e), 0xb) ^ 0x533ab204);
shift = (shift & 0xffffffff);
shift = (shift >> guard((((((((((((var4 * var9) * var3) * var8) * var12) * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift << guard(var15));
state[1] = (state[1] ^ shift);
shift = (rotl(rotl(((round + 0x8c6f5595) * 0x999b4570), 0x11), 0x1e) * 0xd04d3caa);
shift = (shift & 0xffffffff);
shift = (shift >> guard((((((((((((var4 * var9) * var3) * var8) * var12) * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[2] = (state[2] ^ shift);
shift = (rotl(rotr((0xc032bcd8 ^ round), 0xe), 0x14) ^ 0x7d86b52);
shift = (shift << guard((((((((((((var4 * var9) * var3) * var8) * var12) * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift << guard(var15));
state[3] = (state[3] ^ shift);
shift = (rotr(((0xf37a81b5 + 0xf6876b7b) + round), 0x5) * 0x404096b6);
shift = (shift << guard(((((((((((var9 * var3) * var8) * var12) * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[0] = (state[0] ^ shift);
shift = ((((rotr(round, 0x1e) ^ 0x63be2db5) * 0xeb397c2e) * 0x84282b2a) + 0x952f4033);
shift = (shift << guard(((((((((((var9 * var3) * var8) * var12) * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift << guard(var15));
state[1] = (state[1] ^ shift);
shift = (((rotr((0x48ca0729 + round), 0x19) + 0x2ae23eee) + 0xb03bf9a8) ^ 0x24496e53);
shift = (shift << guard(((((((((((var9 * var3) * var8) * var12) * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[2] = (state[2] ^ shift);
shift = (rotr(round, 0x6) + 0xcf9dec0f);
shift = (shift & 0xffffffff);
shift = (shift >> guard(((((((((((var9 * var3) * var8) * var12) * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift << guard(var15));
state[3] = (state[3] ^ shift);
shift = ((0x7eee5dfb * round) + 0x25823449);
shift = (shift & 0xffffffff);
shift = (shift >> guard((((((((((var3 * var8) * var12) * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[0] = (state[0] ^ shift);
shift = (rotr(rotl(rotl(round, 0x10), 0x12), 0x1b) ^ 0xa3b3de72);
shift = (shift << guard((((((((((var3 * var8) * var12) * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift << guard(var15));
state[1] = (state[1] ^ shift);
shift = ((((rotl(round, 0x5) + 0xb21e2c4c) + 0xbce439a2) + 0x4a639aa4) + 0xf2aab8ad);
shift = (shift & 0xffffffff);
shift = (shift >> guard((((((((((var3 * var8) * var12) * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift << guard(var15));
state[2] = (state[2] ^ shift);
shift = (((rotr(0x1c, round) * 0xc1a9c957) * 0x3093378c) + 0x444262c8);
shift = (shift & 0xffffffff);
shift = (shift >> guard((((((((((var3 * var8) * var12) * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[3] = (state[3] ^ shift);
shift = (rotl(rotr(((0x8c8236d3 ^ 0x814322d0) ^ round), 0x14), 0x1d) * 0xb8c10f5f);
shift = (shift << guard(((((((((var8 * var12) * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[0] = (state[0] ^ shift);
shift = (rotr((((round ^ 0xb309fe73) * 0xa94c9100) * 0x7059a9c8), 0x1f) ^ 0x967b228d);
shift = (shift << guard(((((((((var8 * var12) * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[1] = (state[1] ^ shift);
shift = ((rotl(rotl(0xe, 0x11), 0x12) + round) ^ 0x165c0fa5);
shift = (shift & 0xffffffff);
shift = (shift >> guard(((((((((var8 * var12) * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift << guard(var15));
state[2] = (state[2] ^ shift);
shift = ((((0x9f674bf4 + round) ^ 0x752bea1e) ^ 0xd20defa5) + 0xe1c2e413);
shift = (shift << guard(((((((((var8 * var12) * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift << guard(var15));
state[3] = (state[3] ^ shift);
shift = ((rotr(rotl(0xc, 0x7), 0x1c) ^ round) + 0xcb828487);
shift = (shift & 0xffffffff);
shift = (shift >> guard((((((((var12 * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[0] = (state[0] ^ shift);
shift = ((((round ^ 0x7527a6ed) ^ 0x7c6a6758) * 0xeb18f7f3) + 0xf568bacb);
shift = (shift << guard((((((((var12 * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift << guard(var15));
state[1] = (state[1] ^ shift);
shift = (((0xda741724 + 0x85c793c1) + round) ^ 0x7ace9fe);
shift = (shift & 0xffffffff);
shift = (shift >> guard((((((((var12 * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift << guard(var15));
state[2] = (state[2] ^ shift);
shift = (rotr(rotl(((round ^ 0xda048644) ^ 0x1709ba1b), 0x1e), 0x8) ^ 0xd8466d6e);
shift = (shift & 0xffffffff);
shift = (shift >> guard((((((((var12 * var14) * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[3] = (state[3] ^ shift);
shift = (rotr(round, 0x19) ^ 0xeaa8ea51);
shift = (shift << guard(((((((var14 * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift << guard(var15));
state[0] = (state[0] ^ shift);
shift = (rotr((((0x81975088 * round) + 0xe8fea743) + 0x54dbfa12), 0xf) + 0xb11c18d);
shift = (shift << guard(((((((var14 * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift << guard(var15));
state[1] = (state[1] ^ shift);
shift = (rotr(0x15, round) ^ 0xa6da17bf);
shift = (shift << guard(((((((var14 * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift << guard(var15));
state[2] = (state[2] ^ shift);
shift = (rotl(rotl(0x16, round), 0x14) ^ 0xbeeba5d4);
shift = (shift & 0xffffffff);
shift = (shift >> guard(((((((var14 * var13) * var7) * var2) * var0) * var5) * var1)));
shift = (shift << guard(var15));
state[3] = (state[3] ^ shift);
shift = (((round ^ 0xb52cee81) * 0xcc0dac17) ^ 0xc669b11b);
shift = (shift & 0xffffffff);
shift = (shift >> guard((((((var13 * var7) * var2) * var0) * var5) * var1)));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[0] = (state[0] ^ shift);
shift = (((rotl((0x9c6de2ac ^ 0xca001388), 0x11) * round) ^ 0x95e20672) ^ 0x616ca13d);
shift = (shift << guard((((((var13 * var7) * var2) * var0) * var5) * var1)));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[1] = (state[1] ^ shift);
shift = (rotr((rotr((round ^ 0xce83b9b7), 0xe) ^ 0x9b168237), 0xa) ^ 0xd5f73425);
shift = (shift & 0xffffffff);
shift = (shift >> guard((((((var13 * var7) * var2) * var0) * var5) * var1)));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[2] = (state[2] ^ shift);
shift = (rotr((round + 0x62cdff05), 0x9) * 0x7aac499c);
shift = (shift & 0xffffffff);
shift = (shift >> guard((((((var13 * var7) * var2) * var0) * var5) * var1)));
shift = (shift << guard(var15));
state[3] = (state[3] ^ shift);
shift = ((rotl((0xf845e7c7 * 0x90b3c06c), 0x1c) + round) ^ 0x152ae01a);
shift = (shift & 0xffffffff);
shift = (shift >> guard(((((var7 * var2) * var0) * var5) * var1)));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[0] = (state[0] ^ shift);
shift = ((round + 0xed4aa3d8) * 0x4f2129fa);
shift = (shift << guard(((((var7 * var2) * var0) * var5) * var1)));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[1] = (state[1] ^ shift);
shift = (((((round * 0x7ad54a32) ^ 0xa068578e) + 0x6269f7eb) + 0x664f92d9) ^ 0x12c1f5b2);
shift = (shift & 0xffffffff);
shift = (shift >> guard(((((var7 * var2) * var0) * var5) * var1)));
shift = (shift << guard(var15));
state[2] = (state[2] ^ shift);
shift = (((0xb9a89b14 ^ round) + 0xcf62f9ef) ^ 0xf67560ce);
shift = (shift & 0xffffffff);
shift = (shift >> guard(((((var7 * var2) * var0) * var5) * var1)));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[3] = (state[3] ^ shift);
shift = (((round ^ 0xccf0d105) + 0xdf1c25f0) * 0xc8e76e14);
shift = (shift & 0xffffffff);
shift = (shift >> guard((((var2 * var0) * var5) * var1)));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[0] = (state[0] ^ shift);
shift = ((rotr(round, 0x17) * 0xaa07b30d) ^ 0xcefbedfe);
shift = (shift << guard((((var2 * var0) * var5) * var1)));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[1] = (state[1] ^ shift);
shift = ((rotr(0xf, round) ^ 0x3bbab7ef) + 0xd7c1e1a7);
shift = (shift << guard((((var2 * var0) * var5) * var1)));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[2] = (state[2] ^ shift);
shift = (rotl(rotl(((round ^ 0x4227fceb) * 0x1c4b7b26), 0x14), 0x1d) + 0xb7a5e337);
shift = (shift << guard((((var2 * var0) * var5) * var1)));
shift = (shift << guard(var15));
state[3] = (state[3] ^ shift);
shift = (rotr((0x423797af * 0x9f726cf4), round) + 0x6c8ba5bf);
shift = (shift << guard(((var0 * var5) * var1)));
shift = (shift << guard(var15));
state[0] = (state[0] ^ shift);
shift = (rotr(rotl(((round ^ 0xb0053cfd) + 0x9533f8ef), 0x19), 0x8) + 0x9beb216d);
shift = (shift & 0xffffffff);
shift = (shift >> guard(((var0 * var5) * var1)));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[1] = (state[1] ^ shift);
shift = (rotl(rotl(0x1d, 0x12), round) ^ 0x5b72c2c6);
shift = (shift & 0xffffffff);
shift = (shift >> guard(((var0 * var5) * var1)));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[2] = (state[2] ^ shift);
shift = ((0xfa566e48 + round) + 0x2491c127);
shift = (shift & 0xffffffff);
shift = (shift >> guard(((var0 * var5) * var1)));
shift = (shift << guard(var15));
state[3] = (state[3] ^ shift);
shift = ((0x2ef805f8 * round) + 0x9d5b80ba);
shift = (shift & 0xffffffff);
shift = (shift >> guard((var5 * var1)));
shift = (shift << guard(var15));
state[0] = (state[0] ^ shift);
shift = (((0x980f58e2 ^ round) ^ 0x8e21baae) * 0x8c2bd0e2);
shift = (shift & 0xffffffff);
shift = (shift >> guard((var5 * var1)));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[1] = (state[1] ^ shift);
shift = (rotl(rotl((0x78460382 + round), 0x1e), 0x7) * 0xf436841e);
shift = (shift & 0xffffffff);
shift = (shift >> guard((var5 * var1)));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[2] = (state[2] ^ shift);
shift = (((rotr(round, 0x1c) ^ 0x849e0440) + 0x6ccf46ee) ^ 0xbcc1aef0);
shift = (shift & 0xffffffff);
shift = (shift >> guard((var5 * var1)));
shift = (shift << guard(var15));
state[3] = (state[3] ^ shift);
shift = (rotl((rotl(0x8, 0x1c) ^ round), 0x1b) ^ 0x7b34cec4);
shift = (shift & 0xffffffff);
shift = (shift >> guard(var1));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[0] = (state[0] ^ shift);
shift = ((((round + 0xbd8b6316) + 0x7ac47d13) ^ 0x22863bd0) ^ 0x679cfcd8);
shift = (shift << guard(var1));
shift = (shift << guard(var15));
state[1] = (state[1] ^ shift);
shift = (rotl(rotr(0x8, round), 0x18) ^ 0x284680f6);
shift = (shift << guard(var1));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[2] = (state[2] ^ shift);
shift = (rotl(round, 0xe) ^ 0xb68d6ce9);
shift = (shift << guard(var1));
shift = (shift & 0xffffffff);
shift = (shift >> guard(var15));
state[3] = (state[3] ^ shift);
}
Заключение
В заключении хочется сказать, что код, сгенерированный с помощью описанного метода, никак не получится использовать в реальных проектах, так как количество кода в AddRoundKey прямо пропорционально количеству шифруемых сообщений, а на одно сообщение генерируется около 400 строк кода. Для примера — у меня в crackme всего 12 функций, при этом количество строк в AddRoundKey равно 3675, и при расшифровке каждой функции выполняются они все, что сильно влияет на скорость исполнения (в моём случае время работы увеличилось с 13 миллисекунд до 17 секунд). Также это существенно влияет на скорость компиляции, у меня она длится около 18 секунд.
В следующей части я расскажу, как создать исполняемый файл, в котором все функции зашифрованы собственными ключами, и расшифровываются только на время выполнения. В третьей части мы рассмотрим, как добавить в полученный файл наномиты.