Smart Contract Vulnerabilities that ChatGPT cannot uncover (Part 3)

TechJD
3 min readApr 10, 2024

Welcome back to the series. I’m sure you know the drill by now. I review some crafty hacks I’ve either discovered from other sources or acquired from practice. I then ask ChatGPT to review the code to see if it can find the exploit. I don’t give ChatGPT additional context or hints where they are not needed. Often, the exploit is staring you right in the face. The reason for this is that if you don’t actually know what you’re looking for, ChatGPT isn’t going to help you, and if you already know what you’re looking for, you’re not going to ask ChatGPT.

With introductions out of the way, I wanted to return to a classic exploit that not only ChatGPT cannot catch, but I rarely hear your favorite YouTube gurus mention this either, especially since the contracts they code along with you often contain this very exploit. In fact, I’m not even sure what to actually call the exploit, so I just named it “The Decoded Constructor.”

“The Decoded Constructor” is a vulnerability that was particularly relevant during the NFT boom, where projects would display hidden images and then reveal them later. But as you may have already heard with private variables being readable, if you store the IPFS hash of your real NFTs a certain way, a smart hacker can steal them rather easily.

Per usual, ChatGPT gave me some gobbledygook about input validation, error handling, the size of a uint96, and inheriting from ERC721, so I asked again:

Again, I’m not here to give ChatGPT any hints. Not only did ChatGPT not find the vulnerability, it told me that the constructor function was well-made and perfectly fine to use. After all, nothing can happen to us as long as the source code is secure, right?

Well, friends, let me introduce you to a new world. A, perhaps, far scarier world than this. A world far beyond the comfort and safety of our human-readable source code. Enter, if you will, the world of byte code:

Thought you could ignore all this, huh?

If you have a constructor, and you input supposedly private data into it like this:

 constructor(uint96 _royaltyFeesInBips, string memory _uri) ERC721("Top Secret NFT", "CLASSIFIED") {
setNotRevealedURI(
"ipfs://QmTejqEevGGYN36zSLxuXHS42yHf3qMgUuubRXsYLGHm6z"
);
setBaseURI(_uri);
royaltyFeesInBips = _royaltyFeesInBips;
royaltyAddress = royaltyRecipient;
_setRoyaltyInfo(royaltyAddress, _royaltyFeesInBips);
}

Then, the arguments to your constructor will show up at the end of the byte code looking like this:

I can simply follow the transaction id for the contract creation, copy this string, buffer it with a ‘0x’ at the front, and use web3js to decipher the arguments (You can easily do this in RemixIDE):

web3.eth.abi.decodeParameters(['uint96','string'], '0x00000000000000000000000000000000000000000000000000000000000001f400000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000036697066733a2f2f516d563654756d6454515a4376546e66636f7a456874473250775a584641526733416f4b533751395941654163632f00000000000000000000')

And the output will read:

{"0":"500","1":"ipfs://QmV6TumdTQZCvTnfcozEhtG2PwZXFARg3AoKS7Q9YAeAcc/","__length__":2} 

As the magician says, “is this your card?”

I believe it is.

If you found this article helpful and enjoy learning about all sorts of cool tricks with Solidity and DeFi hacks, star our CryptoCadet Academy repo on GitHub for more!

TechJD is the Founder of Ascendant.Finance, which assists web2 businesses to transition to web3, consulting on all facets including SEC compliance, tokenomics, development, and connecting with angel investors. For more information check out ascendant.finance or join the Discord.

https://twitter.com/ascendantfi

https://twitter.com/cryptocadetapp

https://twitter.com/thetechjd

--

--

TechJD

Law, programming, and everything in-between! Coming up with fun coding projects with real-world application.