diff --git a/.gitignore b/.gitignore index 85d9223..3c73ebb 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,8 @@ data/web.db .tmp_reverse/ apks/ evidence/qr_login_success.json +evidence/final_capture.json +evidence/final_wupdata.bin +evidence/frida_cred_dump.json + +work/ \ No newline at end of file diff --git a/docs/虎牙hyCred签发-侦察笔记.md b/docs/虎牙hyCred签发-侦察笔记.md index fe4793a..095d8ae 100644 --- a/docs/虎牙hyCred签发-侦察笔记.md +++ b/docs/虎牙hyCred签发-侦察笔记.md @@ -195,3 +195,10 @@ _Z16createWupPackageP...PKci @0x274ea4 (0x610B): func硬编码"wupudbrequest_v0"; payload=x1。 => 对应TAF RequestPacket{iVer,cPktType,iReqId,servant,func,sBuffer,...} 下步: 找PLT调用点看运行时实参(payload格式/id值), 复刻帧->Python直连wup.huya.com:443。 + +## ★★★ 纯Python护照登录打通! + +build_password_login_wup(前会话复刻包) POST https://wup.huya.com + body=原始TAF字节(非b64!), CT=application/multipart-formdata, Dalvik UA +→ HTTP200 588B结构化TAF响应(huyaudbwebui/hypasswordLogin回显)! +假密码测试通过=帧格式实锤。下步: 解响应取rc/cred字段,真密码登录取cred->铸证->cookie。 diff --git a/evidence/sslwrite.json b/evidence/sslwrite.json new file mode 100644 index 0000000..21484da --- /dev/null +++ b/evidence/sslwrite.json @@ -0,0 +1 @@ +[{"type": "armed"}, {"type": "hooked", "idx": 0, "at": "0x79316bef90"}, {"type": "hooked", "idx": 1, "at": "0x78937bfa30"}, {"type": "hooked", "idx": 2, "at": "0x786abe2948"}, {"type": "wire", "idx": 0, "len": 4268, "head": "POST / HTTP/1.1\r\ni-ver: 1\r\nContent-Type: application/octet-stream\r\nContent-Length: 4081\r\nHost: wsapi.huya.com\r\nConnection: Keep-Alive\r\nAccept-Encoding: gzip\r\nUser-Agent: okhttp/3.14.9\r\n\r\n\ufffd\ufffd\u000f\ufffd\u0010\u0003,5\u0017\ufffdc\ufffds\u000f\ufffd\ufffd\ufffd\u0007?\ufffdQ\u0153\u0012\ufffd\u0018{j", "hex": "504f5354202f20485454502f312e310d0a692d7665723a20310d0a436f6e74656e742d547970653a206170706c69636174696f6e2f6f637465742d73747265616d0d0a436f6e74656e742d4c656e6774683a20343231340d0a486f73743a2077736170692e687579612e636f6d0d0a436f6e6e656374696f6e3a204b6565702d416c6976650d0a4163636570742d456e636f64696e673a20677a69700d0a557365722d4167656e743a206f6b687474702f332e31342e390d0a0d0a0000107610032c3c4c560c68757961756462776562756966096466705265706f72747d0001104a0800010604745265711d0001103c0a06001607616e64726f69642d00011026571882cf664bb39401ee6cdb1195c8f5289c4dc491dea19b3e35179b63a7730fa8ebe9073f8d51c59312e6187b6a7787802fe526284a60f373f026fd5c70e6b01aca20d73ad7299cd4ecfadd1146fee52f38372ab9030a62f87f0af64b0f08035d61e665b472db38a94eff8890e399a5160fbd6387da09d9d7996b247eb5b6bb5692401d2b55d9c3bfb0fc17cff2d27c50b1b1d98da0afa91a37013a694caff6bff6f75d351bd7e5c24047def56fd294da08d27401a3304a07e4d9ebfab7a782e2ec82bd52e36419e9b8887ca8ee6ffcad6a66db547af5a3d3f3c4439cf22ec53dc08347f98971fe209d4c1c3a66a6236bb4cfc864cce838f9df63569c46785a62fb4fb8bf3d4b256090a0fd3ad19c306b5e98a8fe5396681d283b2df4583edd936badd564e9f616be3975f12d31ea21736efb5e26a380e150c58cb3c8b177d93cdca9cfebf1b02f46ee2d391301edb29d0d6ffc4a272f697217b302ba94559bef3b6b6a3a4ea0d11ac9f020635c157f3ed87e06b036c9683883d3068bef5b1341c85cf1ecb2d76fb5087652e524ac3c26aa0ace31714f173817de60a1fd56ac1b854146154c9ebe24232ee81504e8bade1e0b50a29d71bfc4c01affe560fdca7c452675fdc3d4236f1c27af9508f3274690c7782c75e97153ae68fc127e1486be14e64ffe9c0ad4fa62efec1d7c75492ce7c363159e4e617788c055461d1da7516fd9b0fae671696a4ac67023a0d154d89408b0fcf3ba3e9d8181f10835ea6e8dbdb7eb9d43fcb4689f4d419e704894b55f45f2b710f34da0f3856ae6747efda5ef1ea0c0876c02d566e40d999032e32b37f46e0eddb23de4b3336f795bcb8dccf2ddb0c1620428ef8c6e69ad3ff0c4b724362e68fb5bb2bad9f3a0dda98fd061271ee75c01b65353f83b89830e42fd32e690bcc3453c5ae7cbd2c6424f780e88181a50539420ed732b2c03e7707109cc89d9945d6b3ea4f43d5717884e3760fa82a98f50f9ef74fe569d158c98362dc3c2e5221465b3e1a22db88b0d5c780bc4dce30d7ddaf036ed9d84d4eaf9a2ce5901f440910e73aaba6b075d395df63c1e0fc91084845bf2f3f4f87b05c2d36dd73f0a35d86c93b8f2ed91206df8fb09737d01fa8718ef39ee1f110d3b0ff6bc215f7e4f849fc2b20a49cb01b2a53f1db63a0d905358479d2be84d3ceb4ec07cdab4af21d8c7a6aaeac034ba8db7897fc3385ce0eb0fcfa53858a0953f53604c268212e83351a9081fc3b8eda7e6699ec8ff06a035d3d8f32ad969e48549407249d80a5488395900a6312e783f094b0a7b909f34c6d60330bab02830aff102cc4c38454e9e049d815331266885333d0cb7bff58917a0af51ef1368a114f6b842ab7af4cb52a567eddb2540faabd744d4898f159332147454d793f96ceb499dcff0123a34012597ff16c31b9e3cff5e37d1ed63caad8f6752a4dcea958655d886dfb9dec53a8670f35acf1ca79ac66be16cf6f8c22eb41250a743e78f7ebf7026e9f2850afd04c7dffda429eb49de3ca53e9f8ebd9d80c829d89f78e46dd4a72f500639df3dc626fb0ee6c9f18bedb7c57d30aeab1aadfe153222a00af18c07fb81a85116366630d37670b2667554eef6d77c17078798e8512714ce4342109782df33dbbef7b33f45b42fdd390e9c7361d6efb9d1d27e98eff8bf9dde0e6a5d0d9d345c6a9dfd9e2d251f33dee3c88470f5022b7977a324e25eb6cc02d4f35c99f96d89869be7b287170bc385c30644b8d7ebef985144d27802dea9149e29f8b407df9efb041374326ee850305b2ba48cc5c24687d0cf0e9aabd4bd177ef54fe2132e043e7d1eba82a7412782bf9f68041150732af7efc59b054741710bb80d398d801b12a17967e8768857ccabd46b8136951f9b1f48afc15fd48ea1b1f3c1f944bedc3fbc61c4ca7da055f9caaedf35c0c86daa48420b5f8889dfb7e2e6e05de0629c9f496efd54ea02f666849aeea01117b8c1eddc44a14f873de3cf64df0077c0012a2720eae08a665130b4766667e66ef34d2480d1aecef2eaebc9d1e42d7e458e03de037c84c70b7f36bd83192f6ce46075a28df501e8ab0cbcb5fb52e6580c3983e32e76c25b462ecaa0c28a4fea804e7d5fda8a39b2ebbc8ae24086222bdaa9dba48a872f465f8da4fbaa0cfe9ebe0b1ae95186ee02f16161d28626ddf8920e51415b00b5c6a3b6f2d0bea3fb4ecdc2bcce527dccf77e533defa36e774f365a56791704e323db1814f02bd2ff3279c3f628bd6554c5039c2759b01b8af74095233de73700873972a9c372b02f233071659c3603ad45c52f041f6494736a068e5da157016a77285aaccf4412bc4564399fd0b1ad22ee40ac83a4b0e99a40671db56145307ea7b0103a2acff27b577ec6d365b7489e890d48508979679e132713f8b64491dee1526a173708ae83c9522e9d7892a13a226003b141f35eb17747d1e76f3596d550b4b632b72ea22eefab1394e275ef3eb3f48ccf518fbd88aed7cbc4d38ae56b46e1b3dcea968425c9b36f688739c97bcf6f3297006bcd0d22da71946e5b6f0ad0461f7e81d0d2c0d899c6399ca48dfdfc2e7c1cd1a83d1fb407b0ae4efe8e1c273f7c312fe6cb3c453e02cdafb3e21dacc1bb8aad0017a7c0dc31c95f70ebfea475597efe5aef05fa2dc97700e1cf094955fdea93a499247f2eff38992a6dd389de3baa8fca2e0272869e85f8fde7296d7bf571cedc0d998d75e78ae5ca42cb55ac093e020f3d105fd5c98cadeec999a2ae247a6955c12671933a9a3ffc67ff82da61f1ce836038ed99594da794336a57443cd28a7294e24e1ca5ae38fdcddeca6bf515205bffd9768823ec966c9b1c442cf6b3dcc5ab8151e37dfabae6d3a4f61b967945fcd5dc2fec8c8b9a2b0430d170a59ac0cc3a617e064a820e622e28a50752fbe6b2ad88c724409c7b201db3e8c7c596d6c5d1aa4015e4996b306fc7b92436c9eb8261b85f0e4731cd1c8440075820c52003803217cc0e2f7d837aced214881974310f113e92b888c6021d3114239bad151fc6c5c2ed37cf243706cace01374cb57a0954ebc311ecd25f15aa98088d9ecd2b571f4d0bfef3593d782675a816952b67b9a22a02a13872e8d8f0d22092f617988e718ac492a5a76a988ee373ff845ea17f078f1bd0522da67e99631b2d4c4e0a0369345a4df046ee3e5221e83076e762a7da4fa8bfd0bcddf15528b6bc88ef54c82292225558039b7b7c5e627050e732769aaf373d434a24f68bc0dee03ba96f676415ce3dd261000836aff6d38ac5484ab628c9713e9a1f03b0cd73ccc01be147d6d1b15079974e8e107954ce31e096f2d52a951682b48b7d751ed463da84d853b5148c4ec92a0b5f28d59c89ec2baacbc8d765b069cd45d26f4d6059cc49e2be1746cd84081a00c5fc3afb0837fa49ac294b100f685af8a80cd7cd7318c86ccc278127406ef810cd8e9b53efe492a6f2eb6afb280898a1ba6c4cd5899eafbb1556c9bdba6aae2e637c6c2ccb1f4c447a0b525e71c9e23e24caa7af7eddce09a276ee16a087ec12ca877e68986026dc6b54476f71733648a51f0eb149c641c173d5f784d907f2ddef4c8dcd8a23477b3d9bee187ee8212efcf4693d3289ae6b0fb4c5e1d050f6877ee553b3ce2a22f22713a402e9bace4cde85cdd672d6aa597b62409c83cf8699be397e3ca22127db29d353c3be9a1b8f30b75f6e34741734953f18b905cce8cae1fd995072fab79562f3921dc1d072e200919096809c5429c17605cc404c7b01b8ff40b9ef054992a85934142e73d7658d5bc05ea91dbf092663361ef30ca29e7dc76472dc6d1b3b3632ce1884136d8b9f6dc574da2050d7db2a4b4081691efc2d1d07cda0af7c8649dd58b4248cf16093ec424211834719b589f68318b9add567fcd11b912bc240a3b4f069c48d7e30cad436e33565028043fe39cfec0df324b722af401b3c34d126f95ea8677632946212b3654a3cdd1e4cdb83d97b43e43f30cc27db22affc25e6363d1509a45502fb25b727c8c203f6b472e402fb4368b9d75abc775a9fe5b56d192fbcb8aa4ec6cdd69e3508e0b8f6d402763fbeb53d2e0a5912b61b50123753ce1a2cccfad44b10988730dcdf0710183c4f7bc10a559b4e99a2e4d8b930a70555f9c5b707aa91bb3f96de6634d4d23d16fc2450043a32e3ecd81935e56ff219e937dac901a3d953ae8a579c1f9906af4322950c414988d097db15c3da2216ce5c1bc358109ee6486b3eaaaaeaf229060d0965f95b2696ab9d14598eff78b265fd14ec4ad864e93b7daf0c029095204261f399bf6fb0e1085accf9b1c20c0dd9f1be5ebbf281aad0b9e316cc501184bff813e006522b363ce863745c1e62798d7c1e620aea6514a6bc77e7babf35117375c39b8937a8d2de2804218b46de1f2ee16ba743bdd4cd0fdd9144dd1081b00cd9e16721eceeba03798983cbf13b4b8cd10077261f1bdb548fd90d772d4b89788be5b416178da04b2a3cbcc56cdd1440d8a7789e441e1d4148aa56f321e673e08dd73003b0016f8a26221c8f5af8eeeae649c11b946b51df6e81c1e60b81f1f7c80b468466bec2a6a597f9a693e79866b9d41d056a7e365877095156cb6437e313b2fe31330fe15a548a0060349b4710b1842f7a79931f7334a55be178c8e2482799ac85d49eaaa017523dfd96f1da2dc50d664147b515868cb5044b0714fe3698aea5a4654d5d7390a5044c6b0bba6e1b9578a009a9c256d55ae07d62dff9e85893fa2e9be32965e40b094372c84359d4107d714afcf41fcf94eec891f7cfbf46349d981a8bb1f81c0058698a9adeaf3100d5b98227e2f87d4a2bb7a6371d94261472323711aad614d9c86543a756e11d5b58b931a91d47242732912ed267c1087d71cedcab5f2204d96500c812928e6aeb8782080e70fa21c5f2f62419d0340ac759f796f64f156648ee127380ef69d31d135396e719511437076ac828e5a65bfd7ccac27aa8ab22a53e1957863513ac011ff2e98240045d73689d1e4fb6750fc925d591cb20c27eca82581bf87b684660351f7acd1d6dd45d87d02f39006d34fa9e318173fefb9c88a56fdf60f25bd7c3a313b3968029150e0782d8267861ec7f1330e64a987bd0d823fca5ef2a0b508552517c0c758eb68ab87305bd2b224a44da8e9d4e2b75f1903a676a7da3d58278494d59a8aef6b3d40c1fd93a698f153fb65904cbf5cd74138eac27561c51432165a32246fb7c271deb768bf30636f2ec1baa629a2ec24ab558ae59c277b697c11fe136725401d94b3e52e2fbfc8118cf0bce177011870d10295cb0a831e0a0c6a3ef13f600552b7dd2c30217c7de56c2e9af96dc8b39b4249a21133ec4a3ed0dae77bbf853c68b432130b05d86fd501d2cf634d12a7a518083a236ed3fb3d0ab81aa4c403764007f67f934412bdb2efba0c0fe311da8976b5d38ca9b55c82cce731220356d0b3ae58e5c7748c37ef3b3903b7c6942faebc9f0b9808102e6952d2c2a5ed8cfe8e0e498acb2e759fe656a0cf04423545e2a8f70b5f1e8b861e7b2d11ec4ae557f9aaa66cd0e4403e90dff10cd471fa1398a7464f912c597b3c8c81e15b96a33b5b047080b38b43921e690a3002cdfd388347cf7e4e7cde41b06d8f813a4c7a5775896b0688552b72591ee845d4a50cefd0634ac8b7d9321b66f0c3085e8ee00c3aa36547d0650c2e2674766b5041476e53ea712d905cd898a0a742c8242c392d12ee67fb97debc6804a983adbf02031e77edd713b1f303ec96ced63d76f780fc4dbdd2e68b66e0a847858b276cf824d96959db6b3fa19f2e9582e3d0135c5173600400c0b8c980ca80c"}, {"type": "wire", "idx": 0, "len": 4485, "head": "POST / HTTP/1.1\r\ni-ver: 1\r\nContent-Type: application/octet-stream\r\nContent-Length: 4298\r\nHost: wsapi.huya.com\r\nConnection: Keep-Alive\r\nAccept-Encoding: gzip\r\nUser-Agent: okhttp/3.14.9\r\n\r\n\ufffd\ufffd\u0010\ufffd\u0010\u0003,&V\fhuyaudbwebuif\u0006report}\ufffd\u0001\u0004)\b\ufffd\u0002\u0006\t_wup_data\u001d\ufffd\u0001\u0003\ufffd", "hex": "504f5354202f6f70656e2f6c6f672f7265706f727420485454502f312e310d0a436f6e74656e742d547970653a206170706c69636174696f6e2f782d7775700d0a436f6e6e656374696f6e3a204b6565702d416c6976650d0a557365722d4167656e743a2044616c76696b2f322e312e3020284c696e75783b20553b20416e64726f69642031313b204d323130324a325343204275696c642f524b51312e3230303832362e303032290d0a486f73743a207564626c6f672e687579612e636f6d0d0a4163636570742d456e636f64696e673a20677a69700d0a436f6e74656e742d4c656e6774683a20313131300d0a0d0a0000045610032c3c4200493e26560c68757961756462776562756966067265706f72747d0001042908000206095f7775705f646174611d000103fb0a0a0c1603312e3026c67b226173736f63696174696f6e4964223a383234312c2266756e634e616d65223a227265706f7274222c2267726f7570223a31312c226964223a343134352c2273657373696f6e223a343830303033382c2273746570223a312c227374696c6c4c6f67696e223a66616c73652c2274726163654964223a22306238663039386666363461356264632d33323636332d32393733363434373837363530393438313937222c2274797065223a332c22756964223a302c2275736572436f6e74657874223a22227d360435303038400356b4505177656d414e394e486b5a4b6f4d71575952356a78497970714d546151454f726d5872333778515668515a7172442b696b4b45513131317645306769356b5777747266713268425471654270594739663574556d6a38757361704d5042426543576244717a667841494e414e625432337959566f41766e553036376e32714275522b5334476466564a704f793468556c384462537033506d7668694c592f43795a7a6f4b5a7433546e52725a4477346e79333366007600860096000b16076875796175646227000002555b7b226170705f766572223a2231332e342e3232222c226170706964223a2235303038222c22627970617373223a332c22636172726965725f74797065223a322c226368616e6e656c223a227869616f6d69222c22637265644c6f67696e54797065223a302c226465766963655f6964223a2230326466333938373937343332656164656663633132373637313139616435653830393939333839222c226465766963655f6e616d65223a224d323130324a325343222c2268646964223a2237633533383765303533396330323363333163346666306538303765373235363131373338356565222c226c6576656c223a312c226c6f63616c5f74696d65223a313738373635303934373131322c226e65745f7374617465223a302c226e65745f74797065223a342c22706964223a33323636332c22726571756573744964223a343739393935312c22726573636f6465223a2230222c227273705f74696d65223a3131322c2273646b5f766572223a22312e302e3830313338222c227369676e223a22222c2273797374656d5f696e666f223a22616e64726f6964222c2273797374656d5f766572223a224d323130324a3253432c33302c3131222c227465726d5f74797065223a312c2274726163655f6964223a22306238663039386666363461356264632d33323636332d3134323130323437373837363530393436393935222c22756964223a313139393636363931343637312c22757269223a22687970617373776f72644c6f67696e222c2275736572223a2268795f333030303233383837222c22757365725f74797065223a307d5d0b0610777570756462726571756573745f76301d0000050200493e268c980ca80c"}] \ No newline at end of file diff --git a/evidence/wupbuild.json b/evidence/wupbuild.json new file mode 100644 index 0000000..70a60b9 --- /dev/null +++ b/evidence/wupbuild.json @@ -0,0 +1 @@ +[{"type": "armed"}, {"type": "wupbuild", "id": "0x218de4", "payload": "hylogout", "payload_head": "68796c6f676f7574007eca770000000070b1cc770000006f65b8511701000000363461356264630000796c6f676f7574007eca770000000070b1cc7700000031000000000000002b0000000000000040335f85780000b4904a8bca770000006f"}, {"type": "wupbuild", "id": "0x218de6", "payload": "hyanonymousCredlogin", "payload_head": "6879616e6f6e796d6f7573437265646c6f67696e00000000000000000000000002000000000000000079616e6f6e796d6f7573437265646c6f67696e00000031000000000000002b00000000000000b01126bc770000b408a68aca7700000000"}, {"type": "wupbuild", "id": "0x1", "payload": "getAppComomData", "payload_head": "676574417070436f6d6f6d44617461006261645f7765616b5f70747200772b6265004a00510064006c6f772d6c696e6500710074776f00766572746963616c2d7461620074656d706c61746520706172616d65746572206f626a65637420666f"}, {"type": "wupbuild", "id": "0x218df1", "payload": "report", "payload_head": "7265706f727400000000000000000000000000000000000000000000000000004cd76ba75c28be00000000000000000000000000000000000000000000000031000000000000002b000000000000000043864d770000b401eb8dbe7700000000"}, {"type": "wupbuild", "id": "0x218df3", "payload": "hypasswordLogin", "payload_head": "687970617373776f72644c6f67696e0070b1cc770000000000000000000000000200000000000000007970617373776f72644c6f67696e0070b1cc7700000031000000000000002b0000000000000010dfba47770000b438a88aca7700000018"}, {"type": "wupbuild", "id": "0x1", "payload": "getAppComomData", "payload_head": "676574417070436f6d6f6d44617461006261645f7765616b5f70747200772b6265004a00510064006c6f772d6c696e6500710074776f00766572746963616c2d7461620074656d706c61746520706172616d65746572206f626a65637420666f"}, {"type": "wupbuild", "id": "0x218df6", "payload": "", "payload_head": "00501c78000000aceb68ca770000000600000000000000000000000000000000000000000000000000501c78000000aceb68ca77000000060000000000000031000000000000002b000000000000006064411e750000b4d8cd8aca7700000000"}, {"type": "wupbuild", "id": "0x218df7", "payload": "report", "payload_head": "7265706f727400000000000000000000000000000000000000000000000000004cd76ba75c28be00000000000000000000000000000000000000000000000031000000000000002b00000000000000d0de6219750000b401eb8dbe7700000000"}] \ No newline at end of file diff --git a/scripts/hook_sslwrite.py b/scripts/hook_sslwrite.py new file mode 100644 index 0000000..560647b --- /dev/null +++ b/scripts/hook_sslwrite.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +"""抓 hyCred 签发链: 挂 saveLoginData(落盘) + LoginCred handler(vtable探测)。 + +用法: 脚本跑起来后, 在手机上 手动退出登录 -> 重新登录 一次。 +捕获: 登录态JSON明文(含cred字段) / saveLoginData 入参 / LoginCred handler 触发。 +""" +from __future__ import annotations + +import json +import subprocess +import time +from pathlib import Path + +import frida + +RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0") + +JS = r"""var base = Process.getModuleByName('libudbauthunify.so').base; +send({type:'armed'}); +var hooked=0; +var fns=[]; +try{ fns=DebugSymbol.findFunctionsNamed('SSL_write'); }catch(e){} +if(!fns.length){ try{ var r=new ApiResolver('module'); fns=r.enumerateMatchesSync('exports:*!SSL_write').map(function(x){return x.address;}); }catch(e2){} } +fns.slice(0,6).forEach(function(p,i){ + Interceptor.attach(p,{ onEnter:function(a){ + var len=a[2].toInt32(); if(len<40||len>20000) return; + var head=''; try{ head=a[1].readCString(Math.min(len,400)); }catch(e){ return; } + if(head.indexOf('wupudbrequest')<0 && head.indexOf('huyaudbwebui')<0) return; + var arr=new Uint8Array(a[1].readByteArray(len)); + var hex=''; for(var j=0;j>> 请在手机上退出登录并重新登录 <<<') + + sc = s.create_script(JS) + events = [] + def on_msg(m, _): + if m.get('type') == 'error': + print('JS ERR:', str(m)[:200]); return + if m.get('type') != 'send': + return + p = m.get('payload') or {} + t = p.get('type') + events.append(p) + if t == 'vt_attached': + print(f"[vt] {p['name']} slot{p['slot']} @+{p['fp']}") + elif t == 'saveLD_str': + print(f"[saveLD_str] str={p['str']['t']}:{p['str']['len']}\n bean={p['bean_head']}\n bt={p['bt']}") + elif t == 'saveLD_bean': + print(f"[saveLD_bean] flag={p['flag']}\n bean={p['bean_head']}\n bt={p['bt']}") + elif t == 'cred_unpack': + print(f"[cred_unpack] in={p['in']['t']}:{p['in']['len']}:{p['in']['v'][:160]} <- {p['ra']}") + elif t == 'bigaes': + print(f"[bigaes] len={p['x2']['len']} head={p['x2']['v'][:120]}") + elif t == 'vtable': + print(f"[VT!] {p['name']}#{p['slot']} a1={p['a1']} bt={p['bt']}") + sc.on('message', on_msg) + sc.load() + deadline = time.time() + 300 + while time.time() < deadline: + time.sleep(3) + if any(e.get('type') == 'saveLD_bean' for e in events): + time.sleep(10) + break + Path('/Users/yml/codes/douyu_login_py/evidence/sslwrite.json').write_text(json.dumps(events)) + from collections import Counter + print('saved:', Counter(e.get('type') for e in events)) + + +if __name__ == '__main__': + for attempt in range(4): + try: + main() + break + except frida.InvalidOperationError as e: + print('detached:', e) + subprocess.run(['adb', 'shell', 'am', 'force-stop', 'com.duowan.kiwi']) + time.sleep(3) diff --git a/scripts/hook_wupbuild.py b/scripts/hook_wupbuild.py new file mode 100644 index 0000000..30acc19 --- /dev/null +++ b/scripts/hook_wupbuild.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +"""抓 hyCred 签发链: 挂 saveLoginData(落盘) + LoginCred handler(vtable探测)。 + +用法: 脚本跑起来后, 在手机上 手动退出登录 -> 重新登录 一次。 +捕获: 登录态JSON明文(含cred字段) / saveLoginData 入参 / LoginCred handler 触发。 +""" +from __future__ import annotations + +import json +import subprocess +import time +from pathlib import Path + +import frida + +RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0") + +JS = r"""var base = Process.getModuleByName('libudbauthunify.so').base; +send({type:'armed'}); +function hx(p,n){ try{ return Array.from(new Uint8Array(p.readByteArray(n))).map(x=>('0'+x.toString(16)).slice(-2)).join(''); }catch(e){ return 'ERR'; } } +function rdC(p){ try{ var t=p.readCString(); return t&&t.length<20000?t.slice(0,3000):''; }catch(e){ return ''; } } +Interceptor.attach(base.add(0x274ea4), { + onEnter: function(a){ + var pl=rdC(a[1]); + send({type:'wupbuild', id:String(a[2]), payload:pl, payload_head:hx(a[1],96)}); + } +});""" + + +def main(): + d = frida.get_device_manager().add_remote_device('127.0.0.1:31877') + pid = d.spawn(['com.duowan.kiwi']) + s = d.attach(pid) + s.create_script((RE / "evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text()).load() + s.create_script((RE / "evidence/scripts/mask_frida_maps_only.js").read_text()).load() + d.resume(pid) + time.sleep(11) + s.create_script((RE / "evidence/scripts/patch_guard_block_termination.js").read_text()).load() + print('armed. >>> 请在手机上退出登录并重新登录 <<<') + + sc = s.create_script(JS) + events = [] + def on_msg(m, _): + if m.get('type') == 'error': + print('JS ERR:', str(m)[:200]); return + if m.get('type') != 'send': + return + p = m.get('payload') or {} + t = p.get('type') + events.append(p) + Path('/Users/yml/codes/douyu_login_py/evidence/wupbuild.json').write_text(json.dumps(events)) + if t == 'vt_attached': + print(f"[vt] {p['name']} slot{p['slot']} @+{p['fp']}") + elif t == 'saveLD_str': + print(f"[saveLD_str] str={p['str']['t']}:{p['str']['len']}\n bean={p['bean_head']}\n bt={p['bt']}") + elif t == 'saveLD_bean': + print(f"[saveLD_bean] flag={p['flag']}\n bean={p['bean_head']}\n bt={p['bt']}") + elif t == 'cred_unpack': + print(f"[cred_unpack] in={p['in']['t']}:{p['in']['len']}:{p['in']['v'][:160]} <- {p['ra']}") + elif t == 'bigaes': + print(f"[bigaes] len={p['x2']['len']} head={p['x2']['v'][:120]}") + elif t == 'vtable': + print(f"[VT!] {p['name']}#{p['slot']} a1={p['a1']} bt={p['bt']}") + sc.on('message', on_msg) + sc.load() + deadline = time.time() + 300 + while time.time() < deadline: + time.sleep(3) + if any(e.get('type') in ('java_cred','wupbuild') for e in events): + time.sleep(10) + break + Path('/Users/yml/codes/douyu_login_py/evidence/wupbuild.json').write_text(json.dumps(events)) + from collections import Counter + print('saved:', Counter(e.get('type') for e in events)) + + +if __name__ == '__main__': + for attempt in range(4): + try: + main() + break + except frida.InvalidOperationError as e: + print('detached:', e) + subprocess.run(['adb', 'shell', 'am', 'force-stop', 'com.duowan.kiwi']) + time.sleep(3) diff --git a/tools/huya_longconn.py b/tools/huya_longconn.py new file mode 100644 index 0000000..ae9553b --- /dev/null +++ b/tools/huya_longconn.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +"""虎牙长连接(wup.huya.com:443)纯 Python 客户端骨架。 + +协议: TAF/mobileuif 长连接; 请求包=标准 TAF RequestPacket JCE 结构 + {iVer0,cPktType1,iReqId2,sServant3,sFunc4,sBuffer5,iTimeout6,context7,status8} + createWupPackage@0x274ea4 实证: ver=3, servant=huyaudbwebui, + func=wupudbrequest_v0, sBuffer=JSON 载荷, id=arg(疑似 msgId 0x1001)。 + +用法: python3 huya_longconn.py probe # 测连通性+观察服务器主动下发 +""" +from __future__ import annotations + +import json +import socket +import ssl +import struct +import sys +import time +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) + +HOST, PORT = "wup.huya.com", 443 + + +# ---------- 极简 JCE(TAF) 编码器: 只覆盖本协议需要的类型 ---------- +def jce_zero(tag: int) -> bytes: + return bytes([(tag << 4) | 0]) # ZERO_TAG + + +def jce_int(tag: int, v: int) -> bytes: + if v == 0: + return jce_zero(tag) + if -128 <= v <= 127: + return bytes([(tag << 4) | 1]) + struct.pack(">b", v) + if -32768 <= v <= 32767: + return bytes([(tag << 4) | 2]) + struct.pack(">h", v) + if -(1 << 31) <= v <= (1 << 31) - 1: + return bytes([(tag << 4) | 3]) + struct.pack(">i", v) + return bytes([(tag << 4) | 4]) + struct.pack(">q", v) + + +def jce_str(tag: int, s: str | bytes) -> bytes: + b = s.encode() if isinstance(s, str) else s + n = len(b) + head = bytes([(tag << 4) | 6]) + if n <= 255: + return head + bytes([n]) + b + return head + b"\xff" + struct.pack(">I", n) + b # LONG_LEN + + +def jce_bytes(tag: int, b: bytes) -> bytes: + """SimpleList(vector): head byte + size(int) + data。""" + return bytes([(tag << 4) | 13, 0]) + struct.pack(">i", len(b)) + b + + +def jce_map_str(tag: int, d: dict[str, str]) -> bytes: + out = bytes([(tag << 4) | 8]) + struct.pack(">i", len(d)) + for k, v in d.items(): + out += jce_str(0, k) + jce_str(1, v) + return out + + +def taf_request_packet(req_id: int, servant: str, func: str, buffer: bytes, + timeout_ms: int = 3000, + context: dict | None = None, + status: dict | None = None) -> bytes: + body = (jce_int(0, 3) # iVersion=3 + + jce_int(1, 0) # cPacketType + + jce_int(2, req_id) # iRequestId + + jce_str(3, servant) + + jce_str(4, func) + + jce_bytes(5, buffer) + + jce_int(6, timeout_ms) + + jce_map_str(7, context or {}) + + jce_map_str(8, status or {})) + return struct.pack(">I", len(body) + 4) + body + + +# ---------- 连接层 ---------- +def connect_tls(timeout: float = 10.0) -> ssl.SSLSocket: + raw = socket.create_connection((HOST, PORT), timeout=timeout) + ctx = ssl.create_default_context() + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + return ctx.wrap_socket(raw, server_hostname=HOST) + + +def probe(recv_seconds: float = 6.0): + print(f"connect {HOST}:{PORT} ...") + s = connect_tls() + print("TLS ok:", s.version(), s.getpeercert(binary_form=False) is not None) + s.settimeout(recv_seconds) + try: + data = s.recv(4096) + print(f"server push on connect: {len(data)}B {data[:64].hex()}") + except socket.timeout: + print("no push within", recv_seconds, "s") + s.close() + + +if __name__ == "__main__": + (probe,) or None + if len(sys.argv) > 1 and sys.argv[1] == "probe": + probe()