Flash CTF – Tampered Seal

In this forensics challenge, we’re given a packet capture that contains, among other things, encrypted traffic. There must be some way to decrypt it…

Solution

After opening the .pcap file in Wireshark, we can open the “Protocol Hierarchy” window via the “Statistics” menu to get an overview of the protocols and corresponding number of packets present in this file.

From the Protocol Hierarchy window, we can see that most of the packets use the TLS protocol, meaning that a majority of the traffic is encrypted. Wireshark can decrypt TLS traffic via three methods:

  1. Using a key log file that consists of pre-shared session secrets
  2. Using an RSA private key
  3. Using a pre-shared key (PSK)

Unfortunately, we don’t have a key log file, so option 1 won’t work for us here. If we take a look at the fields in the Server Hello packet, we can see that the Handshake Protocol is using TLS 1.2 with a cipher suite of TLS_RSA_WITH_AES_256_CBC_SHA (0x0035):

The interesting thing about the chosen cipher suite is that it uses RSA for exchanging the AES key, which is then used to encrypt traffic. The RSA key exchange doesn’t support Perfect Forward Secrecy, which means that if we recovered the private key, we can decrypt all previous transmissions that used the key.

Decrypting Traffic

RSA works by taking two prime numbers with a large difference between them (commonly known as p and q) and multiplying them to compute the modulus (known as n). The difficulty in factoring n is what RSA is reliant on for security. This is because if an attacker knows p and q, they can use them along with the public exponent e (which provided alongside n as part of the public key) to calculate the private exponent d, which can then be used to form the private key and decrypt any ciphertexts obtained.

If we want to try and recover p and q, we’re going to need n first. To get this, we can select the public certificate within the Server Hello packet and use “Export Packet Bytes” to export the certificate into a file called public.der.

Once we have the certificate file, we can use openssl to inspect the certificate information, including the modulus n:

$ openssl x509 -inform DER -in public.der -text -modulus
Certificate:
    Data:
        Version: 1 (0x0)
        Serial Number:
            7d:a6:a1:c7:57:75:7c:44:4c:23:23:cc:77:04:7e:e1:70:a2:eb:c2
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN = bak.metactf.com
        Validity
            Not Before: Apr 14 10:11:27 2025 GMT
            Not After : Apr 14 10:11:27 2026 GMT
        Subject: CN = bak.metactf.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (4093 bit)
                Modulus:
                    10:10:40:8f:e6:cc:8f:cb:6f:ed:e3:17:34:4b:9d:
                    8c:85:69:4a:e7:39:65:01:10:05:b9:fc:ae:09:c8:
                    .............................................
                    41:0a:dd:7e:d2:9c:4c:6d:0a:3f:4a:29:d7:3e:b5:
                    f1:31:1c:c2:da:6a:74:47:72:b3:46:7a:36:5f:b0:
                    77:39
                Exponent: 65537 (0x10001)
    Signature Algorithm: sha256WithRSAEncryption
         0b:ce:3e:75:d4:46:4c:dd:20:b8:1a:68:04:22:7a:bf:c7:69:
         ad:c8:ea:01:5a:6a:a3:cf:a0:be:7a:0e:b1:cb:96:d0:8b:ee:
         ......................................................
         c0:d1:93:cd:aa:9f:e8:3e:c6:f0:f3:8f:7a:ee:97:59:35:ee:
         90:4a:ec:ab:c4:f2:70:88:7e:1f:f8:eb:c6:f5:b4:1c:ef:35:
         dd:f5:1c:7f:ba:72:76:71
Modulus=1010408FE6CC8FCB6FEDE317344B9D8C85694AE73965011005B9FCAE09C8D70125FA8F8AE0326E8C80FFE52A904C836A3296D2C40C982BF14BEF7415F0840889D9A4A90A66E7B61839EC591BF2D559F1D9479FC5C682C9A1F8FDA65D8B0D3E2DA7271BCD7831B7CE1B4E8468BB951EC5D52FE3C5BAA3945043ACE5785FC8B8A05043A37E1DC9BC9C6958660F99D5CCCCD575760964AAC931FA8AA81BD7EB1B7B0FB8FD06A6DCCA3D6ED29348F556663A9D5CAB49E3A3887C7A29C9C165F535299F4ECDA977AB541D1E147527BA432D09D9F559FD5718E7E2A96846F48827B2C06E76B30F4D730FF043C25BDC7D99E323732D2ACAF9E96E967476BCE5AB1C8BDB2AE9D805060F56127F55594683B760036994F2A5608EE91C01DE4DA6DED3312C85B303602423EC2A7E81B1A7C7CDF6B3545AA42EBCE6792C80A8D54A608223392FC1FF9E775CA00C9583B529160275220440DCD46D89F4449C58EF8ABA17EB4C598ABA5F3245B4B9296C24BF97EE08E19A403AE7C8C26A2149A02B984BDDD1FD4A94AC88D530FC7E61300F8D146DEB6A0F29F2AEDB7167846B7FC288A314E87816AF70043AAA3D9DB18EEF9FDABFAE2D05609D85AF0D3FD4DB46A58C4C4B1AA5C40B9D7E4EABE2531B6E0C5880BFD1CA7D76343AB11AAD382EDD7A8CEBA77F7D410ADD7ED29C4C6D0A3F4A29D73EB5F1311CC2DA6A744772B3467A365FB07739
-----BEGIN CERTIFICATE-----
MIIEujCCAqICFH2mocdXdXxETCMjzHcEfuFwouvCMA0GCSqGSIb3DQEBCwUAMBox
GDAWBgNVBAMMD2Jhay5tZXRhY3RmLmNvbTAeFw0yNTA0MTQxMDExMjdaFw0yNjA0
................................................................
xAKLBsKRHduaUXXoHoC1UPMcwNGTzaqf6D7G8POPeu6XWTXukErsq8TycIh+H/jr
xvW0HO813fUcf7pydnE=
-----END CERTIFICATE-----

Now that we have the modulus, we can use the Python console to get the integer representation:

$ python3
Python 3.8.10 (default, Nov 22 2023, 10:22:35)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 0x1010408FE6CC8FCB6FEDE317344B9D8C85694AE73965011005B9FCAE09C8D70125FA8F8AE0326E8C80FFE52A904C836A3296D2C40C982BF14BEF7415F0840889D9A4A90A66E7B61839EC591BF2D559F1D9479FC5C682C9A1F8FDA65D8B0D3E2DA7271BCD7831B7CE1B4E8468BB951EC5D52FE3C5BAA3945043ACE5785FC8B8A05043A37E1DC9BC9C6958660F99D5CCCCD575760964AAC931FA8AA81BD7EB1B7B0FB8FD06A6DCCA3D6ED29348F556663A9D5CAB49E3A3887C7A29C9C165F535299F4ECDA977AB541D1E147527BA432D09D9F559FD5718E7E2A96846F48827B2C06E76B30F4D730FF043C25BDC7D99E323732D2ACAF9E96E967476BCE5AB1C8BDB2AE9D805060F56127F55594683B760036994F2A5608EE91C01DE4DA6DED3312C85B303602423EC2A7E81B1A7C7CDF6B3545AA42EBCE6792C80A8D54A608223392FC1FF9E775CA00C9583B529160275220440DCD46D89F4449C58EF8ABA17EB4C598ABA5F3245B4B9296C24BF97EE08E19A403AE7C8C26A2149A02B984BDDD1FD4A94AC88D530FC7E61300F8D146DEB6A0F29F2AEDB7167846B7FC288A314E87816AF70043AAA3D9DB18EEF9FDABFAE2D05609D85AF0D3FD4DB46A58C4C4B1AA5C40B9D7E4EABE2531B6E0C5880BFD1CA7D76343AB11AAD382EDD7A8CEBA77F7D410ADD7ED29C4C6D0A3F4A29D73EB5F1311CC2DA6A744772B3467A365FB07739
65533301861911863972716512639167235165064274113042722391423927416017946400687912351396816702641261392630151674587954933696853872562681897385038118819487152708993681377155481936835296390219807320857515271871604376575478196292945280751914468582358452410969063030425316210527778719710578641789596539879025990072631309156224235447063273781606471332327392695358109757663386827914106266332870766699495091547244812126596682814201968221764593579616818990171627488304957724183036553370606633965800179527571820996191163331492501398723131949298965912645865847762194982390285415336915734506076177702287405507149024423044781381598000564986654154397282618287479434554777333341156652471917529642165624350746564183248754964644705807383789364754004484105028037944731410317988050826996548688551385215965578717044730817079020618731778220079934710199707072826654802244375527406625287880348085659361416553183605846598383629979843201057505567898258931428886771088098722026335483921482766495537054595805644538277636083352756134885246439679626707188580564900607783729416588845002040273391051833017585215339566502016065209682658249767767932402722489541809582418996814724555056020625564640859976740925317610485334979626156950843733986932458893989330648332089

With the value of the modulus, we can use something like dCode’s Prime Factors Decomposition tool to get the factors p and q. Once we have these values, we can plug them into a calculator such as rsatool to generate all the values for RSA and the private key:

$ rsatool -p 8095264162577516776554016731786910962768781880394696035455743731691882779484784870117879012676990505640856223475843851973394424465004316921212760632213972570687210775782297770276548476749293781641877143368794430277993447344992239748870702784506363458307062507328962342692515046895067184355061617551884504166061601730265057857488197407951684172405538507706368459944953050405146801224682010733754169111631132303492891703918042831114750352470807168859787350400887834063511854414661300718136290974698289768880529708756807216950355557351872914085947567080961675666676080475738474802760359083416881280694685548944362334061 -q 8095264162577516776554016731786910962768781880394696035455743731691882779484784870117879012676990505640856223475843851973394424465004316921212760632213972570687210775782297770276548476749293781641877143368794430277993447344992239748870702784506363458307062507328962342692515046895067184355061617551884504166061601730265057857488197407951684172405538507706368459944953050405146801224682010733754169111631132303492891703918042831114750352470807168859787350400887834063511854414661300718136290974698289768880529708756807216950355557351872914085947567080961675666676080475738474802760359083416881280694685548944362337149
Using (p, q) to calculate RSA paramaters

n =
1010408fe6cc8fcb6fede317344b9d8c85694ae73965011005b9fcae09c8d70125fa8f8ae0326e8c80ffe52a904c836a3296d2c40c982bf14bef7415f0840889d9a4a90a66e7b61839ec591bf2d559f1d9479fc5c682c9a1f8fda65d8b0d3e2da7271bcd7831b7ce1b4e8468bb951ec5d52fe3c5baa3945043ace5785fc8b8a05043a37e1dc9bc9c6958660f99d5ccccd575760964aac931fa8aa81bd7eb1b7b0fb8fd06a6dcca3d6ed29348f556663a9d5cab49e3a3887c7a29c9c165f535299f4ecda977ab541d1e147527ba432d09d9f559fd5718e7e2a96846f48827b2c06e76b30f4d730ff043c25bdc7d99e323732d2acaf9e96e967476bce5ab1c8bdb2ae9d805060f56127f55594683b760036994f2a5608ee91c01de4da6ded3312c85b303602423ec2a7e81b1a7c7cdf6b3545aa42ebce6792c80a8d54a608223392fc1ff9e775ca00c9583b529160275220440dcd46d89f4449c58ef8aba17eb4c598aba5f3245b4b9296c24bf97ee08e19a403ae7c8c26a2149a02b984bddd1fd4a94ac88d530fc7e61300f8d146deb6a0f29f2aedb7167846b7fc288a314e87816af70043aaa3d9db18eef9fdabfae2d05609d85af0d3fd4db46a58c4c4b1aa5c40b9d7e4eabe2531b6e0c5880bfd1ca7d76343ab11aad382edd7a8ceba77f7d410add7ed29c4c6d0a3f4a29d73eb5f1311cc2da6a744772b3467a365fb07739

e = 65537 (0x10001)

d =
7a11068c7ff23d44546c07246de1ae7a07d4f9bde0080aeb16991febe9aeab29aed13bbab9b8831a71ae22682fe33f3fe2eefcf390414eab75bf4d06eb8e6ad41f71869de51f284330e738bf89b27eadb7d4efec9b758385b5f6ce244a6777f8b41e1cc8667669ab7116585b081b469d544b773339f79535db1021357baa7254cb9b32eb4cb198f57e12a2802b22e60a288f514a5011029728ae428c535de8966003ffc26c7fa2d8f32e7cce0e8b608e4b01c922d5180034eaa062e9a540979c935ce2bccb04c74bfd28b89e0c702df08eba45fd43bf9f606eb85b7a1eae8d4c8220c0d1a85a96e3d6053ecb6a29de43a519b5f67ae5c5d8cd6d0046c6e5465f863db66ec5035ca8f44c2d440cfa966e081fbd3f477c126b8234f3d753f7bd159c6835464fbef0d204f07005c2cb6d45375e7980abf8b0b584cdef8da4a29f6287f1e6c91261e4f79ef566b4fa9aaee67668ce9b2e6d7943b7e6bb4ec5daf119277dbaf2b62da89c6b4176620215c4a10fd92bc3b5cc2cccb6cacc063ffb23bd54b422d5df933c4b55acf89b7345cc6d4bb6281f686cc81f2bc11391fb9d10ad3b398b7d0de9bf84d5c6c1f64303d56e5f91f9cf4f09a6d643feaa16beff42f2b5a6902a2c5607eb8d5b162fca866cc54bd53bc8b9100dded000449acfdf6660fa5408eef7e0bc5cd041389fe049459b7f3fc37207ef4f8272c7ba9f8f666e1

p =
402078e2e9f91a12e234697469455d6ff533e55a954110fa22c747fc07adf46fae589f3c335cb4e3e2ad1a3b8627493fedbdd0a0a519f9bddd23c771e93286cfb954db1ccb0ee373ece70f85019dd0aea3eaf414a545feb5f0fe7ed49428520802429993406ef07bbb10304d81d04f7c0c1c76e57b6de5b0ddf3993099cd3247349138148c636baf27c76ef9e21c6d5cca1e4bae57a0825ff62a31171cad5f6839575f22a0d59e5c309d842a9826f801a7d8f6b74f05fe753b8f9f28af9fc0d047106641b22ae18090934db9718f7bb7fa8248c605042eb982f84c9fa8ce0f601e1907f497753824af8e153da473cdecf2d6d6c34cec04144c2df5ce11f4676d

q =
402078e2e9f91a12e234697469455d6ff533e55a954110fa22c747fc07adf46fae589f3c335cb4e3e2ad1a3b8627493fedbdd0a0a519f9bddd23c771e93286cfb954db1ccb0ee373ece70f85019dd0aea3eaf414a545feb5f0fe7ed49428520802429993406ef07bbb10304d81d04f7c0c1c76e57b6de5b0ddf3993099cd3247349138148c636baf27c76ef9e21c6d5cca1e4bae57a0825ff62a31171cad5f6839575f22a0d59e5c309d842a9826f801a7d8f6b74f05fe753b8f9f28af9fc0d047106641b22ae18090934db9718f7bb7fa8248c605042eb982f84c9fa8ce0f601e1907f497753824af8e153da473cdecf2d6d6c34cec04144c2df5ce11f4737d

-----BEGIN RSA PRIVATE KEY-----
MIIJJAIBAAKCAgAQEECP5syPy2/t4xc0S52MhWlK5zllARAFufyuCcjXASX6j4rg
Mm6MgP/lKpBMg2oyltLEDJgr8UvvdBXwhAiJ2aSpCmbnthg57Fkb8tVZ8dlHn8XG
................................................................
K5+GUdOXPN0auv1+hJyHOVQFmDu62VHNzJbVXdYEcMfBWPrLh8S+jcrC9nf+SNnB
12ENxqb7OEBG82+Q0KpSK3I3bjXVkNF/4107urmtQfQzLd4ZpWondA==
-----END RSA PRIVATE KEY-----

With the private key, we can save it to a file called private.pem. Then, we can go back to Wireshark, open the “Edit” menu, select “Preferences”, open the “Protocols” dropdown menu, scroll to “TLS” on the left-side pane, and add our own entry to the RSA keys list:

Traffic Analysis

After adding the private key to the RSA keys list for TLS, we can view the decrypted traffic. If we right-click on a request and click “Follow TLS Stream”, we can see the content of the requests and responses, which shows that the user visited bak.metactf.com, which hosts “cracked” software:

Furthermore, we see that the user downloaded a file named CorelDRAW.exe. We can use the “Export Objects” window Wireshark to export the executable and run the file command on it, which reveals that it’s a .NET executable.

$ file CorelDRAW.exe
CorelDRAW.exe: PE32+ executable (GUI) x86-64 Mono/.Net assembly, for MS Windows

Malware Analysis

We can use tools such as ILSpy or dotPeek to reverse engineer the malware. After loading the executable into our tool of choice and clicking on the csharp_streamer namespace, we can explore the different classes. Clicking on the ProgramCodeWrapper class shows us that the malware attempts to connect to bak.metactf.com on several ports: 8080, 80, 443, 25, 2525, 110, 993, 3389, 139, and 135.

Moving back to Wireshark, the traffic shows that after failing to connect on ports 8080, 80, and 443, the malware finally establishes communication on port 25:

Wireshark automatically decodes the packets on port 25 as SMTP. If we check the SocketInterface class, we see a function called EnterConnectionLoop which indicates that the malware makes a connection using the WebSocket Secure protocol over TLS 1.2:

The malware also appears to reuse the certificate from the web service on port 443, but in order to get Wireshark to correctly decode the packets, we need to remap the port using a tool like tcprewrite:

tcprewrite --portmap=25:443 -i Tampered_Seal.pcap -o out.pcap

After remapping, Wireshark automatically decodes the TLS traffic and reveals WebSocket packets:

If we take a closer look at the TunneledSocket class, the function SendProtocolPacket indicates that the WebSocket payloads are further encrypted using RC4. Luckily, if we go to the RC4 class under the csharp_streamer.Utils namespace, it seems that the password is hardcoded into the executable.

Now that we have the RC4 key, we can start decrypting packets in the WebSocket stream, (stream 11), ourselves using CyberChef.

The information decoded from our packet corresponds to the information gathered in the BuildRegisterMessage function, located under the Ident class:

If we continue decrypting packets, making sure to remove extra leading bytes that define file metadata, we’ll eventually start reconstructing screenshots of the victim’s desktop, displaying the flag.

MetaCTF{Security_1s_n0t_4_0n3-7im3_Event}