0x00 Packet List
- Challenge
- Login
- Logout
- KeepAlive_1
- KeepAlive_2
0x01 Login & Logout Route
0x011 Login Route
First, a chanllange packet is made and sent to server, and the response should start with 0x02 and contain 4 bytes salt at the offset 0x14.
Second, a login packet can be made with the salt we’ve got, after we send it to the server, it should respond with data which starts with 0x04 provided that the username and password are correct, if not, the response data should start with 0x?? 0x05. Then at the offset 0x17 we can find the 16 bytes tail, which can be used for next KeepAlive procedure or logout route.
Third, with the help of salt and tail, a KeepAlive_1 packet can be built and sent. If the server response starts with 0x07, we can go to next phase, which is KeepAlive_2.
Fourth, a KeepAlive_2 packet is made by tail2 and svr_num then sent to server, these data are in return updated within the procedure.
Finally, we step into the final procedure, KeepAlive_Loop. In this stage, we continually make and send KeepAlive_1 and KeepAlive_2 packet, but different to KeepAlive_2 phase, the tail2 is now a constant and only the svr_num is getting updated in the progress.
0x012 Logout Route
First, make challenge to obtain the salt just like what we do in Login Route.
Second, use the salt acquired in first phase and the tail got in Login Route to generate the logout packet, if the return data from server starts with 0x04, the logout procedure ends.
Aforementioned routes can be illustrated via following figure.
0x02 Packet Detail
0x021 Challenge
Challenge packet should be consist of 20 bytes, it follows the pattern below:
1 | 0x01,0x02,0x??,0x??,0x09,0x00,...,0x00 |
the third byte and fourth byte is random byte, it is doesn’t matter what they are, but make sure that every challenge use different bytes.
A code sample to generate Challenge packet
1 2 3 4 5 6 7 8 9 10 11 | public void MakeChallengePacket(out byte[] data) { int i = 0; data = new byte[20]; data[i++] = 0x01; data[i++] = 0x02; // Random data[i++] = (byte)rand.Next(0, 255); data[i++] = (byte)rand.Next(0, 255); data[i++] = 0x09; } |
0x22 Login
Login packet should be consist of 400 bytes, it follows the struct below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | struct _tagLoginPacket { struct _tagDrCOMHeader Header; unsigned char PasswordMd5[MD5_LEN]; char Account[ACCOUNT_MAX_LEN]; unsigned char ControlCheckStatus; unsigned char AdapterNum; unsigned char MacAddrXORPasswordMD5[MAC_LEN]; unsigned char PasswordMd5_2[MD5_LEN]; unsigned char HostIpNum; unsigned int HostIPList[HOST_MAX_IP_NUM]; unsigned char HalfMD5[8]; unsigned char DogFlag; unsigned int unkown2; struct _tagHostInfo HostInfo; unsigned char ClientVerInfoAndInternetMode; unsigned char DogVersion; }; |
In Header part, it follows the pattern below:
1 | 0x03,0x01,0x00,0x?? |
the fourth byte should be the username’s length plus 20.
in PasswordMd5 part, we have to undergo a MD5 encryption progress, the MD5 source’s size is equal to password‘s length plus 6, and it follows the pattern below:
1 | 0x03,0x01,0x??,0x??,0x??,0x??,0x??,0x??,...,0x?? |
the first 4 0x?? byte is the salt we obtained in above challenge process, and the following 0x?? byte is the password.
the PasswordMd5 is 16 bytes long and it is the hash of MD5 source, we can call it MD5A, it will be used in following KeepAlive packet.
In Account part, we should fill the bytes with username. Attention should be paid to it that we should truncate the bytes if the username is too long and make sure it won’t be longer than 36.
In ControlCheckStatus part, we should fill two bytes with _CONTROLCHECKSTATUS and _ADAPTERNUM. To different college’s client, these two parameters may vary, when it comes to HHU, they should be 0x20 and 0x01.
In MacAddrXORPasswordMD5 part, we xor on the PasswordMD5 with user’s mac address, it should be 6 bytes long.
In PasswordMd5_2 part, we create a new MD5 source whose length is password’s length plus 9, we fill the first byte with ox01, and copy the password and salt to the rest part of it. Finally, we copy the 16 bytes long hash of the new MD5 source to the login packet.
In HostIpNum and HostIPList part, we fill the first byte with 0x01, which is the ip_number, and the rest part should be hexadecimal form of host_ip, which is 4 bytes long. If your college has multiple campuses, the host_ip may differ. In HHU, this part should be:
1 | 0x01,0x10,0x61,0xAE,0xA5 |
In HalfMD5 part, we create another MD5 source whose length is the current packet length(not 400) plus 4, and we fill the the current packet into the source then fill the last 4 bytes with:
1 | 0x14,0x00,0x07,0x0b |
afterwards, we insert the hash of this source, which should be 8 bytes long, into the packet.
In DogFlag and unkown2 part, it consists of 4 bytes data, we can leave the rest 3 bytes 0x00, and the first byte, it depends on client version and protocol, for HHU, it should be 0x01.
In HostInfo part, the _tagHostInfo can be desribed as the following struct:
1 2 3 4 5 6 7 8 9 10 | struct _tagHostInfo { char HostName[HOST_NAME_MAX_LEN]; unsigned int DNSIP1; unsigned int DHCPServerIP; unsigned int DNSIP2; unsigned int WINSIP1; unsigned int WINSIP2; struct _tagDrCOM_OSVERSIONINFO OSVersion; }; |
the HostName in HHU is “fuyumi”. The DNSIP1,DHCPServerIP,DNSIP2 depend on your campus, for HHU, they are { 202, 119, 112, 32 }, { 202, 119, 113, 125 } and { 202, 119, 112, 32 }.
WINSIP1 and WINSIP2 can be left blank.
the OSVersion’s struct is depicted as follows:
1 2 3 4 5 6 7 8 9 | struct _tagOSVERSIONINFO { unsigned int OSVersionInfoSize; unsigned int MajorVersion; unsigned int MinorVersion; unsigned int BuildNumber; unsigned int PlatformID; char ServicePack[128]; }; |
the OSVersionInfoSize is 0x94,0x00,0x00,0x00.
the MajorVersion is 0x05,0x00,0x00,0x00.
the MinorVersion is 0x01,0x00,0x00,0x00.
the BuildNumber is 0x28,0x0A,0x00,0x00.
the PlatformID is 0x02,0x00,0x00,0x00.
the ServicePack is the hash of “Windows 10” or other system’s tag.
Now we have come to the last part of Login packet, the CheckSum, I will show you the code since it is hard to describe how the algorithm works in text.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | // data is the packet, i is the current length of packet // checksum point int check_point = i; data[i++] = 0x01; data[i++] = 0x26; data[i++] = 0x07; data[i++] = 0x11; // 0x00 0x00 mac i += 2; Buffer.BlockCopy(MAC, 0, data, i, 6); i += 6; // CheckSum ulong sum = 1234; ulong check = 0; for (int k = 0; k < i; k += 4) { check = 0; for (int j = 0; j < 4; j++) { check = (check << 2) + data[k + j]; } sum ^= check; } sum = (1968 * sum) & 0xFFFFFFFF; for (int j = 0; j < 4; j++) { data[check_point + j] = (byte)(sum >> (j * 8) & 0x000000FF); } // auto logout / default: False // broadcast mode / default : False i += 2; // unknown, filled numbers randomly data[i++] = 0xe9; data[i++] = 0x13; |
That’s the end of the login packet, A sample code is listed as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 | public byte[] MakeLoginPacket(byte[] salt, out byte[] data, out int len) { int pwlen = Password.Length; byte[] md5_src; data = new byte[400]; int i = 0; /* struct _tagLoginPacket { struct _tagDrCOMHeader Header; unsigned char PasswordMd5[MD5_LEN]; char Account[ACCOUNT_MAX_LEN]; unsigned char ControlCheckStatus; unsigned char AdapterNum; unsigned char MacAddrXORPasswordMD5[MAC_LEN]; unsigned char PasswordMd5_2[MD5_LEN]; unsigned char HostIpNum; unsigned int HostIPList[HOST_MAX_IP_NUM]; unsigned char HalfMD5[8]; unsigned char DogFlag; unsigned int unkown2; struct _tagHostInfo HostInfo; unsigned char ClientVerInfoAndInternetMode; unsigned char DogVersion; }; */ // Begin data[i++] = 0x03; data[i++] = 0x01; data[i++] = 0x00; data[i++] = (byte)(Username.Length + 20); // MD5 A md5_src = new byte[6 + pwlen]; md5_src[0] = 0x03; md5_src[1] = 0x01; Buffer.BlockCopy(salt, 0, md5_src, 2, 4); Buffer.BlockCopy(Password, 0, md5_src, 6, pwlen); MD5A = _md5.ComputeHash(md5_src); Buffer.BlockCopy(MD5A, 0, data, 4, 16); i += 16; // Username Buffer.BlockCopy(Username, 0, data, i, Username.Length); i += Username.Length > 36 ? Username.Length : 36;// I dont know whether there is need to truncate here // Wait for further test // CONTROLCHECKSTATUS ADAPTERNUM data[i++] = _CONTROLCHECKSTATUS; data[i++] = _ADAPTERNUM; /* dump(int(binascii.hexlify(data[4:10]), 16) ^ mac) */ Buffer.BlockCopy(MD5A, 0, data, i, 6); for (int j = 0; j < 6; j++) data[i + j] ^= MAC[j]; i += 6; // MD5 B md5_src = new byte[9 + pwlen]; md5_src[0] = 0x01; Buffer.BlockCopy(Password, 0, md5_src, 1, pwlen); Buffer.BlockCopy(salt, 0, md5_src, 1 + pwlen, 4); Buffer.BlockCopy(_md5.ComputeHash(md5_src), 0, data, i, 16); i += 16; // Ip number, 1, 2, 3, 4 data[i++] = 0x01; Buffer.BlockCopy(Host_ip, 0, data, i, 4); i += 16; // MD5 C md5_src = new byte[i + 4]; Buffer.BlockCopy(data, 0, md5_src, 0, i); md5_src[i + 0] = 0x14; md5_src[i + 1] = 0x00; md5_src[i + 2] = 0x07; md5_src[i + 3] = 0x0b; Buffer.BlockCopy(_md5.ComputeHash(md5_src), 0, data, i, 8); i += 8; // IPDOG(0x01) 0x00*4 data[i++] = _IPDOG; i += 4; /* struct _tagOSVERSIONINFO { unsigned int OSVersionInfoSize; unsigned int MajorVersion; unsigned int MinorVersion; unsigned int BuildNumber; unsigned int PlatformID; char ServicePack[128]; }; struct _tagHostInfo { char HostName[HOST_NAME_MAX_LEN]; unsigned int DNSIP1; unsigned int DHCPServerIP; unsigned int DNSIP2; unsigned int WINSIP1; unsigned int WINSIP2; struct _tagDrCOM_OSVERSIONINFO OSVersion; }; */ // Host Name md5_src = Encoding.Default.GetBytes(HostName); Buffer.BlockCopy(md5_src, 0, data, i, md5_src.Length > 32 ? 32 : md5_src.Length); i += 32; // Primary dns /* data[i++] = 0x0a; data[i++] = 0x0a; data[i++] = 0x0a; data[i++] = 0x0a; */ Buffer.BlockCopy(_default_dns, 0, data, i, 4); i += 4; // DHCP Server Buffer.BlockCopy(_default_dhcp, 0, data, i, 4); i += 4; // Secondary dns /* data[i++] = 0xca; data[i++] = 0x62; data[i++] = 0x12; data[i++] = 0x03; */ Buffer.BlockCopy(_secondary_dns, 0, data, i, 4); i += 4; // WINSIP1 && WINSIP2 i += 8; // OSVersionInfoSize data[i++] = 0x94; i += 3; // MajorVersion data[i++] = 0x05; i += 3; // MinorVersion data[i++] = 0x01; i += 3; // BuildNumber data[i++] = 0x28; data[i++] = 0x0a; i += 2; // PlatformID data[i++] = 0x02; i += 3; // ServicePack md5_src = Encoding.Default.GetBytes(HostOS); Buffer.BlockCopy(md5_src, 0, data, i, md5_src.Length > 32 ? 32 : md5_src.Length); i += 128; // END OF _tagHostInfo // AuthVersion Buffer.BlockCopy(_AUTH_VERSION, 0, data, i, 2); i += 2; // TODO ROR_VERSION NOT IMPLEMENTED // _tagDrcomAuthExtData.Code data[i++] = 0x02; // _tagDrcomAuthExtData.Len data[i++] = 0x0c; // checksum point int check_point = i; data[i++] = 0x01; data[i++] = 0x26; data[i++] = 0x07; data[i++] = 0x11; // 0x00 0x00 mac i += 2; Buffer.BlockCopy(MAC, 0, data, i, 6); i += 6; // CheckSum ulong sum = 1234; ulong check = 0; for (int k = 0; k < i; k += 4) { check = 0; for (int j = 0; j < 4; j++) { check = (check << 2) + data[k + j]; } sum ^= check; } sum = (1968 * sum) & 0xFFFFFFFF; for (int j = 0; j < 4; j++) { data[check_point + j] = (byte)(sum >> (j * 8) & 0x000000FF); } // auto logout / default: False // broadcast mode / default : False i += 2; // unknown, filled numbers randomly data[i++] = 0xe9; data[i++] = 0x13; len = i; return data; } |
0x23 Logout
The logout packet is much simpler than the login packet, if you are using a unlimited traffic account, you even don’t need to do logout. But if not, you have to pay attention to do a logout so as to avoid additional traffic fee.
The logout packet should be 80 bytes long, and it start with 0x06,0x01,0x00,0x??. The fourth byte is the byte form of username‘s length plus 20.
First, we create a MD5 source whose length is password’s length plus 6, it follows the pattern below:
1 2 | 0x06,0x01,0x??,0x??,0x??,0x??,0x??,...,0x?? |-------salt-------|---password--| |
Then, we calculate the hash of the source, and paste the 16 bytes hash to the packet.
Afterwards, we fill the next 36 bytes with the username.
The next 2 bytes should be _CONTROLCHECKSTATUS and _ADAPTERNUM mentioned before in Login packet.
Furthermore, now we should use the MD5 source we created in first step, and copy the first 6 bytes into the packet and make a XOR operation over the bytes.
Finally, we copy the 16 bytes tail to the end of the packet. A logout packet is done.
The sample code is listed below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | public void MakeLogoutPacket(out byte[] data,out int len,byte[] salt,byte[] tail) { int i = 0; byte[] md5_src = new byte[6 + Password.Length]; data = new byte[80]; data[i++] = 0x06; data[i++] = 0x01; i++; data[i++] = (byte)(Username.Length + 20); md5_src[0] = 0x06; md5_src[1] = 0x01; Buffer.BlockCopy(salt, 0, md5_src, 2, 4); Buffer.BlockCopy(Password, 0, md5_src, 6, Password.Length); MD5A = _md5.ComputeHash(md5_src); Buffer.BlockCopy(MD5A, 0, data, i, 16); i += 16; Buffer.BlockCopy(Username, 0, data, i, Username.Length > 36 ? 36 : Username.Length); i += 36; data[i++] = _CONTROLCHECKSTATUS; data[i++] = _ADAPTERNUM; Buffer.BlockCopy(MD5A, 0, data, i, 6); for (int j = 0; j < 6; j++) data[i + j] ^= MAC[j]; i += 6; Buffer.BlockCopy(tail, 0, data, i, 16); i += 16; len = i; } |
0x24 KeepAlive_1
The KeepAlive_1 packet is a 42 bytes long byte array, which starts with 0xff.
Remember the hash MD5A we made in Login packet? Now we have to copy the first 16 bytes of it to the KeepAlive_1 packet.
After doing this, we add another 3 0x00 to the packet and copy the 16 bytes tail and 2 bytes random number to the end of packet.
Then the KeepAlive_1 packet is finished.
The sample code is listed as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public void MakeKeepAlive1Packet(out byte[] data,out int len, byte[] salt, byte[] tail) { int i = 0; len = 42; data = new byte[len]; data[i++] = 0xff; // MD52 Buffer.BlockCopy(MD5A, 0, data, i, 16); i += 16; // 0x00,0x00,0x00 i += 3; // Tail Buffer.BlockCopy(tail, 0, data, i, 16); i += 16; data[i++] = (byte)rand.Next(0, 255); data[i++] = (byte)rand.Next(0, 255); } |
0x25 KeepAlive_2
There are 3 types of KeepAlive_2 packet, the first 2 types is used in KeepAlive_1 and KeepAlive_2 Routing and the last type is used in KeepAlive_Loop Routing, they are almost the same, but a small difference exists.
The KeepAlive_2 packet is a 40 bytes long packet, which starts with {0x07,0x??,0x28,0x00,0x0b,0x??}.
The first 0x?? is the svr_num, the second one is the type_num used to distinguish KeepAlive_2 packet in different route, the first 2 types are 1, the last type is 3.
For the first 2 types, there are difference in the next 2 bytes. One type is {0x0F,0x27}, the other one is the _KEEPALIVE_VERSION, for HHU, it is { 0xdc, 0x02 }.
The next 8 bytes should be:
1 | 0x2F,0x12,0x00,..,0x00 |
Then we insert the first 4 bytes of tail to the packet.
If the type_num is 1, we are finished here, but if it is 3, we have to copy the host_ip to the postition which is 12 bytes after the tail inserted position.
The example code is listed as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | public void MakeKeepAlive2Packet(out byte[] data,out int len,int count,byte[] tail,int type=1,bool isFirst=false) { int i = 0; len = 40; data = new byte[len]; data[i++] = 0x07; data[i++] = (byte)count; data[i++] = 0x28; i++; data[i++] = 0x0b; data[i++] = (byte)type; if (isFirst) { data[i++] = 0x0f; data[i++] = 0x27; } else { data[i++] = _KEEPALIVE_VERSION[0]; data[i++] = _KEEPALIVE_VERSION[1]; } data[i++] = 0x2f; data[i++] = 0x12; i += 6; Buffer.BlockCopy(tail, 0, data, i, 4); i += 8; if (type == 3) { i += 4; // Fill CRC with zeros Buffer.BlockCopy(Host_ip, 0, data, i, 4); } } |