 All right, next lecture here is from Artem, next to the fact that he's, how would I spell it? Earning a nice amount of money probably at the lab that's quite renowned in the world as Kaspersky. And from that point on he's not just looking in this lecture to exploit development for Cisco stuff. We all suffered from this last year or we all heard about it and don't know the impact maybe, but he's going to explain us the work he did on that field. So please can I ask you all a warm welcoming applause for Artem Kodradenko. Okay, good. Please give him a warm applause and start. Hello everyone, so excited to finally be able to attend KS Communication Congress. Very happy to see you all. So without further ado let's jump into some practical iOS exploitation. So a few words about myself. My name is Artem. I do stuff, mostly security related stuff, but mostly my areas of expertise are penetration tests, both internal and external, and also do research on my free time. And a bit of bug bounty here and there. And this talk is actually kind of a continuation of my talk this summer at DEFCON about Cisco Catalyst Exploitation. So for those of you who are out of context, let's recap what happened earlier this year. So year 2017 was reaching vulnerabilities for Cisco iOS devices. So we had at least three major advisories for Cisco iOS that represented three remote code execution vulnerabilities. So the first one is a vulnerability in cluster management protocol which resulted in unauthenticated remote code execution via telnet. The second one is SNMP overflow and DHCP, remote code execution. In this lecture I'm going to be talking about two of those vulnerabilities because DHCP, RCE, is yet to be researched. So hopefully by the end of this talk I'm going to be able to show you a live demo of exploiting the SNMP service in Cisco iOS. But first, what happened earlier? So on March 26, 2017, we had a major advisory from Cisco that announcing that hundreds of models of different switches are vulnerable to remote code execution vulnerability. No public code, no public exploit was available and no exploitation in the wild. So it was critical and main points of the vulnerability were as follows. So Cisco switches can be clustered and there's a cluster management protocol built on top of telnet. And this vulnerability is a result of actually two errors, a logic error and a binary error. So the telnet options get parsed regardless whether the switch is in cluster mode or not. And the incorrect processing of this cluster management protocol options result in overflow. So what is interesting about this vulnerability that actually the source of research for Cisco guys was another internal research by the world seven leak that happened in March this year. So many hacking techniques and tools were released to public by WikiLeaks and among many vendors that were affected were Cisco systems. So basically, except for the advisory, you could go to WikiLeaks and read about this potential exploitation technique for Cisco catalyst. So basically, these were notes of an engineer who was testing the actual exploit. There were no actual exploits in the leak. So basically, this worked as follows. There were two modes of interaction. So for example, an attacker could connect to the telnet, overflow the service and be presented with a privilege 15 shell. The other mode of operation is to set for all the subsequent connections to the telnet, there will be credentials without credentials. So I did rediscover this exploit. Full research was presented at DEF CON 25. I was targeting Cisco catalyst to 2660 as a target switch. I also described PowerPC platform exploitation and the way you can debug it. You can look at my blog post about exploiting the service and also the performance of exploit on my GitHub page. But today, I want to talk about something else, about another vulnerability that was announced this year about SNMP remote code execution. So the actual motivation behind this research was that I was conducting an external pen test and it was revealed, and NMAP scan revealed that Cisco router with a default community string was available. So the goal was to get access to the internal network. So the actual advisory said that the attacker needs a read-only community string to get remote code execution on the device. The target router was a 2800 integrated services router, which is a very common device on the networks. So the technical specs for it is it has MIPS, big engine architecture. You don't have any client debugging tools for it available. And it's interesting in that sense that the firmware is relatively new for this router. And it might be interesting to look at the defensive and exploit prevention mechanisms employed by Cisco iOS. What I say relatively new is that interesting thing is that this device is actually end of support. It's not supported. So the last patch for it was came out at 2016. And to remind you, the advisory for SNMP overflow appeared in 2017, in June 2017. But nonetheless, this is still a widely used device. If you search for SNMP banner on showdown, you will find at least 3000 devices with SNMP service available with default public string. So these devices are all supposedly vulnerable to SNMP overflow. The question is whether we can build a remote code execution exploit for it. Since we're going to be exploiting SNMP protocol, let's make a quick recap of how it works. Just a light touch. So SNMP comes with several abbreviations like MIB, which stands for management information base. And it's kind of a collection of objects that can be monitored from the SNMP manager. And so management information base actually consists of object identifiers. And as an example, you all know that printers usually use SNMP, for example, if there's a certain level of ink in the cartridge you can query the SNMP service on this device for the percentage of ink left. So that's kind of example how it works. Management information base looks like a tree. So you have your base element at the top and your leaf elements. So all these elements represent an object that could be queried. We're going to be looking at get request. And that is why the advisory states that for the vulnerability to be triggered, you only have to know the read only community string. So it's a relatively simple protocol. You just supply the object identifier, your querying, and you'll get back the results. So here, for example, we get the router version, the description field. And you can also do this with readily available Linux tools like SNMP Get. So before we will build an exploit, we have a starting point. How do we look for the crash? So the advisory actually states that there are nine different vulnerable management information bases. And you only have to know the read only community string. So for the fuzzing to be done, I'll be using Scapi as a toolkit to work with network protocols. And here you can see that I'm building an object identifier, a valid object identifier that references the description field. And then I'm appending some letters A, which is 65, and an ASCII table. Then I build an IP packet, I build an UDP packet, and SNMP packet inside of it with community string public, and object identifier. So, of course, this will not trigger the overflow because this object identifier is completely fine. How do we get all the object identifiers that our router will respond to? So basically, there are two ways. You can take the firmware and just extract all the oids from it. It's easy to grab them, they're stored in plain text. Another way is to actually look at the vulnerable MIPS and visit the website, OID views, and get all object identifiers from this website. So, as a matter of fact, the first crash I had was in Cisco Alps MIPS, which is kind of related to airplane protocol, which does not concern us, because it's not a focus of our exploitation. So, the actual overflow was in one of its object identifiers. So, this request, this actually crashed the router. When you connect to the Cisco router via a serial cable, you will be, and there's a crash, you will be presented with a stack trace. So, we see here that we got a corrupted program counter, and we also see the state of registers that we have at the moment of crash. So, here you can see that we have control of a program counter. It's called EPC. We control the contents of registers as zero, as one, as two, as three, as four, as five, as six. Further inspection also provided me with knowledge that we have 60 spare bytes on the stack to work with. But before we build the exploit, we have some problems, issues to be solved, and that is, yes, we do control the program counter, but what do we jump to? Is ASLR on? And can we execute shell code directly on the stack? Is stack executable if we can place the shell code on it? Is data caching a problem for us? And if we launch our shell code, can we just patch the code? Is the code section writable? Is the code integrity check on? But the most important question is how can we return the code flow back to the SNMP service? Because iOS is a single binary running in the memory, and if you have an exception in any thread of this big binary, the Cisco device will crash. And if we look at the advisory, one of the indicators of compromise as Cisco states is device reload. So exploitation of the vulnerabilities will cause an affected device to reload. We will build an exploit. We'll try to build an exploit that will not crash the SNMP service on it. Before we dive deeper into the firmware, I want to reference previous researches on this matter. This is by no means a complete list. But this researches actually helped me a lot and seemed interesting and very insightful to me. You should definitely check them out. So, for example, router exploitation by Felix FX Lindner and Cisco iOS shell code by George Nassanko is a great resource for iOS internals and great reference to how iOS works in terms of exploitation. And the third resource, how to cook Cisco, is a great info on exploiting power PC-based Cisco switches and also great info on bypassing common mechanisms and exploit prevention stuff in iOS. So, basically, if I were to tell you how iOS works in one slide, it's basically a single binary running in memory. Everything is statically linked into a single L file which gets loaded on startup. Of course, you have no API whatsoever. Everything has no symbols whatsoever. Yes, there is a Julep C library at the end of the firmware, but it's also kind of hard to use it because you have so many different versions of firmware and the offsets jump and you don't know the exact location of those functions. So, to start with static analysis, you should probably copy the firmware from the flash memory of the router. Use the copy command. It supports TFTP and FTP protocols. So, you download the firmware. The next thing you do is unpack the firmware. The firmware itself, when the router starts loading it, has an initial stop that does the unpacking. But you don't have to reverse engineer that. You just use Beanwalk that will do the unpacking for you. You load the result of unpacking with Beanwalk into Ida Pro. You have to change the processor type to MIPS32 Big India. And we know that this is MIPS because we saw the registers. These registers tell us that it was indeed MIPS architecture. So, one thing I want to note, the actual firmware gets loaded into address 800F00, but the program counter is located at address 4. And this is because iOS, when it loaded the firmware, transfers, I mean maps the memory to 400F00. And this is important because to have correct cross-references in Ida Pro, you have to rebase your program to 4. And after that, you will have all correct string cross-references. You will have all the necessary strings and your static analysis setup will be complete. But in order to build an exploit, it will not suffice to only have the Ida Pro loaded with the firmware with all the cross-references. You probably want to set up a debug environment. It is well known that iOS can be debugged via a serial port. And actually, there is a GDB kernel command that is used to start the internal GDB server. Or it was because functionality was removed in the recent versions of iOS and you can't really run the GDB. But nonetheless, there is a way to enable the GDB. And this way is to reboot the device, send an escape sequence to the serial line. This will bring up the ROM monitor shell. So ROM monitor is a simple piece of firmware that gets loaded and run just before your firmware starts running. And in this ROM on, you can manually boot your firmware with a flag N which will launch the whole firmware under GDB. And after your firmware is loaded, the GDB will kick in. Now, you can just use your favorite GDB debugger and Linux and connect it to iOS via serial port because iOS uses a slightly different subset of commands of GDB protocol. It has a server-side GDB, but the client-side should be accustomed to this GDB server. Basically, there is no publicly and officially available client-side debugging tools for iOS. And that is because this is intended for Cisco engineers to be done. Although there have been some efforts from the community to build tools to debug several versions of routers and switches with iOS. And if you look for ways to debug Cisco iOS, you most definitely will find a tutorial that says that you can actually patch an old version of GDB that still supports iOS, but it actually never works because I tried it and all I could do is read memory, the stepping, the tracing. It just doesn't work. So, another way is to use a cool tool by NCC Group. It's called iAdit. It's a graphical debugger for iOS. It really works. It's a great tool, but the thing is it targets PowerPC architecture. And it has some problems. You probably have to patch the debugger to be able to work with it. And the third option, the last resort is to implement your own debugger for the router. And to do that, you have to know which commands actually Cisco supports. And not a lot. So, you can basically read memory and write memory and set and write registers. And the only program counter control command is a step instruction. So, basically, it's kind of easy to implement such a debugger because all the information is just sent as a plain text over a serial cable and appended with a checksum, which is just a CRC. So, this way, I was able to make a quick Python script using Capstone to be able to debug iOS. You can inspect registers. There's a basic breakpoint management. You just write a special control double word to be able to break. You can step over, step into, and also a good feature is to be able to dump memory, which we will use later. So, to find the overflow, the SNMP overflow in the code, how do you do it? Basically, you can follow, since we have all the string cross references, you can follow the strings that reference SNMP get request and just step until the crash. But a more efficient method is just to crash the device and start inspecting the stack after the device is already crashed. You just have to dump some memory on the stack, looking into the values that reference the code. Some of them will be return addresses, and this will give you a hint where the crash actually is. So, the actual program counter corruption happens in the function, I call this function, SNMP stack overflow. You can see here that at the end of a function, we load the values from the stack to registers as 0 to S6, and also we load value into register RA, and this is an important register. It's called a return address register, and almost every function in MIPS uses this register to jump back to its parent function. So, basically we have some space on the stack, but the question is can we place our shell code on the stack, and can we execute it? Because, you know, stack location is unpredictable. Every time you trigger this vulnerability as separate, space on the stack is allocated and you cannot really predict it. So, no valid jump to stack instructions in the firmware like we did on Intel X86, like jump ESP, no such instructions in the firmware, but even if we could find such instruction, the address space layer randomization is on, which means the code section and data section is based on different offset. Each time we reboot the device, which means that we can reliably jump to the instruction. And also, an unfortunate thing is that data caching is also in place. So, about ASLR, this is the first time I encountered the randomization in iOS. The previous researches that I've been dealing with, they said a lot about diversity of the firmware. So, basically, you had so many different versions of firmware. When you exploited the Cisco device, it couldn't really reliably jump to any code because there's so vast diversity of different firmware that was built by different people. We actually have the stack address space randomization and the text section and data section is loaded different offset after each reboot. So, another thing that really upsets us is data caching. So, when we write our shell code to stack, we think that it will be on the stack. But what actually happens, everything gets written into data cache. And when we place our program counter to the stack, we get executing garbage instructions, which results in a crash once again. So, this is basically data execution prevention. Well, it's not. It's cache. But the solution to this problem is the same as for data execution prevention and it is return-oriented programming. So, but unfortunately, we still have ASLR. So, we can really jump to anything because it's on random offset. But here, the raw monitor that I was talking about comes to our rescue. So, this little piece of software that gets loaded before the actual firmware might actually help us. So, the first thing we want to find is where this bare bones firmware is located. And the interesting feature of this ROM mon shell is actually allowing you to disassemble arbitrary memory parts. And if you target the disassembler at an invalid address, you will get a stack trace revealing the actual address of the ROM monitor. And what's the most interesting thing is the ROM monitor is located at BFC all zeros, and you can dump it using the debugger. Or you can just search the internet for the version and download it. The most interesting part about this piece of firmware is that ROM monitor is located at the same address and it's persistent across reboots. It's really great because we can use it for building ROP chains inside of it. So, now we have a theoretical possibility of circumventing ASLR defeating the cache problem. So, how do we build an exploit? So, the overview is as follows. We jump to ROM mon, we initiate a ROP chain which makes an arbitrary write using the code reuse technique. And after that we have to recover the stack frame to allow the SNMP service to restore the legitimate code flow. This is really important because we will be writing only four bytes. And that is not enough for a full-fledged shellcode. And we don't crash SNMP, we can exploit this vulnerability over and over again, thus building a shellcode in the memory. So, after we build a shellcode, we make a jump to it. So, here's how it works. We overflow the stack, we overflow the return address, so it points to ROM monitor. We jump to the ROM monitor. Then what we do, we actually find a gadget that reuses the data on our stack to make an arbitrary four-byte write just before the text section. Then we have to find a gadget that will recover stack for us so we can restore the legitimate SNMP execution code flow. So, this is basically an overview of one cycle of how we write a four-byte double-word. Now, a little bit on building ROP chains. So, what is it? What is return-oriented programming? So, basically the idea is to not execute the shellcode directly, but is to use existing code in the binary to execute your payload. So, you use stack not as a source of instructions, but you use stack as a data for the code that you're reusing. So, basically you chain the snippets of code, we call them gadgets, and you chain them together with jump or call instructions. And candidate gadgets has to meet two requirements. It has to actually execute our payload, and it also has to contain instructions that will transfer execution flow to the next gadget. Or, if it's the last gadget, it should transfer execution back to the SNMP service. The problems with the return-oriented approach is that there is a limited set of gadgets available. So, if we're talking about the firmware, it's around 200 megabytes of code, so there are plenty of different gadgets there. If we're talking about a raw monitor, it's only 500 kilobytes of code, so not a lot of code available. And the second major problem is that gadgets, because most of them are function epilogues, they modify the stack frame because they delete the local variables after they jump back to the parent function. And you have to account for that, because this might crash the process you're exploiting. Rockchains can be basically forced to do anything, but mostly, most of the times we do arbitrary memory writes, and this actually might lead to arbitrary code execution. So, the idea for looking for gadgets is that you find a gadget that loads data from the stack into the registers, and then you find a second gadget that works with the data in those registers. For example, you have one register, V0, which contains the value you want to write, and the other gadget, S0, which has the address you want to write to. So, we actually want to find gadgets that also load data from stack to return register, so we can jump to the next gadget. You don't have to look for these gadgets manually in IDA. There are a lot of different tools for building Rockchains. One of those tools is Rapper. You can find it on GitHub. It's a really handy tool. You just search for the necessary instructions to build the necessary Rockchain. So, now the last technical part of how the Rockchains in this particular exploit work, and then we'll get to the demo. So, this is how a perfectly healthy stack frame looks like. So, you basically have local variables on the stack. You have return address. You also have a stack frame of parent functions underneath the stack frame of our vulnerable function. So, when we overflow the local variables with our long object identifier, here's what happens. We overflow the local variables, and these variables actually partly get written to S0 and S6 general purpose registers. So, of course, overflow the return address, which will jump for us to run minor, and we also have some 60 bytes. After that, we overflow the stack frame of the next function, and we use that data also for our Rockchain. What we do here, we take the value of S0, we control the value of S0, as you remember, and we move it to register V0, and that's for only solid purpose, because there are no other gadgets in ROM minor that use S0 as a target register to write data. So, we have to use register V0. After that, the most important part is that we load the return address from the RobData2, and also we load the address we will write to from the RobData2. So, basically, right now, after this gadget stops executing, we have S0 points to a memory we want to write to, and V0 contains four bytes we'll be writing just before the code section. So, the final gadget that is performing the arbitrary write is the gadget that takes the value of register V0 and writes it to a pointer referenced by register S0, and the last thing it does is actually transfers the control back to the gadget which will recover the stack for us. The most important gadget it allows us to run the exploit several times. You might have noticed that the previous gadgets actually moved the stack pointer 30 bytes and hacks down the stack, and this actually means that the process that we will return to will crash if we don't point the stack pointer just between two stack frames. We find a gadget that will move the stack pointer down to 228 bytes and hacks, which will result in a perfectly healthy stack. Also, we load the return address to register RA, and it points to the parent function that called our vulnerable function. So, this way we perform an arbitrary four byte write. We can do this several times until our shell code is actually built just before the text section, and the final thing we do is overflow the stack again and jump to the shell code. A few words about the shell code. The device I was working with had a telnet service and it had a password, so I designed a simple shell code that will just patch the authentication code flow. So, as you can see here, we have a function, a login password check, and the result which is in v0 register is checked, whether the authentication was successful or not. We can build a shell code which will just patch this instruction, which checks login password check, and it will allow us to make credentialless authentication against telnet service. So, what it does, basically this shell code inspects the stack and the return address in it to calculate the ASLR offset because, of course, the ASLR is on for the code section, and we want to patch something in it, and after that it writes a zero, which is a nub instruction in MIPS to a call that checks for password for telnet and also for enable password. And then it just jumps back to SNMP service. So now, the long awaited demo. Let's see if I can make it a live demo. Alrighty, so here we have the serial connection to the device. You can see we have a shell. So what we do now, we inspect the password on the telnet service to make sure it's working as intended. So we see that bad passwords. We don't know the valid password for the device. What we do now is we launch the actual exploit. As parameters, it takes the host, community, and shell code in hex. So this is the shell code I was talking about that patches the code flow in authentication. So let's try it. So here you see that we initiate writing for byte sequences into the text section. Basically, this writes the shell code into the memory. So after the exploit finishes this, we just have to jump to the shell code. So let's see. Please do not crash. So, yes. So back to the slides. And, of course, you can build a shell code that will unset this behavior and patch the process back to enable the password. And on the side notes, how reliably can you exploit this vulnerability? So, of course, the SNMP public community will leak you the version of the particular router, but it does not leak you the version of Ramon. And we're basically constructing ROP chains in the ROM monitor. So, actually, you have not that many versions of ROM monitors available. You have only five if we're talking about 2,800 router. So the worst case scenario is just you crash it four times. It's not like you have to crash it 4,000 times to, you know, beat ASLR. But there's a second option, which is interesting. Ramon is designed to be upgraded. So, basically, a system administrator can download a new version and update it. But the thing is that read-only region that contains the stock Ramon is always in place. And it is always at the same offset. So even if you update it, the ROM monitor, the read-only version of it, the old version that always been there, will always be at BFC all zeros. So, basically, the assumption is that all the devices manufactured at the same time and place that will have the same read-only ROM monitor, you can query your serial number of your router using SNMP GET. For example, my lab router is manufactured in the year of 2008 in Czech Republic. So it has the following version of ROM monitor. So, guys, to summarize about all this, do not leave default credentials on external networks. So, public communities are not designed to be placed on external networks for the showdown to find it. Take care of what you exposed on the external networks. Of course, patch your devices and watch for the end-of-life announcement by Cisco. Sorry? Sure. Why not? All right, guys, thank you so much for your attention. Thanks for having me. I suppose there are some questions in this audience. Please take a microphone. If you can, no one on the internet. They are flabbergasted here, it seems. Microphone number one. Hi. I'm a random network admin. And I know that people tend to use the same SNMP community on many of their routers. My view is that, basically, if you can get access to read-only on those routers, you will be able to eject that or use the same principle. So, basically, don't use the same SNMP community on all your devices. That would be also something. The main thing is to update your routers because it's a patch vulnerability. The patch was released in September of 2017. But if you tend to use the end-of-life products like router 2800, you probably should use a strong community strength for it. Thank you. Thank you. Someone else having a question there? Yes, someone on the internet is alive. It's alive. Let's try it. Yeah, now I've actually got a microphone. The internet is asking, how much time did you put into this whole project? Well, working on this exploit, I consumed around, I'd say, four weeks. From discovering the device on the external network to the final exploit. Yes. Thank you. I have a question maybe for you as well. You as well have lots of volunteers who are working with you as well in researching these exploits. Volunteers? Yeah, I don't know. No, actually, we don't have any volunteers. This is all part of my work. Okay. Thank you very much for this really revealing lecture. If someone wants to... Oh, I just forgot to say, is my mic on? Okay, so the actual proof of concept and debugger will be released in a few days. So the Python script with the capstone and the actual proof of concept, I'll publish it in a week or so. Okay, thank you.