2023 year in review
- Left the Air Force
- Joined Defi protocol as a Solidity dev
- Wrote contracts that held > $2mil TVL
- Dipped toes in C4 contests
- Recovered $0.6M of users' funds against N Korean hackers (crazy story)
- Joined more contests
- Top 5 in Sherlock contest
-
This was how my first private audit went:
- Pay-per-Vulnerability
- Just shy of 5-digit payout
- Found 4 Highs, 4 Meds, several Lows
- Life lessons in the art of negotiation
- Highly satisfied client who then offered me a job 🤯
Absolutely thrilled to share that I will be joining
@GuardianAudits
as a full-time Security Researcher
From watching
@0xOwenThurm
's tutorials during my lunch breaks, to now working alongside his core team, this journey feels like a dream turned reality 🥲
Looking forward to
Pattern recognition is one of the easiest ways to pick out low-hanging fruit in an audit
When you see a proxy pattern, several red flags should stick out immediately. What do you see here?
just picked up my first pengu recently and discovered a gem of a community! The people i met at
#SGPengsHuddle
were kind, authentic and v knowledgeable (builders, entrepreneurs, traders etc). Glad to be part of the fam!! 🐧
@pudgypenguins
#PudgyPenguins
First taste of
@immunefi
bounty
Applied a known bug from one protocol to a different protocol...
I may not be the most technical auditor, but I hope to become the most creative and resourceful one 😉
First time cracking top 3.. with just one solo medium 🏆
Quite unexpected results:
- I'm usually on Sherlock/Cantina, joined this in between contests
- Was on vacation so only spent a couple of days on this
Did a deep dive into OZ's Initializable.sol and finally grokked the difference concepts around:
initialize
initializer
initialized
initializing
reinitializer
/🧵
🚨 Ownable2StepUpgradeable edge case
(devs and auditooors should take note)
TLDR:
- Breaking change between v4 and v5 may result in owner being silently uninitialized
- Initialize function must implement _transferOwnership(newOwner)
Some have asked about my mentorship with
@GeorgeHNTR
so I'd like to share some thoughts:
1) Yes, mentors are incredibly valuable.
During my time in the Air Force, I learnt that scholars were assigned a mentor (usually senior officer) from the start of their careers. This gave
Fun fact:
In my FIRST ever contest (GMX v2) i found a solo medium 😅
Didn't even realize the significance till much later
How?
@0xOwenThurm
tweeted some alpha on the topic of try-catch and the 63/64 rule..
By luck, I saw it then went to search the codebase for any try/catch
🚨Solidity quirk that has led to a real-life exploit:
EVM reads calldata in 32 byte chunks. What happens if you pass in a 33 byte calldata?
The extra bytes are truncated!
In the POC below, 5 is passed as argument along with extra bytes aaaaaa... but only first 32 bytes (i.e.
Recently learnt something fascinating about Merkle trees🌲
@RareSkills_io
When submitting Proof E, ever wondered how the contract knows whether to..
hash right (Hash E-F)
or
hash left (Hash F-E)?
Found my first Medium severity finding in the GMX contest with
@sherlockdefi
🔥🥳
The codebase was quite extensive and v well-written
Managed to pick out something due to an obscure rule on forwarding of gas during external calls -- which I learnt during my
#30daysweb3security
Took 5th place in
@RioRestaking
contest 🦒
I enjoyed learning about Eigenlayer integrations (esp. since I have personal funds there) and hope to do more restaking audits in future!
Set a goal for five top 5 contest results this year. Looks like I'll be achieving it soon 👀
🚨What are the common pitfalls of using .pop()?
1. Incorrect implementation (which can be quite subtle)
Take a look at this function below. What is wrong and how could it be exploited?
Deleting Items from an array
There are 2 ways to delete an item from an array in Solidity - using `delete` and `pop` methods. Let's take a look at the differences and which one you should use in your project:
1. Using `delete`
In this example, we can see that the `removeItem`
Solidity/Security job interview alpha 👀
Have an interesting, creative live hack/exploit example in mind that you can speak intelligently about
Preferably one that is recent too
Found a bug in a live protocol’s codebase with >$100M TVL.
No bounty on immuneFi, so am reaching out to them directly with a report and POC
Hope they will 1) respond and 2) consider it valid! 🤞🏻
Happy with my small win of $29 from my first ever audit with
@code4rena
Using it to treat myself to a nice meal tomorrow! 🍔🍟
Seems like the space is getting pretty crowded -- I am focusing my efforts on higher value H/M findings for future audits!
Join my first
@code4rena
audit and submitted 2 gas optimization, 4 low-severity and 1 medium-severity finding for the
@NeoTokyoCode
contest!
Looking forward to see how I fare and more importantly which were the bugs I didn't catch
🚨 Find your next critical bug in "liquidate"
@0xOwenThurm
does an amazing video covering the different types of vulnerabilities:
I've summarized them into a checklist with quick notes for future reference:
Completed
@code4rena
audit contest for Asymmetry Protocol
Feeling good about this one. Submitted 3 high severity and 1 medium finding
Did not bother with gas optimizations/QA as there were so many other juicy findings!
Denial-of-Service(DOS) attacks via gas griefing is a popular bug/finding
A typical attack goes:
- Attacker finds a cheap way to push spam into storage array
- Reading from that array becomes increasingly expensive
- Critical functions that rely on that array are unable to be
1 month of staking ETH with
@Rocket_Pool
🥳
Minimal hassle, just check-in everyday to see effectiveness healthy
Rewards:
ETH emissions: 0.07901 (half goes to me)
Fees + MEV rewards: 0.02544
RPL: 1.95
Works out to be roughly 8.3% yield annually 🔥🔥$RPL
Initial findings from
@cantinaxyz
's first ever competition
@MorphoLabs
are out!
While I personally could not find any bugs 😔, so far there are 1 high and 9 meds confirmed 🤯
I'm committing to review what I missed. Let's dive into the high finding 👇
Inspired by .
@gh0stlygh0sts
I wrote and deployed my first omni chain NFT using the amazing tech from .
@LayerZero_Labs
and with the wonderful guide from .
@labfordup
LFG!! 😍
my NFT travelled from $FTM to $AVAX 🚀
Next... to brainstorm for ideas to push this further..
1/ Questions to ask during an audit
Can this ___ ?
- revert
- be front run
- be griefed
- re-enter
- receive donation
- value change
- run out of gas
- round incorrectly
It's crazy how many 'audit' firms out there just run automated analysis, generate an automated report and declare the contracts secure and safe 😱
For that same money, I bet the client could have paid for a decent solo auditor and get 100x more value
IMO the most important skill for any new solidity dev or auditor is learning to be RESOURCEFUL
If you have to ask for a roadmap, or where to start you're sadly ngmi
So many good resources out there you're literally spoilt for choice
I compare this to my previous career as a..
TIL that even though data is omitted in:
(bool success, ) = _to.call{value: msg.value}("")
it doesn't mean that contract doesn't handle it ❌
data returned is STILL copied to memory which presents a form of gas griefing attack 👇...
Seneca is a fork of
@Abracadabra_MIM
's Cauldron V4 contract
Bigger lesson here is the danger in blindly forking code
_call is a dangerous function only protecting callee if the address is blacklisted
In Seneca's case the BentoBox (vault) was blacklisted and safe, but users who
Here's the bug in Senaca's code that is getting users drained. Revoke approvals to them immediately.
I've been kicked out of their Discord for trying to warn users, and they are actively deleting messages about this there.
Most interesting/unique bugs come towards the end of the review period
Don’t underestimate the potential of doing one more pass through the codebase… never know what you or others have missed!
Another milestone this month ✨
Got my first private audit (small one)… and without putting up “DM for audits” !
Creativity is so underrated…
Applies not just to the actual audit but also for related tasks (escalations, networking, getting clients etc)
Dencun upgrade hits mainnet in a few days time 📣
Among the ugprades include EIP 6780: changes to SELFDESTRUCT
Is it going to be deprecated? TLDR no
What are the security implications?
Read more to find out 👇
4th place! A bittersweet victory 🦒
In my audit journey where i'm no longer new but also not quite pro yet - the hardest phase imo
To all those in the same boat, an encouragement to keep pushing and not give up. Light at the end of a tunnel, we will have our glory one day
Best days are doing what you love and making money while at it. Today:
Breakthrough and finding first bug
in a complex codebase
Also made big change buying Ansem’s cat while browsing CT over breakfast
Having a mentor is priceless, but finding a good one is not easy.
Thanks
@GeorgeHNTR
, I'll do my best and hope to similarly give back to the community one day 🙏
@nisedo_
@milotruck
@deadrosesxyz
@0xT1MOH
I think I've done something similar with a few other people who are either close to me or just seem very serious and motivated, but
@deadrosesxyz
showed the best results and managed to extract the most value out of me. Someone I'm currently helping in a similar way is
@giraffe0x
Reviewing what I missed from
@MorphoLabs
contest on
@cantinaxyz
Here's another creative one from
@milotruck
to learn from:
"Deviation in oracle price could lead to arbitrage in high LLTV markets" /🧵
With 3074 going live soon, such EOA checks may no longer work as expected
Submitted a related finding in the Rio contest, but unfortunately was judged OOS ☹️
EIP-3074 was just approved to go live in the next Ethereum hard fork.
This EIP will forever change how users interact on EVM chains, making wallet UX simpler, cheaper, and more powerful.
Here's a high level overview of EIP-3074 and how it'll change the game 🧵:
Can you spot the bad 'K' error here?
Related to Uniswap V2 'swap' function that also doubles up as a flash loan function
Dived deep into this with
@RareSkills_io
Definitely good alpha. Came up in the recent Aloe contest too:
❌ Error: uint160(uint160 a * uint56 b)
Intermediate result of a * b can overflow uint160
✅ Fix: uint160(uint256(uint160 a) * uint56 b)
Cast to uint256 first then multiply
Solidity Alpha: When casting the result of any arithmetic operation like:
int256 diff = int256(currPrice - lastPrice);
The result is first stored in the larger type of the 2 variables and only then casted.
This can lead to a critical vulnerability that may easily be missed. 👇
🔧 Foundry Tip:
Common to see this in tests:
vm.assume(a > 0 && a < 100)
Which might cause "cheatcode rejected too many inputs" error. 🚨
"assume" should only be used for narrow checks i.e.
vm.assume(a != 1);
For broader checks, use "bound" instead:
a = bound(a, 1, 99)
I watched 2 whole hours of
@milotruck
so you don't have to. Here are my takeaways...
Kidding 🤣 Was an absolute joy to listen to this on the plane
Took down some notes for future reference:
1/
The wait is over, and it's finally here! 🎉
I just dropped an exclusive interview with the
@code4rena
rising star,
@MiloTruck
.
You won't believe the insights and secrets revealed! 🕵️♂️ 🧵
(Either watch now or bookmark for later!)
Honestly, when I left the Air Force I had no idea how the year would play out. Expected to take things slow and was worried if I would even find a job as a software engineer..
Crazy what happens when you just follow where your passion and interests leads you to...
In 2024 I will
- Set aside time daily to increase knowledge in security
- Grind contests weekly, aiming for 5 more top 5s
- Post regularly about my wins, my losses and my learnings
- Triple my followers on X
- Find 3 clients for private audits
Looking forward to review this in
100% agree. Very exhausting for me as a person who’s generally conflict avoidant.
Part about the community judges also true.
Yes they’re so valuable to the process but sometimes can’t help but wonder if they:
1) have enough context and insight to the codebase,
2) are spread too
I couldn't do any auditing last week, but I had the "pleasure" of experiencing the judging process on one of the major platforms. Like
@deadrosesxyz
, I'd like to share my thoughts.
1. I dislike the adversarial setup
The system is set up so that each actor is pitted against
I was analyzing the
@SocketDotTech
exploit while it was ongoing, which then gave me ideas to look for a similar bug in a contest I was in...
Led to a confirmed high 🔥
Learning Yul/Assembly has given me a deeper understanding of the EVM
Ever wondered how Events are emitted under the hood?
How many max arguments?
How indexed vs non-indexed args are handled?
Anonymous events?
Gas cost?
/🧵
Great challenge to strengthen my Solidity knowledge
Despite reading the answers in the comments, it still took me a while to really understand it
Sharing in more detail here for anyone keen! 🧵
Wasted a good 5 days doing the Seneca contest on
@sherlockdefi
only to see it cancelled due to some licensing issue
Had 3 highs / 4 med reports written up and ready to submit
Frustrated but moving on.
@SenecaUSD
@sherlockdefi
@SenecaUSD
I found 3 highs and 4 mediums during the
@sherlockdefi
contest which I believe to be valid.
Some of the findings are related to the LayerZero implementations which are unrelated to
@MIM_Spell
.
Rather than let them go to waste, I can share them with you privately
Choosing the right audit contest is crucial. 🎯
Initially, the Ethena Labs contest seemed to check all the boxes: 1) an interesting project, 2) not too complex code, 3) low sLOC. 😌
Answer: transfer and burn should not be msg.sender as approved account could be calling this
transfer would send tokens to approved account instead of owner of tokenId (dangerous)
burn() would revert when called by approved account as they do not own the tokenId (DOS)
B:
If upgradeability involves inheritance (as in the example above), then the inherited contract should have a storage 'gap'
Why?
So that if PoolModel is upgraded with additional variables, it will not conflict with storage slots previously written on the proxy
Proofs also represent a decimal value, which makes comparison possible
During tree construction, proofs are pre-sorted by value. So, E < F
Now, when the contract verifies the proof, it also sorts by value, guiding it to the correct left or right hash.
Pretty neat, right? 😊
A:
If contract is meant to be upgradeable, the imports from OZ should not be from 'regular' contracts but from "contracts-upgradeable"
These upgradeable contracts will have special init functions instead of a constructor to ensure proper interaction with the proxy
I agree this is a problem
These days I hesitate to submit an obvious bug/error if I know it will be invalidated by contest rules
Yet, I recognize that clear rules are necessary to avoid overwhelming submissions that are OOS or lacking in impact
A sponsor agreeing to fix
Gd step-by-step analysis of the hack
Combination of donation + precision error
1. Flashloan cbETH and donate to inflate collateral index
2. Mint shares taking advantage of precision calc issue
3. Redeem back for cbETH to repay flashloan
4. Borrow $R off shares, sell & profit
The stablecoin protocol
@raft_fi
was under a flash loan attack. It resulted in ~6.7m stablecoin $R being minted and the protocol lost $3.6M 🚨
The root cause is the precision calculation issue when minting share tokens, which is used by the hacker to get extra share tokens.
1/N
Smart contract auditor roadmap:
1. Do a lot of security reviews
2. After each review, figure out how to do better on the next one
You can get a lot of this on a competitive audit platform like
@codehawks
.
Just keep doing.
That’s the roadmap.
My last contest for the year!
Didn't do as well as expected... 1 High 1 Med valid out of 7 submissions, due to my misunderstanding of a key logic in the codebase
Will reflect and learn from my mistakes and look forward to the next 💪
Anyone else doing the Zerolend contest on
@cantinaxyz
?
Found a bug but can’t figure out how to show/prove it. Would love to have someone to discuss with. DM me discord giraffe0x
Day 27 of
#30daysweb3security
@Web3SecurityDAO
Amazing video by
@0xOwenThurm
explaining signature malleability exploits in such a clear and concise way.. i finally get it!
I try to summarize below which helps me remember better 👇
C:
No initial storage values should be be declared!
It is akin to setting it in the implementation's constructor...
Meaning when the proxy does a delegatecall, it expects `duration` to be 3 * 30 days but it is actually 0 (the default vault for uint256)
Only constants and
User submits tx to depositWithPermit()
Attacker frontruns, takes the signature and call token.permit() themselves
Since the signature is valid, token will accept it and increase the nonce
When user's tx is mined, it fails due to incorrect nonce
The contract has only two pieces of information:
1. Relevant proofs (Proof F, Hash G-H, Hash A-D)
2. Merkle root
The solution? It's surprisingly elegant!
Great refresher on abi.encode / encodePacked for those attempting the
@BrahmaFi
audit with
@code4rena
Really gets to the nuances that I can't find anywhere else
I've prepared a 🧵 for you on
- Whats the difference between abi.encode() & abi.encodePacked()
- Whats their use case with keccak256
- How to use them safely and prevent hash collisions
If you find this helpful, retweet to reach more devs & auditors and make this space safer 🫡
We believe it is in the protocol's best interest to distribute tokens to durable users — not sybil farmers.
If you are a sybil, you have two options:
– Self-report sybil addresses for 15% of your intended allocation. No questions asked. The deadline to do so is May 17th.
– Do
@optimizoor
In 2021 accidentally uploaded my private key to github and got drained…
The account held 2
@pudgypenguins
😭Could never bring myself to buy back in.. now i watch from the sidelines as they pump 😭😭😭
🔥 Day 2 of
#30daysweb3security
@Web3SecurityDAO
Today I learnt THREE interesting gas saving tips 🧵:
1. Changing a uint from zero to non-zero cost 20k gas, but only 5k gas when changed from non-zero to non-zero.
So, start your NFT totalSupply from 1 instead of 0!
If the _to receiver is malicious, it can send back a very large data payload
Memory allocation then becomes very costly such that the tx reverts, or the msg.sender of the tx has to pay a huge amount of gas
Learnt this reading pashov's writeup here:
Earlier this month,
@BuildOnBase
released a challenge with a small bounty
It was a well-designed and super fun test of knowledge of EVM ECDSA signatures
I will share how I solved it, a 🧵👇
BOUNTY ALERT: We're looking for web3 enthusiasts to solve our crypto bounty!
First 50 finishers will get $250 in ETH, first 500 finishers will earn an exclusive NFT, and an opportunity to score a convo with a recruiter at Coinbase. 👀
Check it out 👇
🧵1 / 10
This thread will test your understanding of delegatecall, and introduce a mental model for understanding its execution context.
Quiz
The following chain of function calls is executed:
In the context of the call from C to D, msg.sender is which of the following?
a) EOA
For others like me who have a previously compromised wallet that is eligible for the
@arbitrum
airdrop, I wrote a script that *might* help against the hackers and bots
Not sure who it might help. And let me know if it can be improved!
Wow. Used
@OpenAI
DALL-E 2 to merge some photos to represent my transition into the metaverse and was completely blown away! Humanoid avatar was generated by AI too.
Penguin NFT and ENS added by me to complete a new twitter profile banner 😅
#dalle2
#Metaverse
#PudgyPenguins
D:
_disableInitializer() should be called in implementation's constructor
why?
This sets `initialized = type(uint64.max)`. So no one can call `initialize()` directly on the implementation, which otherwise could have impact on the proxy
`initialize` should be only callable