L2PacketHackФорум программы L2PacketHack, всё что с ней связано находится тут.
Правила раздела!
Категорически запрещено:
* Просить пакеты/скрипты на шмот/адену/другие вещи;
* Просить (помочь) взломать/сломать сервак/аккаунт или раздеть/наказать кого-то на любом серваке;
* Просить научить работать с программой, если не понимаете элементарных вещей в отношениях клиент-сервер (так как, если вы их понимаете, то вопросов по основным возможностям программы быть недолжно);
* Все посты не удовлетворяющие этим запретам, а так же не относящиеся к теме будут удаляться при первой возможности.
* Запрещается создавать темы с просьбой выполнить какую-то работу за автора темы.
* Форум является средством общения и общего поиска решения. Вашу работу за Вас никто выполнять не будет.
* Данные запреты неподлежат обсуждению.
В методе LS_LoginOk есть запись двух ключей SessionKey1_1 и SessionKey1_2 в секцию пакета для RequestServerList().
SessionKey приходит от сервера и его же нужно отправлять серверу в следующем пакете без каких-либо изменений.
Главное чтобы BF-шифрование верно отрабатывало.
к верно посоветанному
Цитата:
Сообщение от supernewbie
нужно также заменить PChar на PAnsiChar, Char на AnsiChar
могу добавить: заменить "string" на "AnsiString" и "Chr" на "AnsiChar" (об этом многие забывают, что может приводить к ошибкам). Можно тупо прогнать полную замену без анализа (но с опцией "только целые слова"), должно заработать.
PS. По статьям: зайди к нам на сайт, там на главной странице ссылки есть.
PS. По статьям: зайди к нам на сайт, там на главной странице ссылки есть.
Хочется поблагодарить следующих людей: TechnoWiz@rd, за базовую информацию для понимания протокола Lineage, а также KyberPrizrak за доработку исходной статьи.
Т.е. лучше изучать по статье KyberPrizrak? Его статья более расширенная? На глаз пока сложно определить отличия
UPD1
Еще один момент смущает: статья KyberPrizrak исправляющая баги (со слов KyberPrizrak) статьи TechnoWiz@rd имеет последнее редактирование 06.07.2007, в то время как статья TechnoWiz@rd была отредактирована 23.11.2007
Какой же версии описания протокола стоит доверять больше?
Последний раз редактировалось Linux, 01.06.2016 в 15:54.
Прогнал через WireShark подключение L2.exe и la2_client_emu,
Используемый фильтр: tcp && tcp.port==2106 || tcp.port==7777
Собранная статистика:
1) L2.exe (L2_LS_GS_orig.png)
Для наглядности я подсветил строки с пакетами у которых Len>0, а содержимое пакетов с данными (L2Orig_data.txt) представил в спойлере с указанием номера пакета: Оффтоп
"21", Len=186
0000 ba 00 15 40 5b eb 3f 57 06 fa 97 ff fe e1 5b b8
0010 ee 04 e5 db 05 cc aa 80 bb 07 a7 0b f4 43 d1 ff
0020 b4 b0 b3 14 e9 33 33 47 0a c5 59 d5 fc be a2 9d
0030 0d 5a 6a 04 18 03 6c b0 dd 7f 71 55 73 a4 d3 ad
0040 c3 0c 96 7e 10 4e 1a dd 28 d1 d0 a4 0c cb 67 10
0050 be 4a 3b 4a f1 7f f2 a5 f9 03 e8 a0 93 8b be f9
0060 e5 44 e4 80 ee 10 64 48 b2 82 c6 db 5d be 03 c8
0070 59 67 ce 8f bc 20 51 59 51 28 31 8c 0f fe ce 87
0080 c3 5a 01 b0 5a f3 80 40 5a 23 7b 17 c0 f7 da dd
0090 2f 6d 58 d0 a5 6b 3b 36 2e 03 73 f0 60 be 92 07
00a0 be 09 9d ba 57 99 44 51 b3 10 8d c4 10 2a 56 a7
00b0 fa 6d 3e fc 7e fa 1f dc 32 63
2) L2Connect.exe (L2_LS_GS_L2Connect.png)
Для наглядности я подсветил строки с пакетами у которых Len>0, а содержимое пакетов с данными (L2Connect_data.txt) представил в спойлере с указанием номера пакета:
"23", Len=178
0000 b2 00 4f a6 c1 1d 2b 05 4b 15 73 b7 0e 49 4c 81
0010 03 03 45 4e 52 31 76 2a 2a b1 14 66 6c d7 06 c5
0020 2d ee 33 e7 f5 06 d8 6c 05 fe 33 66 e8 6c 86 2f
0030 12 b9 2f 49 05 f5 3e c8 94 69 74 8c ff 5e 5b f1
0040 5e bc 16 a4 06 21 52 cd 21 26 4b 10 3b 14 88 6c
0050 6c c9 61 4a fe 65 c0 89 7a 87 78 bd b1 0c ad d2
0060 a2 89 56 64 ca 38 52 5d 31 0f 88 1b c8 56 14 38
0070 e2 6c 51 69 7e 46 e5 c4 cc 7f 25 95 93 43 ea 42
0080 43 13 61 3d 12 5f a7 a9 78 7f 9b af 52 91 45 9d
0090 1f dd 0e 43 8b 93 5b 58 90 98 9b af 52 91 45 9d
00a0 1f dd 8f a6 51 0f ed 6b 10 35 9b af 52 91 45 9d
00b0 1f dd
"25", Len=66
0000 42 00 bf d3 e9 0d c8 b8 4e fd 15 e5 81 8a e9 dc
0010 29 2e cb 18 de 9d 19 3b 45 3e 9b af 52 91 45 9d
0020 1f dd 9b af 52 91 45 9d 1f dd 9b af 52 91 45 9d
0030 1f dd 56 8c d5 71 e7 f3 9b 68 10 73 a9 4f e4 bf
0040 81 6a
"27", Len=34
0000 22 00 f6 f3 30 42 83 5d 6c 3c ba ef 64 dc b9 48
0010 51 69 33 90 ca ad ae c4 fb 08 9b af 52 91 45 9d
0020 1f dd
По собранной статистике можно понять поведение двух разных подключений? Где в моем случае была допущена ошибка, из-за чего до Game-Server так и не доходила программа?
Последний раз редактировалось Linux, 03.06.2016 в 14:03.
Т.е. лучше изучать по статье KyberPrizrak? Его статья более расширенная? На глаз пока сложно определить отличия
это очень давно было, но на сколько помню у KyberPrizrak более верный протокол описан (как и по последовательности пакетов, так и по их содержимому)
Цитата:
Какой же версии описания протокола стоит доверять больше?
доверять можно только самостоятельно проснифанному трафику, т.к. часть очень важных пакетов (RequestAuthLogin, NetPing, s:8D) могут меняться от сервера к серверу
Цитата:
Сообщение от Linux
По собранной статистике можно понять поведение двух разных подключений? Где в моем случае была допущена ошибка, из-за чего до Game-Server так и не доходила программа?
у тебя пакет RequestAuthLogin значительно меньше, чем в оригинальном клиенте, расшифровывай пакет, смотри что внутри, возможно поймешь, что нужно передавать
у тебя пакет RequestAuthLogin значительно меньше, чем в оригинальном клиенте, расшифровывай пакет, смотри что внутри, возможно поймешь, что нужно передавать
1.1) А не подскажете чем расшифровывать или по какому алгоритму?
В проекте идет метод BlowFish.pas (by xkor) с комплектом методов:
procedure AddCheckSumm(var data: array of byte; Len: Integer);
function RepairKey(key: String): String;
function RSAEncrypt(a,p,rsakey: String): String;
function AntiXOR(s: String): String;
Что из этого подойдет?
1.2) А зачем пакет расшифровывать если пакет идет на сервер?
2) Добавил сравнительную таблицу пакетов (compare_LS.png), учитывая что первые 2 байта это размер, с 3-го кодируются данные, как по размеру пакета понять какой это пакет: Init, RequestAuthLogin и т.д.??
1.1) А не подскажете чем расшифровывать или по какому алгоритму?
По умолчанию трафик в Ла2 шифруется с помощью BF (также для RequestAuthLogin используется RSA и для части пакетов еще какой-то xor). Ответить на вопрос "чем и как" в данном случае, к сожалению, не смогу, т.к. ответ потянет минимум на полновесную статью, а то и на главу какой-нить книги, к тому же не являюсь большим специалистом в этой области.
Цитата:
Что из этого подойдет?
Все из перечисленных функций как раз занимаются расшифровкой пакетов.
Цитата:
1.2) А зачем пакет расшифровывать если пакет идет на сервер?
Это как проход через часового: ты должен знать пароль для прохода и лучший вариант его узнать - подслушать пароль.
Цитата:
как по размеру пакета понять какой это пакет
никак, в данном случае можно догадаться по очередности пакетов, на сколько помню очередность такая:
1) сервер присылает Init
2) клиент отвечает AuthGameGuard
3) сервер GGAuth
4) клиент RequestAuthLogin
5) сервер LoginOk либо LoginFail
6) клиент RequestServerList
7) сервер ServerList
8) клиент RequestServerLogin
9) сервер PlayOk либо PlayFail
плюс в эти пакеты могут вмешиваться NetPing и 8D
PS. В очередности могу немного ошибиться, за подробностями обращайся к статьям KyberPrizrak.
const
MAX_PKT_SIZE = $FFFF;
var
BLOWFISH_KEY: String; { 6B60CB5B82CE90B1CC2B6C556C6C6C6C }
MainBuff: array[0..MAX_PKT_SIZE-1] of byte;
RecvPktCount: Integer;
DecodeBF: boolean;
L2BF: L2BlowFish;
L2BF_Key: String;
RSA_Key: String;
SessionID: Integer;
GGAuthResponse: Integer;
SessionKey1_1, SessionKey1_2: Integer;
SessionKey2_1, SessionKey2_2: Integer;
procedure TfrmMain.WMSOCK_RS_EVENT(var Msg: TMessage);
var
SocketError: Integer;
Len: Integer;
begin
SocketError := WSAGetSelectError(Msg.lParam);
if (SocketError<>0) then begin
if SocketError=10053 then
ToLog('[WMSOCK_RS_EVENT]: WSAECONNABORTED (#10053) - Software caused connection abort')
else
ToLog('[WMSOCK_RS_EVENT]: WS_ERROR (#number): '+IntToStr(SocketError));
ToLog('');
L2SocketClose(RS_Socket);
Exit;
end;
case WSAGetSelectEvent(Msg.lParam) of
FD_Read:
begin
FillChar(MainBuff, Length(MainBuff), 0);
Len := recv(RS_Socket, MainBuff, MAX_PKT_SIZE, 0);
inc(RecvPktCount);
LS_Parser(MainBuff, Len);
end;
FD_Close:
L2SocketClose(RS_Socket);
end;
end;
procedure TfrmMain.LS_Parser(buff: Array of Byte; Len: Integer);
var
temp: array [0..MAX_PKT_SIZE-1] of char;
s: string;
i: integer;
begin
if RecvPktCount=1 then
begin
if buff[2]=0 then
DecodeBF := False
else
DecodeBF := True;
end;
if DecodeBF then
begin
Move(buff[2],temp[0],Len-2);
L2BF.bfDecode(temp,Len-2);
if temp[0]=#0 then
begin
SetLength(s,Len-2);
for i:=0 to Len-3 do
s[i+1]:=temp[i];
s:=AntiXor(s);
for i:=0 to Len-3 do
temp[i]:=s[i+1];
end;
Move(temp[0],buff[2],Len-2);
end;
PacketView(buff,Len,True);
case buff[2] of
$00:
LS_Init(buff,Len);
$0B:
LS_GGAuth(buff,Len);
$01:
LS_LoginFail(buff,Len);
$03:
LS_LoginOk(buff,Len);
$04:
LS_ServerList(buff,Len);
$06:
LS_PlayFail(buff,Len);
$07:
LS_PlayOk(buff,Len);
end;
end;
Порядок авторизации на Login-Server:
1) Init().................................................. .[OK] { Server->Client } Оффтоп
Код:
procedure TfrmMain.LS_Init(buff: array of byte; Len: integer);
begin
if Len<8 then exit;
Move(buff[3], SessionID, 4);
if Len>=155 then
begin
SetLength(RSA_key,128);
Move(buff[11],RSA_key[1],128); { RSA Key changed }
end;
if Len>=171 then
begin
SetLength(L2BF_key,16);
Move(buff[155],L2BF_key[1],16); { BlowFish Key changed }
L2BF.Init(L2BF_key);
end;
end;
{ sending RequestGGAuth packet }
RequestGGAuth();
procedure TfrmMain.RequestGGAuth;
var
Data: array[0..39] of byte;
begin
FillChar(data, Length(data), 0);
data[0]:=7;
Move(SessionID, data[1], 4);
AddCheckSumm(data, 24);
PacketSend(data, 40);
end;
3) LS_GGAuth().........................................[OK] { Server->Client } сервер ответил, что авторизация на GG прошла успешно Оффтоп
Код:
procedure TfrmMain.LS_GGAuth(buff: array of byte; Len: integer);
begin
if Len>=7 then
Move(buff[3], GGAuthResponse, 4);
RequestAuthLogin(Login, Password);
end;
procedure TfrmMain.RequestAuthLogin(_login, _password: string);
var
data: array[0..175] of byte;
crypt_text: string;
begin
FillChar(data,Length(data),0);
crypt_text := RSAEncrypt(_login, _password, RepairKey(RSA_key));
Move(crypt_text[1], data[1], 128);
Move(GGAuthResponse, data[129], 4);
data[149]:=8;
AddCheckSumm(Data, 160);
PacketSend(Data, 176);
end;
5) LoginOK().............................................[OK] { Server->Client } проверка Логина и Пароля прошла успешно, +Session_Key_1 Оффтоп
Код:
procedure TfrmMain.LS_LoginOk(buff: array of byte; Len: integer);
begin
if Len<12 then exit;
ToLog('[LS_LoginOk]: Login Successful!');
ToLog('');
Move(buff[3],SessionKey1_1,4);
Move(buff[7],SessionKey1_2,4);
RequestServerList();
end;
Далее описывать код пока не буду - до тех пунктов даже не доходит программа
7) ServerList()..........................................[NoNe] { Server->Client } передается список рабочих серверов
8) RequestServerLogin()............................[NoNe] { Client->Server } запрос игры на выбранном сервере
9) PlayOK()..............................................[NoNe] { Server->Client } подключение к Game-Server разрешено, +Session_Key_2
Лог выполнения программы - L2Connect_Log.txt
Вопросы:
1) Ну раз программа прошла GGAuth и LoginOK значит с шифрованием все нормально: иначе бы в методе LS_Parser программа не смогла бы определить тип пакета ( [LS_Parser]: buff[2]=0x0B ) и не продвинулась бы дальше по цепочке. Что не так в пакете RequestServerList? в упор не пойму, где я ошибся
Последний раз редактировалось Linux, 07.06.2016 в 17:42.
Linux, шифрование начинается с пакета RequestServerList
А конкретнее: какой алгоритм, какие параметры?
В этих двух статьях я не нашел ссылки, что пакет RequestServerList как-то по особому должен шифроваться
Какие из приведенных ниже методов из этих статей надо применить к пакету?
Вспомогательные методы - Шифрация пакетов GameServer (KyberPrizrak) Оффтоп
Код:
char key_sc[8];
char key_cs[8];
void Encrypt(char *raw, int size) {
int temp = 0;
for (int i=0; i<size; i++) {
int temp2 = raw[i] &0xff;
raw[i] = (char)(temp2 ^ (key_cs[i&7] &0xff) ^ temp);
temp = raw[i];
}
int old = key_cs[0] &0xff;
old |= key_cs[1] << 8 &0xff00;
old |= key_cs[2] << 0x10 &0xff0000;
old |= key_cs[3] << 0x18 &0xff000000;
old += size;
key_cs[0] = (char)(old &0xff);
key_cs[1] = (char)(old >> 0x08 &0xff);
key_cs[2] = (char)(old >> 0x10 &0xff);
key_cs[3] = (char)(old >> 0x18 &0xff);
}
void Decode(char *input, int size) {
int i = 0;
int j = 0;
for(int k=0; k < size; k++) {
int i1 = input[k] & 0xFF;
input[k] = (char)(i1 ^ key_sc[j++] & 0xFF ^i);
i = i1;
if(j>7) j=0;
}
long l = key_sc[0] & 0xFF;
l |= key_sc[1] << 8 & 0xFF00;
l |= key_sc[2] << 16 & 0xFF0000;
l |= key_sc[3] << 24 & 0xFF000000;
l += size;
key_sc[0] = (char)(l & 255L);
key_sc[1] = (char)(l >> 8 & 255L);
key_sc[2] = (char)(l >> 16 & 255L);
key_sc[3] = (char)(l >> 24 & 255L);
}
void Key(char* packet, int len) {
key_sc[0] = packet[4];
key_sc[1] = packet[5];
key_sc[2] = packet[6];
key_sc[3] = packet[7];
key_sc[4] = (char)0xA1;
key_sc[5] = (char)0x6C;
key_sc[6] = (char)0x54;
key_sc[7] = (char)0x87;
for(i=0;i<8;i++) {
key_cs[i] = key_sc[i];
}
}
// вычисляет чексумму и вставляет её в пакет
// как пользоватся: add_ckecksum(CelyjPaket+2, DlinaPaketa-(2+4+4));
void add_ckecksum(unsigned char *raw, int count) {
unsigned long chksum = 0;
unsigned long ecx;
int i = 0;
for(i = 0; i < count; i += 4) {
ecx = (unsigned long)raw[i];
ecx |= (unsigned long)raw[i + 1]<<8;
ecx |= (unsigned long)raw[i + 2]<<16;
ecx |= (unsigned long)raw[i + 3]<<24;
chksum ^= ecx;
}
memcpy(raw+count, (char *)&chksum, 4);
}
void RSAKeyDecode(char* RSAKey) {
for (int i = 0; i < 64; i++) {
RSAKey[i + 64] ^= RSAKey[i];
}
for (int i = 0; i < 4; i++) {
RSAKey[i + 13] ^= RSAKey[i + 52];
}
for (int i = 0; i < 64; i++) {
RSAKey[i] ^= RSAKey[i + 64];
}
for (int i = 0; i < 4; i++) {
BYTE tmp = RSAKey[i];
RSAKey[i] = RSAKey[i + 77];
RSAKey[i + 77] = tmp;
}
}
Вспомогательные методы - Шифрация пакетов GameServer (TechnoWiz@rd) Оффтоп
Код:
Каждый пакет состоит из размера пакета (2 байта), типа пакета (1 байт) и блока параметров (переменная длина). В дополнение к этому, в пакетах Login-Server, в конце добавляется контрольная сумма и дополняется нулями так, чтобы размер пакета был кратен 8-ми байтам. Контрольная сумма может быть рассчитана следующей функцией:
unsigned long checksum( unsigned char *packet, int count ) {
long chksum = 0L;
for( int i = 0; i < count; i += 4 )
chksum ^= *((unsigned long *)&raw[i]);
return chksum;
};
Все последующие пакеты будут шифроваться динамическим BlowFish ключом. Пакет LoginRequest дополнительно шифруется по алгоритму RSA. Ключ состоит из следующих частей: B = 1024, E = 65537, N = передается в пакете Init. Вместе эти 3 части составляют целый RSA ключ. Байты N в пакете зашифрованы функцией:
void scrambleMod( char *n ) {
typedef unsigned char byte;
int i;
for( i=0; i<4; i++ ) {
byte temp = n[0x00 + i];
n[0x00 + i] = n[0x4d + i];
n[0x4d + i] = temp;
};
// step 2 xor first 0x40 bytes with last 0x40 bytes
for( i=0; i<0x40; i++ ) {
n[i] = (byte)(n[i] ^ n[0x40 + i]);
};
// step 3 xor bytes 0x0d-0x10 with bytes 0x34-0x38
for( i=0; i<4; i++ ) {
n[0x0d + i] = (byte)(n[0x0d + i] ^ n[0x34 + i]);
};
// step 4 xor last 0x40 bytes with first 0x40 bytes
for( i=0; i<0x40; i++ ) {
n[0x40 + i] = (byte)(n[0x40 + i] ^ n[i]);
};
};
Для расшифровки можно воспользоваться следующей функцией:
void unscrambleMod( char *n )
{
typedef unsigned char byte;
int i;
// step 4 xor last 0x40 bytes with first 0x40 bytes
for( i=0; i<0x40; i++ ) {
n[0x40 + i] = (byte)(n[0x40 + i] ^ n[i]);
};
// step 3 xor bytes 0x0d-0x10 with bytes 0x34-0x38
for( i=0; i<4; i++ ) {
n[0x0d + i] = (byte)(n[0x0d + i] ^ n[0x34 + i]);
};
// step 2 xor first 0x40 bytes with last 0x40 bytes
for( i=0; i<0x40; i++ ) {
n[i] = (byte)(n[i] ^ n[0x40 + i]);
};
for( i=0; i<4; i++ ) {
byte temp = n[0x00 + i];
n[0x00 + i] = n[0x4d + i];
n[0x4d + i] = temp;
};
};
Для шифрования пакетов Game-Server используется алгоритм XOR. Ключ XOR генерируется случайно и передается клиенту в пакете CryptInit. Функции шифрования и дешифрации приведены ниже:
/* Декодирует данные */
void decrypt( unsigned char *data, unsigned int len, unsigned char *Key ){
int temp = 0;
for( unsigned int i = 0; i < len; ++i ) {
int temp2 = data[i] & 0xff;
data[i] = (temp2 ^ (Key[i & 15] & 0xff) ^ temp);
temp = temp2;
};
int old = Key[8] & 0xff;
old |= (Key[9] << 0x08) & 0xff00;
old |= (Key[10] << 0x10) & 0xff0000;
old |= (Key[11] << 0x18) & 0xff000000;
old += len;
Key[8] = old &0xff;
Key[9] = (old >> 0x08) & 0xff;
Key[10] = (old >> 0x10) & 0xff;
Key[11] = (old >> 0x18) & 0xff;
};
/* Кодирует данные */
void encrypt( unsigned char *data, unsigned int len, unsigned char *Key ){
int temp = 0;
for( unsigned int i = 0; i < len; i++) {
int temp2 = data[i] & 0xff;
data[i] = (temp2 ^ (Key[i & 15] & 0xff) ^ temp);
temp = data[i];
};
int old = Key[8] & 0xff;
old |= (Key[9] << 0x08) & 0xff00;
old |= (Key[10] << 0x10) & 0xff0000;
old |= (Key[11] << 0x18) & 0xff000000;
old += len;
Key[8] = old &0xff;
Key[9] = (old >> 0x08) & 0xff;
Key[10] = (old >> 0x10) & 0xff;
Key[11] = (old >> 0x18) & 0xff;
};
С каждым кодированным/декодированным пакетом ключ изменяется на длину пакета, поэтому нужно использовать два отдельных экземпляра ключа – один для шифрования исходящих пакетов, второй для расшифровки входящих.
Все пакеты шифруются начиная с 3-го байта, т.е. размер пакета никогда не шифруется.
Может ктото поделиться порядком подключения на ЛС/ГС по-байтно, что делается с каждым байтом?
Может кто поделиться рабочим примером кода подключения к ЛС/ГС? можно в личку если что
Последний раз редактировалось Linux, 07.06.2016 в 18:19.
Linux, я имел ввиду что с RequestServerList начинается дополнительное секретное шифрование, алгоритм которого накрыт виртуализатором кода. этот алгоритм был добавлен админинстрацией и как правило имеет отличия для каждого инстанса защиты, чтобы узнать алгоритм шифрования необходимо произвести обратный инжиниринг кода защиты.