Jump Oriented Programming: Ethereum Smart Contract #2 – Real World CTF 2018

Jump Oriented Programming: Ethereum Smart Contract #2 – Real World CTF 2018


In the first video I talked about how I approached
this challenge and some of the thoughts and
ideas I had.
I’d like to emphasize again that I worked
on this for both days of the CTF, so I cannot
include every small detail and struggle I
had, but I hope I showed the important steps.
At some point I was looking closer at the
deployed contracts bytecode and transactions
and noticed that the hardcoded logger address
of the game contract was wrong.
And so early on the second day I went to the
organizers and told them that.
I thought all my attack ideas didn’t work
because of that.
However the author then kinda dropped a hint
because he said:”oh, but it doesn’t really
matter.
The exploit doesn’t need the logger contract”.
And I was like WHAT THE F’’’’’.
This is all just distraction?
All the function signature collision, all
the delegate call stuff here?
All just unimportant.
F’ my life…
He then also announced this hint publicly
over the microphone, so that the competition
stays fair and everybody knew about that as
well.
But for me this meant, back to square one.
I was so lost…
Out of desperation, also because I knew the
trick must be here in the game contract, I
decided to reverse engineer the deployed game
contract binary code more closely, which I
got through calling getCode when attached
to the web3 API.
Together with a teammate we were looking over
the opcodes and stumbled over a backdoor in
the TheAnswerIs function.
Oh ship.
So here is the call graph of this contract
in Binary ninja using the etherplay plugin
from trailofbits.
If you go into the TheAnswerIsFfunction, follow
it down, you find the first check.
This is the isHuman check where it looks at
the codesize.
Following further the execution you reach
some code that contains a SHA3 opcode, so
that’s the keccak256 call.
So here it calculates the hash of your supplied
answer and compares to the stored hash.
After that we find another condition where
the callvalue, so the ether you sent along
the transaction, is compared to 0xde0b-blahblah,
so this checks if you sent 1 Ether.
So far so good.
It’s exactly what you see in the contract
code.
But then we find a check on the calldatasize,
so this is the input data you send with your
transaction.
This input contains the function signature
you want to call including the parameters,
serialized.
And that size has to be 0xa0.
If that is not the case, which is the normal
case for this function, then you see here
how the hardcoded address of the logger is
loaded, it checks the extcodesize of the logger
and either reverts (this is btw what happened
last video when we played with the remix debugger),
or everything is fine.
But there are two weird things.
First of all this code DOES NOT include the
transfer of the ether if you win.
It’s simply not there in the bytecode.
And instead we have this check of the input
size which was not in the solidity source.
So if the codesize is 0xa0, the caller address,
so our address if you get to here, is loaded
onto the stack, ffff is pushed onto the stack.
Then a binary AND is executed, which leaves
only the last 2 bytes of the address, and
then it uses that value and performs a JUMP.
This is the backdoor.
You have here an arbitrary jump that you can
control with the last 2 bytes of your address.
The question is just, where do you to jump
to now?
And this is what I basically spent most my
time on during the second day.
In ethereum you can’t just jump anywhere,
you can only jump to addresses where there
is a JUMPDEST instruction, which you see all
over the place.
So while there are a limited amount of places
you can jump to, it’s still a lot.
And I just knew it wouldn’t be one single
jump, it probably must mean you have to CHAIN
multiple jumpgadgets together.
It’s like ROP, or more like JOP, Jump Oriented
Programming, but in an ethereum smart contract.
So this means you have to find and reuse code
that is already there, to send away the ether
to your account.
But the only transfer we know about from the
original source, was missing in the backdoored
binary code.
You can only send ether away with something
like a CALL instruction, so opcode f1, or
with a DELEGATECALL opcode f4.
While there were a few f1 calls, nothing looked
good.
But during this research I also found this
“bzz” string.
And when you disassemble that then there is
a JUMPDEST, so you can jump here AND there
is a DELEGATECALL.
WHAT THE F”?
Through a bit of research about the bzz string,
I learned that this belongs to metadata the
solidity compiler appends at the end of smart
contracts.
Encoding of the Metadata Hash in the Bytecode.
And the challenge author made it so that the
32 bytes swarm hash contains a jumpdest and
delagate call.
This cannot be a coincidence.
I swear this was done on purpose to hide a
gadget.
Problem was though, that jumping here right
away, doesn’t work, because the parameters
that are needed on the stack for the delegate
call don’t match.
It will run into an error.
So we need to jump somewhere else first, but
I’m SURE we must jump here at SOME point.
One other small thing I found out was from
disassembling the contract creation code which
is sent along the contract creation transaction,
and is different from the actual deployed
contract on the blockchain.
It contains the constructor code.
So all this code actually deploys the backdoored
contract but at the end we reach this jump
relative to the program counter.
And so we can calculate where it jumps to
and disassemble the code there and it’s
very short.
It simply pushes these 4 bytes on the stack,
pushes another byte identifier on the stack
and then stores this 4byte value in the contract
storage at this key.
For whatever reason I looked up these 4 bytes
in a big database of solidity function signatures
and it turns out this matches the function
signature for withdraw().
Again, must not be a coincidence.
One other thin I also remembered, were those
weird characters in this one string.
I also assumed, these must have purpose.
And because these hardcoded strings are just
in regular code of the smart contract, I assumed
it contains ethereum bytecode.
And there was actually a JUMPTDEST!
The opening bracket is 5b, the opcode for
JUMPDEST.
So I thought we could jump there.
I assumed that also MUST be a gadget.
And this is as far as I got during the CTF.
The last struggle I had was fighting with
that I just couldn’t jump there.
It would always fail, even though there was
a JUMPDEST!
I was sad but also learned SO MUCH more about
reversing.
I later realized the problem was that the
ethereum VM saw this as part of a large PUSH32.
The 5b is in there, and the EVM doesn’t
allow to jump into the middle of an instruction.
But after the CTF I really wanted to talked
to the challenge author.
Because I wanted to understand how to solve
it.
So let me introduce you to him.
But don’t be fooled by his good looks.
THIS IS THE FACE OF PURE EVIL, HE IS A MANIAC,
WHO MADE ME SPEND PROBABLY 50 HOURS or more
ON THIS CHALLENGE.
He answered some of my questions, which was
really nice and he was really happy that I
enjoyed the challenge so far.
He told me it took him a week to create it,
and that it was the hardest challenge he could
come up with.
Yeah I believe that.
One of the things he told me, and I didn’t
notice that, that the weird string appeared
twice.
In the backdoored contract further down.
There a few bytes different and so here there
were invalid commands and so the JUMPDEST
was in the clear and reachable.
Dangit!
Well.. after the CTF was over, after I talked
to the challenge creator I also talked to
the only team that solved it.
217.
But unfortunately the person who solved it
was not on-site, and they only knew that there
was a backdoor, which I also already knew.
The small technical details were what I was
struggling with.
So I was still SO obsessed for not having
solved this challenge, that back in hotel
after the CTF, during our over 24h trip back
to Germany, in the train home and then more
hours at home, I just had to PWN THIS DAMN
CONTRACT!
At this point it wasn’t about fun anymore.
It was pure self-loathing.
Driven by the hate about my stupid little
brain who just can’t solve this damn challenge.
My own obsession and owing it to my team members
for having spent all my time on this, did
not allow me to give up.
Anyway…
I solved…
Finally…
About a week after the CTF the challenge finally
fell…
I’m free again.
Before I walk you through that, I just want
to mention a share a few notes on my experience
testing and debugging smart contracts.
I never really had to debug smart contracts
beyond simple debugging with remix.
So this complex setup of this backdoored contract
and stuff like that was new to me.
For most of the time during the CTF I was
using this evm program to run the code and
debug it, but there were a lot of these checks
like answering the question correctly, which
I couldn’t figure out how to do.
So I actually always patched the binary bytecode
to ignore those checks.
So that was very cumbersome.
Eventually I realized I need more of a dynamic
debugging environment and that’s when I
went through the trouble and used geth to
run my own local environment and deployed
the contracts there.
I will link all my deploy and setup scripts
in the description so you can just do the
same.
This is very cleaned up code but it was obviously
VERY messy during the CTF.
I basically just replay all the contract creation
inputs that I extracted with my simple transaction
logger.
However because I don’t have the private
key of the real admin, my contract addresses
will be different.
So a lot of the hardcoded values, like the
logger address, and the function calls had
to be adjusted.
Oh, and I also added a few different accounts
in the genesis block, so we have ether there
to deploy the contracts.
By having this local ethereum chain running
with geth, we can also expose the debug api,
which allows us to retrieve a detailed execution
trace of transactions.
So I could try to trigger the backdoor and
then request a trace and look through what
happened.
I also wrote a small python script to just
filter and more nicely display the important
checkpoints of the trace.
I really wish remix would support that.
One other detail that is important is, that
I forgot about the a() contrct which calls
Start() on b.
Which is a bit distraction because the real
address for b is passed in dynamically, and
I knew already that it passes in the address
of the game contract.
So it calls Start() on AcoraidaMonicaGame.
So I forgot that in my local deployment script,
and only had the second transaction that calls
start() directly, but that one used a longer
answer.
And the problem was, that this long answer
was then the correct one for my local test,
and the way the backdoor worked meant that
the answer reached into one address used as
another jump gadget.
Blah blah.
I just got stuck there.
theoretically I knew that the a() contract
was there which also called Start, but I forgot
about it.
And that contract was actually using the same
question, but just used a single character
as an answer.
And it was executed first.
So that meant to answer the question correctly
to trigger the backdoor, the answer was short,
and in reality there was never a problem with
the long answer.
But that little mistake cost me probably another
8-16h of running in circle, trying to find
another backdoor, or using the existing backdoor
to somehow set a short new answer.
And then in a second stage do the actual exploit.
But that was wrong because like I said, I’m
so dumb and forgot about the a contract.
So the purpose of the a contract was another
obfuscation trick used by the author, because
this transaction doesn’t immediatly look
on first sight like it would call Start(),
but it actually does, in the constructor.
Long story short, the real answer for the
question is just a single character “r”.
And the second call to start was just distraction
and at the same time a way to transfer the
ether we have to steal to the contract.
Anyway… this is the final payload.
The whole attack actually requires an additional
contract, an attack contract that I have to
deploy first, but let’s look at that contract
once we reach that step.
Now just lean back and enjoy the ride of what
this input does.
This sends one transaction with 1 Ether, using
this payload as input, to the AcoraidaMonicaGame
contract.
So first of all we see the function signature
that is being used.
0x46a3ec67 So that’s just calling the TheAsnwerIs()
function.
We can follow this here in binary ninja.
Then comes the isHuman check.
But this is a normal transaction, no contract,
so perfectly fine.
After that we get the SHA3 calculation again
and the equal, and we know now from the obfuscated
Start() call, that our answer is just one
byte long.
A single ‘r’.
After that it checks the call value, so if
we sent one ether, and that is also fine.
Next we check the calldatasize.
It has to be 0xa0, so 160 bytes long.
And yes, I made the payload exactly 160 bytes
long.
That’s why there is a bit of padding.
And now we enter the backdoor code.
It takes my address, apply the FFFF and, leaves
the last two bytes of my carefully chosen
address.
0x5e6.
I wanted to jump there because we identified
that as an interesting gadget earlier inside
of that weird string here.
And so I wrote a small script to bruteforce
a private key until I get these two last bytes.
So this private key that we use for this transaction
has this public address.
Anyway, this means we continue execution in
our first JUMP gadget at 0x5e6.
This gadget was carefully created by the author
and it prepares the parameters for the delegatecall
gadget, I showed you earlier.
So for example this will actually load a function
signature from the contract’s storage at
262a, which if you remember was set at the
injected constructor code.
So this actually loads the function signature
of withdraw().
It also calls CALLDATALOAD to load the address
of the target contract from your input and
puts it onto the stack.
So the address here in my input is actually
the contract that is being called if we execute
a delegatecall.
And this is how the stack in the end looks
like, this whole gadget inside this string
is just to prepare the stack carefully.
So this is the GAS for the transaction, the
target address, and then some parameters that
define the input data.
So the withdraw() function signature.
And the other calldataload loads the next
jump address, also from the input.
It’s this value here, and this is also where
the error happened that caused me to run in
circles.
If the answer would be super long, the answer
would reach into here.
And because the ethereum VM works on these
32byte values, that JUMP address was obviously
completely wrong.
But now it’s fine.
And this address for this jump here, points
at the delegate call gadget, we found in the
contract metadata which the compiler placed
there.
we jump there, and execute delegatecall.
So this will call now into the contract I
defined here.
Now let me show you this attack contract I
mentioned at the beginning.
this is how it looks like.
I simply define here the player and the game
contract address.
And then we have the withdraw function that
it wants to call.
So this will be called.
And it’s simple, it just performs a transfer
with the balance of the game contract, so
all the ether from the game contract, and
transfers it to our player account.
This works because of delegatecall.
A delegatecall is a special call which basically
means you still operate in the environment
of the original contract, but execute the
code of the other contract.
And so if we delgatecall this withdraw function,
and do a transfer, it’s as if the game contract
would have done the transfer directly.
So that allows us to send ether away.
But to make sure this transaction actually
happens, we have to ensure that the code doesn’t
run into an error and reverts.
So you probably wonder what that weird huge
return value is, but it’s also carefully
chosen.
So when we return from the delegatecall it
will load another jump destination onto the
stack, which when debugging we can see jumps
to 0x49d.
And there we see a load which actually loads
the return value from the delegatecall and
then performs an ADD.
If we hadn’t returned a value, or for example
when you do a selfdestruct instead of a transfer,
which I originally tried, this would be 0,
and we would perform 0x4b1 + 0 and jump to
0x4b1.
And that would then continue in the original
execution flow, with the logger contract and
eventually it will run into a revert.
So to avoid that we have to return a value
from the call, so that when it adds the return
value on 0x4b1, it will actually jump somewhere
where the contract gracefully stops and commits
all the transactions.
So like a clean exit in a ROP chain.
And so here we just do an integer overflow
with the add of ffffbe7, or more like a subtraction
of -0x419, and the result is 0x98.
Jumping there means it leads to 0x99, and
this is where it STOPs.
BOOM!
Now the blockchain recorded the transfer of
all the ether from the game contract to the
player contract.
Here I queried the balance of the game contract
before, and here afterwards.
F’ YEAH!
The game contract is empty.
And we won.
This was probably the hardest CTF challenge
I have solved in a long time.
And some of you might think that it was a
waste of time to spend dozens of hours on
this.
I can’t really show everything in these
videos, but you have to remember that I didn’t
know all what I explained before, and I gained
SO MUCH practical experience and insight into
the ethereum VM.
I kinda understood smart contracts before,
but now I really understand them – down to
the opcode level and I got very comfortable
reading evm bytecode and working with it.
This was incredibly helpful to me. as I mentioned
somewhere else briefly, because I do some
smart contract audits professionally, this
also me to do an even better job.
This challenge was the perfect example how
I use CTFs to help me advance my professional
career on the technical level.

89 thoughts on “Jump Oriented Programming: Ethereum Smart Contract #2 – Real World CTF 2018

  1. Wow that was a great explanation! Thank you for the video, it always helps fill in knowledge gaps during the struggle to learning application analysis. Happy Holidays!

  2. Same, trying to solve Rwext5 help me learn a lot about linux filesystem, before that, i dont even know wtf is linux filesystem. But it's an real obsession, i was spend like a week after that doing nothing but that challenge, even during my exam, fuck it!

  3. even i have written a python script to find collision in function signature , ran it on public bugbounty smart contract but no luck

  4. could you record your audits of a popular (or other interesting open source) smart contracts. this was so cool and I enjoyed both of the videos

  5. Conditional jumps… nightmare to debug without the right tools. Things haven't changed much I guess, just more people "frown" on learning the techniques to make code more transparent in general.

  6. Great video and definitely not a waste of time! At the end of the video you mentioned that you also do smart contract audits, how do you get your audit work? Do you use a specific platform or do people contact you because they know you?

  7. Amazing explanation. I didnt know much about smart contracts before, but I feel you've improved the internet's collective knowledge by a huge amount! Kudos for Christmas 🙂

  8. Euhhh why did I subscribe ?? Donno !!… but i guess congrats on solving what i don’t know and don’t understand, and hope for more future videos that will make feel better for not being a programer or whatever you do in life !!

  9. What a satisfying journey for me, watching your struggling process and the bingo process. So damn good. Would you consider making another video which you recreates the challenge as if you were the challenge author?

  10. Glad you enjoy it and great explanation! This was a great birthday&christmas gift, especially the drawings lol(wait where's my glasses). BTW Monica wants to say "You beat me! :D"

  11. When explaining the solution, it is extremely clear. However, if you don't pause for a second, someone that doesn't know exactly what you're talking about (i.e. me :p) tunes out. So if you just pause for like one or two seconds, just to breathe, I think these video would be on a whole other level. Apart from that, great video!

  12. As someone who knows a bit of assembly, and a bit about Ethereum smart contracts: wtf did I just watch. This challenge is insane, nice work figuring it out!

  13. PLEASE stop writing the word "answer" as "anwser", you did it pretty much every time on your hand drawn text, but never on typed text xD still a great challenge and videos but god damn I saw that typo too many times to shut up about it :p

  14. A question i have though, when doing CTFs you know there is a vuln to be found
    But when working for someone you don't know if there even is one and that's why you are here, so do you get paid if you didn't found any
    and at what point do you stop searching for a vuln in a particular software ?

  15. Wow. Just mad respect to you for digging into this in such depth in such short time.
    I'm now scared for live, writing evm code just when thinking about function signature collision O_O

  16. I was really exited for part 2 of this series! I only had some basic knowledge of ethereum smart contracts but you explained everything pretty good. It's quite bizarre that smart contracts allow inline assembly. A bit too 'low level' in my opinion. Hope you keep up your good work!

    Would you say that your experience with CTFs help you generally in your career? I personally love to look into 'implementations' of copy protections, licence validations or 'trial-periods' but I don't really know how to make money of it. (At the moment I do regular software development work)

    Viele Grüße aus 🇦🇹

  17. whoooow what a great explanation. To be honest, i'm not there yet. However, I do got one question already. At the end you mention you gained professional insights and knowledge. My question is, given your efforts, who would have the same experience and insights you have. To rephrase, why not hire you on the spot given your gained knowledge. To rephrase again, how many would resolve this ctf and can tell me how they did it in such clear insights.

  18. Why did you pad with 4848? I was so sure I found a cute little easteregg in the video, but G is 0x47, so no GG…
    Meine Trauer lässt sich nicht in Worte fassen :p

  19. Sweet video, I too fell out with the detail. But you know, this was probably the most enjoyable video I've seen from you. Mainly because you sounded so determined to nail it and got excited nearing the end. Congratulations

  20. Hiya Live, I admire the honest and clean approach your channel is still maintaining. Love you man, keep on the good work learning and teaching at the same time is the best, especially for the noobs like me 🙂

  21. "anwser". Literally unwatchable.

    Seriously though that was a deeeeeep dive in Smart contracts. I never realised how in depth a eth ctf challenge could go. If only that ctf was a day longer huh 😁

Leave a Reply

Your email address will not be published. Required fields are marked *