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

LiveOverflow · Intermediate ·🔐 Cybersecurity ·7y ago

Key Takeaways

Exploits a backdoor and performs Jump Oriented Programming in an Ethereum smart contract using EVM Opcodes and Binary Ninja

Full Transcript

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 hard-coded 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 kind of dropped the 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 eff 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 [Music] 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 get code when attached to the web 3 API together with a teammate we were looking over the up codes and stumbled over a backdoor in the answerís function au ship so here's the call graph of this contract in binary ninja using the ather play plugin from trail of bits if you go into the answer is function follow it down you find the first check this is the is human check where it looks at the code size following further the execution you reach some code that contains a sha-3 of code so that's the cash actor five six call so here it calculates the hash of your supplied answer and compares it to the store - after that we find another condition where the call value so the ether you send along the transaction is compared to hex de OB blah blah so this checks if you send one either so far so good it's exactly what you see in the contract code but then we find a check on call data size so this is the input data you sent with your transaction this input contains the function signature you want to call including the parameters serialized in that size has to be hex a 0 if that is not the case which is the normal case for this function then you see here how the hard-coded address of the logger is loaded it checks the external code size of the logger and either reverts this is by the way 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 byte code and instead we have this check of the input size which was not in the solidity source so if the code size is hex a 0 the caller Edwards so our address if you get here is loaded onto the stack ffff is pushed onto the stack then a binary end is executed which leaves only the last two bytes of the address and it uses that value and performs a jump this is the back door you have here an arbitrary jump that you can control with the last two bytes of your address the question is just where do you want to jump now and this is what I basically spent most my time on during the second day in E theorem you can't just jump anywhere you can only jump to addresses where there is a jump test instruction which you see all over the place so while there are 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 jump gadgets together it's like rope or more like job jump oriented programming but in an etherium 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 backdoor binary code you can only send ether away with something like a call instruction so up code f1 or with a delegate call opcode f4 while there were a few f1 calls nothing looked good but during this research I also found this string and when you disassemble that then there is a jump test so you can jump here and there is a delegate call what the F through a bit of research about the 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 byte code and the challenge author made it so that the 32 bytes swarm hash contains a jump test and a delegate 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 and along the contractor Asian transaction and is different from the actual deployed contract on the blockchain it contains a constructor code so all this code actually deploys the backdoor 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 four bytes on the stack pushes another byte identified on the stack and then stores this four byte value in the contract storage at this key for whatever reason I looked up these four 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 thing I also remembered were those weird characters in this one string I also assumed these must have purpose and because these hard-coded strings are just in regular code of the smart contract I assumed that contains if they are in byte code and there was actually a jump test the opening bracket is 5b the OP code for jump test so I thought we could jump there I assumed that 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 jump test I was so sad but also learned so much more about reversing and later realized the problem was that the etherium BM saw this as part of a large push 32 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 talk 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's a maniac who made me spend probably 50 hours or more on this challenge he answered some of my question 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 here twice in the back door contract further down there are a few bytes different and so here there were invalid commands and so the jump test was in the clear and reachable Dan well after the CTF was over after I talked to the challenge crater I also talked to the only team that solved it to 17 but unfortunately the person who solved it was not on site and they only knew that there was a backdoor which I already knew the small technical details were what I was struggling with so I was still so obsessed for not having softest challenge that back in the hotel after the CTF during our over twenty four-hour trip back to Germany in the train home and then more hours at home I just had to pawn 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 cannot solve this damn challenge my own obsession and owning it to my team members for having spent all the time on this did not allow me to give up anyway I solved it finally about a week after the CTF that challenge finally fell I'm free again before I walk you through that I just want to mention and share a few notes on my experience testing and debugging smart contracts I never really had to debug smart contracts beyond simply debugging with remix so this complex setup of the spec toward contract and stuff like that was new to me for most of the time during the CTF I was just using this EVM program to run the code in D bucket but there were a lot of these checks like answering the question correctly which I couldn't figure out how to do so I basically always patched the binary bytecode to ignore those checks so that was very cumbersome eventually I realised I need more of a dynamic debugging environment and that's when I went through the trouble and use get 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 raishin inputs that I extracted with a simple try action logger however because I don't have the private key of the real admin my contract adversus will be different so a lot of the hard-coded values like the logo 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 aetherium chain running with death 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 check points of the trace I really wish remix would support that one other detail that is important is that I forgot about the a contract which caused a start on B which serves as a distraction because a real address for B is passed in dynamically and I knew already that it passes the address of the game contract so it calls a start on Alcala Demonica game 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 longer answer was than the correct one for my local test and the way the back door worked meant that the answer reached into one address used as a 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 to 16 hours of running in circles trying to find another backdoor or using the existing vector to somehow set a short new answer and then in the second stage to 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 immediately look like on first site like it would cost art but it actually does in the constructor long story short the real answer for the question is just a single R and the second call to start was just a 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 and a tech contract that I have to deploy first but let's look at the contract once we reach that step now I'll just lean back and enjoy the right of what this input does this sends one transaction with one either using this payload as input to the e.coli Demonica game contract so first of all we see the function signature that is being used hex f6 a3e c6 7 so that's just calling the answer is function we can follow this here in binary ninja then comes the is human check but this is a normal transaction so no contracts are perfectly fine after that we get the sha-3 calculation again enter equal and we know 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 send one ether and that is also fine next we check the call data size it has to be hex a zero 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 applies to ffff and leaves the last two bytes of my carefully chosen address hex fe6 I want to jump there because we identified that as an interesting gadget earlier inside of the weird string here and so I wrote a small script to brute-force 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 edx v e6 the schedule was carefully created by the author and it prepared the parameters for the delegate call gadget i showed you earlier so for example this will actually load a function signature from the contract storage at - 6 - a which if you remember was set at the injected constructor code so this actually loads the function signature of with the draw it also calls call dataload to load the address of the target contract from our input and puts it into the stack so the address here in my input is actually the contract that is being called if we execute the delegate call and this is how the stack in the end looks like this whole gadget inside the string is just to prepare the stick carefully so this is the gas for the transaction the target address and then some parameters that define the input data so their withdrawal function signature and the other call detail 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 have run in circles if the answer would be super long the answer would reach into here and because the theorem VM works on 32-bit values that jump address was obviously completely wrong but now it's all 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 the delegate call 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 it simply defines here the player and the game contract address and then we have the withdrawal 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 etho from the game contract and transfers it to our play account this works because of the delegate call a delegate call as a special call which basically means you still operate in the environment of the original contract but execute the code of the other contract so if we delegate call 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 the 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 this weird huge return value is but it's carefully chosen so when we return from the delegate call it will load another jump destination onto the stick which when debugging we can she jumps to hex 490 and there we see a load which actually loads a return value from the delegate call and then performs an ad if we hadn't returned a value of for example when you do a self-destruct instead of a transfer which I originally tried this would be a zero we would perform hex for be one plus zero and jump two for v1 and that would then continue in the original execution flow with the logger contract and then 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 for b1 it will actually jump somewhere where the contract gracefully stops and commits all the transactions so like a clean exit in a rope chain and so here we just do an integer overflow with the add of ffff b e7 or more like a subtraction of X minus 4 1 9 and the result is hex 98 jumping there means it leads to 99 and this is where it stops boom now the blockchain recorded the transaction 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 year 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 this 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 ethier mbm I kinda understood smart contracts before but now I really understand them 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 helps me to do an even better job this challenge was a perfect example how I use CTF to help me advance my professional career on the technical level [Music] you [Music]

Original Description

In part 2 we identify a backdoor and find the gadgets to do JOP in a ethereum smart contract. Challenge Files + Solution: https://gist.github.com/LiveOverflow/21c8a505ca176e5bb20bc94eb23acdf1 Remix: http://remix.ethereum.org EVM Opcodes: https://github.com/trailofbits/evm-opcodes Ethereum plugin for Binary Ninja: https://github.com/trailofbits/ethersplay -=[ 🔴 Stuff I use ]=- → Microphone:* https://geni.us/ntg3b → Graphics tablet:* https://geni.us/wacom-intuos → Camera#1 for streaming:* https://geni.us/sony-camera → Lens for streaming:* https://geni.us/sony-lense → Connect Camera#1 to PC:* https://geni.us/cam-link → Keyboard:* https://geni.us/mech-keyboard → Old Microphone:* https://geni.us/mic-at2020usb US Store Front:* https://www.amazon.com/shop/liveoverflow -=[ ❤️ Support ]=- → per Video: https://www.patreon.com/join/liveoverflow → per Month: https://www.youtube.com/channel/UClcE-kVhqyiHCcjYwcpfj9w/join -=[ 🐕 Social ]=- → Twitter: https://twitter.com/LiveOverflow/ → Website: https://liveoverflow.com/ → Subreddit: https://www.reddit.com/r/LiveOverflow/ → Facebook: https://www.facebook.com/LiveOverflow/ -=[ 📄 P.S. ]=- All links with "*" are affiliate links. LiveOverflow / Security Flag GmbH is part of the Amazon Affiliate Partner Programm. #CTF #SmartContract
Watch on YouTube ↗ (saves to browser)
Sign in to unlock AI tutor explanation · ⚡30

Playlist

Uploads from LiveOverflow · LiveOverflow · 0 of 60

← Previous Next →
1 LiveOverflow - Trailer
LiveOverflow - Trailer
LiveOverflow
2 Introduction to Linux - Installation and the Terminal - bin 0x01
Introduction to Linux - Installation and the Terminal - bin 0x01
LiveOverflow
3 Writing a simple Program in C
Writing a simple Program in C
LiveOverflow
4 Writing a simple Program in Python - bin 0x03
Writing a simple Program in Python - bin 0x03
LiveOverflow
5 Live Hacking - Twitch Recording overthewire.org - Vortex 0x01-0x03 (3h)
Live Hacking - Twitch Recording overthewire.org - Vortex 0x01-0x03 (3h)
LiveOverflow
6 Reversing and Cracking first simple Program - bin 0x05
Reversing and Cracking first simple Program - bin 0x05
LiveOverflow
7 Abusing the exception handler to leak flag - 32C3CTF readme (pwnable 200)
Abusing the exception handler to leak flag - 32C3CTF readme (pwnable 200)
LiveOverflow
8 ROP with a very small stack - 32C3CTF teufel (pwnable 200)
ROP with a very small stack - 32C3CTF teufel (pwnable 200)
LiveOverflow
9 Uncrackable Programs? Key validation with Algorithm and creating a Keygen - Part 1/2 - bin 0x07
Uncrackable Programs? Key validation with Algorithm and creating a Keygen - Part 1/2 - bin 0x07
LiveOverflow
10 Uncrackable Program? Finding a Parser Differential in loading ELF - Part 2/2 - bin 0x08
Uncrackable Program? Finding a Parser Differential in loading ELF - Part 2/2 - bin 0x08
LiveOverflow
11 Syscalls, Kernel vs. User Mode and Linux Kernel Source Code - bin 0x09
Syscalls, Kernel vs. User Mode and Linux Kernel Source Code - bin 0x09
LiveOverflow
12 Smashing the Stack for Fun and Profit - setuid, ssh and exploit.education - bin 0x0B
Smashing the Stack for Fun and Profit - setuid, ssh and exploit.education - bin 0x0B
LiveOverflow
13 Live Hacking - EFF-CTF 2016 - Level 0-4 (Enigma Conference)
Live Hacking - EFF-CTF 2016 - Level 0-4 (Enigma Conference)
LiveOverflow
14 First Stack Buffer Overflow to modify Variable - bin 0x0C
First Stack Buffer Overflow to modify Variable - bin 0x0C
LiveOverflow
15 First Exploit! Buffer Overflow with Shellcode - bin 0x0E
First Exploit! Buffer Overflow with Shellcode - bin 0x0E
LiveOverflow
16 Buffer Overflows can Redirect Program Execution - bin 0x0D
Buffer Overflows can Redirect Program Execution - bin 0x0D
LiveOverflow
17 Doing ret2libc with a Buffer Overflow because of restricted return pointer - bin 0x0F
Doing ret2libc with a Buffer Overflow because of restricted return pointer - bin 0x0F
LiveOverflow
18 Reverse engineering C programs (64bit vs 32bit) - bin 0x10
Reverse engineering C programs (64bit vs 32bit) - bin 0x10
LiveOverflow
19 pwnable.kr - Levels: fd, collision, bof, flag
pwnable.kr - Levels: fd, collision, bof, flag
LiveOverflow
20 Reverse Engineering and identifying Bugs - BKPCTF cookbook (pwn 6) part 1
Reverse Engineering and identifying Bugs - BKPCTF cookbook (pwn 6) part 1
LiveOverflow
21 Leaking Heap and Libc address - BKPCTF cookbook (pwn 6) part 2
Leaking Heap and Libc address - BKPCTF cookbook (pwn 6) part 2
LiveOverflow
22 Arbitrary write with House of Force (heap exploit) - BKPCTF cookbook (pwn 6) part 3
Arbitrary write with House of Force (heap exploit) - BKPCTF cookbook (pwn 6) part 3
LiveOverflow
23 Live Hacking - Internetwache CTF 2016 - web50, web60, web80
Live Hacking - Internetwache CTF 2016 - web50, web60, web80
LiveOverflow
24 Live Hacking - Internetwache CTF 2016 - crypto60, crypto70, crypto90
Live Hacking - Internetwache CTF 2016 - crypto60, crypto70, crypto90
LiveOverflow
25 A simple Format String exploit example - bin 0x11
A simple Format String exploit example - bin 0x11
LiveOverflow
26 NEW VIDEOS ARE COMING - loopback 0x00
NEW VIDEOS ARE COMING - loopback 0x00
LiveOverflow
27 HTML + CSS + JavaScript introduction - web 0x00
HTML + CSS + JavaScript introduction - web 0x00
LiveOverflow
28 The HTTP Protocol: GET /test.html - web 0x01
The HTTP Protocol: GET /test.html - web 0x01
LiveOverflow
29 Building Poor Man's Logic Analyzer with an Arduino - Reverse Engineering A/C Remote part 1
Building Poor Man's Logic Analyzer with an Arduino - Reverse Engineering A/C Remote part 1
LiveOverflow
30 What is PHP and why is XSS so common there? - web 0x02
What is PHP and why is XSS so common there? - web 0x02
LiveOverflow
31 Introducing the AngularJS Javascript Framework - XSS with AngularJS 0x00
Introducing the AngularJS Javascript Framework - XSS with AngularJS 0x00
LiveOverflow
32 Sandbox Bypass in Version 1.0.8 - XSS with AngularJS 0x1
Sandbox Bypass in Version 1.0.8 - XSS with AngularJS 0x1
LiveOverflow
33 Capturing & Analyzing Packets with Saleae Logic Pro 8 - Reverse Engineering A/C Remote part 2
Capturing & Analyzing Packets with Saleae Logic Pro 8 - Reverse Engineering A/C Remote part 2
LiveOverflow
34 XSS Contexts and some Chrome XSS Auditor tricks - web 0x03
XSS Contexts and some Chrome XSS Auditor tricks - web 0x03
LiveOverflow
35 Previous Bypass is now fixed in version 1.4.7 - XSS with AngularJS 0x2
Previous Bypass is now fixed in version 1.4.7 - XSS with AngularJS 0x2
LiveOverflow
36 New Sandbox Bypass in 1.4.7 - XSS with AngularJS 0x3
New Sandbox Bypass in 1.4.7 - XSS with AngularJS 0x3
LiveOverflow
37 The Heap: what does malloc() do? - bin 0x14
The Heap: what does malloc() do? - bin 0x14
LiveOverflow
38 The Heap: How to exploit a Heap Overflow - bin 0x15
The Heap: How to exploit a Heap Overflow - bin 0x15
LiveOverflow
39 Reverse Engineering with Binary Ninja and gdb a key checking algorithm - TUMCTF 2016 Zwiebel part 1
Reverse Engineering with Binary Ninja and gdb a key checking algorithm - TUMCTF 2016 Zwiebel part 1
LiveOverflow
40 Scripting radare2 with python for dynamic analysis - TUMCTF 2016 Zwiebel part 2
Scripting radare2 with python for dynamic analysis - TUMCTF 2016 Zwiebel part 2
LiveOverflow
41 Live Hacking - Internetwache CTF 2016 - exp50, exp70, exp80
Live Hacking - Internetwache CTF 2016 - exp50, exp70, exp80
LiveOverflow
42 Sandbox bypass for the latest AngularJS version 1.5.8 - XSS with AngularJS 0x4
Sandbox bypass for the latest AngularJS version 1.5.8 - XSS with AngularJS 0x4
LiveOverflow
43 Channel is growing and Riscure hardware CTF starting soon - loopback 0x01
Channel is growing and Riscure hardware CTF starting soon - loopback 0x01
LiveOverflow
44 Explaining Dirty COW local root exploit - CVE-2016-5195
Explaining Dirty COW local root exploit - CVE-2016-5195
LiveOverflow
45 What is CTF? An introduction to security Capture The Flag competitions
What is CTF? An introduction to security Capture The Flag competitions
LiveOverflow
46 The Heap: How do use-after-free exploits work? - bin 0x16
The Heap: How do use-after-free exploits work? - bin 0x16
LiveOverflow
47 The Browser is a very Confused Deputy - web 0x05
The Browser is a very Confused Deputy - web 0x05
LiveOverflow
48 The Heap: Once upon a free() - bin 0x17
The Heap: Once upon a free() - bin 0x17
LiveOverflow
49 Simple reversing challenge and gaming the system - BruCON CTF part 1
Simple reversing challenge and gaming the system - BruCON CTF part 1
LiveOverflow
50 int0x80 from DualCore lent me his lockpicking set and I'm a horse - BruCON CTF part 2
int0x80 from DualCore lent me his lockpicking set and I'm a horse - BruCON CTF part 2
LiveOverflow
51 The Heap: dlmalloc unlink() exploit - bin 0x18
The Heap: dlmalloc unlink() exploit - bin 0x18
LiveOverflow
52 MD5 Length Extension and Blind SQL Injection - BruCON CTF part 3
MD5 Length Extension and Blind SQL Injection - BruCON CTF part 3
LiveOverflow
53 TCP Protocol introduction - bin 0x1A
TCP Protocol introduction - bin 0x1A
LiveOverflow
54 Socket programming in python and Integer Overflow - bin 0x1B
Socket programming in python and Integer Overflow - bin 0x1B
LiveOverflow
55 Linux signals and core dumps - bin 0x1C
Linux signals and core dumps - bin 0x1C
LiveOverflow
56 [Live] Remote oldschool dlmalloc Heap exploit - bin 0x1F
[Live] Remote oldschool dlmalloc Heap exploit - bin 0x1F
LiveOverflow
57 Riscure Embedded Hardware CTF setup and introduction - rhme2 Soldering
Riscure Embedded Hardware CTF setup and introduction - rhme2 Soldering
LiveOverflow
58 Rooting a CTF server to get all the flags with Dirty COW - CVE-2016-5195
Rooting a CTF server to get all the flags with Dirty COW - CVE-2016-5195
LiveOverflow
59 How to learn hacking? ft. Rubber Ducky
How to learn hacking? ft. Rubber Ducky
LiveOverflow
60 Format String to dump binary and gain RCE - 33c3ctf ESPR (pwn 150)
Format String to dump binary and gain RCE - 33c3ctf ESPR (pwn 150)
LiveOverflow

Related Reads

📰
Building a Mental Map for Blind SQL Injection (The Hard Way)
Learn to build a mental map for blind SQL injection to improve your bug bounty hunting skills
Medium · Cybersecurity
📰
The Code & The Gavel: Navigating § 202c StGB for Builders and Founders
Learn to navigate § 202c StGB, a German law impacting developers and founders, to protect your freedom and company's legal standing
Dev.to AI
📰
# I Submitted Multiple Bug Bounty Reports Before Finding My First Valid Bug.
Learn from a bug bounty hunter's journey to finding their first valid bug, and why persistence is key in this field.
Medium · Programming
📰
Romania leads its first private ESA mission with CyberCUBE launch
Romania leads its first private ESA mission with the launch of CyberCUBE to probe European spacecraft vulnerabilities
The Next Web AI
Up next
Why Cyber security is important for your SEO
Menerva Digital
Watch →