wire_legacy_crypt.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. package icq_legacy
  2. import (
  3. "encoding/binary"
  4. "math/rand"
  5. )
  6. // ICQ Legacy Protocol Encryption/Decryption
  7. // Ported from iserverd v5crypt.cpp
  8. // v5Table is the encryption lookup table used by V5 protocol
  9. // This table is used for XOR-based encryption
  10. var v5Table = [256]byte{
  11. 0x59, 0x60, 0x37, 0x6B, 0x65, 0x62, 0x46, 0x48,
  12. 0x53, 0x61, 0x4C, 0x59, 0x60, 0x57, 0x5B, 0x3D,
  13. 0x5E, 0x34, 0x6D, 0x36, 0x50, 0x3F, 0x6F, 0x67,
  14. 0x53, 0x61, 0x4C, 0x59, 0x40, 0x47, 0x63, 0x39,
  15. 0x50, 0x5F, 0x5F, 0x3F, 0x6F, 0x47, 0x43, 0x69,
  16. 0x48, 0x33, 0x31, 0x64, 0x35, 0x5A, 0x4A, 0x42,
  17. 0x56, 0x40, 0x67, 0x53, 0x41, 0x07, 0x6C, 0x49,
  18. 0x58, 0x3B, 0x4D, 0x46, 0x68, 0x43, 0x69, 0x48,
  19. 0x33, 0x31, 0x44, 0x65, 0x62, 0x46, 0x48, 0x53,
  20. 0x41, 0x07, 0x6C, 0x69, 0x48, 0x33, 0x51, 0x54,
  21. 0x5D, 0x4E, 0x6C, 0x49, 0x38, 0x4B, 0x55, 0x4A,
  22. 0x62, 0x46, 0x48, 0x33, 0x51, 0x34, 0x6D, 0x36,
  23. 0x50, 0x5F, 0x5F, 0x5F, 0x3F, 0x6F, 0x47, 0x63,
  24. 0x59, 0x40, 0x67, 0x33, 0x31, 0x64, 0x35, 0x5A,
  25. 0x6A, 0x52, 0x6E, 0x3C, 0x51, 0x34, 0x6D, 0x36,
  26. 0x50, 0x5F, 0x5F, 0x3F, 0x4F, 0x37, 0x4B, 0x35,
  27. 0x5A, 0x4A, 0x62, 0x66, 0x58, 0x3B, 0x4D, 0x66,
  28. 0x58, 0x5B, 0x5D, 0x4E, 0x6C, 0x49, 0x58, 0x3B,
  29. 0x4D, 0x66, 0x58, 0x3B, 0x4D, 0x46, 0x48, 0x53,
  30. 0x61, 0x4C, 0x59, 0x40, 0x67, 0x33, 0x31, 0x64,
  31. 0x55, 0x6A, 0x32, 0x3E, 0x44, 0x45, 0x52, 0x6E,
  32. 0x3C, 0x31, 0x64, 0x55, 0x6A, 0x52, 0x4E, 0x6C,
  33. 0x69, 0x48, 0x53, 0x61, 0x4C, 0x39, 0x30, 0x6F,
  34. 0x47, 0x63, 0x59, 0x60, 0x57, 0x5B, 0x3D, 0x3E,
  35. 0x64, 0x35, 0x3A, 0x3A, 0x5A, 0x6A, 0x52, 0x4E,
  36. 0x6C, 0x69, 0x48, 0x53, 0x61, 0x6C, 0x49, 0x58,
  37. 0x3B, 0x4D, 0x46, 0x68, 0x63, 0x39, 0x50, 0x5F,
  38. 0x5F, 0x3F, 0x6F, 0x67, 0x53, 0x41, 0x25, 0x41,
  39. 0x3C, 0x51, 0x54, 0x3D, 0x5E, 0x54, 0x5D, 0x4E,
  40. 0x4C, 0x39, 0x50, 0x5F, 0x5F, 0x5F, 0x3F, 0x6F,
  41. 0x47, 0x43, 0x69, 0x48, 0x33, 0x51, 0x54, 0x5D,
  42. 0x6E, 0x3C, 0x31, 0x64, 0x35, 0x5A, 0x00, 0x00,
  43. }
  44. // getV5Key extracts and descrambles the check code from a V5 packet to derive the decryption key
  45. // From iserverd GetKey() function
  46. func getV5Key(packet []byte, packetLen int) uint32 {
  47. if len(packet) < 0x18 {
  48. return 0
  49. }
  50. // Read the scrambled check code at position 0x14 (little-endian)
  51. check := binary.LittleEndian.Uint32(packet[0x14:0x18])
  52. // Descramble the check code (from iserverd GetKey)
  53. A1 := check & 0x0001F000
  54. A2 := check & 0x07C007C0
  55. A3 := check & 0x003E0001
  56. A4 := check & 0xF8000000
  57. A5 := check & 0x0000083E
  58. A1 = A1 >> 0x0C
  59. A2 = A2 >> 0x01
  60. A3 = A3 << 0x0A
  61. A4 = A4 >> 0x10
  62. A5 = A5 << 0x0F
  63. descrambledCheck := A5 + A1 + A2 + A3 + A4
  64. // Calculate the key: packetLen * 0x68656C6C + descrambledCheck
  65. key := uint32(packetLen)*0x68656C6C + descrambledCheck
  66. return key
  67. }
  68. // DecryptV5Packet decrypts a V5 packet in place
  69. // From iserverd V5Decrypt() function
  70. func DecryptV5Packet(packet []byte, sessionID uint32) {
  71. if len(packet) < 0x18 {
  72. return
  73. }
  74. // Get the packet length (the actual UDP packet size)
  75. packetLen := len(packet)
  76. // Get the decryption key
  77. key := getV5Key(packet, packetLen)
  78. // Decrypt from offset 0x0A to end of packet
  79. // The algorithm processes 4 bytes at a time, but skips the checkcode positions (0x14-0x17)
  80. // Loop: for (i=0x0a; i < pack.sizeVal+3; i+=4)
  81. for i := 0x0A; i < packetLen+3; i += 4 {
  82. k := key + uint32(v5Table[i&0xFF])
  83. // XOR bytes, but skip checkcode positions
  84. // if (i != 0x16) { buff[i] ^= ...; buff[i+1] ^= ...; }
  85. if i != 0x16 {
  86. if i < len(packet) {
  87. packet[i] ^= byte(k & 0x000000FF)
  88. }
  89. if i+1 < len(packet) {
  90. packet[i+1] ^= byte((k & 0x0000FF00) >> 8)
  91. }
  92. }
  93. // if (i != 0x12) { buff[i+2] ^= ...; buff[i+3] ^= ...; }
  94. if i != 0x12 {
  95. if i+2 < len(packet) {
  96. packet[i+2] ^= byte((k & 0x00FF0000) >> 16)
  97. }
  98. if i+3 < len(packet) {
  99. packet[i+3] ^= byte((k & 0xFF000000) >> 24)
  100. }
  101. }
  102. }
  103. }
  104. // calculateV5CheckCode calculates the check code for a V5 packet
  105. // From iserverd calculate_checkcode() function
  106. func calculateV5CheckCode(packet []byte) uint32 {
  107. if len(packet) < 10 {
  108. return 0
  109. }
  110. // number1 is calculated from specific byte positions
  111. B2 := packet[2]
  112. B4 := packet[4]
  113. B6 := packet[6]
  114. B8 := packet[8]
  115. var number1 uint32
  116. number1 += uint32(B8)
  117. number1 <<= 8
  118. number1 += uint32(B4)
  119. number1 <<= 8
  120. number1 += uint32(B2)
  121. number1 <<= 8
  122. number1 += uint32(B6)
  123. // r1 and r2 are random values for number2
  124. r1 := uint16(rand.Intn(0x10))
  125. r2 := uint16(rand.Intn(0xFF))
  126. X4 := byte(r1)
  127. X3 := packet[X4]
  128. X2 := byte(r2)
  129. X1 := v5Table[X2]
  130. var number2 uint32
  131. number2 += uint32(X4)
  132. number2 <<= 8
  133. number2 += uint32(X3)
  134. number2 <<= 8
  135. number2 += uint32(X2)
  136. number2 <<= 8
  137. number2 += uint32(X1)
  138. number2 ^= 0x00FF00FF
  139. cc := number1 ^ number2
  140. return cc
  141. }
  142. // EncryptV5Packet encrypts a V5 packet in place
  143. // From iserverd V5Encrypt() function
  144. func EncryptV5Packet(packet []byte, sessionID uint32) {
  145. if len(packet) < 0x18 {
  146. return
  147. }
  148. // Calculate check code
  149. cc := calculateV5CheckCode(packet)
  150. // Insert the checkcode at position 0x14
  151. packet[0x14] = byte(cc)
  152. packet[0x15] = byte(cc >> 8)
  153. packet[0x16] = byte(cc >> 16)
  154. packet[0x17] = byte(cc >> 24)
  155. packetLen := len(packet)
  156. // Calculate the encryption key
  157. key := uint32(packetLen)*0x68656C6C + cc
  158. // Encrypt from offset 0x0A
  159. // SLAB(LEN, POS) = LEN - POS >= 4 ? 4 : LEN - POS
  160. for pos := 0x0A; pos < packetLen; {
  161. slab := packetLen - pos
  162. if slab > 4 {
  163. slab = 4
  164. }
  165. if slab <= 0 {
  166. break
  167. }
  168. // Read bytes as little-endian uint32
  169. var tmpUint uint32
  170. for leftI := slab - 1; leftI >= 0; leftI-- {
  171. tmpUint <<= 8
  172. if pos+leftI < len(packet) {
  173. tmpUint |= uint32(packet[pos+leftI])
  174. }
  175. }
  176. // XOR with key + table value
  177. tmpUint ^= key + uint32(v5Table[pos&0xFF])
  178. // Write back
  179. for rightI := 0; rightI < slab; rightI++ {
  180. if pos < len(packet) {
  181. packet[pos] = byte(tmpUint >> (rightI * 8))
  182. pos++
  183. }
  184. }
  185. }
  186. // Put the scrambled key (for server packets, checkcode is not scrambled)
  187. // The checkcode is already at 0x14, but for server packets we put it at 0x11
  188. // Actually for server packets, iserverd uses PutKey which puts at 0x11
  189. // But the client expects it at 0x14, so we leave it there
  190. }
  191. // GenerateSessionID generates a random session ID for V5 connections
  192. func GenerateSessionID() uint32 {
  193. return rand.Uint32()
  194. }
  195. // ScramblePassword scrambles a password for V4/V5 login
  196. // This is used when the password is sent in the login packet
  197. func ScramblePassword(password string) []byte {
  198. if len(password) == 0 {
  199. return nil
  200. }
  201. scrambled := make([]byte, len(password))
  202. for i := 0; i < len(password); i++ {
  203. scrambled[i] = password[i] ^ v5Table[i%256]
  204. }
  205. return scrambled
  206. }
  207. // UnscramblePassword unscrambles a password from V4/V5 login
  208. func UnscramblePassword(scrambled []byte) string {
  209. if len(scrambled) == 0 {
  210. return ""
  211. }
  212. password := make([]byte, len(scrambled))
  213. for i := 0; i < len(scrambled); i++ {
  214. password[i] = scrambled[i] ^ v5Table[i%256]
  215. }
  216. return string(password)
  217. }
  218. // V5CheckCode is an alias for calculateV5CheckCode for backward compatibility
  219. func V5CheckCode(packet []byte) uint32 {
  220. return calculateV5CheckCode(packet)
  221. }
  222. // V4Table is the encryption lookup table used by V4 protocol
  223. // This is the same table as documented in dault-v4.txt and wumpus-v4.txt
  224. // It's the ASCII text "[1] You can modify the sounds ICQ makes..."
  225. var V4Table = [256]byte{
  226. 0x0a, 0x5b, 0x31, 0x5d, 0x20, 0x59, 0x6f, 0x75,
  227. 0x20, 0x63, 0x61, 0x6e, 0x20, 0x6d, 0x6f, 0x64,
  228. 0x69, 0x66, 0x79, 0x20, 0x74, 0x68, 0x65, 0x20,
  229. 0x73, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x20, 0x49,
  230. 0x43, 0x51, 0x20, 0x6d, 0x61, 0x6b, 0x65, 0x73,
  231. 0x2e, 0x20, 0x4a, 0x75, 0x73, 0x74, 0x20, 0x73,
  232. 0x65, 0x6c, 0x65, 0x63, 0x74, 0x20, 0x22, 0x53,
  233. 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x22, 0x20, 0x66,
  234. 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, 0x20,
  235. 0x22, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65,
  236. 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x6d, 0x69, 0x73,
  237. 0x63, 0x22, 0x20, 0x69, 0x6e, 0x20, 0x49, 0x43,
  238. 0x51, 0x20, 0x6f, 0x72, 0x20, 0x66, 0x72, 0x6f,
  239. 0x6d, 0x20, 0x74, 0x68, 0x65, 0x20, 0x22, 0x53,
  240. 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x22, 0x20, 0x69,
  241. 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f,
  242. 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x20, 0x70, 0x61,
  243. 0x6e, 0x65, 0x6c, 0x2e, 0x20, 0x43, 0x72, 0x65,
  244. 0x64, 0x69, 0x74, 0x3a, 0x20, 0x45, 0x72, 0x61,
  245. 0x6e, 0x0a, 0x5b, 0x32, 0x5d, 0x20, 0x43, 0x61,
  246. 0x6e, 0x27, 0x74, 0x20, 0x72, 0x65, 0x6d, 0x65,
  247. 0x6d, 0x62, 0x65, 0x72, 0x20, 0x77, 0x68, 0x61,
  248. 0x74, 0x20, 0x77, 0x61, 0x73, 0x20, 0x73, 0x61,
  249. 0x69, 0x64, 0x3f, 0x20, 0x20, 0x44, 0x6f, 0x75,
  250. 0x62, 0x6c, 0x65, 0x2d, 0x63, 0x6c, 0x69, 0x63,
  251. 0x6b, 0x20, 0x6f, 0x6e, 0x20, 0x61, 0x20, 0x75,
  252. 0x73, 0x65, 0x72, 0x20, 0x74, 0x6f, 0x20, 0x67,
  253. 0x65, 0x74, 0x20, 0x61, 0x20, 0x64, 0x69, 0x61,
  254. 0x6c, 0x6f, 0x67, 0x20, 0x6f, 0x66, 0x20, 0x61,
  255. 0x6c, 0x6c, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61,
  256. 0x67, 0x65, 0x73, 0x20, 0x73, 0x65, 0x6e, 0x74,
  257. 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e,
  258. }
  259. // V5EncryptionKey derives the V5 encryption key (for backward compatibility)
  260. func V5EncryptionKey(sessionID uint32, checkCode uint32) uint32 {
  261. // This is not used in the new implementation
  262. return 0
  263. }
  264. // V4 Protocol Encryption/Decryption
  265. // V4 uses a simpler encryption scheme than V5
  266. // V4CheckCode calculates the checksum for a V4 packet
  267. // The checksum is calculated from specific bytes in the packet
  268. func V4CheckCode(packet []byte) uint32 {
  269. if len(packet) < 10 {
  270. return 0
  271. }
  272. // Calculate first part of checksum from fixed positions
  273. chk1 := uint32(packet[8])<<24 | uint32(packet[4])<<16 |
  274. uint32(packet[2])<<8 | uint32(packet[6])
  275. // Calculate second part using random position
  276. r1 := rand.Intn(len(packet)-5) + 1
  277. r2 := rand.Intn(256)
  278. chk2 := uint32(r1)<<24 | uint32(packet[r1])<<16 |
  279. uint32(r2)<<8 | uint32(v5Table[r2])
  280. return chk1 ^ chk2
  281. }
  282. // V4EncryptionKey derives the encryption key from packet length and checksum
  283. func V4EncryptionKey(packetLen int, checkCode uint32) uint32 {
  284. return uint32(packetLen)*0x66756B65 + checkCode
  285. }
  286. // EncryptV4Packet encrypts a V4 packet in place
  287. // Only the first quarter of the packet (after version and random) is encrypted
  288. func EncryptV4Packet(packet []byte, key uint32) {
  289. if len(packet) < 8 {
  290. return
  291. }
  292. // Encryption starts at offset 4 (after version and random)
  293. encryptStart := 4
  294. // Encrypt only first quarter of remaining data
  295. encryptLen := (len(packet) - encryptStart) / 4
  296. if encryptLen < 1 {
  297. encryptLen = 1
  298. }
  299. keyBytes := make([]byte, 4)
  300. binary.LittleEndian.PutUint32(keyBytes, key)
  301. for i := 0; i < encryptLen; i++ {
  302. idx := encryptStart + i
  303. if idx >= len(packet) {
  304. break
  305. }
  306. tableIdx := byte(i&0xFF) ^ keyBytes[i%4]
  307. packet[idx] ^= v5Table[tableIdx]
  308. }
  309. }
  310. // DecryptV4Packet decrypts a V4 packet in place
  311. // Decryption is the same operation as encryption (XOR is symmetric)
  312. func DecryptV4Packet(packet []byte, key uint32) {
  313. EncryptV4Packet(packet, key)
  314. }
  315. // AddV5ServerCheckcode calculates and adds the checkcode to a V5 server packet
  316. // Server packets are NOT encrypted, but have a checkcode at offset 0x11 (17)
  317. // From iserverd PutKey() function
  318. func AddV5ServerCheckcode(packet []byte) {
  319. if len(packet) < 21 {
  320. return
  321. }
  322. // Calculate checkcode using the same algorithm as for client packets
  323. cc := calculateV5CheckCode(packet)
  324. // Server checkcode is NOT scrambled (unlike client packets)
  325. // Insert at offset 0x11 (17)
  326. binary.LittleEndian.PutUint32(packet[0x11:], cc)
  327. }
  328. // SetV3Checkcode calculates and sets the checkcode for a V3 server packet
  329. // V3 server packet format: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + CHECKCODE(4) + DATA
  330. // The checkcode is at offset 12 (bytes 12-15)
  331. // From v4-notes.txt documentation (wumpus create_icq3_header function):
  332. // First part: pack bytes 8, 4, 2, 6 (MSB to LSB)
  333. // Second part: (random offset into table << 8) | (byte at that table offset)
  334. // XOR second part with 0x00FF00FF, then XOR both parts together
  335. func SetV3Checkcode(packet []byte) {
  336. if len(packet) < 16 {
  337. return
  338. }
  339. // First part: pack bytes 8, 4, 2, 6 (MSB to LSB)
  340. // checkA = (byte8 << 24) | (byte4 << 16) | (byte2 << 8) | byte6
  341. checkA := uint32(packet[8])<<24 | uint32(packet[4])<<16 | uint32(packet[2])<<8 | uint32(packet[6])
  342. // Second part: use a random offset into the v5Table
  343. // For simplicity, use a fixed offset based on packet content
  344. tableOffset := int(packet[0]) ^ int(packet[2]) ^ int(packet[4]) ^ int(packet[6])
  345. tableOffset &= 0xFF
  346. // checkB = (tableOffset << 8) | v5Table[tableOffset]
  347. checkB := uint32(tableOffset)<<8 | uint32(v5Table[tableOffset])
  348. // XOR checkB with 0x00FF00FF
  349. checkB ^= 0x00FF00FF
  350. // Final checkcode is checkA XOR checkB
  351. checkcode := checkA ^ checkB
  352. // Set checkcode at offset 12
  353. binary.LittleEndian.PutUint32(packet[12:16], checkcode)
  354. }