R0 CREW

Решения Easy Crackme

Доброго времени суток!

Сегодня я расскажу, как я решал crackme, который находится тут: http://forum.reverse4you.org/thread/1379&p=4665#post4665

Приступим к решению.

Запустим сам crackme и посмотрим, что он из себя представляет…

Видим, что у нас есть 2 поля для ввода и 2 кнопки для выхода, и проверки введенных данных. Пробуем что-то ввести и нажать кнопку Check. Видим, что программа закрылась(или вы с первого раза угадали, какой там серийник и увидели сообщение внизу приложение, где со старта написано “Hi!”), что же не будем отчаиваться и пойдем дальше.

Открываем crackme в Immunity Debugger. Посмотри какие функции импортированы в текущем приложении. Для этого делаем правый клик мышки -> Search for -> Name(label) in current module. Получим следующее окошко:

Лично меня очень заинтересовала функция GetDlgItemTextA, т.к. именно она получает данные, которые мы вводили в текстовые поля. Поставим бряк на эту функцию и запустим приложение. Видим, что мы остановились на этой функции, нажимаем один раз F8 и видим, что получаем введенный мной серийник:

Теперь рассмотрим код, который идет далее. Видим, что наша введенная строка передается далее какой-то функции, давайте выполним эту функцию и остановимся на инструкции сравнения. Эта функция вернула, число 0х20, что равняется длине введенной нами строки, т.е. имеем, что эта функция возвращает нам длину введенной строки ( кто не верит, может сделать F7 и проанализировать код :wink: - задание 1). Соответственно, получаем, что длина нашего серийника должна быть 0х20. Кто ввел другую длину, либо делаем до этого шага все сначала, либо меняем значение регистра EAX на 0х20 ручками и идем дальше ;). Т.к. длина введенной строки у меня нужная, то я попадаю на адрес 0040132E, где видим, что запихивается наша введенный серийник и еще какая область памяти. Посмотрим, что передается и что происходит в функции по адресу 0040338:

00401417  /$ 55             PUSH EBP
00401418  |. 8BEC           MOV EBP,ESP
0040141A  |. 56             PUSH ESI
0040141B  |. 57             PUSH EDI
0040141C  |. 53             PUSH EBX
0040141D  |. 8B75 08        MOV ESI,DWORD PTR SS:[EBP+8]
00401420  |. 8B7D 0C        MOV EDI,DWORD PTR SS:[EBP+C]
00401423  |. EB 08          JMP SHORT KeygenMe.0040142D
00401425  |> 83E3 0F        /AND EBX,0F
00401428  |. 03C3           |ADD EAX,EBX
0040142A  |. 8807           |MOV BYTE PTR DS:[EDI],AL
0040142C  |. 47             |INC EDI
0040142D  |> 0FB616          MOVZX EDX,BYTE PTR DS:[ESI]
00401430  |. 83FA 40        |CMP EDX,40
00401433  |. 1BDB           |SBB EBX,EBX
00401435  |. 83EA 37        |SUB EDX,37
00401438  |. 83E3 07        |AND EBX,7
0040143B  |. 46             |INC ESI
0040143C  |. 03DA           |ADD EBX,EDX
0040143E  |. 78 1A          |JS SHORT KeygenMe.0040145A
00401440  |. 8BC3           |MOV EAX,EBX
00401442  |. C1E0 04        |SHL EAX,4
00401445  |. 8807           |MOV BYTE PTR DS:[EDI],AL
00401447  |. 0FB616         |MOVZX EDX,BYTE PTR DS:[ESI]
0040144A  |. 83FA 40        |CMP EDX,40
0040144D  |. 1BDB           |SBB EBX,EBX
0040144F  |. 83EA 37        |SUB EDX,37
00401452  |. 83E3 07        |AND EBX,7
00401455  |. 46             |INC ESI
00401456  |. 03DA           |ADD EBX,EDX
00401458  |.^79 CB          \JNS SHORT KeygenMe.00401425
0040145A  |> 5B             POP EBX
0040145B  |. 5F             POP EDI
0040145C  |. 5E             POP ESI
0040145D  |. C9             LEAVE
0040145E  \. C2 0800        RETN 8

Видим, что в ESI и EDI запихиваются адреса памяти, переданных в функцию и дальше происходит манипуляции с введенным нами серийником… Придется разбираться, что уж тут поделать=)… Проводим, анализ цикла, который начинается на адресе 00401425 и заканчивается по адресу 00401458, мы можем заключить:
0. Из цикла мы выходим, тогда когда очередной рассчитанный, по вышеуказанному алгоритму, будет меньше нуля.

  1. Берется символ нашей введенной строки.
  2. Отнимается от этого символа 55 ( в десятичной системе ).
  3. Проверятся, что полученное значение больше или меньше 0х40, если меньше то надо еще прибавить к символу число 7.
  4. Записывается, то что получили по определенному адресу.
  5. Берется следующий введенный символ.
  6. Умножается на 16
  7. Далее повторение пунктов 2-3.
  8. Далее получаем старшие 4 бита полученного числа и записываем по определенному адресу
  9. Переходим опять на пункт 1.
    Из этого всего можно сделать один вывод, что результирующий символ после процедуры “шифрования” равен двум символам введенного серийника.

Отлично, самую тяжелую часть я думаю мы сделали ;). Выходим из функции и смотрим, как то, что мы “нашифровали” используется:

0040133D  |. 8D35 A8324000  LEA ESI,DWORD PTR DS:[4032A8]
00401343  |. 8D3D EA324000  LEA EDI,DWORD PTR DS:[4032EA]
00401349  |. B9 10000000    MOV ECX,10
0040134E  |. FC             CLD
0040134F  |. F3:A6          REPE CMPS BYTE PTR ES:[EDI],BYTE PTR DS:>
00401351  |. 75 5D          JNZ SHORT KeygenMe.004013B0

Исходя из этого кода, мы получаем, что наш “зашифрованный” серийник сравнивается с серийником, к-ый правильный ;). Соответственно, если эти строки не равны, то наша приложение закроется, если равны, то мы вместо строки “Hi!”, внизу приложению, получим сообщение, что мы нереально круты и справились с заданием.
Исходя из вышеописанного алгоритма генерации серийного номера, мы можем сесть и спокойно написать генератор серийники, т.к. “зашифрованный” правильный серийник мы знаем где достать ( DWORD PTR DS:[4032A8] ). Исходя из всего выше описанного, получаем следующий код генератора серийника:

#include <stdio.h>

#define SERIAL_SIZE 0x20
#define ENCRYPTED_SERIAL_SIZE (SERIAL_SIZE / 2)

int main() {
	char serial[SERIAL_SIZE];
	char encryptedSerial[ENCRYPTED_SERIAL_SIZE] = {0xE9, 0x7F, 0x65, 0x04, 0x42, 0x44, 0x0B, 0x14, 0x4E, 0x39, 0xC4, 0xD2, 0x30, 0x1C, 0xDF, 0x1D};

	unsigned int i;

	for (i = 0; i < ENCRYPTED_SERIAL_SIZE; i++) {
		char currByte = encryptedSerial[i];
		char tmp;

		tmp = ((currByte & 0xF0) >> 4) + 55;
		tmp -= (tmp < 0x40 ? 7 : 0);
		serial[2 * i] = tmp;

		tmp = (currByte & 0x0F) + 55;
		tmp -= (tmp < 0x40 ? 7 : 0);
		serial[2 * i + 1] = tmp;
	}

	// for (i = 0; i < SERIAL_SIZE; i++) {
	// 	printf("%x ", serial[i]);
	// }
	// printf("\n");

	for (i = 0; i < SERIAL_SIZE; i++) {
		printf("%c", serial[i]);
	}
	printf("\n");

	return 0;
}

Собственно все! Хотелось напоследок, только заметить следующие:

  1. Для генерации серийники мы вообще не используем никак логин, только введенный нами серийник.
  2. Создается какой-то непонятный файл keygen.dat и потом удаляется. Что это за файл и нафиг он нужен, кому сильно интересно разбирайтесь в качестве домашнего задания :wink:

Вот и все! Всем успехов.

Надеюсь кому-то понравилось и вопросов нету :wink:

P.S.: я не нашел как загрузить на наш форум картинки, поэтому пока пусть будут на radikal.ru =(

Не в обиду будет сказано, но я не согласен с выводами. Логин используется, непонятный файл keygen.dat - это динамическая библиотека, из который вызывается экспорт calc, которому передаётся дважды шифрованный логин, на выходе имеется результат шифровки внутри calc, именно с ним сравнивается введенный серийник. А функция, которую так детально разбирали, преобразует строку в число побайтово. Т.е. каждые два символа преобразуются в байт. И код, который приведен выше, никак не может быть “генератором серийника”, как им генерировать ключи под произвольные имена?

Я не обращал на этот файл внимание, видимо зря, но просто когда я пробывал менять логин, то шифрованная строка, с которй сравнивается введенный нами серийник, не меняется.

Никак не может генерировать.

Судя по всему зря я не обратил внимание на этот файл.

Спасибо за замечание. Приду вечером, надо будет тогда еще раз посмотреть!

Все разобрался я с ним до конца и да это оказался keygen, а не crackme… Надо было сразу обратить внимание на файл keygen.dat и ковырять его… Спасибо ARCHANGELу, что это заметил и сказал, что я был не прав. И также спасибо ему, что отвечал периодически на возникшее у меня вопросы.

Перейдем теперь к тому, как я осилил написать keygen :D…
Открываем еще раз данный KeygenMe.exe в отладчике и смотрим, а как используется наш файл keygen.dat… Пролистуем листинг программы и видим, что происходит вызов функции LoadLibraryA с параметром в виде имени нашего файла:

00401299  |> 68 2A304000    PUSH KeygenMe.0040302A                   ; /FileName = "keygenme.dat"
0040129E  |. E8 15030000    CALL <JMP.&kernel32.LoadLibraryA>        ; \LoadLibraryA
004012A3  |. 0BC0           OR EAX,EAX
004012A5  |. 0F84 05010000  JE KeygenMe.004013B0
004012AB  |. A3 9C314000    MOV DWORD PTR DS:[40319C],EAX
004012B0  |. E8 D7080000    CALL KeygenMe.00401B8C
004012B5  |. 5A             POP EDX
004012B6  |. 52             PUSH EDX                                 ; /Arg2
004012B7  |. 68 A8324000    PUSH KeygenMe.004032A8                   ; |Arg1 = 004032A8
004012BC  |. E8 E7080000    CALL KeygenMe.00401BA8                   ; \KeygenMe.00401BA8
004012C1  |. E8 2E090000    CALL KeygenMe.00401BF4
004012C6  |. 8BF8           MOV EDI,EAX
004012C8  |. 6A 20          PUSH 20                                  ; /Length = 20 (32.)
004012CA  |. 68 A8324000    PUSH KeygenMe.004032A8                   ; |Destination = KeygenMe.004032A8
004012CF  |. E8 F6020000    CALL <JMP.&kernel32.RtlZeroMemory>       ; \RtlZeroMemory
004012D4  |. 68 37304000    PUSH KeygenMe.00403037                   ; /ProcNameOrOrdinal
004012D9  |. FF35 9C314000  PUSH DWORD PTR DS:[40319C]               ; |hModule = 00AD0000 (keygen_1)
004012DF  |. E8 CE020000    CALL <JMP.&kernel32.GetProcAddress>      ; \GetProcAddress
004012E4  |. A3 A0314000    MOV DWORD PTR DS:[4031A0],EAX
004012E9  |. 57             PUSH EDI                                 ;  KeygenMe.00403300
004012EA  |. FF15 A0314000  CALL DWORD PTR DS:[4031A0]               ;  keygen_1.calc
004012F0  |. 68 A8324000    PUSH KeygenMe.004032A8
004012F5  |. 56             PUSH ESI
004012F6  |. E8 25030000    CALL KeygenMe.00401620

Отлично, исходя из вышеуказанного листинга видим, что вначале наша библиотека “keygen.dat” динамически подгружается и сразу можно заметить, что из этой библиотеки происходит вызов функции calc ( это мы можем видеть в конце листинга ). Давайте посмотрим, что передается этой функции.

Первые две функции, которые мы встречаем на своем пути после LoadLibraryA - это высчитывание длины введенной строки и копирование ее в опрделенную область памяти(0х0043310). В них нет ничего интересного, далее идет вызов функции 00401BF4. Вот она, то нам и интересна:

00401BF4  /$ 56             PUSH ESI
00401BF5  |. 57             PUSH EDI
00401BF6  |. A1 44334000    MOV EAX,DWORD PTR DS:[403344]
00401BFB  |. BA 10000000    MOV EDX,10
00401C00  |. 2BD0           SUB EDX,EAX
00401C02  |. 8DB8 10334000  LEA EDI,DWORD PTR DS:[EAX+403310]
00401C08  |. 81E2 FF000000  AND EDX,0FF
00401C0E  |. 8BC2           MOV EAX,EDX
00401C10  |. 8BCA           MOV ECX,EDX
00401C12  |. F3:AA          REP STOS BYTE PTR ES:[EDI]
00401C14  |. E8 57FEFFFF    CALL KeygenMe.00401A70
00401C19  |. BE 30334000    MOV ESI,KeygenMe.00403330
00401C1E  |. BF 10334000    MOV EDI,KeygenMe.00403310                ;  ASCII "coldfire"
00401C23  |. B9 10000000    MOV ECX,10
00401C28  |. F3:A4          REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[>
00401C2A  |. E8 41FEFFFF    CALL KeygenMe.00401A70
00401C2F  |. B8 00334000    MOV EAX,KeygenMe.00403300
00401C34  |. 5F             POP EDI
00401C35  |. 5E             POP ESI
00401C36  \. C3             RETN

Видим, что тут мы берем нашу строку, дополняем ее до 0х10 байт, если она меньше ( символ, к-ым дополняется высчитуется так - 0х10 - длина введеной строки). Дальше наша строка шифруется дважды ( саму функци шифроку я не буду детально описывать, во-первых - она в целом понятно, во-вторых - ее можно увидеть в коде генератора, который я предоставлю ниже).

После этой функции мы видим, что происходит вызов функции calc из динамической библиотеки keygen.dat. Смотрим, что передается этой функции - АГА, туда передается указатель на наш дважды зашифрованный буфер!!! Т.е. наш буфер еще раз шифруется. УЖАС! Заглянем в эту функцию, и видим, что то что оно нашифровало будет находится по адресу, который находится в регистре esi… Более детально мы не будем рассматривать эту функции, т.к. мы ее просто заиспользуем при написании генератора =)

Далее видим, что далее происходит ( в функции 00401620 ) копирование зашифрованной строки в область памяти, которая потом преобразовывается в строку ( Алгортим преобразование, я собственно описал в самом начале, в первом моем посте ).

Собственно, вот и все. Прилагаю код генератора, изучаем, хвалим или наоборот :wink:

#include <stdio.h>
#include <string.h>

#include <windows.h>

char *enteredLogin = "coldfire";

#define BUFF_SIZE 0x48

#define SERIAL_SIZE 0x20
#define ENCRYPTED_SERIAL_SIZE (SERIAL_SIZE / 2)

unsigned char encryptedSerial[ENCRYPTED_SERIAL_SIZE];

unsigned char buff[BUFF_SIZE];

unsigned char buff403080[] = {
                                0x29, 0x2E, 0x43, 0xC9, 0xA2, 0xD8, 0x7C, 0x01, 0x3D, 0x36, 0x54, 0xA1, 0xEC, 0xF0, 0x06, 0x13,
                                0x62, 0xA7, 0x05, 0xF3, 0xC0, 0xC7, 0x73, 0x8C, 0x98, 0x93, 0x2B, 0xD9, 0xBC, 0x4C, 0x82, 0xCA,
                                0x1E, 0x9B, 0x57, 0x3C, 0xFD, 0xD4, 0xE0, 0x16, 0x67, 0x42, 0x6F, 0x18, 0x8A, 0x17, 0xE5, 0x12,
                                0xBE, 0x4E, 0xC4, 0xD6, 0xDA, 0x9E, 0xDE, 0x49, 0xA0, 0xFB, 0xF5, 0x8E, 0xBB, 0x2F, 0xEE, 0x7A,
                                0xA9, 0x68, 0x79, 0x91, 0x15, 0xB2, 0x07, 0x3F, 0x94, 0xC2, 0x10, 0x89, 0x0B, 0x22, 0x5F, 0x21,
                                0x80, 0x7F, 0x5D, 0x9A, 0x5A, 0x90, 0x32, 0x27, 0x35, 0x3E, 0xCC, 0xE7, 0xBF, 0xF7, 0x97, 0x03,
                                0xFF, 0x19, 0x30, 0xB3, 0x48, 0xA5, 0xB5, 0xD1, 0xD7, 0x5E, 0x92, 0x2A, 0xAC, 0x56, 0xAA, 0xC6,
                                0x4F, 0xB8, 0x38, 0xD2, 0x96, 0xA4, 0x7D, 0xB6, 0x76, 0xFC, 0x6B, 0xE2, 0x9C, 0x74, 0x04, 0xF1,
                                0x45, 0x9D, 0x70, 0x59, 0x64, 0x71, 0x87, 0x20, 0x86, 0x5B, 0xCF, 0x65, 0xE6, 0x2D, 0xA8, 0x02,
                                0x1B, 0x60, 0x25, 0xAD, 0xAE, 0xB0, 0xB9, 0xF6, 0x1C, 0x46, 0x61, 0x69, 0x34, 0x40, 0x7E, 0x0F,
                                0x55, 0x47, 0xA3, 0x23, 0xDD, 0x51, 0xAF, 0x3A, 0xC3, 0x5C, 0xF9, 0xCE, 0xBA, 0xC5, 0xEA, 0x26,
                                0x2C, 0x53, 0x0D, 0x6E, 0x85, 0x28, 0x84, 0x09, 0xD3, 0xDF, 0xCD, 0xF4, 0x41, 0x81, 0x4D, 0x52,
                                0x6A, 0xDC, 0x37, 0xC8, 0x6C, 0xC1, 0xAB, 0xFA, 0x24, 0xE1, 0x7B, 0x08, 0x0C, 0xBD, 0xB1, 0x4A,
                                0x78, 0x88, 0x95, 0x8B, 0xE3, 0x63, 0xE8, 0x6D, 0xE9, 0xCB, 0xD5, 0xFE, 0x3B, 0x00, 0x1D, 0x39,
                                0xF2, 0xEF, 0xB7, 0x0E, 0x66, 0x58, 0xD0, 0xE4, 0xA6, 0x77, 0x72, 0xF8, 0xEB, 0x75, 0x4B, 0x0A,
                                0x31, 0x44, 0x50, 0xB4, 0x8F, 0xED, 0x1F, 0x1A, 0xDB, 0x99, 0x8D, 0x33, 0x9F, 0x11, 0x83, 0x14
                            };

void init() {    
    int enteredLoginLen = strlen(enteredLogin);

    memset(buff, 0, BUFF_SIZE);

    memset(buff + 0x44, enteredLoginLen, 1);

	memcpy(buff + 0x10, enteredLogin, enteredLoginLen);
	memset(buff + 0x10 + enteredLoginLen, (0x10 - enteredLoginLen) & 0xFF, (0x10 - enteredLoginLen) & 0xFF);
}

void f(unsigned int off1, unsigned int off2, unsigned res_off) {
    unsigned char tmp[4];
    unsigned char tmp1[4];

    memcpy(tmp, buff + off1, 4);
    memcpy(tmp1, buff + off2, 4);

    int i;
    for(i = 0; i < 4; i++){
        tmp[i] ^= tmp1[i];
    }

    memcpy(buff + res_off, tmp, 4);
}

// need to add to off's - i....
void f1(unsigned int off1, unsigned int off2, unsigned char *byte) {
    // byte - edx; i - edi
//    unsigned char debug_byte;

    unsigned char tmp_byte_1;               // ecx
    unsigned char tmp_byte_2;               // eax

    tmp_byte_1 = *(buff + off1);
    tmp_byte_2 = *(buff + off2);

    (*byte) ^= tmp_byte_1;

//    debug_byte = *(buff403080 + *byte);

    tmp_byte_2 ^= *(buff403080 + *byte);

    *(buff + off2) = tmp_byte_2;

    *byte = tmp_byte_2;
}

// need to add to off's - i....
void f2(unsigned int off, unsigned char *byte) {
    unsigned char tmp_byte_2 = *(buff + off); // ebx
    *byte = *(buff403080 + *byte);
    *byte ^= tmp_byte_2;
    *(buff + off) = *byte;
}

void cryptBuffer() {
    f(0x10, 0, 0x20);
    f(0x14, 0x4, 0x24);
    f(0x18, 0x8, 0x28);
    f(0x1c, 0xC, 0x2c);

    unsigned char byte = *(buff + 0x3f);    // edx

    int i = -24;                            // edi
    do {
        f1(i + 0x28, i + 0x48, &byte);
        f1(i + 0x29, i + 0x49, &byte);
        f1(i + 0x2a, i + 0x4a, &byte);
        f1(i + 0x2b, i + 0x4b, &byte);

        i += 4;

    } while(i);

    byte = 0;
    i = -48;
    unsigned char j = 0;

    do {
        do {
            f2(i + 0x30, &byte);
            f2(i + 0x31, &byte);
            f2(i + 0x32, &byte);
            f2(i + 0x33, &byte);

            i += 4;
        } while(i);

        byte += j;
        j++;
        byte &= 0xff; // there is no any sense...
        i = -48;
    } while(j != 18);
}

void f_00401bf4() {
    cryptBuffer();

    memcpy(buff + 0x10, buff + 0x30, 0x10);

    cryptBuffer();
}

void f_00401620(int addr) {
	signed int i;
	int byte;

	i = -1;
	do
	{
		++i;
		byte = *(BYTE *)(i + addr);
		*(BYTE *)(i + encryptedSerial) = byte;
	} while ( byte );
}

int main(void)
{
    init();

    f_00401bf4();

    typedef int (__stdcall *calcfunc)(DWORD);
        
    HMODULE lib = LoadLibraryA("keygenme.dat");
    if (!lib) {
       printf("lib not loaded\n");
       return 1;
    }
    
    calcfunc calc = (calcfunc) GetProcAddress(lib, "calc");
    if (!calc) {
       printf("func address bad...\n");
       return 1;
    }        

	DWORD addr = (DWORD)(&buff[0]);

    calc(addr);
 	
	__asm {
		mov addr, esi
	}
	
	f_00401620(addr);
                
    FreeLibrary(lib);

	// translate encrypted buffer to string...
	char serial[SERIAL_SIZE];

	unsigned int i;

	for (i = 0; i < ENCRYPTED_SERIAL_SIZE; i++) {
		char currByte = encryptedSerial[i];
		char tmp;

		tmp = ((currByte & 0xF0) >> 4) + 55;
		tmp -= (tmp < 0x40 ? 7 : 0);
		serial[2 * i] = tmp;

		tmp = (currByte & 0x0F) + 55;
		tmp -= (tmp < 0x40 ? 7 : 0);
		serial[2 * i + 1] = tmp;
	}

	for (i = 0; i < SERIAL_SIZE; i++) {
		printf("%c", serial[i]);
	}
	printf("\n");

	system("PAUSE");
    return 0;
}

На этом все! Спасибо за внимание! Если у кого есть еще какие-то вопросы, то пишем сюда буду пробовать отвечать :wink:

И, да, root, наверно, надо все таки все, что касается этого keygen’a перенести в соотвесвующий раздел, а то я был уверен, что это crackme :smiley: Но все мы ошибаемся, особенно, когда находимся вначале пути :wink:

Offtop

P.S.: лично у меня еще возникли проблемы с ассемблерной вставкой, вот такого рода:

Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call.  This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.

Сегодня, когда начал писать статью запустил еще раз и уже ничего этого не выскакивало, не знаю почему… К сожалению, сам я не смог справиться адекватно, думал, что это из-за того, что я вначале не указывал __stdcall, но когда, я указал - что-то не помогало, сегодня помагло.
Кому интересно, я обратился в этом моменте за помощью к ARCHANGELу и вот, собственно, что он мне ответил ( я думаю он не будет против, если я скину сообщение… ):
А, ну, вот в чём проблема:

00411ACF 8BF4 MOV ESI,ESP
00411AD1 8B45 D0 MOV EAX,DWORD PTR SS:[EBP-0x30]
00411AD4 50 PUSH EAX
00411AD5 FF95 7CFFFFFF CALL DWORD PTR SS:[EBP-0x84]
00411ADB 3BF4 CMP ESI,ESP

Это - код вашего кейгена. В нём вызов функции calc по указателю объявлен правильно, с соглашением __sdtcall. Вот это место в исходниках:

typedef int (__stdcall *calcfunc)(DWORD);

Ну, если уж на то пошло, то должно быть так:

typedef int (__stdcall *calcfunc)(BYTE*);

Но суть ошибки не в этом. Теперь вернёмся к коду выше.

00411ACF 8BF4 MOV ESI,ESP // сохранение старого значения esp - вершины стека
00411AD1 8B45 D0 MOV EAX,DWORD PTR SS:[EBP-0x30] // чтение входного параметра для вызова calc из локальной переменной
00411AD4 50 PUSH EAX // заталкиваем его в стек
00411AD5 FF95 7CFFFFFF CALL DWORD PTR SS:[EBP-0x84] // собвственно, вызов
00411ADB 3BF4 CMP ESI,ESP // сравнение текущей вершины стека с сохранённой

Фишка в проверке последней строки. ESI перезаписывается внутри calc, но компилятор, который ожидает от вызываемой функции полной поддержки спецификации __stdcall, никак не ожидает такого подвоха. В результате старое значение вершины стека, которое должно равняться новому, уже ему не равно, т.к. новое значение действительно отображает состояние стека, а старое - это адрес массива с байтами, и к стеку уже не имеет никакого отношения.

Для исправления положения я пропатчил кусок в вашем кейгене:

00411AE2 8945 D0 MOV DWORD PTR SS:[EBP-0x30],EAX

И кусок - в библиотеке:

10005B20 2BC0 SUB EAX,EAX
10005B22 8D05 645C0010 LEA EAX,DWORD PTR DS:[0x10005C64]
10005B28 C9 LEAVE
10005B29 8BF4 MOV ESI,ESP
10005B2B 83C6 08 ADD ESI,0x8
10005B2E 90 NOP
10005B2F 90 NOP
10005B30 90 NOP
10005B31 90 NOP
10005B32 90 NOP
10005B33 90 NOP
10005B34 90 NOP
10005B35 C2 0400 RETN 0x4

Так а чего последний косяк не исправили? Или вы выбрали Release сборку, и ошибка исчезла?

Выбрал Release сборку и она куда-то пропала… и в Debug сборке тоже…

При том, что раньше я делал в 2008 студии (на ноуте стоит), а на ПК - 2010. И вот на ПК все норм работало…

Ну, компилятор чуток по-другому код пересобрал, и, видать, прокатило. Странно, что имя жёстко забито, и его задавать нельзя, а так - работает, и слава богу ))

Почему же, можно задать другое имя только для этого надо будет пересобрать всё =)