Flattened Vyper
I achieve to obtain this smart contract, but I can’t understand what it does. Can you help me?
The only information that I have are the followings:
- The flag is cut in three parts and each part is emitted once.
- The first part is emitted in raw bytes.
- The second part is emitted in base58 encoding.
- The last part has to be xor-ed with the second part.
Author :
Fabrisme
Flag format:
PWNME{.........................}
by Fabrisme
So we have EVM
bytecode that we need to reverse engineer. The contract is written in Vyper
which is a higher-level language that compiles to EVM
bytecode. The contract is flattened, so we need to decompile it to the original source code.
0x6003361161001843405d6106b4576106ac43405d6106b4565b5f3560e01c346106b043405d6106b4576301002b1f81186106aa43405d6106b457608436106106b043405d6106b45760243560040160408135116106b043405d6106b4578035602082018181606037508060405250507f1b47819435df544ae4e6a35d3c2d0eb2900cab1460ec254d464c1d82d70db60a7fe4b87e6bca20abb51b195ca36f2af2f5438b8a072af4f6f657b9a8304e8d36910133186106b043405d6106b4577f400000000000000000000000000000000000000000000000000000000000000060a0527f100000000000000000000000000000000000000000000000000000000000000060c0527f6d1cd107e7ef14bc558622a86cb621d9f18c50764e98df43777f3b33164b87dd7f42e0da5ae4babe43a564859fc944bb6033a02fb2741ff60444793a962b5ccf627fa2b11742daceaab03c583a6aa15e32d664450c3eb36da7897f6ce121490078597f8d4d1c1fd99b004fccba9d5d04aca86fa66973fa89ea8ece4c6ae08474173fa6181803517f781d48306de91b1cbdc32a7036761292d1c1cf57e84fe74689f7583d7e24c64f7f781d48306de91b1cbdc32a7036761292d1c1cf57e84fe74689f7583d7e24c6ef18517f0512bd13110722311710cf5327ac435a7a97c643656412a9b8a1abcd1a6916c77f459142deccea264542a00403ce80c4b0a4042bb3d4341aad06905269ed6f0b097f4083ffcddded047455b0cb50e92c87eade93edf0b1500804be31f9a4f7061dc2180335181861069f43405d6106b4577f805d92843fa8a2a28a4c797f73b9b4d5a4d8fd4f515d4e8cdfc2c303969081497f4c45d48945056a0c8203311b215240ed84ad80e70629f350834b13c2c0f510d47f0768ff01e42043ef7236fc833ca34516c059f32dce8faaa7c3085cb73a85b9df18037fadaf670881744dc7aa596a2d1eb68dbc21fef929481f4bc6ec75331d53acbd2e7f61ec705ea65e9bbf5b5a26cc5fa4f55ad1074377dce1ffbc52d65a8816af56a47f9c1a44f72090b2084eff4360bf11986150f7b5b16b3d4c0a999ed8953cfd668a010360e05260207f5a589468edfec83b6b027d7dc5bb900ed2921f1620aae0da1aa294cc3ef1dd717fa5a76b97120137c494fd82823a446ff12d6de0e9df551f25e55d6b33c10e236f01a17f101aa2511b4501cd6f4bedff54bee2f014409dfdf0c34e4a64b6f4f4e72d12a17f6aaf1061dc80372e146e902c25be0914f9997523a728f840c73791d7d1c5554e7fed7a429f015e2d1de96ca3ecadf167436726c87d156b2102064cdc2db8a65eba7f8a0afd773820c30d4395e19e96c0e921f3358da90bb44eb99631bbcc1afc52d27f2933690f7726bee93b2fb03b006b8c4f0cd1d2602dd08d3884e88cb07c9985557ff498467872da19bd835f3a0d95241a369f38625609e70acb1761497dddfe741101010101516060201861069f43405d6106b4577f7d32a137160083987c5b53f4a7153ac04f8d6c93569d179ad4df3963572af76b7f9650daf8bc6e9af151f6c7125f922329197841d23a7933641b5c2b5a836f208d7f0d973dfa900ec9b8498e449d6a71d2eed81d94c326c4acc325db9c6420acc6bd01037fc86afc4c1f60387b16cce4e4b7c47baf30b9fc18389a2563b2afbe90f43b938b7f9de848aa2e1f7fadbc76fdeb1e6d86e23f62c033260728029884a9e1533cf0907f4535f7c4dddcbd2e353778b864f7e5aadeb48786211942f0066b5715f41e13447fcd1941de2602e740c239ec7184cd0608b8cebb856e7243bca25b386695fc5d4f7f11fea6990cda43a993f3c8cb366da2c23305d78b65ad90ea8578bc704a1b53617f81385805084428d13c1994377979238f1c9f574353e344bf9674b56f2380894f031818010160e0527fb861afb70639f08b7f0a674d3ce0216ce6746772b2c753574d99d19c2507759b7f479e5048f9c60f7480f598b2c31fde93198b988d4d38aca8b2662e63daf88a85017f5a589468edfec83b6b027d7dc5bb900ed2921f1620aae0da1aa294cc3ef1dd717fa5a76b97120137c494fd82823a446ff12d6de0e9df551f25e55d6b33c10e236f01a1610a286002604435181861069f43405d6106b4577f6751ff9969e5beee7bb9fd6731aba2e2a213bd96a1f54b0a24d11452a915ab967fa3f4105dff5270ad5b285974bd7f411880965825ce709ecdb52d7d484df31bfd7f493267cb0f8113a9bd75b52869d3344723cd0d18ac091431f0c8c0557d4d69c37f8a7d2dab1437a704175dec5cd4ac755fa35b553d62798afc45e5bd1d30be723e180360e052602060e0a1600160e052602060e06106a843405d6106b4565b5f60e052602060e05bf35b505b5f5ffd5b5f80fd5b43405c8081181856
We can use ethervm.io/decompile to decompile the bytecode to the original source code. And ethervm.io to view the opcode of decompiled code.
So the decompiled code have three LOG1
instructions that emit the flag in three parts. The first part is emitted in raw bytes, the second part is emitted in base58 encoding, and the last part has to be xor-ed with the second part.
First Part
The first part is emitted in raw bytes.
02A9 7F PUSH32 0xadaf670881744dc7aa596a2d1eb68dbc21fef929481f4bc6ec75331d53acbd2e
02CA 7F PUSH32 0x61ec705ea65e9bbf5b5a26cc5fa4f55ad1074377dce1ffbc52d65a8816af56a4
02EB 7F PUSH32 0x9c1a44f72090b2084eff4360bf11986150f7b5b16b3d4c0a999ed8953cfd668a
030C 01 ADD
030D 03 SUB
030E 60 PUSH1 0xe0
0310 52 MSTORE
0311 60 PUSH1 0x20
0313 7F PUSH32 0x5a589468edfec83b6b027d7dc5bb900ed2921f1620aae0da1aa294cc3ef1dd71
0334 7F PUSH32 0xa5a76b97120137c494fd82823a446ff12d6de0e9df551f25e55d6b33c10e236f
0355 01 ADD
0356 A1 LOG1
Here is the more readable version of the first part.
A = ADD(0x9c1a44f72090b2084eff4360bf11986150f7b5b16b3d4c0a999ed8953cfd668a, 0x61ec705ea65e9bbf5b5a26cc5fa4f55ad1074377dce1ffbc52d65a8816af56a4)
A = SUB(A, 0xadaf670881744dc7aa596a2d1eb68dbc21fef929481f4bc6ec75331d53acbd2e)
// Offset and store A at B
B = 0xe0
STORE A at B
// Size
C = 0x20
// Topic
D = ADD(0xa5a76b97120137c494fd82823a446ff12d6de0e9df551f25e55d6b33c10e236f, 0x5a589468edfec83b6b027d7dc5bb900ed2921f1620aae0da1aa294cc3ef1dd71)
LOG1(D, C, B)
Second Part
The second part is emitted in base58 encoding.
0495 7F PUSH32 0xc86afc4c1f60387b16cce4e4b7c47baf30b9fc18389a2563b2afbe90f43b938b
04B6 7F PUSH32 0x9de848aa2e1f7fadbc76fdeb1e6d86e23f62c033260728029884a9e1533cf090
04D7 7F PUSH32 0x4535f7c4dddcbd2e353778b864f7e5aadeb48786211942f0066b5715f41e1344
04F8 7F PUSH32 0xcd1941de2602e740c239ec7184cd0608b8cebb856e7243bca25b386695fc5d4f
0519 7F PUSH32 0x11fea6990cda43a993f3c8cb366da2c23305d78b65ad90ea8578bc704a1b5361
053A 7F PUSH32 0x81385805084428d13c1994377979238f1c9f574353e344bf9674b56f2380894f
055B 03 SUB
055C 18 XOR
055D 18 XOR
055E 01 ADD
055F 01 ADD
0560 60 PUSH1 0xe0
0562 52 MSTORE
0563 7F PUSH32 0xb861afb70639f08b7f0a674d3ce0216ce6746772b2c753574d99d19c2507759b
0584 7F PUSH32 0x479e5048f9c60f7480f598b2c31fde93198b988d4d38aca8b2662e63daf88a85
05A5 01 ADD
05A6 7F PUSH32 0x5a589468edfec83b6b027d7dc5bb900ed2921f1620aae0da1aa294cc3ef1dd71
05C7 7F PUSH32 0xa5a76b97120137c494fd82823a446ff12d6de0e9df551f25e55d6b33c10e236f
05E8 01 ADD
05E9 A1 LOG1
Third Part
The third part has to be xor-ed with the second part.
05FE 7F PUSH32 0x6751ff9969e5beee7bb9fd6731aba2e2a213bd96a1f54b0a24d11452a915ab96
061F 7F PUSH32 0xa3f4105dff5270ad5b285974bd7f411880965825ce709ecdb52d7d484df31bfd
0640 7F PUSH32 0x493267cb0f8113a9bd75b52869d3344723cd0d18ac091431f0c8c0557d4d69c3
0661 7F PUSH32 0x8a7d2dab1437a704175dec5cd4ac755fa35b553d62798afc45e5bd1d30be723e
0682 18 XOR
0683 03 SUB
0684 60 PUSH1 0xe0
0686 52 MSTORE
0687 60 PUSH1 0x20
0689 60 PUSH1 0xe0
068B A1 LOG1
Python Script
Here is the python script to get the flag.
import base58
flag = ''
a = 0x9c1a44f72090b2084eff4360bf11986150f7b5b16b3d4c0a999ed8953cfd668a + 0x61ec705ea65e9bbf5b5a26cc5fa4f55ad1074377dce1ffbc52d65a8816af56a4
a -= 0xadaf670881744dc7aa596a2d1eb68dbc21fef929481f4bc6ec75331d53acbd2e
a %= (2**256) # Stay in 256-bit
print(hex(a))
data = bytes.fromhex(hex(a)[2:])
data = data.replace(b'\x00', b'')
flag += data.decode()
b = 0x81385805084428d13c1994377979238f1c9f574353e344bf9674b56f2380894f - 0x11fea6990cda43a993f3c8cb366da2c23305d78b65ad90ea8578bc704a1b5361
b ^= 0xcd1941de2602e740c239ec7184cd0608b8cebb856e7243bca25b386695fc5d4f
b ^= 0x4535f7c4dddcbd2e353778b864f7e5aadeb48786211942f0066b5715f41e1344
b += 0x9de848aa2e1f7fadbc76fdeb1e6d86e23f62c033260728029884a9e1533cf090
b += 0xc86afc4c1f60387b16cce4e4b7c47baf30b9fc18389a2563b2afbe90f43b938b
b %= (2**256) # Stay in 256-bit
print(hex(b))
data = bytes.fromhex(hex(b)[2:])
data = data.replace(b'\x00', b'')
flag += base58.b58decode(data).decode()
c = 0x8a7d2dab1437a704175dec5cd4ac755fa35b553d62798afc45e5bd1d30be723e ^ 0x493267cb0f8113a9bd75b52869d3344723cd0d18ac091431f0c8c0557d4d69c3
c -= 0xa3f4105dff5270ad5b285974bd7f411880965825ce709ecdb52d7d484df31bfd
c %= (2**256) # Stay in 256-bit
print(hex(c))
c ^= b
c %= (2**256) # Stay in 256-bit
print(hex(c))
data = bytes.fromhex(hex(c)[2:])
data = data.replace(b'\x00', b'')
flag += data.decode()
print(f'Flag: {flag}')
PWNME{SuCh_4_M3t4R3veRS3r}