Ingénierie inverse du protocole Ngrok v2

Ingénierie inverse du protocole Ngrok v2


ngrok est un service qui vous permet de créer des tunnels vers l'ordinateur local de l'utilisateur. En d'autres termes, une adresse publique est réservée, dont tous les appels sont renvoyés vers le port local.

Malheureusement, depuis 2016, la prise en charge de la version open-source du client (ngrok v1) a été interrompue, et pour utiliser le service, vous devez exécuter la version fermée (ngrok v2), ce qui dans de nombreux cas est inacceptable. Cet article décrit le processus d'apprentissage du protocole utilisé par un client officiel et de création d'un autre client ouvert.


Mais est-ce nécessaire? Alternatives à Ngrok


Curieusement, ce service a très peu d'alternatives. Plus précisément, trois:


  • serveo.net . Fournit des fonctionnalités similaires, mais utilise la redirection de port inverse SSH, pas un client personnalisé. Malheureusement, le projet est actuellement fermé.

    Serveo est temporairement désactivé en raison du phishing.

    Serveo reviendra dans quelques jours avec quelques nouvelles restrictions pour dissuader les abus. Merci de votre patience!

  • localtunnel.me . Il fournit uniquement un tunnel HTTP avec une distribution basée sur l'en-tête Host, et dans le cas de HTTPS, les données sont déchiffrées sur le serveur et envoyées à l'application cliente en texte clair. Le site du projet est actuellement indisponible.
  • pagekite.net . Fournit des tunnels HTTP et TLS. Après une période d'essai de 30 jours, vous devrez payer pour une utilisation ultérieure.

(PS Les commentaires suggèrent qu'il existe localhost.run qui fournit des tunnels HTTP via la redirection de port SSH, similaire à serveo.net. Cependant, apparemment, le service ne fournit que des tunnels HTTP, contrairement à ngrok)


Tentative naïve n ° 1: mitmproxy


Essayons d'écouter le trafic d'application officiel à l'aide de mitmproxy:


$ mitmproxy $ http_proxy=http://127.0.0.1:8080 https_proxy=http://127.0.0.1:8080 ngrok http 8080 #     

La demande, bien sûr, commence à jurer sur un certificat invalide. Cependant, le texte d'erreur montre que ngrok essaie de résoudre l'adresse du serveur tunnel.us.ngrok.com via DNS sur HTTPS:


 Get https://dns.google.com/resolve?cd=true&name=tunnel.us.ngrok.com&type=AAAA: x509: certificate signed by unknown authority 

Essayons de tirer tunnel.us.ngrok.com lui-même:


 $ curl https://tunnel.us.ngrok.com/ curl: (60) SSL certificate problem: unable to get local issuer certificate More details here: https://curl.haxx.se/docs/sslcerts.html curl failed to verify the legitimacy of the server and therefore could not establish a secure connection to it. To learn more about this situation and how to fix it, please visit the web page mentioned above. 

Apparemment, le client utilise l'épinglage de certificat avec un certificat auto-signé. Essayons d'ignorer l'erreur:


 $ curl -k https://tunnel.us.ngrok.com Warning: Binary output can mess up your terminal. Use "--output -" to tell Warning: curl to output it to your terminal anyway, or consider "--output Warning: <FILE>" to save to a file. $ curl -k --output - https://tunnel.us.ngrok.com     -illegal WNDINC frame length: 0x474554 

Google à la demande de "longueur de trame WNDINC illégale" fournit une bibliothèque pour les connexions Go to multiplex TCP. La même bibliothèque est mentionnée dans le problème avec un appel aux codes open source de ngrok v2.


Bibliothèque Muxado


Vérifiez si ngrok utilise vraiment la bibliothèque muxado:


 $ nm ./ngrok | grep muxado 00000000008ae2c0 T github.com/inconshreveable/muxado.(*addr).Network 00000000008ae2e0 T github.com/inconshreveable/muxado.(*addr).String 0000000000e31b40 B github.com/inconshreveable/muxado.bufferClosed 0000000000e31b50 B github.com/inconshreveable/muxado.bufferFull 00000000008ad430 T github.com/inconshreveable/muxado.Client 0000000000e31b60 B github.com/inconshreveable/muxado.closeError 00000000008b4c00 T github.com/inconshreveable/muxado.(*condWindow).Broadcast 00000000008b2ed0 T github.com/inconshreveable/muxado.(*condWindow).Decrement 00000000008b2da0 T github.com/inconshreveable/muxado.(*condWindow).Increment 00000000008b2d30 T github.com/inconshreveable/muxado.(*condWindow).Init ... 

Plusieurs conclusions peuvent être tirées de la sortie de cette commande (désolé pour la tautologie):


  1. ngrok utilise vraiment cette bibliothèque.
  2. L'auteur n'a pas tenté d'obscurcir le fichier exécutable de quelque manière que ce soit, car des caractères y étaient restés.

Notez également que l'erreur du serveur a été reçue via une connexion sécurisée (TLS), ce qui signifie que le protocole muxado est utilisé dans une session TLS. Cela suggère que les données de multiplexage sont transmises en texte clair, car un chiffrement supplémentaire serait redondant. Ainsi, pour supprimer le vidage de trafic non chiffré, il suffit d'intercepter les appels (* stream) .Read et (* stream) .Write.


Abi


Avant d'essayer d'intercepter des appels, vous devez comprendre comment les paramètres qui nous intéressent sont transmis. Pour ce faire, nous écrirons un programme simple sur Go en utilisant la bibliothèque (netcat agira en tant que destinataire):


Code
 package main import "net" import "github.com/inconshreveable/muxado" func main() { var conn net.Conn conn, _ = net.Dial("tcp", "127.0.0.1:1234") sess := muxado.Client(conn, &muxado.Config{}) conn, _ = sess.Open() data := []byte("Hello, world!") conn.Write(data) } 

Donc, pour intercepter le trafic, nous nous intéressons à:


  • Identifiant de thread unique (nécessaire pour distinguer plusieurs threads simultanément actifs).
  • Pointeur vers un tampon contenant des données.
  • La longueur du tampon de données.

La sortie de objdump sur github.com/inconshreveable/muxado.(*stream).Fonction d'écriture (drôle que les développeurs de Go ne semblent pas s'embêter à manipuler les noms.) Montre clairement le chargement des arguments de la pile:


  4de2d6: 48 8b 44 24 58 mov 0x58(%rsp),%rax 4de2db: 48 89 44 24 08 mov %rax,0x8(%rsp) 4de2e0: 48 8b 44 24 60 mov 0x60(%rsp),%rax 4de2e5: 48 89 44 24 10 mov %rax,0x10(%rsp) 4de2ea: 48 8b 44 24 68 mov 0x68(%rsp),%rax 4de2ef: 48 89 44 24 18 mov %rax,0x18(%rsp) 

Il reste à comprendre exactement où se trouvent les valeurs dont nous avons besoin sur la pile. Pour ce faire, utilisez gdb et affichez l'état de la pile au moment de l'appel de la fonction.


 Thread 1 "test" hit Breakpoint 1, github.com/inconshreveable/muxado.(*stream).Write (buf=..., err=..., n=<optimized out>, s=<optimized out>) at /home/sergey/muxado/src/github.com/inconshreveable/muxado/stream.go:81 81 func (s *stream) Write(buf []byte) (n int, err error) { (gdb) set language c Warning: the current language does not match this frame. (gdb) p {char*[4]}$rsp $1 = { 0x4e0cbf <main.main+319> "H\213l$XH\203\304`\303\350\002A\367\377\351\255\376\377\377", '\314' <repeats 13 times>, "dH\213\f%\370\377\377\377H;a\020vKH\203\354\bH\211,$H\215,$\017\266\005k\003\025", 0xc0000b4000 "", 0xc000014300 "Hello, world!", 0xd <error: Cannot access memory at address 0xd>} 

Le premier élément de ce tableau est l'adresse de retour et il ne peut pas participer au passage d'arguments. Les deux derniers, évidemment, sont l'adresse du tableau et sa longueur; puisque le pointeur vers le flux va en premier dans la liste des arguments, il est logique de supposer qu'il se trouve dans la deuxième cellule. Il peut (sous réserve) être utilisé comme identifiant unique pour le flux.

Donc, maintenant, nous savons comment les arguments de la fonction (* stream) .Write sont situés en mémoire (pour (* stream) .Lisez tout est exactement le même, car les fonctions ont le même prototype). Il reste à réaliser l'interception elle-même.


Tentative naïve n ° 2: hooks de fonction d'exécution


Essayons de rediriger les appels (* stream) .Écrivons à la fonction proxy:


Quelque chose comme ça
 unsigned long long write_hook() { volatile long long* rsp = RSP(); void* stream = (void*)rsp[1]; char* buf = (char*)rsp[2]; long long len = rsp[3]; UNSET_HOOK(); unsigned long long ans; PUSH(rsp[3]); PUSH(rsp[2]); PUSH(rsp[1]); CALL(syscall_write); POP(24, ans); SET_HOOK(); return ans; } 

Lorsque vous essayez d'appeler ngrok avec ce hook, nous obtenons un plantage de la forme suivante:


 unexpected fault address 0x0 fatal error: fault [signal SIGSEGV: segmentation violation code=0x80 addr=0x0 pc=0x4a2dc6] goroutine 1 [running]: runtime.throw(0xac7ca8, 0x5) /usr/local/Cellar/go/1.8.3/libexec/src/runtime/panic.go:596 +0x95 fp=0xc42016f9f0 sp=0xc42016f9d0 runtime.sigpanic() ... 

Ici, nous attendons un obstacle inattendu face aux goroutines. Le fait est que la pile de goroutine est allouée dynamiquement: s'il n'y a pas assez d'espace dans la pile existante, elle est allouée à nouveau à un autre endroit, et le contenu actuel est copié. Malheureusement, les fonctions générées par gcc stockent l'ancien pointeur de pile dans le registre rbp (le soi-disant pointeur de trame), et lorsque vous revenez d'une telle fonction, le pointeur de pile commence à pointer vers l'ancienne pile déjà libérée (utilisation après libération). Donc, C n'est pas une aide ici.


Tentative n ° 3: script gdb


Nous allons écrire un script pour gdb qui imprimera toutes les données transmises:


 set language c break github.com/inconshreveable/muxado.(*stream).Write commands set $stream={void*}($rsp+8) set $buf={char*}($rsp+16) set $len={long long}($rsp+24) p $stream p (*$buf)@$len p $len cont end run tcp 8080 -log stdout -log-format logfmt -log-level debug 

Cela fonctionne, mais pour un vidage complet, vous devez enregistrer les données reçues. Et ici, il y a plusieurs problèmes:


  • Pour lire les données reçues, vous devez attendre la fin de l'exécution de la fonction. Ce problème est résolu en définissant un point d'arrêt sur l'instruction ret.
  • Une fonction peut lire moins de données que prévu, tandis que le nombre d'octets réellement lus est l'une des valeurs de retour de la fonction. Vous devez comprendre comment les valeurs de retour sont transmises. (Il est également trivial d'imprimer une pile après l'exécution de la fonction. Le nombre souhaité est à $ rsp + 48).
  • Le troisième et le plus important problème. La sortie gdb n'est pas destinée à l'analyse automatique (par exemple, voir l'imprimé de la section ABI), donc les vidages ainsi obtenus ne conviennent qu'à l'analyse visuelle. (En fait, ce n'est pas un problème, car le protocole est extrêmement simple et est reconnu en un coup d'œil).

Tentative n ° 4: assemblage


En ouvrant le binaire avec ngrok objdump, vous pouvez voir qu'il y a un espace de 0xc10 = 3088 octets entre les sections .text et .rodata:


  9773eb: e9 50 ff ff ff jmpq 977340 <type..eq.[2]github.com/kevinburke/cli.Flag>   .rodata: 0000000000978000 <type.*>: 

Le même espace est présent dans le fichier lui-même, où l'espace vide est rempli de zéro octet. Cela vous permet de modifier la taille du segment contenant la section .text enregistrée dans le fichier (rechercher / remplacer dans l'éditeur hexadécimal) et d'ajouter du code à l'espace de journalisation des appels.

L'instruction de transition relative sur l'architecture x86_64 prend 5 octets: opcode (E9) + décalage à l'adresse finale (signé int). Étant donné que la taille du fichier exécutable ngrok est bien inférieure à 2 gigaoctets, cette instruction vous permet de transférer le contrôle n'importe où dans la section .text, y compris notre nouveau code.

La première instruction des deux fonctions prend 9 octets, donc les 5 premiers octets de l'instruction peuvent être remplacés par une instruction de saut:


  8b0e70: 64 48 8b 0c 25 f8 ff mov %fs:0xfffffffffffffff8,%rcx 8b0e77: ff ff 

Pour appeler la fonction d'origine, suivez simplement les instructions d'origine et allez à l'adresse func + 9

Avec l'instruction ret dans la fonction (* stream) .Lire, tout est beaucoup plus intéressant:


  8b0f6d: 7f 22 jg 8b0f91 <github.com/inconshreveable/muxado.(*stream).Read+0xa1> ... 8b0f8c: 48 83 c4 58 add $0x58,%rsp 8b0f90: c3 retq 8b0f91: 48 8b 5c 24 60 mov 0x60(%rsp),%rbx 

L'instruction ret (écrite sous la forme retq, par opposition à retf) occupe seulement 1 octet, tandis que l'instruction qui la suit est une cible de saut, vous ne pouvez donc pas la modifier. Cependant, l'instruction de transition n'est exécutée nulle part sur l'instruction ret elle-même, donc rien ne l'empêche d'être remplacée par la transition avec l'instruction précédente (après la transition, bien sûr, elle devra être terminée).


Code d'enregistrement de l'assembleur complet
 section .text org 0x9773f0 use64 write_pre_hook: ;      (*stream).Write push dword 0x74697277 call log add rsp, 8 mov rcx, [fs:-8] ;   (*stream).Write    jmp 0x8b0e79 read_post_hook: ;     0x8b0f8c add rsp, 0x58 ;    ret, .  push dword 0x64616572 call log add rsp, 8 ret log: ;  ,         push rdi push rsi push rdx push rax ;; stack layout: ;; rsp+32 ret ;; rsp+40 kind ('read' or 'writ') ;; rsp+48 ret0 ;; rsp+56 &stream ;; rsp+64 buf ;; rsp+72 len ;; rsp+80 unknown ;; rsp+88 n ;; rsp+96 err ;       mmap mov rax, 9 mov rdi, 0 mov rsi, 44 add rsi, [rsp+72] mov rdx, 3 push r10 push r8 push r9 mov r10, 34 mov r8, -1 mov r9, 0 syscall test rax, rax js segfault pop r9 pop r8 pop r10 ;     mov edi, [rsp+40] mov [rax], edi lea rdi, [rax+4] lea rsi, [rsp+56] push rcx mov rcx, 40 ;up to rsp+96 rep movsb ;    mov rsi, [rsp+72] ;rsp+64 mov rcx, [rsp+80] ;rsp+72 rep movsb pop rcx ;  write mov rdi, 3 mov rsi, rax mov rdx, 44 add rdx, [rsp+72] push rax call writeall ;   pop rdi mov rsi, 44 add rsi, [rsp+72] mov rax, 11 syscall test rax, rax jnz segfault ;   pop rax pop rdx pop rsi pop rdi ret writeall: mov rax, 1 syscall test rax, rax js segfault add rsi, rax sub rdx, rax test rdx, rdx jnz writeall ret segfault: ;          mov [0], rax 
Programme Python pour analyser les journaux
 import sys stream = sys.stdin.buffer while True: chunk = stream.read(44) if not chunk: break assert len(chunk) == 44 kind = chunk[:4].decode('ascii') assert kind in ('read', 'writ') str_id = hex(int.from_bytes(chunk[4:12], 'little')) l = int.from_bytes(chunk[20:28], 'little') n = int.from_bytes(chunk[36:], 'little') if kind == 'read' else l buf = stream.read(l) assert len(buf) == l if '--full-data' not in sys.argv: buf = buf[:n] print('((%r, %s, %d), (%r, %d))'%(kind, str_id, l, buf, n)) 

Ainsi, nous avons maintenant un outil de travail pour supprimer les vidages de trafic de ngrok. Découvrez-le en action!


Vidage
 (('writ', 0xc420326600, 4), (b'\x00\x00\x00\x00', 4)) (('writ', 0xc420148e00, 4), (b'\xff\xff\xff\xff', 4)) (('writ', 0xc420326600, 227), (b'{"Version":["2"],"ClientId":"","Extra":{"OS":"linux","Arch":"amd64","Authtoken":"3FjYRxVDd2QvNkX13h82k_6Thwfp93PUZEpsz3vYe5v","Version":"2.2.8","Hostname":"tunnel.us.ngrok.com","UserAgent":"ngrok/2","Metadata":"","Cookie":""}}\n', 227)) (('read', 0xc420326600, 512), (b'{"Version":"2","ClientId":"df05b949e58359ea6901cff60935531d","Error":"","Extra":{"Version":"prod","Cookie":"lh7YagbqJ9ixLYyE05ZDMPvaYNVm5isu$xF1Mp8fDc689269YUGlGNAV/0XRyrEH390rwGqILZqYS5+qDUNbMn2l4puKD2CJHAgI83yo49aopujf0uhPBm4t997BTBvpFSg+zrgnrW9cRNuO8ApSe2+OPpUuPK0GZYZ1bpbz7Pod7cJycwVIgDFZZXLxEeNdXylQxSax9YOxgxcHeLBa79OjqrJpEUUWYtTNiMa5wxkr0AwKh","AccountName":"\xd0\xa1\xd0\xb5\xd1\x80\xd0\xb3\xd0\xb5\xd0\xb9 \xd0\x9b\xd0\xb8\xd1\x81\xd0\xbe\xd0\xb2","SessionDuration":0,"PlanName":"Free"}}', 430)) (('read', 0xc420148a00, 4), (b'\xff\xff\xff\xff', 4)) (('writ', 0xc420148200, 4), (b'\x00\x00\x00\x01', 4)) (('writ', 0xc420148200, 111), (b'{"Id":"","Proto":"https","Opts":{"Hostname":"","Auth":"","Subdomain":""},"Extra":{"Balance":false,"Token":""}}\n', 111)) (('read', 0xc420148200, 512), (b'{"Id":"39b1e32e134eff8671b02268945643f9","URL":"https://deb82e2e.ngrok.io","Proto":"https","Opts":{"Hostname":"deb82e2e.ngrok.io","Auth":"","Subdomain":"","HostHeaderRewrite":false,"LocalURLScheme":""},"Error":"","Extra":{"Token":"WUiWWfM9kRbpFpXoEOCydiJdEob7BKN0$EHrXSWq/fY/mRDRSTNqkVWEVCDJUyBdMSU5uSEMH5RHq5D9W1gA1BTWTUEUbltyhQIlhTJvGxezhDeOYqGe5CwNFHnIOVNidToULds48FCVdWc0zRC3Djyack74P9mQ11VHKQKAXPzXUXlUbo6TRkwMWKrpN0q93pmL3fQamRP6cREZTl2YMdnFUZtwHwyh4LGacxGAvdCP867rTKBL/3eWLdkcF2lSPdHuH8V51RzCMWMIbvmtyySzE', 512)) (('read', 0xc420148200, 1024), (b'cOIiZ09W6pMPTHoTcih0"}}', 23)) (('writ', 0xc4200ea200, 4), (b'\x00\x00\x00\x01', 4)) (('writ', 0xc4200ea200, 127), (b'{"Id":"","Proto":"http","Opts":{"Hostname":"deb82e2e.ngrok.io","Auth":"","Subdomain":""},"Extra":{"Balance":false,"Token":""}}\n', 127)) (('read', 0xc4200ea200, 512), (b'{"Id":"9714ddd4cb111adf6599f099cec98482","URL":"http://deb82e2e.ngrok.io","Proto":"http","Opts":{"Hostname":"deb82e2e.ngrok.io","Auth":"","Subdomain":"","HostHeaderRewrite":false,"LocalURLScheme":""},"Error":"","Extra":{"Token":"G4nIrca8GTvq4H62sTmqdb144FmhMgrg$U6TwkKWafv/3+bFM5AP7xIFfkWqx+HUsYWhkYXivrtMfcqan0mKZx99LHGI7mm5lOMmvI+Kdy7WF/GnwrMDXrRFwhYowczaWKRKnUimnNtndq7rdttMevFabwe5WSzwf+IZhWzQ2yvcW31+qVuS7F6uykUSw+mnBNtsdXFSNpToagqQOM66A8LT+l3f3OOHKrWpdq39Bz2RfoRmXaRpkDrdfT6vPUQd6S8uVUnv3t2173Ik7AgT9PlzOMJ', 512)) (('read', 0xc4200ea200, 1024), (b'hhVDbeM2HP+qV6S5I="}}', 21)) (('writ', 0xc420148e00, 4), (b'\x00ys7', 4)) (('writ', 0xc420148e00, 4), (b'\x00ys7', 4)) (('read', 0xc420148a00, 4), (b'\x00ys7', 4)) (('writ', 0xc420148a00, 4), (b'\x00ys7', 4)) (('read', 0xc420148e00, 4), (b'\x00ys7', 4)) (('writ', 0xc420148e00, 4), (b'\x11|\xb9\x99', 4)) (('read', 0xc420148a00, 4), (b'\x11|\xb9\x99', 4)) (('writ', 0xc420148a00, 4), (b'\x11|\xb9\x99', 4)) (('read', 0xc420148e00, 4), (b'\x11|\xb9\x99', 4)) (('read', 0xc4200ea200, 4), (b'\x00\x00\x00\x03', 4)) (('read', 0xc4200ea200, 8), (b'M\x00\x00\x00\x00\x00\x00\x00', 8)) (('read', 0xc4200ea200, 77), (b'{"Id":"9714ddd4cb111adf6599f099cec98482","ClientAddr":"***.***.**.***:17815"}', 77)) (('read', 0xc4200ea200, 32768), (b'GET / HTTP/1.1\r\nHost: deb82e2e.ngrok.io\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Safari/605.1.15 Midori/6\r\nAccept-Encoding: gzip, deflate\r\nAccept-Language: ru-RU\r\nX-Forwarded-For: ***.***.**.***\r\n\r\n', 359)) (('writ', 0xc420148e00, 4), (b'\x0e\xb2\xc0\x01', 4)) (('read', 0xc420148a00, 4), (b'\x0e\xb2\xc0\x01', 4)) (('writ', 0xc420148a00, 4), (b'\x0e\xb2\xc0\x01', 4)) (('read', 0xc420148e00, 4), (b'\x0e\xb2\xc0\x01', 4)) (('writ', 0xc420148e00, 4), (b'X\xa3?+', 4)) (('read', 0xc420148a00, 4), (b'X\xa3?+', 4)) (('writ', 0xc420148a00, 4), (b'X\xa3?+', 4)) (('read', 0xc420148e00, 4), (b'X\xa3?+', 4)) (('read', 0xc4200ea200, 32768), (b'', 0)) xF1Mp8fDc689269YUGlGNAV / 0XRyrEH390rwGqILZqYS5 + qDUNbMn2l4puKD2CJHAgI83yo49aopujf0uhPBm4t997BTBvpFSg + zrgnrW9cRNuO8ApSe2 + OPpUuPK0GZYZ1bpbz7Pod7cJycwVIgDFZZXLxEeNdXylQxSax9YOxgxcHeLBa79OjqrJpEUUWYtTNiMa5wxkr0AwKh", "AccountName": « \ xd0 \ xa1 \ xd0 \ XB5 \ xd1 \ x80 \ xd0 \ xB3 \ xd0 \ XB5 \ xd0 \ XB9 \ xd0 (('writ', 0xc420326600, 4), (b'\x00\x00\x00\x00', 4)) (('writ', 0xc420148e00, 4), (b'\xff\xff\xff\xff', 4)) (('writ', 0xc420326600, 227), (b'{"Version":["2"],"ClientId":"","Extra":{"OS":"linux","Arch":"amd64","Authtoken":"3FjYRxVDd2QvNkX13h82k_6Thwfp93PUZEpsz3vYe5v","Version":"2.2.8","Hostname":"tunnel.us.ngrok.com","UserAgent":"ngrok/2","Metadata":"","Cookie":""}}\n', 227)) (('read', 0xc420326600, 512), (b'{"Version":"2","ClientId":"df05b949e58359ea6901cff60935531d","Error":"","Extra":{"Version":"prod","Cookie":"lh7YagbqJ9ixLYyE05ZDMPvaYNVm5isu$xF1Mp8fDc689269YUGlGNAV/0XRyrEH390rwGqILZqYS5+qDUNbMn2l4puKD2CJHAgI83yo49aopujf0uhPBm4t997BTBvpFSg+zrgnrW9cRNuO8ApSe2+OPpUuPK0GZYZ1bpbz7Pod7cJycwVIgDFZZXLxEeNdXylQxSax9YOxgxcHeLBa79OjqrJpEUUWYtTNiMa5wxkr0AwKh","AccountName":"\xd0\xa1\xd0\xb5\xd1\x80\xd0\xb3\xd0\xb5\xd0\xb9 \xd0\x9b\xd0\xb8\xd1\x81\xd0\xbe\xd0\xb2","SessionDuration":0,"PlanName":"Free"}}', 430)) (('read', 0xc420148a00, 4), (b'\xff\xff\xff\xff', 4)) (('writ', 0xc420148200, 4), (b'\x00\x00\x00\x01', 4)) (('writ', 0xc420148200, 111), (b'{"Id":"","Proto":"https","Opts":{"Hostname":"","Auth":"","Subdomain":""},"Extra":{"Balance":false,"Token":""}}\n', 111)) (('read', 0xc420148200, 512), (b'{"Id":"39b1e32e134eff8671b02268945643f9","URL":"https://deb82e2e.ngrok.io","Proto":"https","Opts":{"Hostname":"deb82e2e.ngrok.io","Auth":"","Subdomain":"","HostHeaderRewrite":false,"LocalURLScheme":""},"Error":"","Extra":{"Token":"WUiWWfM9kRbpFpXoEOCydiJdEob7BKN0$EHrXSWq/fY/mRDRSTNqkVWEVCDJUyBdMSU5uSEMH5RHq5D9W1gA1BTWTUEUbltyhQIlhTJvGxezhDeOYqGe5CwNFHnIOVNidToULds48FCVdWc0zRC3Djyack74P9mQ11VHKQKAXPzXUXlUbo6TRkwMWKrpN0q93pmL3fQamRP6cREZTl2YMdnFUZtwHwyh4LGacxGAvdCP867rTKBL/3eWLdkcF2lSPdHuH8V51RzCMWMIbvmtyySzE', 512)) (('read', 0xc420148200, 1024), (b'cOIiZ09W6pMPTHoTcih0"}}', 23)) (('writ', 0xc4200ea200, 4), (b'\x00\x00\x00\x01', 4)) (('writ', 0xc4200ea200, 127), (b'{"Id":"","Proto":"http","Opts":{"Hostname":"deb82e2e.ngrok.io","Auth":"","Subdomain":""},"Extra":{"Balance":false,"Token":""}}\n', 127)) (('read', 0xc4200ea200, 512), (b'{"Id":"9714ddd4cb111adf6599f099cec98482","URL":"http://deb82e2e.ngrok.io","Proto":"http","Opts":{"Hostname":"deb82e2e.ngrok.io","Auth":"","Subdomain":"","HostHeaderRewrite":false,"LocalURLScheme":""},"Error":"","Extra":{"Token":"G4nIrca8GTvq4H62sTmqdb144FmhMgrg$U6TwkKWafv/3+bFM5AP7xIFfkWqx+HUsYWhkYXivrtMfcqan0mKZx99LHGI7mm5lOMmvI+Kdy7WF/GnwrMDXrRFwhYowczaWKRKnUimnNtndq7rdttMevFabwe5WSzwf+IZhWzQ2yvcW31+qVuS7F6uykUSw+mnBNtsdXFSNpToagqQOM66A8LT+l3f3OOHKrWpdq39Bz2RfoRmXaRpkDrdfT6vPUQd6S8uVUnv3t2173Ik7AgT9PlzOMJ', 512)) (('read', 0xc4200ea200, 1024), (b'hhVDbeM2HP+qV6S5I="}}', 21)) (('writ', 0xc420148e00, 4), (b'\x00ys7', 4)) (('writ', 0xc420148e00, 4), (b'\x00ys7', 4)) (('read', 0xc420148a00, 4), (b'\x00ys7', 4)) (('writ', 0xc420148a00, 4), (b'\x00ys7', 4)) (('read', 0xc420148e00, 4), (b'\x00ys7', 4)) (('writ', 0xc420148e00, 4), (b'\x11|\xb9\x99', 4)) (('read', 0xc420148a00, 4), (b'\x11|\xb9\x99', 4)) (('writ', 0xc420148a00, 4), (b'\x11|\xb9\x99', 4)) (('read', 0xc420148e00, 4), (b'\x11|\xb9\x99', 4)) (('read', 0xc4200ea200, 4), (b'\x00\x00\x00\x03', 4)) (('read', 0xc4200ea200, 8), (b'M\x00\x00\x00\x00\x00\x00\x00', 8)) (('read', 0xc4200ea200, 77), (b'{"Id":"9714ddd4cb111adf6599f099cec98482","ClientAddr":"***.***.**.***:17815"}', 77)) (('read', 0xc4200ea200, 32768), (b'GET / HTTP/1.1\r\nHost: deb82e2e.ngrok.io\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Safari/605.1.15 Midori/6\r\nAccept-Encoding: gzip, deflate\r\nAccept-Language: ru-RU\r\nX-Forwarded-For: ***.***.**.***\r\n\r\n', 359)) (('writ', 0xc420148e00, 4), (b'\x0e\xb2\xc0\x01', 4)) (('read', 0xc420148a00, 4), (b'\x0e\xb2\xc0\x01', 4)) (('writ', 0xc420148a00, 4), (b'\x0e\xb2\xc0\x01', 4)) (('read', 0xc420148e00, 4), (b'\x0e\xb2\xc0\x01', 4)) (('writ', 0xc420148e00, 4), (b'X\xa3?+', 4)) (('read', 0xc420148a00, 4), (b'X\xa3?+', 4)) (('writ', 0xc420148a00, 4), (b'X\xa3?+', 4)) (('read', 0xc420148e00, 4), (b'X\xa3?+', 4)) (('read', 0xc4200ea200, 32768), (b'', 0)) " WUiWWfM9kRbpFpXoEOCydiJdEob7BKN0 $ EHrXSWq / fY / mRDRSTNqkVWEVCDJUyBdMSU5uSEMH5RHq5D9W1gA1BTWTUEUbltyhQIlhTJvGxezhDeOYqGe5CwNFHnIOVNidToULds48FCVdWc0zRC3Djyack74P9mQ11VHKQKAXPzXUXlUbo6TRkwMWKrpN0q93pmL3fQamRP6cREZTl2YMdnFUZtwHwyh4LGacxGAvdCP867rTKBL / 3eWLdkcF2lSPdHuH8V51RzCMWMIbvmtyySzE », (('writ', 0xc420326600, 4), (b'\x00\x00\x00\x00', 4)) (('writ', 0xc420148e00, 4), (b'\xff\xff\xff\xff', 4)) (('writ', 0xc420326600, 227), (b'{"Version":["2"],"ClientId":"","Extra":{"OS":"linux","Arch":"amd64","Authtoken":"3FjYRxVDd2QvNkX13h82k_6Thwfp93PUZEpsz3vYe5v","Version":"2.2.8","Hostname":"tunnel.us.ngrok.com","UserAgent":"ngrok/2","Metadata":"","Cookie":""}}\n', 227)) (('read', 0xc420326600, 512), (b'{"Version":"2","ClientId":"df05b949e58359ea6901cff60935531d","Error":"","Extra":{"Version":"prod","Cookie":"lh7YagbqJ9ixLYyE05ZDMPvaYNVm5isu$xF1Mp8fDc689269YUGlGNAV/0XRyrEH390rwGqILZqYS5+qDUNbMn2l4puKD2CJHAgI83yo49aopujf0uhPBm4t997BTBvpFSg+zrgnrW9cRNuO8ApSe2+OPpUuPK0GZYZ1bpbz7Pod7cJycwVIgDFZZXLxEeNdXylQxSax9YOxgxcHeLBa79OjqrJpEUUWYtTNiMa5wxkr0AwKh","AccountName":"\xd0\xa1\xd0\xb5\xd1\x80\xd0\xb3\xd0\xb5\xd0\xb9 \xd0\x9b\xd0\xb8\xd1\x81\xd0\xbe\xd0\xb2","SessionDuration":0,"PlanName":"Free"}}', 430)) (('read', 0xc420148a00, 4), (b'\xff\xff\xff\xff', 4)) (('writ', 0xc420148200, 4), (b'\x00\x00\x00\x01', 4)) (('writ', 0xc420148200, 111), (b'{"Id":"","Proto":"https","Opts":{"Hostname":"","Auth":"","Subdomain":""},"Extra":{"Balance":false,"Token":""}}\n', 111)) (('read', 0xc420148200, 512), (b'{"Id":"39b1e32e134eff8671b02268945643f9","URL":"https://deb82e2e.ngrok.io","Proto":"https","Opts":{"Hostname":"deb82e2e.ngrok.io","Auth":"","Subdomain":"","HostHeaderRewrite":false,"LocalURLScheme":""},"Error":"","Extra":{"Token":"WUiWWfM9kRbpFpXoEOCydiJdEob7BKN0$EHrXSWq/fY/mRDRSTNqkVWEVCDJUyBdMSU5uSEMH5RHq5D9W1gA1BTWTUEUbltyhQIlhTJvGxezhDeOYqGe5CwNFHnIOVNidToULds48FCVdWc0zRC3Djyack74P9mQ11VHKQKAXPzXUXlUbo6TRkwMWKrpN0q93pmL3fQamRP6cREZTl2YMdnFUZtwHwyh4LGacxGAvdCP867rTKBL/3eWLdkcF2lSPdHuH8V51RzCMWMIbvmtyySzE', 512)) (('read', 0xc420148200, 1024), (b'cOIiZ09W6pMPTHoTcih0"}}', 23)) (('writ', 0xc4200ea200, 4), (b'\x00\x00\x00\x01', 4)) (('writ', 0xc4200ea200, 127), (b'{"Id":"","Proto":"http","Opts":{"Hostname":"deb82e2e.ngrok.io","Auth":"","Subdomain":""},"Extra":{"Balance":false,"Token":""}}\n', 127)) (('read', 0xc4200ea200, 512), (b'{"Id":"9714ddd4cb111adf6599f099cec98482","URL":"http://deb82e2e.ngrok.io","Proto":"http","Opts":{"Hostname":"deb82e2e.ngrok.io","Auth":"","Subdomain":"","HostHeaderRewrite":false,"LocalURLScheme":""},"Error":"","Extra":{"Token":"G4nIrca8GTvq4H62sTmqdb144FmhMgrg$U6TwkKWafv/3+bFM5AP7xIFfkWqx+HUsYWhkYXivrtMfcqan0mKZx99LHGI7mm5lOMmvI+Kdy7WF/GnwrMDXrRFwhYowczaWKRKnUimnNtndq7rdttMevFabwe5WSzwf+IZhWzQ2yvcW31+qVuS7F6uykUSw+mnBNtsdXFSNpToagqQOM66A8LT+l3f3OOHKrWpdq39Bz2RfoRmXaRpkDrdfT6vPUQd6S8uVUnv3t2173Ik7AgT9PlzOMJ', 512)) (('read', 0xc4200ea200, 1024), (b'hhVDbeM2HP+qV6S5I="}}', 21)) (('writ', 0xc420148e00, 4), (b'\x00ys7', 4)) (('writ', 0xc420148e00, 4), (b'\x00ys7', 4)) (('read', 0xc420148a00, 4), (b'\x00ys7', 4)) (('writ', 0xc420148a00, 4), (b'\x00ys7', 4)) (('read', 0xc420148e00, 4), (b'\x00ys7', 4)) (('writ', 0xc420148e00, 4), (b'\x11|\xb9\x99', 4)) (('read', 0xc420148a00, 4), (b'\x11|\xb9\x99', 4)) (('writ', 0xc420148a00, 4), (b'\x11|\xb9\x99', 4)) (('read', 0xc420148e00, 4), (b'\x11|\xb9\x99', 4)) (('read', 0xc4200ea200, 4), (b'\x00\x00\x00\x03', 4)) (('read', 0xc4200ea200, 8), (b'M\x00\x00\x00\x00\x00\x00\x00', 8)) (('read', 0xc4200ea200, 77), (b'{"Id":"9714ddd4cb111adf6599f099cec98482","ClientAddr":"***.***.**.***:17815"}', 77)) (('read', 0xc4200ea200, 32768), (b'GET / HTTP/1.1\r\nHost: deb82e2e.ngrok.io\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Safari/605.1.15 Midori/6\r\nAccept-Encoding: gzip, deflate\r\nAccept-Language: ru-RU\r\nX-Forwarded-For: ***.***.**.***\r\n\r\n', 359)) (('writ', 0xc420148e00, 4), (b'\x0e\xb2\xc0\x01', 4)) (('read', 0xc420148a00, 4), (b'\x0e\xb2\xc0\x01', 4)) (('writ', 0xc420148a00, 4), (b'\x0e\xb2\xc0\x01', 4)) (('read', 0xc420148e00, 4), (b'\x0e\xb2\xc0\x01', 4)) (('writ', 0xc420148e00, 4), (b'X\xa3?+', 4)) (('read', 0xc420148a00, 4), (b'X\xa3?+', 4)) (('writ', 0xc420148a00, 4), (b'X\xa3?+', 4)) (('read', 0xc420148e00, 4), (b'X\xa3?+', 4)) (('read', 0xc4200ea200, 32768), (b'', 0)) " G4nIrca8GTvq4H62sTmqdb144FmhMgrg $ U6TwkKWafv / (('writ', 0xc420326600, 4), (b'\x00\x00\x00\x00', 4)) (('writ', 0xc420148e00, 4), (b'\xff\xff\xff\xff', 4)) (('writ', 0xc420326600, 227), (b'{"Version":["2"],"ClientId":"","Extra":{"OS":"linux","Arch":"amd64","Authtoken":"3FjYRxVDd2QvNkX13h82k_6Thwfp93PUZEpsz3vYe5v","Version":"2.2.8","Hostname":"tunnel.us.ngrok.com","UserAgent":"ngrok/2","Metadata":"","Cookie":""}}\n', 227)) (('read', 0xc420326600, 512), (b'{"Version":"2","ClientId":"df05b949e58359ea6901cff60935531d","Error":"","Extra":{"Version":"prod","Cookie":"lh7YagbqJ9ixLYyE05ZDMPvaYNVm5isu$xF1Mp8fDc689269YUGlGNAV/0XRyrEH390rwGqILZqYS5+qDUNbMn2l4puKD2CJHAgI83yo49aopujf0uhPBm4t997BTBvpFSg+zrgnrW9cRNuO8ApSe2+OPpUuPK0GZYZ1bpbz7Pod7cJycwVIgDFZZXLxEeNdXylQxSax9YOxgxcHeLBa79OjqrJpEUUWYtTNiMa5wxkr0AwKh","AccountName":"\xd0\xa1\xd0\xb5\xd1\x80\xd0\xb3\xd0\xb5\xd0\xb9 \xd0\x9b\xd0\xb8\xd1\x81\xd0\xbe\xd0\xb2","SessionDuration":0,"PlanName":"Free"}}', 430)) (('read', 0xc420148a00, 4), (b'\xff\xff\xff\xff', 4)) (('writ', 0xc420148200, 4), (b'\x00\x00\x00\x01', 4)) (('writ', 0xc420148200, 111), (b'{"Id":"","Proto":"https","Opts":{"Hostname":"","Auth":"","Subdomain":""},"Extra":{"Balance":false,"Token":""}}\n', 111)) (('read', 0xc420148200, 512), (b'{"Id":"39b1e32e134eff8671b02268945643f9","URL":"https://deb82e2e.ngrok.io","Proto":"https","Opts":{"Hostname":"deb82e2e.ngrok.io","Auth":"","Subdomain":"","HostHeaderRewrite":false,"LocalURLScheme":""},"Error":"","Extra":{"Token":"WUiWWfM9kRbpFpXoEOCydiJdEob7BKN0$EHrXSWq/fY/mRDRSTNqkVWEVCDJUyBdMSU5uSEMH5RHq5D9W1gA1BTWTUEUbltyhQIlhTJvGxezhDeOYqGe5CwNFHnIOVNidToULds48FCVdWc0zRC3Djyack74P9mQ11VHKQKAXPzXUXlUbo6TRkwMWKrpN0q93pmL3fQamRP6cREZTl2YMdnFUZtwHwyh4LGacxGAvdCP867rTKBL/3eWLdkcF2lSPdHuH8V51RzCMWMIbvmtyySzE', 512)) (('read', 0xc420148200, 1024), (b'cOIiZ09W6pMPTHoTcih0"}}', 23)) (('writ', 0xc4200ea200, 4), (b'\x00\x00\x00\x01', 4)) (('writ', 0xc4200ea200, 127), (b'{"Id":"","Proto":"http","Opts":{"Hostname":"deb82e2e.ngrok.io","Auth":"","Subdomain":""},"Extra":{"Balance":false,"Token":""}}\n', 127)) (('read', 0xc4200ea200, 512), (b'{"Id":"9714ddd4cb111adf6599f099cec98482","URL":"http://deb82e2e.ngrok.io","Proto":"http","Opts":{"Hostname":"deb82e2e.ngrok.io","Auth":"","Subdomain":"","HostHeaderRewrite":false,"LocalURLScheme":""},"Error":"","Extra":{"Token":"G4nIrca8GTvq4H62sTmqdb144FmhMgrg$U6TwkKWafv/3+bFM5AP7xIFfkWqx+HUsYWhkYXivrtMfcqan0mKZx99LHGI7mm5lOMmvI+Kdy7WF/GnwrMDXrRFwhYowczaWKRKnUimnNtndq7rdttMevFabwe5WSzwf+IZhWzQ2yvcW31+qVuS7F6uykUSw+mnBNtsdXFSNpToagqQOM66A8LT+l3f3OOHKrWpdq39Bz2RfoRmXaRpkDrdfT6vPUQd6S8uVUnv3t2173Ik7AgT9PlzOMJ', 512)) (('read', 0xc4200ea200, 1024), (b'hhVDbeM2HP+qV6S5I="}}', 21)) (('writ', 0xc420148e00, 4), (b'\x00ys7', 4)) (('writ', 0xc420148e00, 4), (b'\x00ys7', 4)) (('read', 0xc420148a00, 4), (b'\x00ys7', 4)) (('writ', 0xc420148a00, 4), (b'\x00ys7', 4)) (('read', 0xc420148e00, 4), (b'\x00ys7', 4)) (('writ', 0xc420148e00, 4), (b'\x11|\xb9\x99', 4)) (('read', 0xc420148a00, 4), (b'\x11|\xb9\x99', 4)) (('writ', 0xc420148a00, 4), (b'\x11|\xb9\x99', 4)) (('read', 0xc420148e00, 4), (b'\x11|\xb9\x99', 4)) (('read', 0xc4200ea200, 4), (b'\x00\x00\x00\x03', 4)) (('read', 0xc4200ea200, 8), (b'M\x00\x00\x00\x00\x00\x00\x00', 8)) (('read', 0xc4200ea200, 77), (b'{"Id":"9714ddd4cb111adf6599f099cec98482","ClientAddr":"***.***.**.***:17815"}', 77)) (('read', 0xc4200ea200, 32768), (b'GET / HTTP/1.1\r\nHost: deb82e2e.ngrok.io\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Safari/605.1.15 Midori/6\r\nAccept-Encoding: gzip, deflate\r\nAccept-Language: ru-RU\r\nX-Forwarded-For: ***.***.**.***\r\n\r\n', 359)) (('writ', 0xc420148e00, 4), (b'\x0e\xb2\xc0\x01', 4)) (('read', 0xc420148a00, 4), (b'\x0e\xb2\xc0\x01', 4)) (('writ', 0xc420148a00, 4), (b'\x0e\xb2\xc0\x01', 4)) (('read', 0xc420148e00, 4), (b'\x0e\xb2\xc0\x01', 4)) (('writ', 0xc420148e00, 4), (b'X\xa3?+', 4)) (('read', 0xc420148a00, 4), (b'X\xa3?+', 4)) (('writ', 0xc420148a00, 4), (b'X\xa3?+', 4)) (('read', 0xc420148e00, 4), (b'X\xa3?+', 4)) (('read', 0xc4200ea200, 32768), (b'', 0)) + IZhWzQ2yvcW31 + qVuS7F6uykUSw + mnBNtsdXFSNpToagqQOM66A8LT + l3f3OOHKrWpdq39Bz2RfoRmXaRpkDrdfT6vPUQd6S8uVUnv3t2173Ik7AgT9PlzOMJ », (('writ', 0xc420326600, 4), (b'\x00\x00\x00\x00', 4)) (('writ', 0xc420148e00, 4), (b'\xff\xff\xff\xff', 4)) (('writ', 0xc420326600, 227), (b'{"Version":["2"],"ClientId":"","Extra":{"OS":"linux","Arch":"amd64","Authtoken":"3FjYRxVDd2QvNkX13h82k_6Thwfp93PUZEpsz3vYe5v","Version":"2.2.8","Hostname":"tunnel.us.ngrok.com","UserAgent":"ngrok/2","Metadata":"","Cookie":""}}\n', 227)) (('read', 0xc420326600, 512), (b'{"Version":"2","ClientId":"df05b949e58359ea6901cff60935531d","Error":"","Extra":{"Version":"prod","Cookie":"lh7YagbqJ9ixLYyE05ZDMPvaYNVm5isu$xF1Mp8fDc689269YUGlGNAV/0XRyrEH390rwGqILZqYS5+qDUNbMn2l4puKD2CJHAgI83yo49aopujf0uhPBm4t997BTBvpFSg+zrgnrW9cRNuO8ApSe2+OPpUuPK0GZYZ1bpbz7Pod7cJycwVIgDFZZXLxEeNdXylQxSax9YOxgxcHeLBa79OjqrJpEUUWYtTNiMa5wxkr0AwKh","AccountName":"\xd0\xa1\xd0\xb5\xd1\x80\xd0\xb3\xd0\xb5\xd0\xb9 \xd0\x9b\xd0\xb8\xd1\x81\xd0\xbe\xd0\xb2","SessionDuration":0,"PlanName":"Free"}}', 430)) (('read', 0xc420148a00, 4), (b'\xff\xff\xff\xff', 4)) (('writ', 0xc420148200, 4), (b'\x00\x00\x00\x01', 4)) (('writ', 0xc420148200, 111), (b'{"Id":"","Proto":"https","Opts":{"Hostname":"","Auth":"","Subdomain":""},"Extra":{"Balance":false,"Token":""}}\n', 111)) (('read', 0xc420148200, 512), (b'{"Id":"39b1e32e134eff8671b02268945643f9","URL":"https://deb82e2e.ngrok.io","Proto":"https","Opts":{"Hostname":"deb82e2e.ngrok.io","Auth":"","Subdomain":"","HostHeaderRewrite":false,"LocalURLScheme":""},"Error":"","Extra":{"Token":"WUiWWfM9kRbpFpXoEOCydiJdEob7BKN0$EHrXSWq/fY/mRDRSTNqkVWEVCDJUyBdMSU5uSEMH5RHq5D9W1gA1BTWTUEUbltyhQIlhTJvGxezhDeOYqGe5CwNFHnIOVNidToULds48FCVdWc0zRC3Djyack74P9mQ11VHKQKAXPzXUXlUbo6TRkwMWKrpN0q93pmL3fQamRP6cREZTl2YMdnFUZtwHwyh4LGacxGAvdCP867rTKBL/3eWLdkcF2lSPdHuH8V51RzCMWMIbvmtyySzE', 512)) (('read', 0xc420148200, 1024), (b'cOIiZ09W6pMPTHoTcih0"}}', 23)) (('writ', 0xc4200ea200, 4), (b'\x00\x00\x00\x01', 4)) (('writ', 0xc4200ea200, 127), (b'{"Id":"","Proto":"http","Opts":{"Hostname":"deb82e2e.ngrok.io","Auth":"","Subdomain":""},"Extra":{"Balance":false,"Token":""}}\n', 127)) (('read', 0xc4200ea200, 512), (b'{"Id":"9714ddd4cb111adf6599f099cec98482","URL":"http://deb82e2e.ngrok.io","Proto":"http","Opts":{"Hostname":"deb82e2e.ngrok.io","Auth":"","Subdomain":"","HostHeaderRewrite":false,"LocalURLScheme":""},"Error":"","Extra":{"Token":"G4nIrca8GTvq4H62sTmqdb144FmhMgrg$U6TwkKWafv/3+bFM5AP7xIFfkWqx+HUsYWhkYXivrtMfcqan0mKZx99LHGI7mm5lOMmvI+Kdy7WF/GnwrMDXrRFwhYowczaWKRKnUimnNtndq7rdttMevFabwe5WSzwf+IZhWzQ2yvcW31+qVuS7F6uykUSw+mnBNtsdXFSNpToagqQOM66A8LT+l3f3OOHKrWpdq39Bz2RfoRmXaRpkDrdfT6vPUQd6S8uVUnv3t2173Ik7AgT9PlzOMJ', 512)) (('read', 0xc4200ea200, 1024), (b'hhVDbeM2HP+qV6S5I="}}', 21)) (('writ', 0xc420148e00, 4), (b'\x00ys7', 4)) (('writ', 0xc420148e00, 4), (b'\x00ys7', 4)) (('read', 0xc420148a00, 4), (b'\x00ys7', 4)) (('writ', 0xc420148a00, 4), (b'\x00ys7', 4)) (('read', 0xc420148e00, 4), (b'\x00ys7', 4)) (('writ', 0xc420148e00, 4), (b'\x11|\xb9\x99', 4)) (('read', 0xc420148a00, 4), (b'\x11|\xb9\x99', 4)) (('writ', 0xc420148a00, 4), (b'\x11|\xb9\x99', 4)) (('read', 0xc420148e00, 4), (b'\x11|\xb9\x99', 4)) (('read', 0xc4200ea200, 4), (b'\x00\x00\x00\x03', 4)) (('read', 0xc4200ea200, 8), (b'M\x00\x00\x00\x00\x00\x00\x00', 8)) (('read', 0xc4200ea200, 77), (b'{"Id":"9714ddd4cb111adf6599f099cec98482","ClientAddr":"***.***.**.***:17815"}', 77)) (('read', 0xc4200ea200, 32768), (b'GET / HTTP/1.1\r\nHost: deb82e2e.ngrok.io\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Safari/605.1.15 Midori/6\r\nAccept-Encoding: gzip, deflate\r\nAccept-Language: ru-RU\r\nX-Forwarded-For: ***.***.**.***\r\n\r\n', 359)) (('writ', 0xc420148e00, 4), (b'\x0e\xb2\xc0\x01', 4)) (('read', 0xc420148a00, 4), (b'\x0e\xb2\xc0\x01', 4)) (('writ', 0xc420148a00, 4), (b'\x0e\xb2\xc0\x01', 4)) (('read', 0xc420148e00, 4), (b'\x0e\xb2\xc0\x01', 4)) (('writ', 0xc420148e00, 4), (b'X\xa3?+', 4)) (('read', 0xc420148a00, 4), (b'X\xa3?+', 4)) (('writ', 0xc420148a00, 4), (b'X\xa3?+', 4)) (('read', 0xc420148e00, 4), (b'X\xa3?+', 4)) (('read', 0xc4200ea200, 32768), (b'', 0)) 

De ce vidage, vous pouvez clairement voir la structure interne du protocole:


  • De toute évidence, les flux d'autorisation et de création de tunnel sont initiés par le client, et les flux avec les connexions elles-mêmes sont initiés par le serveur. Ce n'est pas dans les journaux, mais c'est évident pour des raisons de bon sens.
  • Au début de chaque flux, un nombre de 32 bits est transmis - le type de flux. Il s'agit de 0 pour l'autorisation, 1 pour la création d'un tunnel et 3 pour les connexions entrantes.
  • Un flux de type -1 est un battement de cœur. L'initiateur de connexion y envoie périodiquement au hasard 4 octets et s'attend à les recevoir en sortie. Il existe 2 flux de ce type dans les deux sens.
  • Lors de la réception d'une connexion entrante, un type 32 bits 3, un nombre L 64 bits (petit-boutien) et un objet JSON de longueur L octets décrivant la connexion sont transmis. Après cela, les données brutes sont transmises via la connexion sans aucun paquet de service.

Conclusion


Étant donné que muxado est une bibliothèque open source, le protocole de multiplexage peut être appris à partir de la source . L'apporter ici n'a pas de sens.

Le résultat a été une bibliothèque Python pour travailler avec le protocole ngrok et un client de console alternatif utilisant cette bibliothèque. Github

PS Les critiques constructives sont les bienvenues.

Source: https://habr.com/ru/post/fr485564/


All Articles