• XSS.stack #1 – первый литературный журнал от юзеров форума

Генерация чисел

Left4Dead

(L3) cache
Пользователь
Регистрация
08.05.2010
Сообщения
259
Реакции
3
У крипторов и всякой разной малвары часто стоят задачи обфускации строк, хеш-таблиц для поиска 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:

Код:
#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

вот что выходит в ассемблерном виде (синтаксис 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
 
Idea`s good, но это же как код разростётся...

Из альтернативы:
Сохранение входного файла по частям в:
1)ресурсах
2)секции данных
3)оверлее

В конце же части файла собираются воедино: можно заюзать стеганографию(скрытие файла в bitmap, icon и т.д), сжатие, уменьшение энтропии, разбавление мусорными байтами и т.д.

Данные способы исп-ся в крипторах не один год и ничего - детектятся максимум сигнатурно.
Если же ты криптуешь свой трой, то стоит заюзать обфускацию кода на уровне сорцов.
Спермским и др. ав конторам нельзя давать шансы на восстановление оригинального кода и вообще понимания сути логики программы.
Предлагаю создать для этой темы отдельный топ.
 
кто помнит двиг KME?

Генерация данных кодом, это то к чему нужно стремиться... объясню почему:
ресурсы, секция данных, оверлей хороши для свеже-написанного кода, грузим в мир в больших объемах месяц-три. Что мы видим... Аверы идентифицировали наше семейство криптора и создали отдельных базовый псевдо-скрипт, который отсекает весь мусор\обфускацию\понты и дальше нас спасает лишь антиэмуляция и сложнонсть мусора\обускации это в лучшем случае. В хуждем, наш криптор отнесут к семейству уже известных и адаптируют скрипт под него.
Т.е. при генерации данных кодом, хоть как ты крем говоришь, что кодес разрастется, зато идентифицировать хранение однозначно будет х#й там как просто. При детекте можно будет лишь сменить мат. функи +- поиграть с энтропией. Максимум для закоса под легитим можно секцию данных псевдо-рэндомным буфером ебошить. Ресурсы ебошить генератором ресурсов.

Left4Dead
я помню твой сурс с генерацией кода с испольщованием тангенсов, сильно, а тут PoC.
Чтож, огромный сенкс тебе, прогресс не стоит на месте, и нам нужно развиваться! +10

p.s. по поводу сгенерированного кода.. его надо бы еще разбавлять мусором и возможно лапшой из jmp...
 
Chococream
Код разрастется конечно, если генерировать таким образом всё. Но можно и не все генерить, а частично. Например можно генерить код расшифровщика и код запускателя-в-памяти и передавать им управление, а шифруемый трой держать в секции данных.

demien
кто помнит двиг KME?
Не знаю такого, покажи, интересно.

p.s. по поводу сгенерированного кода.. его надо бы еще разбавлять мусором и возможно лапшой из jmp...
само собой, но это тема другой статьи)

P.S. спасибо за отзывы. Позже выложу еще кое-че интересное
 
кто помнит двиг KME?
http://zalil.ru/34206573

Залил KME 5.52, каж-ся с сайта Z0mbie.

Генерация данных кодом, это то к чему нужно стремиться... объясню почему:
ресурсы, секция данных, оверлей хороши для свеже-написанного кода, грузим в мир в больших объемах месяц-три. Что мы видим... Аверы идентифицировали наше семейство криптора и создали отдельных базовый псевдо-скрипт, который отсекает весь мусор\обфускацию\понты и дальше нас спасает лишь антиэмуляция и сложнонсть мусора\обускации это в лучшем случае. В хуждем, наш криптор отнесут к семейству уже известных и адаптируют скрипт под него.
Т.е. при генерации данных кодом, хоть как ты крем говоришь, что кодес разрастется, зато идентифицировать хранение однозначно будет х#й там как просто. При детекте можно будет лишь сменить мат. функи +- поиграть с энтропией.
Опять же обфускацию на уровне сорцов(если она конечно правильная: используются функции, fake win api calls, вычисления и т.п, т.е то, что не вызовет подозрений) не так-то легко снять.

Сгенерируются данные в буфере, авер перехватит это дело и вылезет детект. Всё упирается в антиэмулятор.
 
само собой, но это тема другой статьи)
у тебя не плохо получается их писать;)

Сгенерируются данные в буфере, авер перехватит это дело и вылезет детект. Всё упирается в антиэмулятор
Смотря как будут генерится данные в буфере:) Т.е. если вычисление функции занимает какое-то время, то это время можно увеличить, и опять же скрытый time-delay ;) по поводу антиэмуля-да! но костыли когда нибудь кончатся...есть тема ... Time-lock пазлы...
кто-то курил в этом направлении?
 
Другой вариант генерирования кода динамически, я его назвал "Dynamic-compiler":
Сама программа - это некое подобие компилятора.
При выполнении программа оперирует структурой данных(псевдокод), из которой позднее получается выполняемый код, всё время разный.
 
При выполнении программа оперирует структурой данных(псевдокод), из которой позднее получается выполняемый код, всё время разный.
- кажется я знаю о чем ты :)
 
Другой вариант генерирования кода динамически, я его назвал "Dynamic-compiler":
Сама программа - это некое подобие компилятора.
При выполнении программа оперирует структурой данных(псевдокод), из которой позднее получается выполняемый код, всё время разный.

Идея вообще крутая, но я думаю ее не так просто будет реализовать... но если реализовать то это будет бомба....
 
>>При выполнении программа оперирует структурой данных(псевдокод), из которой позднее получается выполняемый код, всё время разный.

Этот подход несколько ограничен, тк псевдокод не будет дотягивать по степени выразительности до нормальных компилируемых языков, а реализация компилятора с нуля выглядит несколько избыточным решением. Я делал несколько проще - брал tcc и модифицировал его кодогенератор, добавив туда полиморфную генерацию примитивов, что позволило компилировать сорцы второго слоя криптора полиморфно, но так как tcc довольно глючное поделие пришлось отказаться (главным образом у него на тот момент сильно глючила генерация x64 значений, что нахер рвало стек). В итоге остановился на AST парсере, что позволило генерировать вполне осмысленный мусор, добавляя конструкции напрямую в дерево. Конечно это не тестировалось в бою, но на тестах раскручивали (конечно с фейк апи вызовами) только vba и иногда ms, насколько помню. Но это тоже не оптимально как мне показалось было, следующим шагом нужно было пилить плагин для llvm и работать непосредственно с его IR для изменения CFG/DFG напрямую, но на тот момент я уже немного на это все подзабил.
 
l4d, так neko и говорит про кодогенерацию C кода (tcc - tiny C comiler).
llvm например тоже тема! некий gloomraven с васма начинал прожект написания на ллвм кодогенератора&морфера, был даже репо открыт, потом толи забил, толи забил на всех и сам допиливать стал.
 
demien
ага, вы правы. Тут кстати есть коммерческие разработки на эту тему, белые, http://morpher.com/. Но насколько помню, там модифицированный gcc, что не столь удобно.

На самом деле, как мне кажется, это наиболее правильный подход - так как на уровне компилятора мы обладаем намного большей информацией о коде, нежели при манипуляции непосредственно бинарными примитивами.
 
Cream, гляди
Вот описание некого ботнета аватар, читай что пишут, не то же ли ты имел ввиду?
Avatar использует подход на основе дропперов, состоящих из нескольких уровней. Дроппер первого уровня осуществляет декомпрессию (LZMA) для дроппера второго уровня и драйвера. Фактически, дроппер второго уровня и сам драйвер представляют из себя уникальные файлы, генерируемые какждый раз при распаковке, поскольку начальный дроппер генерирует случайные имена для мьютексов и событий, которые используются в их коде и выполняет эти модификации непосредственно в теле каждого из модулей.
 
demien
Фактически, дроппер второго уровня и сам драйвер представляют из себя уникальные файлы, генерируемые какждый раз при распаковке, поскольку начальный дроппер генерирует случайные имена для мьютексов и событий, которые используются в их коде и выполняет эти модификации непосредственно в теле каждого из модулей.
Как-то не тянет на "уникальные файлы, генерируемые какждый раз при распаковке" :).
 
demien
Одно из применений - это самомодифицирующиеся вирусы.
При наличии внутри тела бота хорошего обфускатора(как довесок к компилятору), он может видоизменять себя раз в 1-3 дня, достигая fud.
Пока это в теории, просто информация для размышления...
 
а если его отловят? весь алго обфускации и работа псу под хвост.
Тогда будут детектировать вирус по нескольким параметрам:
1)Изучат работу алгоритма генерации.
Теоретически, можно создать базу сигнатур(хэшей) под такое дело, но на практике, используя хороший обфускатор(фейковые процедуры, вызов фейковых апи, перемешка всего кода в целом и т.п), возможно отсрочить детект.

2)Станут цепляться к внешним признакам:
- Энтропия секций.
- Набор api в таблице импорта.
- Сигнатуры.
- ...

Стоит косить под легитимные приложения. Это сложно, но реализуемо :)
В таком случае аверу останется: создать сигнатуру или очередное временное решение для детектирования.
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх