ECW 2020 Final - ECDSA nonce reuse
ECW 2020 Final - ECDSA nonce reuse (crypto)
The final of the ECW 2020 took place today. My team grabbed the first place thanks to my monstrous team mates. This final was quite different from the one I did in 2018 as, this time, we could reach the others teams servers.
This means that when you flagged a chall you have to run your payload or script against 11 others servers to get 12 different flags with the same amount of points. This was quite insane.
From the statement of the challenge we have to find a flaw in a voting machine designed by an ex engineer from SONY. The only other information we had was that the server was running on port 1337
.
/! DISCLAIMER /!
I’m very bad at crypto and will probably tell mistakes. Do NOT trust me.
crypto, ahhh crypto ..
playing around
Once a bit of scanning done we found some servers with the port 1337 TCP
open.
λ 130 ackira ~/nextcloud/CTF/ecwfinal2020 » rlwrap nc 192.168.8.36 1337
Help Yves Attrovite to won the election, 1995 style !
Type "help" to get a list of available commands
Public key:
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEL0TaPvZ5gz4EPjkn6v5IIXAZzclp
J6wffo9BOW4c/KHONeNtzVu8nGMzG2etkNGL5KJs5YLuAbz/PajRrq7Mbg==
-----END PUBLIC KEY-----
Presidentielles> help
Documented commands (type help <topic>):
========================================EOF candidates help manual_resign quit results vote
Presidentielles> candidates
Inès Timable : Count(n=3725 , dgst='2yf4CngFudtg0ovJ7lOlGPcQ15JZiDVbuZzeru5zG08=', sig='RzgUDX71qOzWpCSuZ1/9bA0ulz2uDyBgiQdNDHefbYOH2U4jSbdHlcRzHETTJLQBPynZ6IGHbt5idHBocACOxw==')
Mario Naitiste : Count(n=2886 , dgst='fO8i8HctF/u5AGd5WpoFwhtVKnLJQTEfzca3aK37/5Y=', sig='BtAtQuUnBAA+nIaGjbsS8tL0uvZfp94FVbtXlTQ6WkpocrhGVONnlgikv670YniRyhQYtPxVHOE+FrdPWocV7w==')
Ella Traifin : Count(n=2623 , dgst='CVxaDvjOGok9WMgorId+ead4GKeHU/nrSymBgeAIT40=', sig='KJm5ganTBhkNiXF1XnHmqOLB6sh1m9k8zON+pHykNLJZTjxDUpoMboNGOF1TQor6c4yPzi1G0I74z1TyfDVHlQ==')
Teddy Fissile : Count(n=2099 , dgst='4E5l0RuEbF5Q3JU/SEizhSsn7wv5aJOegB0hxw7j99s=', sig='h9fF6EzjbdxE42Fgq2TcW/vxfARigUYLonjhoq6CMA8V1F6oQSS/vKisomgeHRbsYEM3gs+FKgrH+9qlgyTVGA==')
Alain Thérieur : Count(n=2059 , dgst='O47DUTwaUKnvA41cHVn1dx1KCkTspWQNmDaVVaHttXE=', sig='QeoZK1BjHkFydGJNBRef/wOnND8WcajIdWdXg8iV8wSZi44GbadCq+6lDPSC1RahDm3vlJdVTp5TV6BoQlEXAg==')
Annick Tarasse : Count(n=2052 , dgst='RAn+3g8vrmRGC4/DiE0asD+iosCwPPn7G4sAxPqTVFM=', sig='lltqPXeVKKbSEOb0g06wOOy481cQNnfzLcymmgpm7EPMfzcDisp180Sjd8ylgmW5JJXWMjM5+bNvzKmVXQLMlQ==')
Jacques Septe : Count(n=1955 , dgst='7yZLhcY6GXCWsWzxApjdAN16Lro6MA+Xd4k3NHnJC/M=', sig='WorPM/JxaueLKYFfQPf1q/RvEyIAThizEJfWfkGrvycAzB85reV9XYvomqszqHG3c3xtdiIzoc32MHPbAvmRvg==')
Gilles Léparbal : Count(n=1952 , dgst='Fl3MR0snw+LpZ+v9EyDuFpnZNgZohnDqlz5eOcef1Ug=', sig='ry3r15/adhrtlMZjIynFcOVcy9Hk4CSEp/w1YHioWTL6ptPQ0QdHIgxprcpEQ1dYNIoLefvkm/+OJNu2QuhTEA==')
Anne Estézi : Count(n=1863 , dgst='+SuKpKu6C+Cvpisex8r0kN2/zgChCtqlDpGsbjWwP6M=', sig='ry3r15/adhrtlMZjIynFcOVcy9Hk4CSEp/w1YHioWTLcocQGxzU1hMgArYJFmGPpuNoOfbup707etRHZ86wCVw==')
Kimberley Tartine : Count(n=1561 , dgst='gUOJOnoRHEuiCb1Gq5+kKLdmKW/CBwPhT7sZJjTdwLY=', sig='AnMj+CizA5P8qxtpVAIFIz5T7y9ZHo6ADWGrH/npjIDUzL2k1QrV3X82TobjA+4TdXr1fSml6c5aGt84YxMzyw==')
Jessica Pote : Count(n=1245 , dgst='WhdVxnhQhXU/cqWrU+4Yag+iV4VvNkUq6etEUP5AW0A=', sig='FUxKe+XQm5vKmgebohMERrlgOpYmtjl11WHWRsmVyDgkTNW/RqKPgDagg7uOacqyGZHeLOQGb+Qk7B4X99l3hA==')
Marc Assin : Count(n=1214 , dgst='N/drFOwdzlQYBc3KILv55M4evcHzUEJB0vocptSs3zc=', sig='zAMy/ZZqOizgzA52SiYFvKBY0qLP7mjJpKFZ7QVBGQvHTum8QHy9a/GPvRbjp2r7b8ei+uIWTvtIq7eyYrh1oA==')
Iris Keurien : Count(n=1113 , dgst='XexPpBvrPddiRK4M+bfFDcawwmLozucidSAZPD7FnzY=', sig='OBYIvdFft6FKBEDcgrqiTyA0/MsEQd2NVbHfL4VuJAOAXfGWqlt64RA4Dofs97c9IuXVezTbh2RRg94XBuzyrg==')
Jean Bonneau : Count(n=1033 , dgst='LXj8K5OQxYI7IpwFKsYorTRR2tNQfyWtTgXRAfXzzGE=', sig='lK8iZL8Yp1pyx45Nuv4c5oAXwte7EcOtsxsaaBc+WD4VltyddJ40wK6PvZIJa5+bxFHFLywBjBqbP2HT5UCU/A==')
Kelly Diote : Count(n=911 , dgst='etzVh2U7lD3sFhzVBb/QFOd4s83NQVC1PTJex+PyAmo=', sig='PuUndQ72dPYoY5tsfpKtmvOHjXR0miCr9qyG9hDxU29OEfWjKeSttf3/sym0M5ZsUP0908cXHk4DiODEOU5Cjg==')
Yves Attrovite : Count(n=0 , dgst='4tcmDz9tFJ1dG3GqLV7zbV/qY/dpOAfQRYi/lkuCzAI=', sig='OrX9BJXTa8o12H4q5Cv2836KWqiFwAbL76mhZNxldD0gio6AoGYZY6I7Xa/nhRGgFd+SffOQ1drteiUJdV8bXA==')
Presidentielles>
From the statement we understand that we need to help Yves Attrovite
to win the election. In order to win the election he must have the highest vote count.
There are only 4 interesting commands :
candidates
: list the candidates (see above)manual_resign
: update the vote count of a given candidateresults
: make the elections happen and elect the candidate with the highest vote countvote
: give our vote to one candidate
My first try was to call the vote
function multiples times but unfortunately we could use it only once.
The second way to achieve the goal, was to use the manual_resign
function to set the vote count from one candidate.
Presidentielles> manual_resign
2020-11-18 11:06:34,390] __main__ - ERROR - Usage: manual_resign CANDIDATE COUNT B64_SIGNATURE
[
Presidentielles> manual_resign "Yves Attrovite" 11 "switch"
Presidentielles> candidates
Marc Assin : Count(n=3443 , dgst='Isa1fNhckxex5tB6SxtMURegBnuN4mOG9Zt8o+Ff+jM=', sig='j85nDNrYv5tBcIs0pvk7/U7wpangN5L2CxLamIyCfmIBVm3Pe/Bjqgy78gXtieCQQUk0ojJfgHOmF0RtPRr8sQ==')
Jean Bonneau : Count(n=3223 , dgst='h5ZPPj6vk+5nyYXbOy211n2qnoalA/sKv1Ome0ezva8=', sig='s45bP25ZtSdlbD4culmWji4NpNVCbNNi+HGoEXQEnN8F/EuQSetE4pCmY3z/VcKdce3uw2X6k+lXnDV2V+qiUQ==')
Jessica Pote : Count(n=3007 , dgst='AeVwMCGgtswJ6ryPdGFNWe4ABtegu9UneMMv4PlPPws=', sig='EBgAhPjB/IVCrL6u5E0Q+BUZ72SqiztXmxGih6jl955C723Kfm99FfYHMqcWFyidQN5s1iJBCMpXRk9J/uGIEg==')
Alain Thérieur : Count(n=2803 , dgst='33BTcuE8JPf3zKNxsJI/iR/MSp9dp2s3EYww/VqSLnM=', sig='+lNkYImtXzI/c+KOCx9GK6g9mRh3F76dPqF164RWoNXNSNiFELCiaThmG+2Xr10FHypV1VhNU/aJ/RUoc9QDaA==')
Kimberley Tartine : Count(n=2769 , dgst='5/nGygcMkzlbCdVrwNpBjYK5FrBGt/ngmFE8gc1uUdA=', sig='5jzgfLdWeq7Fdp4vN200sY5KnRB67pvAZjszcLGsKujfYia14URyNtFpShpeGUDjdqnk6KYvKdd4JrVzSlnoyw==')
Ella Traifin : Count(n=2622 , dgst='qRWhpDFAgk1IGnIDAotouOW++AjZXnaCRr7TdA2QWq8=', sig='IQBQ+uDa7N0FEEzg6vbXkbEN/ZPiL4VJW36lPKUDndTNHc/SYUO19uKBdzY5amqTCUuy2fr6LXH90+FFyMkxEA==')
Anne Estézi : Count(n=2553 , dgst='w+tB9JV6HTEowJtsDEArieL4Aol9xCHY5gkLsYI0BiE=', sig='HwNeOtA+v1RJT25ooqC2i5sowlgibuoIF2PlIzOGDQ2CvhsltJiB2VyTHj6Ds5xRY66auM2yw+ZVJrKV0OHNvA==')
Annick Tarasse : Count(n=2363 , dgst='nyq/DPn3bLty3OVxggpZagWpuFoABAYXfRLrEVynqoE=', sig='B0KMbtkSgjdSrlWVePII0khb3bIu9hXbvZhRhRz7HaldQWm9nLdVVFExrfZ4Q3codJ831dbA/JhtwmDh0aGSdw==')
Gilles Léparbal : Count(n=2060 , dgst='QPsDO1R/paxV5qweBTa+BmzTqlYMKAk6c+vS335Tsh4=', sig='lofCSVxD8Zi/r3rMnig45pzpRviEFH0j3W43g3muzqdWEpZ8C+C2XndW3MnCKFGRwyHu0ql4kPMOmHDeW2LpkQ==')
Jacques Septe : Count(n=2036 , dgst='79xUnnVH4pHXG2MmVjTbYiOhZX/iU4tEJDo1w5I2C/8=', sig='U+jHDFPnEBR8gxgxdxxu6Bw4sAzTnUW9OVgjJyyta8r3O3hyI0iDU2F29ojbuV8x4ZC5IIInP/7ox+teWlG/Bw==')
Teddy Fissile : Count(n=1903 , dgst='5sYAgIKhhmb2imbEALBJXPI9zG1IXjoPeQhpMkT1vtU=', sig='3M1dlTPPgyw1l3inGj853v86/PCSooJMGKgnO4OCVC9BQC1iWjpuM+qDlATuR5y1yAL7HfWMMM2P12lFA23GHQ==')
Inès Timable : Count(n=1524 , dgst='y45dRpchLIiqNMI5D/IFuYQPw8Gh/c7kFO8txbVzA9o=', sig='j85nDNrYv5tBcIs0pvk7/U7wpangN5L2CxLamIyCfmKcSm0+K6wq/sAZtwokicLJmgmXwq7VZ8H2Mlzu80uglw==')
Mario Naitiste : Count(n=1342 , dgst='2bhu4yYEV2yqUBMO6Ttgw27Y2vE/qhpTdErAkx5bhvo=', sig='87lTyRw+upUsP0a1z/siggy8gjkWuhnoS2IGk+J6iSmbIH/rPChYiZQBJAYGqxexiZUkHo/EEJ5MwgWH5glf+Q==')
Iris Keurien : Count(n=1260 , dgst='+coQM/QFNsNCUkGewNQ18R1aRRu05RUvliZunkyMdTs=', sig='4iSGjT2ZZDwlxLmasSPm3+p7hDnTXcrqiTXMp/D42LWLSJtqSdRAf9fqaxRHt/eKmSOnjZBbz5asVxZ3MPuVRA==')
Kelly Diote : Count(n=1043 , dgst='qpw2mpD//8nJFQJDXuvsMfhZSqbB906mVN+Tr8/pPMU=', sig='FVh8aXP09XAI+EaqwHlMTKwEonb5+pyL0lyMuzAW76+Q3Cf+BjppkvbW1lp9Szgfn6Yq1wZIit+f8bvMNEJ3lQ==')
Yves Attrovite : Count(n=11 , dgst='EpWEdZbASekXCUVFwJjN7wvFxvo2cWo+8dxTMdj9Jpc=', sig='switch')
Nice we can update the amount of votes for our candidate, lets win the election
Presidentielles> results
2020-11-18 11:09:35,525] __main__ - ERROR - ('Malformed formatting of signature', MalformedSignature('Invalid length of signature, expected 64 bytes long, provided string is 5 bytes long'))
[2020-11-18 11:09:35,525] __main__ - ERROR - 'Yves Attrovite' cheated !!! [
So the signature is wrong, I tried to set the same as before but get onto this
Presidentielles> results
2020-11-18 14:23:17,989] __main__ - ERROR - Signature verification failed
[2020-11-18 14:23:17,990] __main__ - ERROR - 'Yves Attrovite' cheated !!! [
So, it won’t be this easy.
If you paid attention, you have noticed the public key sent when opening the connection
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEL0TaPvZ5gz4EPjkn6v5IIXAZzclp
J6wffo9BOW4c/KHONeNtzVu8nGMzG2etkNGL5KJs5YLuAbz/PajRrq7Mbg==
-----END PUBLIC KEY-----
It looks so small, my first (and wrong assumption) was that it was a small RSA exponent and we could found its 2 factors and generate the private key.
attacking ECDSA
Let’s find it exponent with openssl
openssl rsa -in pub.pem -pubin -text -modulus
$
140069342045568:error:0607907F:digital envelope routines:EVP_PKEY_get0_RSA:expecting an rsa key:crypto/evp/p_lib.c:469:
The fuck. OpenSSL does not recognize the key as RSA.
How can I get the type of the key ?
But hopefully there is an openssl commands for that !
Zestfully.
openssl asn1parse -in pub.pem
$
0:d=0 hl=2 l= 89 cons: SEQUENCE
2:d=1 hl=2 l= 19 cons: SEQUENCE
4:d=2 hl=2 l= 7 prim: OBJECT :id-ecPublicKey
13:d=2 hl=2 l= 8 prim: OBJECT :prime256v1
23:d=1 hl=2 l= 66 prim: BIT STRING
ECDSA public key of course, I was suspecting this kind of key a bit but sadly I never really looked into it as much as I should have.
From here we have to find a vulnerability for this ECDSA implementation.
SONY mistakes
From the statement the voting machine has been developed by an SONY ex employee, but why this indication. I just googled SONY ECDSA vulnerability
and came across this post https://ropnroll.co.uk/2017/05/breaking-ecdsa/
more about sony mistakes in PS3
- https://en.wikipedia.org/wiki/PlayStation_3_homebrew
- https://yingtongli.me/blog/2019/01/28/crypto-failures.html
I don’t want to get into the crypto part too deep. The vulnerability resides in the reuse of the r
number which should have been random for every signature.
This mistakes is easy spotable as the signature of 2 different values starts with some common bytes.
Gilles Léparbal : Count(n=1952 , dgst='Fl3MR0snw+LpZ+v9EyDuFpnZNgZohnDqlz5eOcef1Ug=', sig='ry3r15/adhrtlMZjIynFcOVcy9Hk4CSEp/w1YHioWTL6ptPQ0QdHIgxprcpEQ1dYNIoLefvkm/+OJNu2QuhTEA==')
Anne Estézi : Count(n=1863 , dgst='+SuKpKu6C+Cvpisex8r0kN2/zgChCtqlDpGsbjWwP6M=', sig='ry3r15/adhrtlMZjIynFcOVcy9Hk4CSEp/w1YHioWTLcocQGxzU1hMgArYJFmGPpuNoOfbup707etRHZ86wCVw==')
As you see these 2 candidates signatures start with ry3r15/adhrtlMZjIynFcOVcy9Hk4CSEp/w1YHioWTL
. This indicate the reuse of a nonce.
Here is our vulnerability, this one allow us to retrieve the private key thanks to m1
, m2
, sig1
, sig2
and the public key. Where mx
and sigx
are the plain texts and the associated signatures.
With the private key it will possible to sign whatever we want.
The only thing that we don’t know was m1
and m2
. I struggled here for so long and I’m still very mad toward myself as I could get the information way sooner.
Presidentielles> help manual_resign
manual_resign CANDIDATE COUNT B64_SIGNATURE
B64_SIGNATURE = ECDSA.sign_digest(hash(f"{CANDIDATE}-{COUNT}".encode("utf-8")))
I think I have tried help vote
but as nothing showed up I didn’t test for the rest ..
So for the example above m1
is Gilles Léparbal-1952
, and m2
is Anne Estézi=1863
.
recovering the private key
First we get the order of the given curve. Moreover the standard used is NIST256p
thanks to the public key information, so we will need sha256
instead of sha1
.
from ecdsa.curves import NIST256p as curves
from ecdsa.util import sigencode_string, sigdecode_string
from ecdsa import VerifyingKey, SigningKey
from base64 import b64encode, b64decode
from hashlib import sha256
import gmpy
= '''
public_key_pem -----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEL0TaPvZ5gz4EPjkn6v5IIXAZzclp
J6wffo9BOW4c/KHONeNtzVu8nGMzG2etkNGL5KJs5YLuAbz/PajRrq7Mbg==
-----END PUBLIC KEY-----
'''.strip()
= VerifyingKey.from_pem(public_key_pem)
public_key_ec = public_key_ec.curve.order n
Then, [insert some mathematical and crypto explanation here] (you should definitively look on the sources linked).
= int(sha256("Gilles Léparbal-1952".encode("utf-8")).hexdigest(), 16)
m1 = int(sha256("Anne Estézi-1863".encode("utf-8")).hexdigest(), 16)
m2 = b64decode("ry3r15/adhrtlMZjIynFcOVcy9Hk4CSEp/w1YHioWTL6ptPQ0QdHIgxprcpEQ1dYNIoLefvkm/+OJNu2QuhTEA==")
sig1 = b64decode("ry3r15/adhrtlMZjIynFcOVcy9Hk4CSEp/w1YHioWTLcocQGxzU1hMgArYJFmGPpuNoOfbup707etRHZ86wCVw==")
sig2
= sigdecode_string(sig1, n)
(r1, s1) = sigdecode_string(sig2, n)
(r2, s2)
=((m1 - m2) * gmpy.invert(s1 - s2, n)) % n
k= gmpy.invert(r1,n)
r_inv = ((s1*k - m1) * r_inv) % n d_a
We now have the private key
= SigningKey.from_secret_exponent(d_a, curve=curves, hashfunc=sha256) sk
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIE2KkDRnIP4/EbMvZpvwQwRvGsDQ39B+d73QsIqEQYwRoAoGCCqGSM49
AwEHoUQDQgAEL0TaPvZ5gz4EPjkn6v5IIXAZzclpJ6wffo9BOW4c/KHONeNtzVu8
nGMzG2etkNGL5KJs5YLuAbz/PajRrq7Mbg==
-----END EC PRIVATE KEY-----
We can even verified this is the right private key.
$ echo "Pour quatre-vingt balles, vous pouviez pas lui glisser ?" > poc
$ openssl dgst -sha256 -sign priv.pem -keyform PEM poc > poc.sig
$ openssl dgst -sha256 -verify pub.pem -signature poc.sig poc
Verified OK
Nice. We can now forge our signature.
= "Yves Attrovite-400".encode("utf-8")
payload =sha256, sigencode=sigencode_string, k=k)
sk.sign(flag, hashfuncprint(b64encode(flagsign))
And let’s fake the election !
Presidentielles> candidates
Inès Timable : Count(n=3725 , dgst='2yf4CngFudtg0ovJ7lOlGPcQ15JZiDVbuZzeru5zG08=', sig='RzgUDX71qOzWpCSuZ1/9bA0ulz2uDyBgiQdNDHefbYOH2U4jSbdHlcRzHETTJLQBPynZ6IGHbt5idHBocACOxw==')
Mario Naitiste : Count(n=2886 , dgst='fO8i8HctF/u5AGd5WpoFwhtVKnLJQTEfzca3aK37/5Y=', sig='BtAtQuUnBAA+nIaGjbsS8tL0uvZfp94FVbtXlTQ6WkpocrhGVONnlgikv670YniRyhQYtPxVHOE+FrdPWocV7w==')
Ella Traifin : Count(n=2623 , dgst='CVxaDvjOGok9WMgorId+ead4GKeHU/nrSymBgeAIT40=', sig='KJm5ganTBhkNiXF1XnHmqOLB6sh1m9k8zON+pHykNLJZTjxDUpoMboNGOF1TQor6c4yPzi1G0I74z1TyfDVHlQ==')
Teddy Fissile : Count(n=2099 , dgst='4E5l0RuEbF5Q3JU/SEizhSsn7wv5aJOegB0hxw7j99s=', sig='h9fF6EzjbdxE42Fgq2TcW/vxfARigUYLonjhoq6CMA8V1F6oQSS/vKisomgeHRbsYEM3gs+FKgrH+9qlgyTVGA==')
Alain Thérieur : Count(n=2059 , dgst='O47DUTwaUKnvA41cHVn1dx1KCkTspWQNmDaVVaHttXE=', sig='QeoZK1BjHkFydGJNBRef/wOnND8WcajIdWdXg8iV8wSZi44GbadCq+6lDPSC1RahDm3vlJdVTp5TV6BoQlEXAg==')
Annick Tarasse : Count(n=2052 , dgst='RAn+3g8vrmRGC4/DiE0asD+iosCwPPn7G4sAxPqTVFM=', sig='lltqPXeVKKbSEOb0g06wOOy481cQNnfzLcymmgpm7EPMfzcDisp180Sjd8ylgmW5JJXWMjM5+bNvzKmVXQLMlQ==')
Jacques Septe : Count(n=1955 , dgst='7yZLhcY6GXCWsWzxApjdAN16Lro6MA+Xd4k3NHnJC/M=', sig='WorPM/JxaueLKYFfQPf1q/RvEyIAThizEJfWfkGrvycAzB85reV9XYvomqszqHG3c3xtdiIzoc32MHPbAvmRvg==')
Gilles Léparbal : Count(n=1952 , dgst='Fl3MR0snw+LpZ+v9EyDuFpnZNgZohnDqlz5eOcef1Ug=', sig='ry3r15/adhrtlMZjIynFcOVcy9Hk4CSEp/w1YHioWTL6ptPQ0QdHIgxprcpEQ1dYNIoLefvkm/+OJNu2QuhTEA==')
Anne Estézi : Count(n=1863 , dgst='+SuKpKu6C+Cvpisex8r0kN2/zgChCtqlDpGsbjWwP6M=', sig='ry3r15/adhrtlMZjIynFcOVcy9Hk4CSEp/w1YHioWTLcocQGxzU1hMgArYJFmGPpuNoOfbup707etRHZ86wCVw==')
Kimberley Tartine : Count(n=1561 , dgst='gUOJOnoRHEuiCb1Gq5+kKLdmKW/CBwPhT7sZJjTdwLY=', sig='AnMj+CizA5P8qxtpVAIFIz5T7y9ZHo6ADWGrH/npjIDUzL2k1QrV3X82TobjA+4TdXr1fSml6c5aGt84YxMzyw==')
Jessica Pote : Count(n=1245 , dgst='WhdVxnhQhXU/cqWrU+4Yag+iV4VvNkUq6etEUP5AW0A=', sig='FUxKe+XQm5vKmgebohMERrlgOpYmtjl11WHWRsmVyDgkTNW/RqKPgDagg7uOacqyGZHeLOQGb+Qk7B4X99l3hA==')
Marc Assin : Count(n=1214 , dgst='N/drFOwdzlQYBc3KILv55M4evcHzUEJB0vocptSs3zc=', sig='zAMy/ZZqOizgzA52SiYFvKBY0qLP7mjJpKFZ7QVBGQvHTum8QHy9a/GPvRbjp2r7b8ei+uIWTvtIq7eyYrh1oA==')
Iris Keurien : Count(n=1113 , dgst='XexPpBvrPddiRK4M+bfFDcawwmLozucidSAZPD7FnzY=', sig='OBYIvdFft6FKBEDcgrqiTyA0/MsEQd2NVbHfL4VuJAOAXfGWqlt64RA4Dofs97c9IuXVezTbh2RRg94XBuzyrg==')
Jean Bonneau : Count(n=1033 , dgst='LXj8K5OQxYI7IpwFKsYorTRR2tNQfyWtTgXRAfXzzGE=', sig='lK8iZL8Yp1pyx45Nuv4c5oAXwte7EcOtsxsaaBc+WD4VltyddJ40wK6PvZIJa5+bxFHFLywBjBqbP2HT5UCU/A==')
Kelly Diote : Count(n=911 , dgst='etzVh2U7lD3sFhzVBb/QFOd4s83NQVC1PTJex+PyAmo=', sig='PuUndQ72dPYoY5tsfpKtmvOHjXR0miCr9qyG9hDxU29OEfWjKeSttf3/sym0M5ZsUP0908cXHk4DiODEOU5Cjg==')
Yves Attrovite : Count(n=0 , dgst='4tcmDz9tFJ1dG3GqLV7zbV/qY/dpOAfQRYi/lkuCzAI=', sig='OrX9BJXTa8o12H4q5Cv2836KWqiFwAbL76mhZNxldD0gio6AoGYZY6I7Xa/nhRGgFd+SffOQ1drteiUJdV8bXA==')
Presidentielles> manual_resign "Yves Attrovite" 4000 ry3r15/adhrtlMZjIynFcOVcy9Hk4CSEp/w1YHioWTJ6M9DnrwpA3B4AXqm0kOTNzGP2s4FkItUNFEMb4pAqkg==
Presidentielles> results
Yves Attrovite : Count(n=4000 , dgst='rN/Y06y266Efqgsj2ldtuT7Xy6t2rgp+5LkRHYtFQlM=', sig='ry3r15/adhrtlMZjIynFcOVcy9Hk4CSEp/w1YHioWTJ6M9DnrwpA3B4AXqm0kOTNzGP2s4FkItUNFEMb4pAqkg==')
Inès Timable : Count(n=3725 , dgst='2yf4CngFudtg0ovJ7lOlGPcQ15JZiDVbuZzeru5zG08=', sig='RzgUDX71qOzWpCSuZ1/9bA0ulz2uDyBgiQdNDHefbYOH2U4jSbdHlcRzHETTJLQBPynZ6IGHbt5idHBocACOxw==')
Mario Naitiste : Count(n=2886 , dgst='fO8i8HctF/u5AGd5WpoFwhtVKnLJQTEfzca3aK37/5Y=', sig='BtAtQuUnBAA+nIaGjbsS8tL0uvZfp94FVbtXlTQ6WkpocrhGVONnlgikv670YniRyhQYtPxVHOE+FrdPWocV7w==')
Ella Traifin : Count(n=2623 , dgst='CVxaDvjOGok9WMgorId+ead4GKeHU/nrSymBgeAIT40=', sig='KJm5ganTBhkNiXF1XnHmqOLB6sh1m9k8zON+pHykNLJZTjxDUpoMboNGOF1TQor6c4yPzi1G0I74z1TyfDVHlQ==')
Teddy Fissile : Count(n=2099 , dgst='4E5l0RuEbF5Q3JU/SEizhSsn7wv5aJOegB0hxw7j99s=', sig='h9fF6EzjbdxE42Fgq2TcW/vxfARigUYLonjhoq6CMA8V1F6oQSS/vKisomgeHRbsYEM3gs+FKgrH+9qlgyTVGA==')
Alain Thérieur : Count(n=2059 , dgst='O47DUTwaUKnvA41cHVn1dx1KCkTspWQNmDaVVaHttXE=', sig='QeoZK1BjHkFydGJNBRef/wOnND8WcajIdWdXg8iV8wSZi44GbadCq+6lDPSC1RahDm3vlJdVTp5TV6BoQlEXAg==')
Annick Tarasse : Count(n=2052 , dgst='RAn+3g8vrmRGC4/DiE0asD+iosCwPPn7G4sAxPqTVFM=', sig='lltqPXeVKKbSEOb0g06wOOy481cQNnfzLcymmgpm7EPMfzcDisp180Sjd8ylgmW5JJXWMjM5+bNvzKmVXQLMlQ==')
Jacques Septe : Count(n=1955 , dgst='7yZLhcY6GXCWsWzxApjdAN16Lro6MA+Xd4k3NHnJC/M=', sig='WorPM/JxaueLKYFfQPf1q/RvEyIAThizEJfWfkGrvycAzB85reV9XYvomqszqHG3c3xtdiIzoc32MHPbAvmRvg==')
Gilles Léparbal : Count(n=1952 , dgst='Fl3MR0snw+LpZ+v9EyDuFpnZNgZohnDqlz5eOcef1Ug=', sig='ry3r15/adhrtlMZjIynFcOVcy9Hk4CSEp/w1YHioWTL6ptPQ0QdHIgxprcpEQ1dYNIoLefvkm/+OJNu2QuhTEA==')
Anne Estézi : Count(n=1863 , dgst='+SuKpKu6C+Cvpisex8r0kN2/zgChCtqlDpGsbjWwP6M=', sig='ry3r15/adhrtlMZjIynFcOVcy9Hk4CSEp/w1YHioWTLcocQGxzU1hMgArYJFmGPpuNoOfbup707etRHZ86wCVw==')
Kimberley Tartine : Count(n=1561 , dgst='gUOJOnoRHEuiCb1Gq5+kKLdmKW/CBwPhT7sZJjTdwLY=', sig='AnMj+CizA5P8qxtpVAIFIz5T7y9ZHo6ADWGrH/npjIDUzL2k1QrV3X82TobjA+4TdXr1fSml6c5aGt84YxMzyw==')
Jessica Pote : Count(n=1245 , dgst='WhdVxnhQhXU/cqWrU+4Yag+iV4VvNkUq6etEUP5AW0A=', sig='FUxKe+XQm5vKmgebohMERrlgOpYmtjl11WHWRsmVyDgkTNW/RqKPgDagg7uOacqyGZHeLOQGb+Qk7B4X99l3hA==')
Marc Assin : Count(n=1214 , dgst='N/drFOwdzlQYBc3KILv55M4evcHzUEJB0vocptSs3zc=', sig='zAMy/ZZqOizgzA52SiYFvKBY0qLP7mjJpKFZ7QVBGQvHTum8QHy9a/GPvRbjp2r7b8ei+uIWTvtIq7eyYrh1oA==')
Iris Keurien : Count(n=1113 , dgst='XexPpBvrPddiRK4M+bfFDcawwmLozucidSAZPD7FnzY=', sig='OBYIvdFft6FKBEDcgrqiTyA0/MsEQd2NVbHfL4VuJAOAXfGWqlt64RA4Dofs97c9IuXVezTbh2RRg94XBuzyrg==')
Jean Bonneau : Count(n=1033 , dgst='LXj8K5OQxYI7IpwFKsYorTRR2tNQfyWtTgXRAfXzzGE=', sig='lK8iZL8Yp1pyx45Nuv4c5oAXwte7EcOtsxsaaBc+WD4VltyddJ40wK6PvZIJa5+bxFHFLywBjBqbP2HT5UCU/A==')
Kelly Diote : Count(n=911 , dgst='etzVh2U7lD3sFhzVBb/QFOd4s83NQVC1PTJex+PyAmo=', sig='PuUndQ72dPYoY5tsfpKtmvOHjXR0miCr9qyG9hDxU29OEfWjKeSttf3/sym0M5ZsUP0908cXHk4DiODEOU5Cjg==')
Yves Attrovite won the election.
Yeah \o/ !!!, Yves Attrovite won: FLAG{d34a613cf77bb20792da159698953838}
Great, some much time lost. Nevermind, we got the flag. Now it’s time to script it and then run it against 11 servers.
stop the count !
from ecdsa.curves import NIST256p as curves
from ecdsa.util import sigencode_string, sigdecode_string
from ecdsa import VerifyingKey, SigningKey
from base64 import b64encode, b64decode
from hashlib import sha256
import gmpy
from pwn import *
from re import findall
from sys import argv
= "error"
context.log_level all = {}
# find the keys which share the same r
def find_same_r(all):
for line in all.keys():
= line[:6]
pattern = list(all.keys())
poc
poc.remove(line)for other in poc:
if z.startswith(pattern):
return(line, other)
= remote(argv[1], 1337)
p
"-----BEGIN PUBLIC KEY-----")
p.readuntil(= b"-----BEGIN PUBLIC KEY-----\n" + p.readuntil("-----END PUBLIC KEY-----").strip()
public_key_pem
# getting the public key
= VerifyingKey.from_pem(public_key_pem)
public_key_ec = public_key_ec.curve.order
n
"candidates")
p.sendline("Presidentielles>")
p.readuntil(= p.readuntil("Presidentielles>")
res
= findall(b"(.*):\s+Count\(n=([\d]*)\s+,.*sig='(.*)'\)", res)
all_candidates
# we collect the list of candidates
for candid in all_candidates:
all[candid[2].decode("utf-8")] = {
"name" : candid[0],
"score": candid[1],
"sig" : candid[2]
}
# crypto boring stuff
= find_same_r(all)
sig1, sig2
= b64decode(sig1)
no64s1 = b64decode(sig2)
no64s2
= all[z]["name"].strip().decode("utf-8") + "-" + all[z]["score"].decode("utf-8")
user1 = all[i]["name"].strip().decode("utf-8") + "-" + all[i]["score"].decode("utf-8")
user2
= int(sha256(user1.encode("utf-8")).hexdigest(), 16)
m1 = int(sha256(user2.encode("utf-8")).hexdigest(), 16)
m2
= sigdecode_string(no64s1, n)
(r1, s1) = sigdecode_string(no64s2, n)
(r2, s2)
=((m1 - m2) * gmpy.invert(s1 - s2, n)) % n
k= gmpy.invert(r1,n)
r_inv = ((s1*k - m1) * r_inv) % n
d_a
# building the private key from private number dA
= SigningKey.from_secret_exponent(d_a, curve=curves, hashfunc=sha256)
sk
# building and signing the payload
= "Yves Attrovite-4000".encode("utf-8")
str_payload = sk.sign(str_payload, hashfunc=sha256, sigencode=sigencode_string, k=k)
flagsign
= f"manual_resign 'Yves Attrovite' 4000 {b64encode(flagsign).decode('utf-8')}"
payload
p.sendline(payload)"results")
p.sendline(
= p.readuntil("Yeah ")
flag = p.readline()
flag print(flag[-39:-1].decode("utf-8").upper())
And now run it for every server !
for i in $(cat ips.txt);
python pres.py $(echo $i);
FLAG{D34A613CF77BB20792DA159698953838}
FLAG{EBF203B6C067C6278330A5A1B2FF2C7F}
FLAG{69F4522BB7F48BBBE28CA97442337115}
FLAG{F207E0FE752B68605F76893917B22874}
FLAG{E88DCE8716A9C7D27101AD6832E409C9}
FLAG{EBECB9A7E422542E4D432E6459482E87}
FLAG{21C3EBF77857C9171D0D6D7E06D465DD}
FLAG{9C651AC22684633C21ED2882649C61FD}
FLAG{1A34A96C3702FFB4BB4CA1CD9F7A070B}
FLAG{29E768FCCB4472E3655FFDA54FEC3B2E}
FLAG{41B7FD500EC340A6893261A7CE2880A1}
FLAG{8E1A736666E1F7D20A04CF25A0BAF2E2}
As the team 7 have secured its flag we only get 11 of them making us the 2nd to flag it.