HeroCTF v5 - OSINT 1 and 2 (blockchain)

HeroCTF v5 - OSINT 1 and 2 (blockchain)

This weekend (12 to 14 May 2023) was under the HeroCTF v5 CTF. I started Ethereum / Solidity CTF challenges a few time ago, and I still struggle understanding some concepts, but these OSINT challenges were quite nice (13 and 9 solves);

These challenge doesn’t run on mainnet, but on a dedicated blockchain. We got the JSON-RPC endpoint to connect to. It is also mentionned that all the transactions occured before the start of the CTF on May 12 2023 9 pm GMT+2.

The arrest

Given this address 0xf6c0513FA09189Bf08e1329E44A86dC85a37c176 we need to find who send Ether to this account. In Ethereum blockchain, transaction looks like this :

There is a from, the sender of the transaction, and there is a to, the receiver. There are also other value like the value which represent the amount of ether transferred. All these transactions are stored in the blockchain trough stacks of many of them inside a block.

It is possible to look at every transaction within a given block. For example block 0 and block 1001.

w3.eth.get_block(1001).transactions

# [HexBytes('0xb39325bd2382135b77ade617cfe609a3311979601d90caf95366903df27cb306'),
#  HexBytes('0x3ca1b62630a43a3ba749923252ada04238c242a35e734c76a1082f7f25d5d104'),
#  HexBytes('0x185bfcfe660f5d9d25fcb9dc6c51cde0719823247b5b9f50f72eff7ac85b1c28')]

w3.eth.get_block(0).transactions
# []

There is no transaction inside the first block. From this we can try to scan all transactions to look for transactions between the first block and the start of the CTF. This mean that block that have been created past 1683918000 are made after the start of the CTF. The first block after this date is 23258.

I started scanning and quickly find out that there is no transaction before the 430th block.

The algorithm is simple : for every block we look for every transaction in it and for every transaction we look if the receiver is 0xf6c0513FA09189Bf08e1329E44A86dC85a37c176.

import web3
from web3.middleware import geth_poa_middleware
from threading import Thread

w3 = web3.Web3(web3.Web3.HTTPProvider("http://62.171.185.249:8502/"))
w3.middleware_onion.inject(geth_poa_middleware, layer=0)

wanted = "0xf6c0513FA09189Bf08e1329E44A86dC85a37c176".lower()

def do_scan(*args):
    start = args[0]
    
    for i in range(start, start + 100):
        for _tx in w3.eth.get_block(i).transactions:
            tx = w3.eth.get_transaction(_tx)

            _from = getattr(tx, "from")
            _to = getattr(tx, "to")

            if  _to is not None and _to.lower() == wanted:
                print(tx.blockNumber, getattr(tx, "from"), tx.to, tx.nonce, tx.value, tx.hash.hex())

if __name__ == "__main__":

    for i in range(400, 1400, 100):
        Thread(target=do_scan, args=[i]).start()

Few seconds after we got the following

928 0x54741632BE9F6E805b64c3B31f3e052e1eAe73e2 0xf6c0513FA09189Bf08e1329E44A86dC85a37c176 0 7892772282257845895 0x49c97be4a735e88a34ea6b0f4f7ca2e86dee67d1b472214b7cf4c703ffdd3c9e

The address 0x54741632BE9F6E805b64c3B31f3e052e1eAe73e2 sent 7892772282257845895 wei to 0xf6c0513FA09189Bf08e1329E44A86dC85a37c176 !

Tracing the First Transaction

TLDR ; the goal is to identify who sent ether to the address we just found : 0x54741632BE9F6E805b64c3B31f3e052e1eAe73e2. If we run the script and changing the wanted value, we will have this :

901 0x7D61eB01524570DfE5763393F8c1231058130c92 0x54741632BE9F6E805b64c3B31f3e052e1eAe73e2 0 1011893882340749474 0xb807950eec66243c930795261a95f838af777302db9fc10e5995d254f38e246f
902 0x71693bD81C023374c43D4564a1435f1a1b431C5d 0x54741632BE9F6E805b64c3B31f3e052e1eAe73e2 0 1079353474496799439 0xa4a83025ea3a208f41d1d5c8bc5487c89a37d303a5a4be6fe2f3c1ae759588d6
903 0xF725679450D89f8b00e5Bb998B6aDBF3c2e0f1AD 0x54741632BE9F6E805b64c3B31f3e052e1eAe73e2 0 1034380413059432795 0xe208825f0f0f9d16de6f0ef49ad05618132da39603651b42cd2af437c8bd7b2d
905 0x3fe60a1Ed7165b9f1319Ddc5EA12d1A01353a717 0x54741632BE9F6E805b64c3B31f3e052e1eAe73e2 0 1371678373839682620 0x69af1d532e682e91d38a583850d5e11838cce1c764d6344068a7d5f631825be3
906 0x4f750BD555755Ab66e87B24A2DD28310e37c465c 0x54741632BE9F6E805b64c3B31f3e052e1eAe73e2 0 719568982997866292 0x5464fba0cc1501f56a940a3b941ce4806c1137cf34dc1d2046743363a8dcab2d
907 0x25540369aE41F5B1a71ddA1dEff53311CDaB3b1B 0x54741632BE9F6E805b64c3B31f3e052e1eAe73e2 0 1169299597371532725 0x9bb047b6a26fda35e02f2e768b613937e7f9bc9aaeee9af095ffa679ca464bb3
909 0x8c98DDf14543cC791bA2434b4981B429bF3132c4 0x54741632BE9F6E805b64c3B31f3e052e1eAe73e2 0 944434290184699509 0x7140bfa5b05123a2b34b6c7e0551a0c4a18536e5463eac125e9769f1faddb583
910 0xBEe1C37253095EDFb677D9DFae301293D9d4F18a 0x54741632BE9F6E805b64c3B31f3e052e1eAe73e2 0 876974698028649544 0x94f0ced261e7309518754595fa4e253f1aff2ef286ddabfb5badbb6fb9c32e32
911 0xf8C7c772F13c75BE6B9f854Bd12C99c8cE19D2d2 0x54741632BE9F6E805b64c3B31f3e052e1eAe73e2 0 1664003273182565802 0x4ffe1a49d3bc105e72391a5dfc50911c11eaa4dd5a41ded736e20c8f1a0bff23
912 0x290B7365F8D771d76Cc8ff8aae1C7DF858bf9AEc 0x54741632BE9F6E805b64c3B31f3e052e1eAe73e2 0 1371678373839682620 0x6f9f8f1a31fd819d541e7d85b2e9119eb0d7167d955974901f33d6018da63880
913 0x46bACe73B5e743a83ea20dE05C4CE8191aae8c05 0x54741632BE9F6E805b64c3B31f3e052e1eAe73e2 0 1753949396057299088 0xa5525beb99e7a23fb0ecaedb78dd4415f8670ee1370fd30f5a1ad9758448144f
914 0xbDF024D8504C630106d18acD6d470C96B326a7aB 0x54741632BE9F6E805b64c3B31f3e052e1eAe73e2 0 1371678373839682620 0x89a54d045fd32437d9e50aacfbda2bbc540cfc28bb76093eea6ecb1001d11678
899 0xA4dEcDE5c65D94B2A984346dAa5FA37A77F04c2a 0x54741632BE9F6E805b64c3B31f3e052e1eAe73e2 0 1416651435277049263 0xef082750deea98dff5d4a428e132db67fae1078493010fa4b636364532beb2aa

All these addresses sent some ether to 0x54741632BE9F6E805b64c3B31f3e052e1eAe73e2. Let’s find who sent ether to these address now. The script is the same but we changed wanted with :

wanted = list(map(str.lower, [
'0xA4dEcDE5c65D94B2A984346dAa5FA37A77F04c2a',
'0x7D61eB01524570DfE5763393F8c1231058130c92',
'0x71693bD81C023374c43D4564a1435f1a1b431C5d',
'0xF725679450D89f8b00e5Bb998B6aDBF3c2e0f1AD',
'0x3fe60a1Ed7165b9f1319Ddc5EA12d1A01353a717',
'0x4f750BD555755Ab66e87B24A2DD28310e37c465c',
'0x25540369aE41F5B1a71ddA1dEff53311CDaB3b1B',
'0x8c98DDf14543cC791bA2434b4981B429bF3132c4',
'0xBEe1C37253095EDFb677D9DFae301293D9d4F18a',
'0xf8C7c772F13c75BE6B9f854Bd12C99c8cE19D2d2',
'0x290B7365F8D771d76Cc8ff8aae1C7DF858bf9AEc',
'0x46bACe73B5e743a83ea20dE05C4CE8191aae8c05',
'0xbDF024D8504C630106d18acD6d470C96B326a7aB', ]))

-  if  _to is not None and _to.lower() == wanted:
+  if  _to is not None and _to.lower() in wanted:

Output :

879 0x2626e2C853b1F94c012DF03b595BDfb212914b0f 0xA4dEcDE5c65D94B2A984346dAa5FA37A77F04c2a 0 2248653071868332165 0x32489306c9d53e2c857b3c0277bf5cf031d7a6ae81fa0392bbfd8c666084caf1
880 0x2626e2C853b1F94c012DF03b595BDfb212914b0f 0x7D61eB01524570DfE5763393F8c1231058130c92 1 2248653071868332165 0xad91414b686d9ea284d56cc9ae168a0538b7333193082a5fa7ca82a7f58ae2aa
881 0x2626e2C853b1F94c012DF03b595BDfb212914b0f 0x71693bD81C023374c43D4564a1435f1a1b431C5d 2 2248653071868332165 0xa6af37c109b742000dd3e5d13b6bbd828fdb92fae1491cde969414d51ced4187
883 0x2626e2C853b1F94c012DF03b595BDfb212914b0f 0xF725679450D89f8b00e5Bb998B6aDBF3c2e0f1AD 3 2248653071868332165 0x2d9791cf06820ed5fcabc88b22e0a7e9e34901c3c4e9d328afcc071e72468e06
884 0xFfb0c469144D9F187E77C6089B1a882A1E6e832C 0x3fe60a1Ed7165b9f1319Ddc5EA12d1A01353a717 0 2248653071868332165 0x8c7951d919bb4cb2716e701906bf19e4ca30fc32c0060ea0ae475198e9db5a34
887 0xFfb0c469144D9F187E77C6089B1a882A1E6e832C 0x4f750BD555755Ab66e87B24A2DD28310e37c465c 2 2248653071868332165 0x02ecf4d3d793b72a593f2c9ee0c76546e6bbdc83d6d277f23830936901850e27
889 0xe08384432816c029CC8365Df4B2d09fdFaC8640f 0x25540369aE41F5B1a71ddA1dEff53311CDaB3b1B 0 2248653071868332165 0xf8a83db512e5fa8023c2364f6dc64be5b11fee0f934b93e3b2eac2d5ff7100f8
892 0xe08384432816c029CC8365Df4B2d09fdFaC8640f 0x8c98DDf14543cC791bA2434b4981B429bF3132c4 2 2248653071868332165 0x451c27d0daf3ab27281133ff6b8e9c30360b04a40a53355d3294a71a4374ce23
893 0xe08384432816c029CC8365Df4B2d09fdFaC8640f 0xBEe1C37253095EDFb677D9DFae301293D9d4F18a 3 2248653071868332165 0xeeca3ec2c8706dd3d359dd06851b2468e022fe47bf1c2e9a0a6433e39a11427d
894 0xe9fD7987d94596E70450e184a30176FEef440EbE 0xf8C7c772F13c75BE6B9f854Bd12C99c8cE19D2d2 0 2248653071868332165 0x53d5a58f7905084c4426e3c0010757353ec1a9222b226d164f1993cdc766c760
896 0xe9fD7987d94596E70450e184a30176FEef440EbE 0x290B7365F8D771d76Cc8ff8aae1C7DF858bf9AEc 1 2248653071868332165 0x3de01c3b928c47913f84b5b39d0866c9839780108ad58f48524ae93177a827e7
897 0xe9fD7987d94596E70450e184a30176FEef440EbE 0x46bACe73B5e743a83ea20dE05C4CE8191aae8c05 2 2248653071868332165 0xbd065f0db22f6d81ce91e13791dcc0a2470558d103f03608d1935a4ad419657e
898 0xe9fD7987d94596E70450e184a30176FEef440EbE 0xbDF024D8504C630106d18acD6d470C96B326a7aB 3 2248653071868332165 0xbe6f952b6c44d4d71a3a6e912e916249dd6e057c631021785ff059ee8bc76e76

Some addresses are the same but no one seems to be particular, let’s run it once again by using this :

wanted = list(map(str.lower, [
'0x2626e2C853b1F94c012DF03b595BDfb212914b0f',
'0x2626e2C853b1F94c012DF03b595BDfb212914b0f',
'0x2626e2C853b1F94c012DF03b595BDfb212914b0f',
'0x2626e2C853b1F94c012DF03b595BDfb212914b0f',
'0xFfb0c469144D9F187E77C6089B1a882A1E6e832C',
'0xFfb0c469144D9F187E77C6089B1a882A1E6e832C',
'0xe08384432816c029CC8365Df4B2d09fdFaC8640f',
'0xe08384432816c029CC8365Df4B2d09fdFaC8640f',
'0xe08384432816c029CC8365Df4B2d09fdFaC8640f',
'0xe9fD7987d94596E70450e184a30176FEef440EbE',
'0xe9fD7987d94596E70450e184a30176FEef440EbE',
'0xe9fD7987d94596E70450e184a30176FEef440EbE',
'0xe9fD7987d94596E70450e184a30176FEef440EbE',
]))

This time only one address sent some ether to the other : 0x26F8A2D63B06D84121b35990ce8b7FEbac4Fe353

875 0x26F8A2D63B06D84121b35990ce8b7FEbac4Fe353 0x2626e2C853b1F94c012DF03b595BDfb212914b0f 47 44973061437366643312 0xddf8df8a84b703184c06e1b0c5b21b0c91d4ed45a52e8406abd36dd138e51300
876 0x26F8A2D63B06D84121b35990ce8b7FEbac4Fe353 0xFfb0c469144D9F187E77C6089B1a882A1E6e832C 48 44973061437366643312 0xa294ce12f316a48d21781454d9eecfddeef5ee241e25194e741b7c1db159846b
877 0x26F8A2D63B06D84121b35990ce8b7FEbac4Fe353 0xe08384432816c029CC8365Df4B2d09fdFaC8640f 49 44973061437366643312 0x46c6076cee627d8ca854c2bb0ebd64201988a1b41612072b816c77379c8f98c3
878 0x26F8A2D63B06D84121b35990ce8b7FEbac4Fe353 0xe9fD7987d94596E70450e184a30176FEef440EbE 50 44973061437366643312 0x3447e6bf496826662f37770c3063028381f0621ff2cc6a815a4fddb7a64ae40d

Thus, this is the flag of the 2th OSINT challenge : 0x26F8A2D63B06D84121b35990ce8b7FEbac4Fe353.