Flattened Vyper

Category
Reverse Engineering
Points
-1
Solves
-1
Tags

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.

alt text

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}')
Flattened Vyper Flag: PWNME{SuCh_4_M3t4R3veRS3r}