Writeup by sin-infosec for Revaulting

crypto reverse elliptic curve linux x86/x64

November 22, 2023

TLDR

Reverse an ELF binary performing ECDSA with a biased nonce generation and recover the private key.

Recon

$ file revautling

revaulting: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=669adea6c657d58250907fffbf93b5649a18bf92, for GNU/Linux 3.2.0, stripped

So the binary is stripped, which won’t make reversing it easier… Let’s run it:

$ ./revaulting
What do you want to do?
  [1] Create user token
  [2] Log in with token
>>> 1
Enter your login:
>>> abcd
Here is your token:
c6uPfXzh0fARXKbTwgl6qjgwY2a/DqgyZU36nlAkNktkL5BnpRKfVIfxNck4NLYdpF3NaSVAUBfFk+GTd9COww==
What do you want to do?
  [1] Create user token
  [2] Log in with token
>>> 2
Enter your login:
>>> abcd
Enter your token:
>>> c6uPfXzh0fARXKbTwgl6qjgwY2a/DqgyZU36nlAkNktkL5BnpRKfVIfxNck4NLYdpF3NaSVAUBfFk+GTd9COww==
Welcome back, abcd!
What do you want to do?
  [1] Create user token
  [2] Log in with token
>>>

Allright, so we register by giving a login and getting a token back, and then we can login with our token, but that doesn’t seem to change anything. Time to reverse!

Reverse engineering

I won’t go into all the details of the reverse because:

  • this is a crypto challenge

  • I am not great at reversing

  • the reverse was long and tedious

But here is my thought process:

By opening the binary, we realize that these strings are used at the beginning of the main function:

"F1FD178C0B3AD58F10126DE8CE42435B3961ADBCABC8CA6DE8FCF353D86E9C03"
"F1FD178C0B3AD58F10126DE8CE42435B3961ADBCABC8CA6DE8FCF353D86E9C00"
"EE353FCA5428A9300D4ABA754A44C00FDFEC0C9AE4B1A1803075ED967B7BB73F"
"F1FD178C0B3AD58F10126DE8CE42435B53DC67E140D2BF941FFDD459C6D655E1"
"B6B3D4C356C139EB31183D4749D423958C27D2DCAF98B70164C97A2DD98F5CFF"
"6142E0F7C8B204911F9271F0F3ECEF8C2701C307E8E4C9E183115A1554062CFB"

By googling about them, I found out that these are the parameters for the elliptic curve FRP256V1. From the main function (which is the only rather simple to read), The behavior of the binary is the following: if we manage to login as admin, we unlock a new option [3]read flag, but the binary won’t let us generate a token for the admin login. The binary allows us to generate tokens for 32 chosen logins before exiting. The length of the base64 decoded tokens is 512 bits.

From these observation, my first hypothesis was that the binary was performing ECDSA signature using the FRP256V1 elliptic curve, and the token we are getting back is actually the concatenation of $r$ and $s$ (256 bits each because of the curve used) which form the ECDSA signature. Moreover, being allowed to get 32 signatures with the goal to forge an ECDSA signature really looks like the usual setup for biased nonce attack on ECDSA.

I was really lucky to choose to follow this idea, as this drastically enhanced the reverse process.

From the strings in the binary I found out that it was using mbedtls library to perform all the operations on elliptic curve, and by digging into the sub_functions I noticed the constants of the SHA256 algorithm being used.

After this, the tedious part began, with my idea being: “If I can recover the private key for a given set of signatures directly in memory, I can recover the respective nonces of these signatures and try to spot a pattern”.

So I fired up GDB, and started the process of understanding how mbedtls creates an ellitpic curve context, where it is stored, how it calls the different variables from it, and then by elimination which function is applying which operation (that looks nice, but in reality that was just a lot of step calls to inspect memory on every instruction in GDB).

I eventually managed to recover a private key and its 32 corresponding signatures generated for the login abcd, from which I could derive the nonces in this way:

import base64
from hashlib import sha256

p = int("F1FD178C0B3AD58F10126DE8CE42435B3961ADBCABC8CA6DE8FCF353D86E9C03",16)
a = int("F1FD178C0B3AD58F10126DE8CE42435B3961ADBCABC8CA6DE8FCF353D86E9C00",16)
b = int("EE353FCA5428A9300D4ABA754A44C00FDFEC0C9AE4B1A1803075ED967B7BB73F",16)
order = int("F1FD178C0B3AD58F10126DE8CE42435B53DC67E140D2BF941FFDD459C6D655E1",16)
gx = int("B6B3D4C356C139EB31183D4749D423958C27D2DCAF98B70164C97A2DD98F5CFF",16)
gy = int("6142E0F7C8B204911F9271F0F3ECEF8C2701C307E8E4C9E183115A1554062CFB",16)

F = GF(p)
E = EllipticCurve(F,[a,b])
G = E(gx,gy)

d = 0xafb0d1d7cbe11dece3ca90815c3fcfa4af5e74ed406cf19207a0a5440cd01278
P = d * G

h = int(sha256("abcd").hexdigest(),16)

tokens = ["sN+0VW/LJX+xG5X4YHRjYw5bEtO27JOB229dxtTimoFuUkVSC94kolv1WBE/EiKQqiF0ctxSvGBeD37WFvWVWg==","iNJO6Q+Pd07uLCoNBdj/62jlNyO7a7OpnSaW3+OqUqC6DZF4V13xRyjWmRYhS36ymVgst7oTRGqMQu1LVH5Xwg==","hnLvMGlEv9P4Kbi0SjBnBzQLrcNeh5PKexcWlMglvyOWOyrIBis1n2g+5BtfcJ8LlYynIaN3o6ifiWv79I7XPA==","KeuULta8hUJj4s5ep9+HHxIe8TwguV7+qW0H/QCCLKOVHbFnQKF4ok4aD6So9TWW0555zTVeqVwQasW4YWg8LA==","ztu2E/MB2R67MJULEvSk9nL76S01E/ENd29QvWtYzEw4RpE0JeiXIiJr0rnnuOgTE0rnlKY7hcf31R495OArEA==","GO/va9uESZoA4DxkMkl085/QNyEeKab6Nlkdr0Pdo4oHxaU1nel3RG592SNlGOJX8I9rPf9NVh7NSf3QprDG6A==","mp5GqeKtMgqKCVuozpTcbJ9QlBfypbqOR1+8A/hW/Ls6VMIp7eyW/rtlkmkNrcB8Evi66dVk81qMm2kIJ9PefQ==","MRTDmBbm5zsK+oRdHkVo03BskG2kL/tMIbY1jnshKlQydAfZgGejbrMBYbMgv1H6EzsugQyJykhRlTZ2Eo++ZQ==","HDT4ibpvTE0QMWRr1gQhd8lQcrUP67bvVzcGTlyaFN+AamEhO95arlZYc0gh/T0Wh0yRLvCovmARemUcQRxttQ==","grM99oeRdOw9eJnaOEPaLEsdGoAna333z2xPg9PUyh7P/RB8NqBbcCqjANkvsSxKLKkcHHWe/pG80vde1F9Fkw==","qVQtPsuY2p8kN9zfGenNxrnouDzEyLKDvC89WSpEfsg/1GqnPIkW0hCWgJhpRtvAytxHC2/XP1XZ1Qm6j3s0jw==","GEZ2S3EQxY6JDS/lTSNFl2Y0ajU7ucEZvY4NRHqFud0EV3ucsTyIvlnnkuouLxqx6rAW/JjN3qbYu/n138uC2A==","JN9QGHawrhTbLOFSpTpF/WcmRrBTdJnujIEftKDUMStlkO3FhFZ14uZXq5iUrKocZmCfQrFGIEYH6huootu4Sw==","gCn5roUB39n3etK3FjuqqSerUDHnKqSM7tT2X7JZrT9NmgXj/qjR2YDTqlFrxaFpZzRBoGcW+H6r1BM02b+w1w==","DIJ9Ijkdi3S6SpOayuVJM9ineFnpoZFWTKL7og7JxCsNG0cDTcT+V45WhQOOh0oHIkCJ/sltBxrBeq2WjcgSTA==","BeUgCcP1eS4sYq5dKzNeAQLi+4D8fki/nnZFeknAPKe3UataEbd1nBl9K1wKlIIxTBHJ38aL8gj0t8LcP5FaVA==","ObDD0hvWLV2O5EToLqC71IGek9WyAril2H7MHzKY5tVYSXbOBeW1jsjlICAuhWurret9dwQnP4n3uxuc5RudGg==","iP9eeAcRwbeVyphRr8NO7HOd1B7oDl7NiXtnh2tK0SsyFQt2yE5IBqexIbfcgtOchNGYajQBdkWVYWfej8hcOA==","E6vTx+L+4VCy5VuTJ/5bq371FAQaZZWT/jN0AoIqrON6qzYAGPJLFDuxvBA3E+AVpfPnHC+oqyWrtMCTVaSA5A==","4mqE95Z/aF4K2bBIq6+auNLks4Plh68fXt75tnfM+7Gg0kC48uaqgWTmRH0JY+hM7OL40gkoIUGYp0wdX3nh+w==","dgujvhY2xWY1+fouSj5JcongTvF/Zy/PrtXRqAxhPvfuqluGHweYAXPS4W/4sziJXAxgc0USrlVimmKy6oD2Pg==","UBM4S2ecsv95qwqUIJiAPU/t+L/kkIEntewlaH1RlBaabBTikqt6WWsQlOzzWpzrUO0u99VjXS6nFczE6RlwcQ==","m8/tahnzODunTLbnLXWs7jE8jz7t3IKocyH99VTYacG4XjX14dpK+8LEvkkJ+o4hjy9Gs05xY+PF53AYpwqNRw==","FQ4pZDoRcLOiIGZrTnbElxnop8PIIwJ5d6gyUDSYF7a4ohY0cujeeOLwqIoVMQsD9LcoBM/1KTeCspnE63etRA==","b27Ki0aLdXYob2wUrux4pR1JbJNooB9ZyXfRQ3X97BCGVABZunzT03fGtwZ4wYHKtXP0QTkmGV8DohqOM7D9AA==","V7gveSYf1NMHrm9x01RS4Dy7SGcPyZ0LOnQdAGlv3qbHZNnM3u7xpf+70oscZPrqM0ZZtJpN9kijwwKXEkj4Dg==","fIU+R30WxG9dRQpX0IJcxQd4PpDgLt/+Hu1DO2OQABcJGZEbJ9aBqOI390OnMEHmzR2N3YcMXV7beSorBISs7A==","VDbjHigP3VXuQ+/iiHMSriqz6/imSnZ/4+MAu14B2NrhFnCLA43VKpf8qk231Niu/aJ/JoIPi8kglvbfS2p/4A==","G1i3XalV7/DMJ1kAAsPV7Y6wUwthDmDgHmVpGFpJujggZVgRmYidQ5iO7vHPPxTUIe7PGsMeBfZhFclxB87pkA==","62PTDbwOdnEHBU/h7lfMvDOPkjKmR09wVEnWiTW6k6uhznh/cg7C7Of7+eeJ/+vCyqrlS6//nuQeR1fmt1/Eqg==","uuuUxfPum5LneqwywqYercybDRsGku2hCfU7v+/mFJyKpm4/M2RLSYc7y+is24z0tmQZ2H3RMaaCly8ikd97Tg==","wGZlhYC27kh6ENH5Rz1tOZaF+39qIBvjd+Bbx9oWcUGvjtDXs5Aj8sLcatSKBV7U+m5E7mxqwPobwRP3Q3fDGQ=="]

k_values = []
r_values = []
s_values = []

for token in tokens:
	rs = base64.b64decode(token)
	r = bytes_to_long(rs[:32])
	s = bytes_to_long(rs[32:])
	r_values.append(r)
	s_values.append(s)
	k_inv = (s * inverse_mod(h + r*d,order)) % order
	k = inverse_mod(k_inv,order)
	k_values.append(k)
	print(hex(k))

And the output gives:

4fdd5d25ceb1e2ea82c936a25d49c0664fdd5d25ceb1e2ea82c936a25d49c066
19e62d276d73cfcad9da6d431479118f19e62d276d73cfcad9da6d431479118f
3330ecc32a735295c1b502292fc2d1d93330ecc32a735295c1b502292fc2d1d9
813ecd3fd568cc9d765f61fc50e313fe813ecd3fd568cc9d765f61fc50e313fe
aa8d4e3fa0dd04275989150831ba0aceaa8d4e3fa0dd04275989150831ba0ace
a464c5c90bbbe9f365480e62299caacba464c5c90bbbe9f365480e62299caacb
77f5719de6f4d87df6cc101f60ba72de77f5719de6f4d87df6cc101f60ba72de
6841ff9d572bb1325ce5e91a4db048d16841ff9d572bb1325ce5e91a4db048d1
415636788b8bdfb115c28a21dc3b5b2e415636788b8bdfb115c28a21dc3b5b2e
7a95dfadb093a2e28bb81b4310c13ca07a95dfadb093a2e28bb81b4310c13ca0
20add3488de462dadc25cfc6f86fbd0720add3488de462dadc25cfc6f86fbd07
d8aea9cecea4a78da374eb22a0d07840d8aea9cecea4a78da374eb22a0d07840
2750fa553790d4238154410bf7bd8f0d2750fa553790d4238154410bf7bd8f0d
17c23be1257d1e398d662ae3f87382a217c23be1257d1e398d662ae3f87382a2
bec41f2257e378136c76a9ae5418179cbec41f2257e378136c76a9ae5418179c
ac6a9014a3316cacdf522e18d634c394ac6a9014a3316cacdf522e18d634c394
a1b955b9d21b2a86549d22bd6437029aa1b955b9d21b2a86549d22bd6437029a
971cca6f49dd3018fcf7e62f64c36d53971cca6f49dd3018fcf7e62f64c36d53
d151b3d8854bc299aa7d0a7bf8db4848d151b3d8854bc299aa7d0a7bf8db4848
6be41a9afc8c7039e4faf98cb08ab2186be41a9afc8c7039e4faf98cb08ab218
cbb68416b2f943fe13bd27ba9c17379ecbb68416b2f943fe13bd27ba9c17379e
adbc1aba848df1e2570c50560e6a0c07adbc1aba848df1e2570c50560e6a0c07
ec1e8c41ad2a0f84fe277ce063eac186ec1e8c41ad2a0f84fe277ce063eac186
86ac83ad8394ab6c6153984ecabac6e386ac83ad8394ab6c6153984ecabac6e3
512f2569c4cd265e0042d2b458773642512f2569c4cd265e0042d2b458773642
92a86a1c475354720bbab2831e58f8b992a86a1c475354720bbab2831e58f8b9
bd77bb70c660dd343bdaacf63b105a53bd77bb70c660dd343bdaacf63b105a53
3a5da0477f757b757cbd4bbdf5bfbb303a5da0477f757b757cbd4bbdf5bfbb3
ba4b0dbd0a3152fcb737291816f3b514ba4b0dbd0a3152fcb737291816f3b514
b6dff100c4868bec19f86fb5301382b9b6dff100c4868bec19f86fb5301382b9
de6845c72c99eeab2d629b9f1109633dde6845c72c99eeab2d629b9f1109633d
dd086e95ba55d63a78428c3979154675dd086e95ba55d63a78428c3979154675

Notice anything special? I’ll help you out:

('4fdd5d25ceb1e2ea82c936a25d49c066', '4fdd5d25ceb1e2ea82c936a25d49c066')
('19e62d276d73cfcad9da6d431479118f', '19e62d276d73cfcad9da6d431479118f')
('3330ecc32a735295c1b502292fc2d1d9', '3330ecc32a735295c1b502292fc2d1d9')
('813ecd3fd568cc9d765f61fc50e313fe', '813ecd3fd568cc9d765f61fc50e313fe')
('aa8d4e3fa0dd04275989150831ba0ace', 'aa8d4e3fa0dd04275989150831ba0ace')
('a464c5c90bbbe9f365480e62299caacb', 'a464c5c90bbbe9f365480e62299caacb')
('77f5719de6f4d87df6cc101f60ba72de', '77f5719de6f4d87df6cc101f60ba72de')
('6841ff9d572bb1325ce5e91a4db048d1', '6841ff9d572bb1325ce5e91a4db048d1')
('415636788b8bdfb115c28a21dc3b5b2e', '415636788b8bdfb115c28a21dc3b5b2e')
('7a95dfadb093a2e28bb81b4310c13ca0', '7a95dfadb093a2e28bb81b4310c13ca0')
('20add3488de462dadc25cfc6f86fbd07', '20add3488de462dadc25cfc6f86fbd07')
('d8aea9cecea4a78da374eb22a0d07840', 'd8aea9cecea4a78da374eb22a0d07840')
('2750fa553790d4238154410bf7bd8f0d', '2750fa553790d4238154410bf7bd8f0d')
('17c23be1257d1e398d662ae3f87382a2', '17c23be1257d1e398d662ae3f87382a2')
('bec41f2257e378136c76a9ae5418179c', 'bec41f2257e378136c76a9ae5418179c')
('ac6a9014a3316cacdf522e18d634c394', 'ac6a9014a3316cacdf522e18d634c394')
('a1b955b9d21b2a86549d22bd6437029a', 'a1b955b9d21b2a86549d22bd6437029a')
('971cca6f49dd3018fcf7e62f64c36d53', '971cca6f49dd3018fcf7e62f64c36d53')
('d151b3d8854bc299aa7d0a7bf8db4848', 'd151b3d8854bc299aa7d0a7bf8db4848')
('6be41a9afc8c7039e4faf98cb08ab218', '6be41a9afc8c7039e4faf98cb08ab218')
('cbb68416b2f943fe13bd27ba9c17379e', 'cbb68416b2f943fe13bd27ba9c17379e')
('adbc1aba848df1e2570c50560e6a0c07', 'adbc1aba848df1e2570c50560e6a0c07')
('ec1e8c41ad2a0f84fe277ce063eac186', 'ec1e8c41ad2a0f84fe277ce063eac186')
('86ac83ad8394ab6c6153984ecabac6e3', '86ac83ad8394ab6c6153984ecabac6e3')
('512f2569c4cd265e0042d2b458773642', '512f2569c4cd265e0042d2b458773642')
('92a86a1c475354720bbab2831e58f8b9', '92a86a1c475354720bbab2831e58f8b9')
('bd77bb70c660dd343bdaacf63b105a53', 'bd77bb70c660dd343bdaacf63b105a53')
('3a5da0477f757b757cbd4bbdf5bfbb3', '3a5da0477f757b757cbd4bbdf5bfbb3')
('ba4b0dbd0a3152fcb737291816f3b514', 'ba4b0dbd0a3152fcb737291816f3b514')
('b6dff100c4868bec19f86fb5301382b9', 'b6dff100c4868bec19f86fb5301382b9')
('de6845c72c99eeab2d629b9f1109633d', 'de6845c72c99eeab2d629b9f1109633d')
('dd086e95ba55d63a78428c3979154675', 'dd086e95ba55d63a78428c3979154675')

Boom, the 128 MSB of the nonce are always equal to its 128 LSB! So we found a bias in the nonce generation, and that also confirms all our precedent hypothesis :)

Crypto time

We now know that the nonce generation is :

$$k = k_{low}2^{128} + k_{low} $$ where $k_{low}$ represents the 128 SLB of $k$

So instead ofhaving a 256-bit unknown, we only need to recover 128 bits to recover k. So how can we exploit this?

An ECDSA signature is generated as follow (I am not mentioning the edge cases here but you can easily find them online):

  • Pick a random $k$ between $1$ and $n-1$ where $n$ is the order of the generator $G$.

  • Compute $kG = (x,y)$. $r$ is the $x$-coordinate of $kG$ modulo $n$.

  • Compute $s = k^{-1}(H(m) + dr) \mod n$, where $d$ is the private key and $H$ is the hash function used (here SHA256).

  • Return $(r,s)$ as the signature.

But how can we use this and our bias? By using only two signatures, we can write:

$$ \begin{aligned} \begin{cases} dr_1 = k_1s_1 - h_1 \mod n\\ dr_2 = k_2s_2 - h_2 \mod n \end{cases} \end{aligned} $$

$$\begin{aligned} \Rightarrow \quad & dr_1(k_2s_2 - h_2) = dr_2(k_1s_1 - h_1) \mod n\\ \Rightarrow \quad & r_1k_2s_2 - r_1h_2 = r_2k_1s_1 - r_2h_1 \mod n\\ \Rightarrow \quad & r_2s_1k_1 - r_1s_2k_2 + r_1h_2 - r_2h_1 = 0 \mod n\\ \Rightarrow \quad & r_2s_1(2^{128} + 1)k_{1low} - r_1s_2(2^{128} + 1)k_{2low} + r_1h_2 - r_2h_1 = 0 \mod n \end{aligned}$$

Thanks to this reformulating we managed to get rid of the private key $d$ in our equations, and to build a polynomial with two small roots over $\mathbb{Z}/n\mathbb{Z}$, these two small roots being $k_{1low}$ and $k_{2low}$, the unkowns we are looking for!

Lastly, finding small integer roots modulo an integer is a common problem that can be tackled by lattice reduction, and was more specifically studied by Coppersmith et al, hence the name of bivariate (we have two unkown variables) Coppersmith method.

A great implementation of Coppersmith multivariate written by defund, can be found at https://github.com/defund/coppersmith.

We could have made the exploit more reliable by plugin in more signatures in our lattice, but as this method works roughly 25% of the time, I didn’t bother…

So putting it all together, we connect to the server, grab two signatures, try to recover the nonce, and if we succeed we recover the private key by computing:

$$d = r_1^{-1}(k_1s_1 - h_1) \mod n $$

All that is left to do after that is generating a signature for the login admin and reading the flag!

Here is my solving script in Sage:

load('coppersmith.sage')

from Crypto.Util.number import bytes_to_long, long_to_bytes
from pwn import *
import base64
from hashlib import sha256

p = int("F1FD178C0B3AD58F10126DE8CE42435B3961ADBCABC8CA6DE8FCF353D86E9C03",16)
a = int("F1FD178C0B3AD58F10126DE8CE42435B3961ADBCABC8CA6DE8FCF353D86E9C00",16)
b = int("EE353FCA5428A9300D4ABA754A44C00FDFEC0C9AE4B1A1803075ED967B7BB73F",16)
order = int("F1FD178C0B3AD58F10126DE8CE42435B53DC67E140D2BF941FFDD459C6D655E1",16)
gx = int("B6B3D4C356C139EB31183D4749D423958C27D2DCAF98B70164C97A2DD98F5CFF",16)
gy = int("6142E0F7C8B204911F9271F0F3ECEF8C2701C307E8E4C9E183115A1554062CFB",16)

F = GF(p)
E = EllipticCurve(F,[a,b])
G = E(gx,gy)

h = int(sha256(b"abcd").hexdigest(),16)
r_values = []
s_values = []

o = remote("challenges1.france-cybersecurity-challenge.fr", 6003)

r_values = []
s_values = []

for i in range(2):

	o.recvuntil(">>> ")
	o.sendline("1")
	o.recvuntil(">>> ")
	o.sendline("abcd")
	o.recvline()

	rs = base64.b64decode(o.recvline().strip())
	r = bytes_to_long(rs[:32])
	s = bytes_to_long(rs[32:])
	r_values.append(r)
	s_values.append(s)


r1 = r_values[0]
r2 = r_values[1]
s1 = s_values[0]
s2 = s_values[1]

bounds = (2^128, 2^128)

R = Integers(order)
P.<k1, k2> = PolynomialRing(R)
f = r2*s1*(2^128 + 1)*k1 - r1*s2*(2^128 + 1)*k2 + h*(r1 - r2)
result = small_roots(f, bounds,m=2,d=4)
k1_low = int(result[0][0])%order
k2_low = int(result[0][1])%order

k1 = (2 ** 128 +1) * k1_low
k2 = (2 ** 128 +1) * k2_low

k1 %= order
k2 %= order

priv_key = (inverse_mod(r1,order) * (k1 * int(s1) - h)) % order

Q = G * int(priv_key)

print("recovered potential private key:",priv_key)

##Making sure it worked and we have the right private key by verifying a signature
u1 = (h * int(inverse_mod(s1,order))) % order
u2 = (r1 * int(inverse_mod(s1,order))) % order
X = u1 * G + u2 * Q
if X[0] == r1:
	print("The private key is the right one. Forging admin token...")
	h_admin = int(sha256(b"admin").hexdigest(),16)
	k_admin = 123456789
	Q = G * k_admin
	r_admin = int(Q[0]) % order
	s_admin = (inverse_mod(k_admin,order)*(h_admin+r_admin*priv_key))%order
	token_admin = base64.b64encode(long_to_bytes(int(r_admin),32) + long_to_bytes(int(s_admin),32))
	o.recvuntil(">>> ")
	o.sendline("2")
	o.recvuntil(">>> ")
	o.sendline("admin")
	o.recvuntil(">>> ")
	o.sendline(token_admin)
	o.recvuntil(">>> ")
	o.sendline("3")
	print(o.recvline())
o.close()

$in