R0 CREW

Решение keygenme1

Сейчас я Вам расскажу про то как был решен вот этот кейген – https://forum.reverse4you.org/t/335.

Собвственно, запустим кейген, введем какие-то данные и запустим. Если вы не угадали серийник ( :wink: ), то у вас выскочит сообщение вида: «Wrong serial». Его мы и попробуем пожалуй найти. Открыв окошко со строками в IDA ( Shift + F12 ) получил следующие:

.rdata:004020BA 0000000B C user32.dll  
.rdata:004020E8 0000000D C kernel32.dll
.data:00403221  0000000A C Good job!   
.data:0040322B  0000000D C Wrong serial

Перейдем на .data:0040322B и посмотрим в какой функции есть ссылка на эту строку:

.data:0040322B aWrongSerial    db 'Wrong serial',0     ; DATA XREF: sub_40112E+C0#o

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

.text:0040112E sub_40112E      proc near               ; CODE XREF: DialogFunc+43#p
.text:0040112E                 push    offset String
.text:00401133                 call    stringLength
.text:00401138                 cmp     eax, 5
.text:0040113B                 jl      loc_4011EA
.text:00401141                 mov     ecx, 2
.text:00401146                 cdq
.text:00401147                 div     ecx
.text:00401149                 test    edx, edx
.text:0040114B                 jnz     loc_4011EA
.text:00401151                 mul     ecx
.text:00401153                 push    eax
.text:00401154                 push    offset String
.text:00401159                 call    modifiengLogin
.text:0040115E                 mov     dword_403208, eax
.text:00401163                 push    offset byte_403100
.text:00401168                 call    stringLength
.text:0040116D                 cmp     eax, 11h
.text:00401170                 jnz     short loc_4011EA
.text:00401172                 mov     eax, offset byte_403100
.text:00401177                 mov     byte ptr [eax+8], 0
.text:0040117B                 mov     edi, offset byte_403100
.text:00401180                 push    edi
.text:00401181                 call    modifiengSerial
.text:00401186                 mov     esi, eax
.text:00401188                 mov     dword_403200, eax
.text:0040118D                 add     edi, 9
.text:00401190                 push    edi
.text:00401191                 call    modifiengSerial
.text:00401196                 mov     edi, eax
.text:00401198                 mov     dword_403210, eax
.text:0040119D                 mov     ecx, esi
.text:0040119F                 call    sub_4010F3
.text:004011A4                 test    ecx, ecx
.text:004011A6                 jnz     short loc_4011EA
.text:004011A8                 mov     ecx, edi
.text:004011AA                 call    sub_4010F3
.text:004011AF                 test    ecx, ecx
.text:004011B1                 jnz     short loc_4011EA
.text:004011B3                 mov     eax, dword_403208
.text:004011B8                 add     eax, dword_403218
.text:004011BE                 xor     dword_403200, eax
.text:004011C4                 jnz     short loc_4011EA
.text:004011C6                 mov     ecx, 2
.text:004011CB                 cdq
.text:004011CC                 mul     ecx
.text:004011CE                 xor     dword_403210, eax
.text:004011D4                 jnz     short loc_4011EA
.text:004011D6                 push    0               ; uType
.text:004011D8                 push    0               ; lpCaption
.text:004011DA                 push    offset Text     ; "Good job!"
.text:004011DF                 push    0               ; hWnd
.text:004011E1                 call    MessageBoxA
.text:004011E6                 xor     eax, eax
.text:004011E8                 inc     eax
.text:004011E9                 retn
.text:004011EA ; ---------------------------------------------------------------------------
.text:004011EA
.text:004011EA loc_4011EA:                             ; CODE XREF: sub_40112E+D#j
.text:004011EA                                         ; sub_40112E+1D#j ...
.text:004011EA                 push    0               ; uType
.text:004011EC                 push    0               ; lpCaption
.text:004011EE                 push    offset aWrongSerial ; "Wrong serial"
.text:004011F3                 push    0               ; hWnd
.text:004011F5                 call    MessageBoxA
.text:004011FA                 xor     eax, eax
.text:004011FC                 retn
.text:004011FC sub_40112E      endp

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

Отметим, что строка для которой ищется длина строки — это наш введенный логин. Дальше наш введеный логин передается в функцию, которая рассчитывает длину строки, и далее идет проверка, что введенный логин больше 5. Если он меньше, то мы сразу получаем вывод сообщения «Wrong serial». Если длина логина больше 5, то далее идет достаточно забаный кусок, который в целом мне не понятен, либо я не правильно понял, что он делает…:

.text:00401141 mov     ecx, 2
.text:00401146 cdq
.text:00401147 div     ecx
.text:00401149 test    edx, edx
.text:0040114B jnz     loc_4011EA

Тут происходит проверка, что длина введеного логина является четной, если она нечетная, то мы переходим на ветку, где у будет выведено окошко со строкой «Wrong serial».

Дальше у нас запихивается в стек длина логина и сам логин, и это все передается функции, которая делает какие-то преобразования ( рассмотрим их позже ) над нашим логином.

После этого мы переходим к нашему серийному номеру. Высчитываем его длину, сравниваем с 0х11. Соотвественно, если его длина меньше 17 символов, то мы увидим окошко с сообщением «wrong serial». В противном случаи рассматриваем код, который идет дальше:

.text:00401172 mov     eax, offset byte_403100
.text:00401177 mov     byte ptr [eax+8], 0
.text:0040117B mov     edi, offset byte_403100
.text:00401180 push    edi
.text:00401181 call    modifiengSerial
.text:00401186 mov     esi, eax
.text:00401188 mov     dword_403200, eax
.text:0040118D add     edi, 9
.text:00401190 push    edi
.text:00401191 call    modifiengSerial
.text:00401196 mov     edi, eax
.text:00401198 mov     dword_403210, eax
.text:0040119D mov     ecx, esi
.text:0040119F call    sub_4010F3
.text:004011A4 test    ecx, ecx
.text:004011A6 jnz     short loc_4011E

Здесь происходит разбивка введеного серийного номера по сути на 2 части ( на 9ю позицию ставиться 0). Далее по сути береться первая часть введеного серийника и передается в функцию, которая что-то делает с ним ( рассмотрим ее позже ). Затем береться вторая часть серийного номера и передается в эту же функцию. Дальше для меня был немного не понятный момент, но , собственно, он мне ни на что не повлиял =). То что мы получили в результате преобразования над первой частью серийника передается в функцию sub_4010F3, которая проверяет, что бы никакой байт преобразованого серийника не была равной 0, т.*е. Допустим у нас есть строка вида 2233004, тогда функция будет работать так:

  1. Береться последний байт, т.*е. 04.
  2. Проверяется не равен ли этот байт 0.
  3. Если равен 0, то функция возвращает 1, что есть плохо. В этом случаи мы увидим окошко «Wrong serial»
  4. Если не равен, то сдвигается строка на 4, т.*е. Получим уже вот такую строку 223300.
  5. Переход к пункту 1. Это будет повторяться пока мы не пройдем всю строку, либо пока не встреим байт равный 0.

После вот этих всех мутных проверок идет достаточно важный кусок:

.text:004011B3 mov     eax, dword_403208
.text:004011B8 add     eax, dword_403218
.text:004011BE xor     dword_403200, eax
.text:004011C4 jnz     short loc_4011EA

В еах помещается результат модификации логина, к нему прибавляется значение dword_403218 = DDBBCCAA ( при написании кейгена надо не забывать это константу прибовлять ). И в конце концов проверяется, что равна ли первая часть серийника вышеуказанному результату(назовем его result1). Если да, то переходи к проверке второй части серийника:

.text:004011C6 mov     ecx, 2
.text:004011CB cdq
.text:004011CC mul     ecx
.text:004011CE xor     dword_403210, eax
.text:004011D4 jnz     short loc_4011EA

Тут происходит следубщие берем 2, расширяем его до 4 байт ( смотрим как работает cdq) и умножаем result1 на 2 и то, что получим (назовем result2) со второй модифицированной частью введенного серийника. И если они равны, то мы видим, что мы успешно подобрали=), если нет, то опять окошко с сообщением «Wrong serial».

Собственно, как происходит проверка я думаю мы поняли, теперь осталось рассмотреть функции модификации логина и частей серийного номера.
Начнем с модификации логина:

 .text:0040110C modifiengLogin proc near                ; CODE XREF: sub_40112E+2B#p
.text:0040110C
.text:0040110C arg_0= dword ptr  4
.text:0040110C arg_4= dword ptr  8
.text:0040110C
.text:0040110C push    ecx
.text:0040110D push    edi
.text:0040110E push    esi
.text:0040110F and     eax, 0
.text:00401112 and     esi, 0
.text:00401115 mov     edi, [esp+0Ch+arg_0]
.text:00401119 mov     ecx, [esp+0Ch+arg_4]
.text:0040111D
.text:0040111D loc_40111D:                             ; CODE XREF: modifiengLogin+1A#j
.text:0040111D movzx   esi, byte ptr [ecx+edi-1]
.text:00401122 add     eax, esi
.text:00401124 add     eax, ecx
.text:00401126 loop    loc_40111D
.text:00401128 pop     esi
.text:00401129 pop     edi
.text:0040112A pop     ecx
.text:0040112B retn    8
.text:0040112B modifiengLogin endp

Видим, что в регистр есх помещается длина логина, а в регистр edi — сам логин. Далее esi помещаеться последний символ, затем esi складыдвается с eax ( который вначале был инициализирован нулем). И на последок к значению eax прибавляется значение ecx, которое уменьшается каждый раз на один тем самым обеспечивая, то что мы будем проделывать выше указанные действия со всеми символами введоно логина. Сам результат модифицирования логина после выполнения этого цикла будет записан в регистре eax. Для моего ника inisider ( это мой второй ник, кто еще не понял :wink: ) он получается равен 37B.

Теперь рассмотрим функцию модифицирования частей введеного серийного номера:

.text:004010B8 modifiengSerial proc near               ; CODE XREF: sub_40112E+53#p
.text:004010B8                                         ; sub_40112E+63#p
.text:004010B8
.text:004010B8 arg_0= dword ptr  4
.text:004010B8
.text:004010B8 push    edi
.text:004010B9 push    esi
.text:004010BA push    ecx
.text:004010BB and     eax, 0
.text:004010BE and     esi, 0
.text:004010C1 and     ecx, 0
.text:004010C4 mov     edi, [esp+0Ch+arg_0]
.text:004010C8
.text:004010C8 loc_4010C8:                             ; CODE XREF: modifiengSerial+33#j
.text:004010C8 movzx   esi, byte ptr [ecx+edi]
.text:004010CC test    esi, esi
.text:004010CE jz      short loc_4010ED
.text:004010D0 sub     esi, 30h
.text:004010D3 cmp     esi, 0Ah
.text:004010D6 jl      short loc_4010E2
.text:004010D8 sub     esi, 7
.text:004010DB cmp     esi, 17h
.text:004010DE jl      short loc_4010E2
.text:004010E0 xor     esi, esi
.text:004010E2
.text:004010E2 loc_4010E2:                             ; CODE XREF: modifiengSerial+1E#j
.text:004010E2                                         ; modifiengSerial+26#j
.text:004010E2 shl     eax, 4
.text:004010E5 or      eax, esi
.text:004010E7 inc     ecx
.text:004010E8 cmp     ecx, 8
.text:004010EB jnz     short loc_4010C8
.text:004010ED
.text:004010ED loc_4010ED:                             ; CODE XREF: modifiengSerial+16#j
.text:004010ED pop     ecx
.text:004010EE pop     esi
.text:004010EF pop     edi
.text:004010F0 retn    4
.text:004010F0 modifiengSerial endp

Видим, что в edi помещается часть серийного номера ( их у нас 2 кто забыл, почему? Читаем выше, там все описано :wink: ). Далее в esi заносится очередной символ введного серийного номера. Если он не равен 0 ( это именно тот 0, который отдельно вставлялся, что бы разбить введеный серийник на две части, каждый из которой модифицируется отдельно). Соотвественно, если очередной символ не 0, то отнимается от него 0х30 (result1) и сравниваем полученный результат с 0х0A. Если этот результат больше 0x0A, то мы дополнительно к нему (к result1) еще отнимаем 0х07 и сравниваем теперь с 0х17. Если он больше, то то что мы насчитали обнуляем; если нет, то просто запониманием, то что мы насчитали в result1. Далее мы сдвигаем итоговый результат на 4 и записываем на сдвинутые позиции result1. Повторяем все эти действия 8 раз.

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

  1. Вызываем функцию модицифрования логина.
  2. Прибавляем к значению модифицированного логина значение 0xDDCCBBAA
  3. Исходя из полученного значения мы начинаем генерировать первую часть нашего серейника.
  4. Берем очередной символ результата.
  5. Если он больше 0x7, то прибавляем к нему 7h, иначе прибавляем 0.
  6. Если то, что мы получили на преидущем шаге больше 0x0А, то прибавляем к нему 0х30.
  7. После выполнения шага 6, у нас есть оригинальный символ серийника.
  8. Берем следующий символ результата.
  9. Повторяем шаги 5 — 8 еще 7 раз.
  10. Берем результат полученный на шаге 2 и умножаем его на 2.
  11. Над полученным результатом выполняем шаги 4-9.
  12. Получаем 2ю часть правильного серийника.
  13. Ставим между ними любой символ.
  14. Получаем сгенерированный серийный номер.

Теперь рассмотрим как это все работает на примере моего ника.
Функция модификации логина для моего ника выдала 0x37B.
Прибавляем 0xDDCCBBAA + 0x37B = DDCCBF25.
Берем первый символ D, он у нас больше 7h, поэтому прибавляем 7 получаем 14h. Т.к. 14H больше 0х0А, то прибавляем еще 14h, получаем 44h = D
Принцип думаю понятен поэтому я буду писать только расчеты:
Dh + 7h = 14h + 30h = 44h = D
Ch + 7h = 13h + 30h = 43h = C
Ch + 7h = 13h + 30h = 43h = C
Bh + 7h = 12h + 30h = 42h = B
Bh + 7h = 12h + 30h = 42h = B
Fh + 7h = 16h + 30h = 46h = F
2h + 0h = 2h + 30h = 32h = 2
5h + 7h = Ch + 30h = 3Ch = <
Таким образом мы получили первую часть серийного номера = DDCCBBF2<.
Для получения второй части нам надо DDCCBF25 * 2 = BB997E4A. И проделать тоже самое, что мы делали с DDCCBF25. Привожу только расчет:
Bh + 7h = 12h + 30h = 42h = B
Bh + 7h = 12h + 30h = 42h = B
9h + 7h = 10h + 30h = 40h = @
9h + 7h = 10h + 30h = 40h = @
7h + 7h = Eh + 30h = 3Eh = >
Eh + 7h = 15h + 30h = 45h = E
4h + 0h = 4h + 30h = 34h =4
Ah + 7h = 11h + 30h = 41h = A
Вторая часть получилась равной BB@@>E4A.
Теперь ставим между двумя частями любой символ, я выбрал 0, все равно он там потом будет ставится и получаем сгенерированый серийник для моего ника: DDCCBF2<0BB@@>E4A.

Теперь уже вообще всё. Всё, что хотел, то расскзаал.

Надеюсь это кому-то надо ;).

P.S.: Собственно навоял я генератор. Вот:

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

#define SERIAL_LEN 0x11

char *login; // = "coldfire";
char serialNumber[SERIAL_LEN + 1];

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int modifyLogin() {
	int result = 0;
	int loginLen = strlen(login);
	char currChar;

	while (loginLen != 0) {
		currChar = *(login + loginLen - 1);
		result += currChar;
		result += loginLen;

		loginLen--;
	}

	return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void generateSerial(char *serial, int number) {
	int currNum = 0;
	int i = 7;

	for (; i != -1; i--) {
		currNum = (number & 0x0f);

		if (currNum >= 0x07) {
			currNum += 0x07;
		}

		currNum += 0x30;

		serial[i] = currNum;

		number >>= 4;
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void initSerialNumber() {
	int i = 0;
	for (; i < SERIAL_LEN; i++) {
		serialNumber[i] = 'a';
	}

	serialNumber[SERIAL_LEN] = '\0';
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int main(int argc, char *argv[]) {
	if ((argc != 2)) {
		printf("USAGE: keygenme.exe LOGIN\n");
		printf("EXAMPLE: keygenme.exe coldfire\n");
		return 0;
	}	

	login = argv[1];

	if ((strlen(login) <= 5) && (strlen(login) % 2 != 0)) {
		printf("ERROR: length of login must be greater than 5 and be even\n");
		return 0;
	}
	
	initSerialNumber();

	int modifiedLogin = modifyLogin();
	
	modifiedLogin += 0xDDCCBBAA;

	generateSerial(serialNumber, modifiedLogin);	

	modifiedLogin *= 2;

	generateSerial(&serialNumber[9], modifiedLogin);

	printf("login:\t%s\nserial:\t%s\n", login, serialNumber);
	
	return 0;
}

Прошу прощения, но решением является keygen. Собственно, где он?

Сегодня вечером закину исходник.

Собственно, выше добавил исходник + бинарь для генератора…

Решение немножко проще. Без восстановления символов. Но всё равно спасибо большое за разбор, я бы ещё сутки сам с этой программкой сидел.

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

dword hash(char* s){
	dword ret = 0;
	int i = strlen(s) - 1;
	while (i >= 0){
		ret += ((byte)s[i] + (i + 1));i--;
	}
	return ret + 0xDDCCBBAA;
}
int main(int argc, char* argv[]){
	if (argc != 2){
		printf("Usage %s:\n %s login", argv[0], argv[0]);
		return 1;
	}
	if (strlen(argv[1]) % 2){
		printf("Length of login must be odd (%%2 == 0)\n");
		return 1;
	}
	printf("%8.X0%8.X\n", hash(argv[1]), 2*hash(argv[1]));
	return 0;
}