У крипторов и всякой разной малвары часто стоят задачи обфускации строк, хеш-таблиц для поиска API, выплёвывания EXE, шеллкода или генерация ключа шифрования.
Самый примитивный вариант это иметь зашифрованные данные в секции данных или ресурсах, ключ шифрования и код для расшифровки этого дела. Но нехорошо получается, когда например закриптованный файл состоит из небольшой секции кода, которая является расшифровщиком, и здоровой секции данных, которая содержит трой.
А если генерировать данные программно, т.е. алгоритмически, то можно обойтись вообще без секции данных. Либо частично генерировать данные программно, чтобы сократить размер секции данных и привести соотношение размеров секций кода/данных к нормальному.
В общем случае все эти задачи сводятся к генерации чисел, т.к. любые данные и код можно представить в виде набора байт.
Раньше я делал это так: предварительно "шифруем" исходные данные путём вычисления некой обратимой функции типа тангенс от каждого байта исходных данных. Потом рабочий код вычисляет обратную функцию арктангенс от "зашифрованных" байт, при чем вызывая не какие-то математические библиотечные функции, а делает это с помощью циклического подсчёта значения разложения функции арктангенс в ряд Тейлора.
В этом способе мне не нравится недостаточная запутанность и данные хоть и не увеличивают секцию данных, но фактически в явном виде содержатся в командах типа mov eax, 12345h, что весьма некошерно как по мне.
Поэтому я разработал генератор чисел без использования данных вообще. По сути на вход только надо подать любое заранее известное число, например значение возвращаемое какой-нибудь антиэмуляционной API-функцией.
Суть алгоритма: работать будем с 1-байтовыми значениями, генерировать будем формулы. Формула это дерево, вершина которого может быть 3 видов: унарная операция, бинарная операция, операнд. Далее формула-дерево транслируется в С-код.
Задача состоит в том, чтобы сгененировать формулу которая преобразует заданное число X в Y. Таким образом код генерации любого массива будет состоять из последовательных преобразований X -> Y1 -> Y2 -> Y3 -> Y4 ...
Случайное дерево-формула генерируется функцией struct tree* create_random_tree( unsigned int depth )
значение формулы подсчитывается функцией unsigned char count_tree( struct tree* t, unsigned char operand_value )
дерево-формула транслируется в C-код функцией char* tree_to_string( struct tree* t, char* operand )
функция struct tree* generate_tree( unsigned char x, unsigned char y ) генерирует случайные деревья-формулы, пока при подстановке X в качестве значения операнда не получится Y.
Proof-of-concept:
Самый примитивный вариант это иметь зашифрованные данные в секции данных или ресурсах, ключ шифрования и код для расшифровки этого дела. Но нехорошо получается, когда например закриптованный файл состоит из небольшой секции кода, которая является расшифровщиком, и здоровой секции данных, которая содержит трой.
А если генерировать данные программно, т.е. алгоритмически, то можно обойтись вообще без секции данных. Либо частично генерировать данные программно, чтобы сократить размер секции данных и привести соотношение размеров секций кода/данных к нормальному.
В общем случае все эти задачи сводятся к генерации чисел, т.к. любые данные и код можно представить в виде набора байт.
Раньше я делал это так: предварительно "шифруем" исходные данные путём вычисления некой обратимой функции типа тангенс от каждого байта исходных данных. Потом рабочий код вычисляет обратную функцию арктангенс от "зашифрованных" байт, при чем вызывая не какие-то математические библиотечные функции, а делает это с помощью циклического подсчёта значения разложения функции арктангенс в ряд Тейлора.
В этом способе мне не нравится недостаточная запутанность и данные хоть и не увеличивают секцию данных, но фактически в явном виде содержатся в командах типа mov eax, 12345h, что весьма некошерно как по мне.
Поэтому я разработал генератор чисел без использования данных вообще. По сути на вход только надо подать любое заранее известное число, например значение возвращаемое какой-нибудь антиэмуляционной API-функцией.
Суть алгоритма: работать будем с 1-байтовыми значениями, генерировать будем формулы. Формула это дерево, вершина которого может быть 3 видов: унарная операция, бинарная операция, операнд. Далее формула-дерево транслируется в С-код.
Задача состоит в том, чтобы сгененировать формулу которая преобразует заданное число X в Y. Таким образом код генерации любого массива будет состоять из последовательных преобразований X -> Y1 -> Y2 -> Y3 -> Y4 ...
Случайное дерево-формула генерируется функцией struct tree* create_random_tree( unsigned int depth )
значение формулы подсчитывается функцией unsigned char count_tree( struct tree* t, unsigned char operand_value )
дерево-формула транслируется в C-код функцией char* tree_to_string( struct tree* t, char* operand )
функция struct tree* generate_tree( unsigned char x, unsigned char y ) генерирует случайные деревья-формулы, пока при подстановке X в качестве значения операнда не получится Y.
Proof-of-concept:
Код:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define TREE_NODE_OPERAND 0
#define TREE_NODE_UNARY 1
#define TREE_NODE_BINARY 2
#define TREE_MAX_DEPTH 8
struct tree
{
char type, opcode;
struct tree *child_left, *child_right;
};
unsigned char *unary_array;
unsigned char *binary_array;
unsigned char calculate_unary( unsigned char a, unsigned char opcode )
{
switch ( opcode )
{
case 0x00: return (unsigned char) ( a );
case 0x01: return (unsigned char) ( ~( (unsigned char)a ) );
case 0x02: return (unsigned char) ( (unsigned char)a + 1);
case 0x03: return (unsigned char) ( (unsigned char)a - 1);
case 0x04: return (unsigned char) ( (unsigned char)a >> 1 );
case 0x05: return (unsigned char) ( (unsigned char)a >> 2 );
case 0x06: return (unsigned char) ( (unsigned char)a >> 3 );
case 0x07: return (unsigned char) ( (unsigned char)a >> 4 );
case 0x08: return (unsigned char) ( (unsigned char)a << 1 );
case 0x09: return (unsigned char) ( (unsigned char)a << 2 );
case 0x0A: return (unsigned char) ( (unsigned char)a << 3 );
case 0x0B: return (unsigned char) ( (unsigned char)a << 4 );
}
}
char* unary_to_string( char *operand, char opcode )
{
char* result = malloc( strlen( operand ) + 128 );
switch ( opcode )
{
case 0x00: sprintf( result, "(unsigned char)( %s )", operand ); break;
case 0x01: sprintf( result, "(unsigned char)( ~( (unsigned char)%s ) )", operand ); break;
case 0x02: sprintf( result, "(unsigned char)( (unsigned char)%s + 1 )", operand ); break;
case 0x03: sprintf( result, "(unsigned char)( (unsigned char)%s - 1 )", operand ); break;
case 0x04: sprintf( result, "(unsigned char)( (unsigned char)%s >> 1 )", operand ); break;
case 0x05: sprintf( result, "(unsigned char)( (unsigned char)%s >> 2 )", operand ); break;
case 0x06: sprintf( result, "(unsigned char)( (unsigned char)%s >> 3 )", operand ); break;
case 0x07: sprintf( result, "(unsigned char)( (unsigned char)%s >> 4 )", operand ); break;
case 0x08: sprintf( result, "(unsigned char)( (unsigned char)%s << 1 )", operand ); break;
case 0x09: sprintf( result, "(unsigned char)( (unsigned char)%s << 2 )", operand ); break;
case 0x0A: sprintf( result, "(unsigned char)( (unsigned char)%s << 3 )", operand ); break;
case 0x0B: sprintf( result, "(unsigned char)( (unsigned char)%s << 4 )", operand ); break;
}
return result;
}
unsigned char calculate_binary( unsigned char a, unsigned char b, unsigned char opcode )
{
switch ( opcode )
{
case 0x00: return (unsigned char)( (unsigned char) a & (unsigned char) b );
case 0x01: return (unsigned char)( (unsigned char) a ^ (unsigned char) b );
case 0x02: return (unsigned char)( (unsigned char) a | (unsigned char) b );
case 0x03: return (unsigned char)( (unsigned char) a + (unsigned char) b );
case 0x04: return (unsigned char)( (unsigned char) a - (unsigned char) b );
case 0x05: return (unsigned char)( (unsigned char) a * (unsigned char) b );
}
}
char* binary_to_string( char *operand_left, char *operand_right, char opcode )
{
char* result = malloc( strlen( operand_left ) + strlen( operand_right ) + 128 );
switch ( opcode )
{
case 0x00: sprintf( result, "( %s & %s )", operand_left, operand_right ); break;
case 0x01: sprintf( result, "( %s ^ %s )", operand_left, operand_right ); break;
case 0x02: sprintf( result, "( %s | %s )", operand_left, operand_right ); break;
case 0x03: sprintf( result, "( %s + %s )", operand_left, operand_right ); break;
case 0x04: sprintf( result, "( %s - %s )", operand_left, operand_right ); break;
case 0x05: sprintf( result, "( %s * %s )", operand_left, operand_right ); break;
}
return result;
}
void precalculate_unary_array( )
{
int a, opcode;
int sz = ( 0xFF + 1 ) * ( 0x0B + 1 );
unary_array = ( unsigned char * ) malloc( sz );
for ( a = 0x00; a <= 0xFF; a++ )
for ( opcode = 0x00; opcode <= 0x0B; opcode++ )
unary_array[ a * ( 0x0B + 1 ) + opcode ] = calculate_unary( (unsigned char) a, (unsigned char) opcode );
}
void precalculate_binary_array( )
{
int a, b, opcode;
int sz = ( 0xFF + 1) * ( 0xFF + 1 ) * ( 0x05 + 1);
binary_array = ( unsigned char * ) malloc( sz );
for ( a = 0x00; a <= 0xFF; a++ )
for ( b = 0x00; b <= 0xFF; b++ )
for ( opcode = 0x00; opcode <= 0x05; opcode++ )
binary_array[ a * ( 0xFF + 1 ) * ( 0x05 + 1 ) + b * ( 0x05 + 1 ) + opcode ] = calculate_binary( (unsigned char) a, (unsigned char) b, (unsigned char) opcode );
}
struct tree* create_random_tree( unsigned int depth )
{
struct tree* result;
result = (struct tree*) malloc( sizeof( struct tree ) );
if ( depth <= 1 )
{
result->type = TREE_NODE_OPERAND;
result->child_left = NULL;
result->child_right = NULL;
}
else
{
result->type = rand( ) % 3;
if ( result->type == TREE_NODE_UNARY )
{
result->opcode = rand( ) % ( 0x0B + 1 );
result->child_left = create_random_tree( depth - 1 );
result->child_right = NULL;
}
else if ( result->type == TREE_NODE_BINARY )
{
result->opcode = rand( ) % ( 0x05 + 1 );
result->child_left = create_random_tree( depth - 1 );
result->child_right = create_random_tree( depth - 1 );
}
else
{
result->child_left = NULL;
result->child_right = NULL;
}
}
return result;
}
void delete_tree( struct tree* t )
{
if ( t != NULL )
{
if ( t->child_left != NULL )
delete_tree( t->child_left );
if ( t->child_right != NULL )
delete_tree( t->child_right );
free( t );
}
}
unsigned char count_tree( struct tree* t, unsigned char operand_value )
{
switch ( t->type )
{
case TREE_NODE_OPERAND: return operand_value;
case TREE_NODE_UNARY: return unary_array[ count_tree( t->child_left, operand_value ) * ( 0x0B + 1 ) + t->opcode ];
case TREE_NODE_BINARY: return binary_array[ count_tree( t->child_left, operand_value ) * ( 0xFF + 1 ) * ( 0x05 + 1 ) + count_tree( t->child_right, operand_value ) * ( 0x05 + 1 ) + t->opcode ];
}
}
char* tree_to_string( struct tree* t, char* operand )
{
switch ( t->type )
{
case TREE_NODE_OPERAND: return operand;
case TREE_NODE_UNARY: return unary_to_string( tree_to_string( t->child_left, operand ), t->opcode );
case TREE_NODE_BINARY: return binary_to_string( tree_to_string( t->child_left, operand ), tree_to_string( t->child_right, operand ), t->opcode );
}
}
struct tree* generate_tree( unsigned char x, unsigned char y )
{
struct tree* t;
do
{
if ( t != NULL )
delete_tree( t );
t = create_random_tree( TREE_MAX_DEPTH );
}
while ( count_tree( t, x ) != y );
return t;
}
void main()
{
struct tree* t;
unsigned char x, y;
char *st;
FILE *f;
int i;
f = fopen( "/tmp/test.c", "w" );
fprintf( f, "#include <stdio.h>\nvoid main(){\n unsigned char x, y, c;\n" );
srand ( time(NULL) );
precalculate_unary_array( );
precalculate_binary_array( );
for ( i = 0; i < 10; i++ )
{
x = rand( ) % 256;
t = create_random_tree( 3 + rand( ) % 5 );
st = tree_to_string( t, "x" );
y = count_tree( t, x );
delete_tree( t );
printf( "x = %i, y = %i\n%s\n", x, y, st );
fprintf( f, " x = %i;\n", x );
fprintf( f, " y = %i;\n", y );
fprintf( f, " c = %s;\n", st );
fprintf( f, " if ( c != y ) " );
fprintf( f, " printf( \"x = %%i, y = %%i, c = %%i\\t%s\\n\", x, y, c );\n", st );
}
fprintf( f, "}\n" );
fclose( f );
}
Добавлено в [time]1359148931[/time]
пример генерируемых формул:
( ( ( ( ( ( (unsigned char)( ~( (unsigned char)x ) ) + x ) & (unsigned char)( (unsigned char)(unsigned char)( (unsigned char)x << 1 ) >> 3 ) ) | x ) & (unsigned char)( (unsigned char)(unsigned char)( (unsigned char)( (unsigned char)( (unsigned char)x - 1 ) + (unsigned char)( (unsigned char)x >> 1 ) ) >> 1 ) >> 1 ) ) - (unsigned char)( (unsigned char)( ( (unsigned char)( (unsigned char)(unsigned char)( (unsigned char)x >> 4 ) << 4 ) ^ ( ( x - x ) & (unsigned char)( (unsigned char)x + 1 ) ) ) ^ x ) >> 4 ) ) | x )
x = 8, y = 10, count_tree = 10
(unsigned char)( (unsigned char)(unsigned char)( (unsigned char)( x + x ) << 3 ) >> 2 )
x = 235, y = 44, count_tree = 44
( x + ( x | (unsigned char)( (unsigned char)(unsigned char)( (unsigned char)(unsigned char)( ~( (unsigned char)x ) ) << 2 ) >> 4 ) ) )
x = 41, y = 86, count_tree = 86
( ( ( ( ( ( (unsigned char)( ~( (unsigned char)x ) ) + x ) & (unsigned char)( (unsigned char)(unsigned char)( (unsigned char)x << 1 ) >> 3 ) ) | x ) & (unsigned char)( (unsigned char)(unsigned char)( (unsigned char)( (unsigned char)( (unsigned char)x - 1 ) + (unsigned char)( (unsigned char)x >> 1 ) ) >> 1 ) >> 1 ) ) - (unsigned char)( (unsigned char)( ( (unsigned char)( (unsigned char)(unsigned char)( (unsigned char)x >> 4 ) << 4 ) ^ ( ( x - x ) & (unsigned char)( (unsigned char)x + 1 ) ) ) ^ x ) >> 4 ) ) | x )
x = 8, y = 10, count_tree = 10
(unsigned char)( (unsigned char)(unsigned char)( (unsigned char)( x + x ) << 3 ) >> 2 )
x = 235, y = 44, count_tree = 44
( x + ( x | (unsigned char)( (unsigned char)(unsigned char)( (unsigned char)(unsigned char)( ~( (unsigned char)x ) ) << 2 ) >> 4 ) ) )
x = 41, y = 86, count_tree = 86
вот что выходит в ассемблерном виде (синтаксис gas):
Код:
.file "test.c"
.section .rodata
.LC0:
.string "x = %i, y = %i, c = %i\tx\n"
.align 8
.LC1:
.string "x = %i, y = %i, c = %i\t(unsigned char)( (unsigned char)( x | x ) << 3 )\n"
.align 8
.LC2:
.string "x = %i, y = %i, c = %i\t( ( ( x | x ) & ( x + x ) ) - ( x + ( x & x ) ) )\n"
.align 8
.LC3:
.string "x = %i, y = %i, c = %i\t( (unsigned char)( (unsigned char)( x + ( ( (unsigned char)( (unsigned char)x << 4 ) * (unsigned char)( (unsigned char)x << 3 ) ) * (unsigned char)( (unsigned char)( x | x ) << 1 ) ) ) << 4 ) | x )\n"
.align 8
.LC4:
.string "x = %i, y = %i, c = %i\t( x + ( (unsigned char)( (unsigned char)(unsigned char)( (unsigned char)x << 2 ) >> 3 ) ^ ( x - (unsigned char)( ~( (unsigned char)x ) ) ) ) )\n"
.align 8
.LC5:
.string "x = %i, y = %i, c = %i\t(unsigned char)( (unsigned char)x << 2 )\n"
.align 8
.LC6:
.string "x = %i, y = %i, c = %i\t( (unsigned char)( (unsigned char)x >> 4 ) ^ x )\n"
.align 8
.LC7:
.string "x = %i, y = %i, c = %i\t(unsigned char)( (unsigned char)(unsigned char)( (unsigned char)x >> 1 ) << 3 )\n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $32, %rsp
movb $115, -3(%rbp)
movb $115, -2(%rbp)
movzbl -3(%rbp), %eax
movb %al, -1(%rbp)
movzbl -1(%rbp), %eax
cmpb -2(%rbp), %al
je .L2
movzbl -1(%rbp), %ecx
movzbl -2(%rbp), %edx
movzbl -3(%rbp), %eax
movl %eax, %esi
movl $.LC0, %edi
movl $0, %eax
call printf
.L2:
movb $-71, -3(%rbp)
movb $-56, -2(%rbp)
movzbl -3(%rbp), %eax
sall $3, %eax
movb %al, -1(%rbp)
movzbl -1(%rbp), %eax
cmpb -2(%rbp), %al
je .L3
movzbl -1(%rbp), %ecx
movzbl -2(%rbp), %edx
movzbl -3(%rbp), %eax
movl %eax, %esi
movl $.LC1, %edi
movl $0, %eax
call printf
.L3:
movb $111, -3(%rbp)
movb $111, -2(%rbp)
movzbl -3(%rbp), %eax
movb %al, -1(%rbp)
movzbl -1(%rbp), %eax
cmpb -2(%rbp), %al
je .L4
movzbl -1(%rbp), %ecx
movzbl -2(%rbp), %edx
movzbl -3(%rbp), %eax
movl %eax, %esi
movl $.LC0, %edi
movl $0, %eax
call printf
.L4:
movb $82, -3(%rbp)
movb $92, -2(%rbp)
movzbl -3(%rbp), %eax
addl %eax, %eax
movl %eax, %edx
movzbl -3(%rbp), %eax
andl %edx, %eax
movl %eax, %edx
movzbl -3(%rbp), %eax
addl %eax, %eax
movl %edx, %ecx
subl %eax, %ecx
movl %ecx, %eax
movb %al, -1(%rbp)
movzbl -1(%rbp), %eax
cmpb -2(%rbp), %al
je .L5
movzbl -1(%rbp), %ecx
movzbl -2(%rbp), %edx
movzbl -3(%rbp), %eax
movl %eax, %esi
movl $.LC2, %edi
movl $0, %eax
call printf
.L5:
movb $-43, -3(%rbp)
movb $-43, -2(%rbp)
movzbl -3(%rbp), %eax
movl %eax, %ecx
sall $4, %ecx
movzbl -3(%rbp), %eax
leal 0(,%rax,8), %edx
movl %ecx, %eax
imull %edx, %eax
movb %al, -17(%rbp)
movzbl -3(%rbp), %edx
addl %edx, %edx
movzbl -17(%rbp), %eax
imull %edx, %eax
movl %eax, %edx
movzbl -3(%rbp), %eax
addl %edx, %eax
sall $4, %eax
orb -3(%rbp), %al
movb %al, -1(%rbp)
movzbl -1(%rbp), %eax
cmpb -2(%rbp), %al
je .L6
movzbl -1(%rbp), %ecx
movzbl -2(%rbp), %edx
movzbl -3(%rbp), %eax
movl %eax, %esi
movl $.LC3, %edi
movl $0, %eax
call printf
.L6:
movb $48, -3(%rbp)
movb $-87, -2(%rbp)
movzbl -3(%rbp), %eax
sall $2, %eax
shrb $3, %al
movl %eax, %edx
movzbl -3(%rbp), %eax
addl %eax, %eax
addl $1, %eax
xorl %edx, %eax
movl %eax, %edx
movzbl -3(%rbp), %eax
addl %edx, %eax
movb %al, -1(%rbp)
movzbl -1(%rbp), %eax
cmpb -2(%rbp), %al
je .L7
movzbl -1(%rbp), %ecx
movzbl -2(%rbp), %edx
movzbl -3(%rbp), %eax
movl %eax, %esi
movl $.LC4, %edi
movl $0, %eax
call printf
.L7:
movb $-4, -3(%rbp)
movb $-16, -2(%rbp)
movzbl -3(%rbp), %eax
sall $2, %eax
movb %al, -1(%rbp)
movzbl -1(%rbp), %eax
cmpb -2(%rbp), %al
je .L8
movzbl -1(%rbp), %ecx
movzbl -2(%rbp), %edx
movzbl -3(%rbp), %eax
movl %eax, %esi
movl $.LC5, %edi
movl $0, %eax
call printf
.L8:
movb $35, -3(%rbp)
movb $33, -2(%rbp)
movzbl -3(%rbp), %eax
shrb $4, %al
xorb -3(%rbp), %al
movb %al, -1(%rbp)
movzbl -1(%rbp), %eax
cmpb -2(%rbp), %al
je .L9
movzbl -1(%rbp), %ecx
movzbl -2(%rbp), %edx
movzbl -3(%rbp), %eax
movl %eax, %esi
movl $.LC6, %edi
movl $0, %eax
call printf
.L9:
movb $83, -3(%rbp)
movb $83, -2(%rbp)
movzbl -3(%rbp), %eax
movb %al, -1(%rbp)
movzbl -1(%rbp), %eax
cmpb -2(%rbp), %al
je .L10
movzbl -1(%rbp), %ecx
movzbl -2(%rbp), %edx
movzbl -3(%rbp), %eax
movl %eax, %esi
movl $.LC0, %edi
movl $0, %eax
call printf
.L10:
movb $-42, -3(%rbp)
movb $88, -2(%rbp)
movzbl -3(%rbp), %eax
shrb %al
sall $3, %eax
movb %al, -1(%rbp)
movzbl -1(%rbp), %eax
cmpb -2(%rbp), %al
je .L1
movzbl -1(%rbp), %ecx
movzbl -2(%rbp), %edx
movzbl -3(%rbp), %eax
movl %eax, %esi
movl $.LC7, %edi
movl $0, %eax
call printf
.L1:
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu/Linaro 4.7.2-2ubuntu1) 4.7.2"
.section .note.GNU-stack,"",@progbits