So awesome awesome, I hope everybody's having a good day not very hungover anymore But Jake is about to give us a great talk on mainframes and take it away So this talk is going to be about how I found mainframe buffer flows and how you can as well So About me I live in the UK in Basingstoke and So My Twitter handle is that if you want to follow me for basically mainframe shitposting those slides aren't on. Oh Yeah, you got that okay, yeah, okay So Adho University I got my first real job as a security consultant. And at that point I had no, no clue what a mainframe really was, but my Boss is boss asked if I wanted to do a mainframe job. And I was I wasn't gonna say no, so I Luckily we had a Mainframe emulator the company had it and so during the pandemic I was just mucking around with that trying to find out How you would? do ZOS exploits. The emulator is a $10,000 USB you stick into your computer to say, yes, you can run it. So around about last May, I found my first zero day on ZOS, and it kind of typifies how sometimes the ZOS vulnerability development type thing works. And so the job had a, there was a program running on Windows, Linux, and ZOS, and for Windows and Linux you had the normal requirements, but for ZOS, I got told that I could send an email to someone, they could run it on their computer, and they'd tell me what came up. Obviously that is just no way to actually test a program, so I found out that that program that we were going to test was on that mainframe emulator we had. So I just... I took a month-long holiday and worked out that one of the binaries was vulnerable to a APF-authorized DSA overflow, which I'll explain what that is later on in the talk. And so, but the TLDR is that if you are able to get at that exploit, you're able to take full control of the system. So since then, I found a fair number of zero days, found one last night. If you look up on the CVE site, you can see that there's basically no vulnerabilities for ZOS. Don't let someone fool you by saying that that means it's unhackable because there's nothing there. It's because IBM said it doesn't report these, at least not publicly to everyone. They just, they tell people on an internal security portal. So yeah. So when people talk about mainframes... They can mean multiple of things. They can talk about AS400, PHP nonstop, ZOS, and Z on Linux, or Linux on Z. And these basically share nothing between each other, other than the fact that they're big computers. So for the rest of the talk, I'm going to be saying ZOS instead of mainframes, because that's what I'm actually good at exploiting. So one thing that... When you're first starting learning about ZOS and you know about Linux, people will tell you things like this feature on... Feature X on Linux is like feature Y on ZOS. And so one good example of that is files are like data sets. However, this is fine for people who are just regular users of the system or who are pen testing just... Like... I'm not going to be doing a host review of ZOS, but if you want to do vulnerability research, you need to know the nitty-gritty of how these things are actually working. And so one of the big differences between data sets and files is that a file is just a load of bytes, and it's the application that chooses how to process that. But on ZOS, every data set has a... You define what a data set's format is like beforehand. So I might say that this data set is a fixed block and a record length of 80. This is... This can be where you can have buffer overflows on ZOS where you pass at a larger L record than it's expecting. Another big difference is that ZOS has a flat file system, so there's no folders. You might have... You might have... You might have data sets logically separated. So you'll have, for example, jake.homework.math, and you can say, give me all data sets that start with the high-level qualifier jake.blah-dee-blah, and it'll give you all of those, but there's no actual folders. Then you also have a difference between sequential and partition data sets, and the sequential data sets are kind of just like normal files. Partitioned look like a folder, and you can enter them and see that they have lots of different members, which look like files, but it's... It's under... Like, underneath the surface, it's completely different. So here is an example of a partitioned data set, ibmuser.cntl, and the member is allocstd, and this is the contents of that data set. Or that member. And this is a... This is a JCL, which is a job control language, and I've heard it described as old-timey YAML, and the way it... The way this used to work is you would have this on a punch card, you would feed it into the system, and then you would check your line printer for your output. This doesn't happen anymore. Everything is emulated. The first line of every single JCL... Is the... Is the job card. And when I say first line, it's not... Lines one to six count on the same line, because there's a comma at the end of those lines, which means it's a continuation line. It's... It's just how it is. And you have different things on here, like the job name, allocstd. You have message class and message level. This is what's telling the printer... Hey, I want you to print this much information about what happened on the job. And you also have something called user and password. This is kind of like... Sue on Linux, where you can submit a job as another user, either by providing both the user name and password, or just the user and having access to do that. The next line, the step 01, is a... Is a... Is a step which runs the program IEFBR4. That then also has, on the next line, a DD statement. And what the DD statement means is that IEFBR14 can reference a DD fixed 32760. It could write to it. It could read from it. And it would then write to that data set. So what happens is every time you start a new job, it has its own address space. And... This is also a good example of where comparing Linux and ZOS kind of falls apart. So on... People often compare the Nucleus and the CSA of ZOS to the Linux kernel. But as you can see on here, every job on the system, no matter what program you're running, has the ability to address the Nucleus, which is something which you can't do on Linux. And... Like just addressing it normally. And also, if, for example, you were able to write to the Nucleus or the CSA, you would be able to fully, like, break the system. So these are protected. But it's... Yeah. I'll get to that later. Then the next important part of memory is that you have some private memory. And this can be divided into private high and private low. Private high memory is memory that is shared between all steps in a job. So... For example... You might... In that JCL we had, that job name would be some addressable part of memory. And that would be the same in every single step. So... Yeah. And then there's private low, which you could consider basically like normal, like if a program needs memory to run its actual program, that's where it will be running it to. If you're wondering how ASLR works on ZOS... Yeah. It basically moves around the private low memory. So this is how I bypass ASLR, is I just find some gadgets in private high or the Nucleus. Because that won't move. So another important thing about ZArch is that instead of having things like the instruction register or condition code registers, this is all in memory. So... Yeah. And it's... So this is... And this is called a PSW, or program status word. And so you can see it's got the instruction address and the condition code. But the other thing which is important about ZArch is you also have two more things. You have supervisor state and the key. What supervisor state is, is it allows you to run all the fun instructions on ZArch that let you... So, for example, change key, or there's an instruction called MVCS, which literally just lets you read the instructions. And then it allows you to write to another address space. So if you're in that, it's quite good. And then the key is every single page of memory is protected by a storage key. And you need to have the correct key to access it. So if I wanted to write to some key four memory, I would need to be in key four. And also, if I want to read from that memory... If it's fetch protected... Which is the way you can set it to be, you also need to be in key four. So there's also one additional thing, whereas if you're in key zero, you're allowed to access anything you want. So what would we do if we could write to anything we want? We would want to escalate our privileges. And the way we do this is by editing the ZOS Aki. And you can think of this like a UID or a GUID. But it's in our own address space in a private high storage. So it's the same across every single job. So if we could write to this ACEE, we're able to change our privileges in one step. Then in the next step, say, so, for example, one of the privileges we would give ourselves, is RACF special. And what this does is it says, anytime I want to give myself access to something, say I'm allowed to give myself access to it. So we just flip that in one step. We flip our Aki bit in one bit, give ourselves special, and in the next step, we just say, also give us access to everything. And then, yeah. Yeah. Yeah. Yeah. So we only have a key zero memory. So not only certain types of programs are allowed to edit this. So how do we edit it? We can just use a simple ZDOT assembly program which uses mode set, which allows us to go into key zero or supervisor state. And you're only allowed to use this if you are ‑‑ if you yourself are in supervisor state or if you have a ‑‑ or if you're running APF authorized. So you can use it as a key zero. So, after we get into key zero, we load our through what's called control blocks and we find out where our ACE is in memory. We then just flip a bit that says we're special and we're good. This is part of the reason why IBM was only able to put ASLR on the private low memory because if they put it on everything, they would end up breaking all the control blocks which would break all the other programs that are actually running. How do we get into supervisor state? We could either be running an SVC or a PC. These are like Cisco's on Linux, but just completely different. Then there's also what I'm going to be talking about for the majority of this talk which is APF authorized datasets. If we want to. to run a program APF authorized, we need two things to be true. It needs to be part of the list of APF authorized libraries and also the AC1 flag has to be set. So if, for example, we had write access to an APF authorized library, all we would have to do is compile that previous code I showed and compile it with that AC1 flag and we would be ‑‑ have full access. This isn't often ‑‑ I've never seen it where ‑‑ since I've been testing mainframes, I've never seen it where I could just write to an APF authorized data set. Okay. So now we're going to assume that there are ‑‑ we can't write to it, but there are some vulnerable program that someone has written that's AC1. So how do we find these vulnerable programs? Well, we're looking for a Benz. And these are kind of like seg faults. And there's two main categories. We have system and user events. We're just going to ignore user events for the time being because that's kind of ‑‑ people ‑‑ mainframers normally use that to ‑‑ with the regular ‑‑ it's like it's an expected bad input that someone's given gives you a user event. A system event is normally something that happens that the programmer didn't even realize that that could happen. So there's the three main ones that I'm looking for. SOC 4, SOC 1, and SOC 6. SOC 4 means that we try to address an area of memory that we've ‑‑ don't have the right key to or just the memory doesn't exist. Then there's also SOC 6. And so in ZArch, every instruction has to be on a half word boundary. And if it's not, you get a SOC 6 event. So everything that you're doing needs to be on an even bite. And then SOC 1 means that we've tried to execute an instruction, but the operand and opcode don't make sense. It doesn't ‑‑ it's not a real instruction. And if you want to find out more about all the different events, you can read IBM's 854‑page manual about every single one. Okay. So we've now ‑‑ we now know that we're looking for events. How do we work out what caused that event? The one I normally am going for is using a SISU dump. And a SISU dump means a user‑formatted dump. So we can just print this out to any random data set. And we can read it. It's human readable. And I'll explain later what's actually in it. Then we have a C dump, which is only for some very specific compiled programs in language environment. I'll explain what that is later. But the problem with this for us is because we break so much stuff when we're causing events, sometimes we even break it generating the C dump. And then there is SISM dump, which gives you so much information, and it's also not human readable. You need to put it through another program called IPCS that I just don't bother with it. Normally everything I need is in a SISU dump. Okay. So how do we cause a dump to be generated? We just add a DD statement. So here is a JCL to run our FTPD server. And we've added a SISU dump, DD, SISOUT equals star. So now when the programmer bends, we'll be able to see a long list of all the things that were in there. This is the ‑‑ instead of using ‑‑ instead of practicing exploit, dev, form, dev stuff on ZOS, we're going to be able to see what's going on in ZOS. I like going through NVS 3.8 J. It's a 50‑year‑old operating system that's in the public domain. So anyone can just spin up a docker. You don't require a $10,000 USB to run it. And so ‑‑ but all of the exploit techniques are the same on NVS 3.8 J as modern ZOS. So what's actually in a SISU dump? So we have our PSW, which is going to tell us our instruction points. We have our pointer and, like, what key we're in when we are bended. We're also going to get our completion code. So this will be, like, SOC 4, SOC 6. And we also ‑‑ we might get a little reason code, so some extra information about why it abended. We also get our list of registers, 16 general purpose registers. And when we also have a list of sub pools. And sub pools are ‑‑ when I show you that memory map, you're allowed to ask the system for different parts of that ‑‑ different parts of that memory. And so this is useful for saying, okay, what memory and where has my program got ‑‑ where is my ‑‑ what memory is my program allocated? And then we also get a big dump of all of the contents of what was in the ‑‑ the ‑‑ the dump that was in the dumps, the sub pools we actually have access to. So it's ‑‑ when we dump ‑‑ when we get a dump, we need to know what we're actually looking for. And so most programs on ZOS will be compiled language environment. What language environment is, is it's kind of a common way for things to be compiled and for what registers will contain what. Because, yeah, so, for example, if you compile a C program, it will be compiled language environment. And then that will also have DSAs, dynamic save areas. If you ‑‑ one way ‑‑ ZOS doesn't have a stack. So we have to create a stack in memory because stacks are useful. So I'll explain how a DSA overflow works. But you can kind of think of it as a linked list of a lot of stacks. So I'm just going to explain a DSA overflow. This is just a simple nothing code that uses gets. So it's ‑‑ the vulnerability is obvious. But the exploit is a little bit more interesting. So the first thing we do is that the ‑‑ when we're about to call gets, our cooler DSA has a next available byte to some free memory. And because we're calling gets, we can also see that we have a parameter list which has in register ‑‑ which has just one parameter, which is the buffer address. So once ‑‑ now, once the gets program starts, once the gets function starts, it will create its DSA and will also save the contents of our main functions registers so that once we're finished with gets, we can go restore everything we had beforehand. The ‑‑ we also set a previous pointer in the gets DSA so that we know where our cooler DSA is. Then ‑‑ so to exploit it, we just give it a long buffer. And because the buffer that we're using is before our gets DSA, we'll end up overflowing. So that's why we've put our previous DSA pointer in the gets DSA. And so I've put ‑‑ for reasons, I put a long list of LGBT, LGBT, LGBT, LGBT that ended with Kale. And then when the gets function ends, what it's going to try and do is restore its DSA, but it thinks that the DSA is located at address Kale. Kale is not a real address. And so it will ‑‑ it will abend. So then instead of doing this, what we do is we fill it with ‑‑ we actually write an exploit for it. So instead of the previous pointer pointing to some random address, we point it to our buffer. Then it will restore all of its registers that it thought it had at the ‑‑ when the get function was called. But at the moment it's restoring from our buffer. One of those ‑‑ one of those registers it's going to restore is register 14. 14 in language environment is a ‑‑ is a ‑‑ is an important register because it's the return register. The reason why we ‑‑ the reason why is because the typical way to call a function in ZOS ‑‑ in Z arch assembly is a instruction called branch and link register R14, R15. And that says jump to R15. And store the next instruction in memory in R14. So now we've taken over the return register. We then just point that towards our buffer. And then because we're running as APF authorized code, we can just run our Aki flipping code that we've ‑‑ I showed before. And now that's the case. In the next step, we can just give ourselves privileges to access anything. Okay. So that FTP server on the MVS 3.8 J is vulnerable. And it's vulnerable to a DSA overflow. So all we have to do is just keep crashing the FTP server by giving it a ‑‑ I'm not going to go through the vulnerability because it's just a simple C vulnerability. But it's just you give it a user space and then a long list and it crashes on that. And so, yeah. So all we have to do is just keep working out what register is being filled with what buffer. Then find out when we changed that previous DSA pointer, point it towards our buffer, and then edit the return register and then run some shellcode. So if you use a mainframe, one of the useful tools on there is something called Rex. It's somewhere between bash and Python. And so, yeah. So I often use this for generating a Dabroon string on ‑‑ I can use this to generate a data set which contains a Dabroon string. And then I can use that to try and look for vulnerabilities. But we'll use it to crash the FTP server by giving it a user space Dabroon string. And what's kind of curious about a ‑‑ doing an RCE against a DBA. Is that most servers are expecting a ASCII text from a normal host. And it's going to translate that into EPSI deck. And so when we've passed our normal buffer of just a load of characters, it's actually translated that into EPSI deck. So 41 will become C1. And this is going to become ‑‑ this makes RCEs on ZOS quite difficult. Quite tricky. Because you have to think about this before you send your buffer. So if we look at the registers in our SISU dump, we can see that register 2 has been overflown by an EPSI deck string. We then ‑‑ we also see that it's a SOC 4. So we can see that in our register 2, we had 88, F8, C2, 88. But the actual binary ‑‑ the actual bytes that we sent to it was 68, 38, 42, 68. So what this means is that the translation table between ASCII and EPSI deck isn't one‑to‑one. So some bytes will both convert down to the same ‑‑ convert to the same byte. So, for example, on the default IBM ZOS translation table, 00 and 80. Both 0. Both convert to 00. But what's more annoying about that is that that means there's some bytes that have nothing to convert into it. So if we needed ‑‑ if we needed in our buffer a 09 to occur, it's not possible. You just can't ‑‑ you just can't ‑‑ there isn't an ASCII bit that will translate to that. So it is somewhat frustrating when even though it's vulnerable and it looks exploitable, we had to change it to 09. So, yeah, it means that some addresses are just unaddressable. It also means that we need to encode our shellcode so that everything translates properly. So how do we work out the instructions that we're abending on? Like what's actually causing the abends? So I've written a Rex script to just translate ‑‑ to disassemble, like, eight bytes to a ‑‑ I don't know. It's Zarch assembly. Mostly grabbed from someone else's stuff. And so if we want to find out what instruction we abended on, we have a look at our instruction pointer. We then take that instruction pointer, have a look at our ‑‑ at our ‑‑ at the memory, the address where it was ‑‑ where it occurred. We then take that instruction, run it through to Kodai. And find out it's a store multiple R12, R14, 0, R2. And this is writing to ‑‑ at R2. And this is why we got a SOC 4. Because it tried to write to that ‑‑ that register we've overflown. So how do we ‑‑ how do we solve this? Well, we need to find some area of memory which is in key 8. Because if you looked at our PSW at that time. We were in key 8. It also needs to be ‑‑ we then need to create a ‑‑ an address which will convert to something. So I just chose this one because it converted to something that we have access to. And also to note that in ZOS there's three types of addressing modes. There's 24‑bit. There's 31‑bit for some reason. And 64‑bit. So this is at the moment in 24‑bit. So only three bytes. And then so we keep doing it. And we see that there's another event. This one's not particularly interesting. But the next one is. So register 13 is mostly used as the current DSA pointer. So if we've overwritten register 13, it often means that we're on to something good. And we can have a look and we can see that ‑‑ so register 13 is overwritten. We have a look at the instruction that we've tried to run. And it is load R14, R12, R13. And I've also decoded the next couple of instructions. Because this is the normal way that you would exit a function and restore your DSA. So you're loading your return register. Then you're loading all your other registers. And then you're branching with the bit mask 11111. Which means branch every time to register 14. So that's where ‑‑ that's why overwriting register 14. Let's just control the instruction pointer. Okay. So this is where we've now restored all of those registers from our buffer. And you can see that now every register has been replaced. This was the load multiple that happened. And so ‑‑ and you can also see that the instruction pointer has now been changed to C182F5. Which was from our buffer. Oh, and also, you can see that this is SOC 6. Because F5 is not on a half word boundary. So it's another ‑‑ yeah. Okay. So now that we've edited all of our ‑‑ all of our registers, we want to edit the return address. The pointer somewhere that we have control over. And so we just choose somewhere that ‑‑ we just choose an address which does convert nicely. And also converts nicely to somewhere we control. And so we just choose this point here. And we now got a SOC 1. And the reason why we got a SOC 1 is because it tried to run some code. We tried to run our ‑‑ our EPCDIC translated to Brune string. And that's not real ZArch assembly. So all we need to do now is generate some real ZArch assembly and run it. So. We take ‑‑ we take some shell code. And all this is doing is just using SVC 35, which is right to operator. And this will just print out a nice little message that everyone can read on the syslog. So ‑‑ and it will say WTO when it runs. And also something to note is that we've done a using star, comma, R14. The reason why we do this is because we've retrieved this. We've returned on register 14. And that means that the program ‑‑ when this is compiled, it needs to know where the base register is. And that will then allow it to compile things so it knows that, for example, when it does load address, register 1, message WTO, where that is. Okay. So now that we've got the code we want to run. We now need to ZAR that code so that we can ‑‑ so that it actually survives a ASCII to EPSIDIC translation. And we just choose a ‑‑ so we need to have a ZAR key and a ZAR shell code that both survives it. And luckily, the XC instruction is an instruction which translates directly from ASCII to EPSIDIC. So we don't have to do that. But that just allows you to ZAR ‑‑ the XC instruction allows you to ZAR any two strings of, I think, 255 length. So now that we've ‑‑ now that we've actually ZARed it, we now need to go through and ‑‑ there's a Python script that we've written to do this automatically. But it will go through and find all of the bytes that convert to the EPSIDIC bytes. One thing to note is that not all translation tables will be the same. So one thing you can do is if you only use the printable characters, so like the alphanumerics, you know those ones are likely to be the same on every system. So that's what I would recommend using. So your ‑‑ what you send over will only be ASCII characters. Or ASCII, like, alphanumerics. So then all you need to do is send that to the FTP server with all of those addresses we've changed and the shell code added. And you'll see that it WTOs. The way to ‑‑ so this is just a proof of concept. You don't need ‑‑ there's only rights to operator. But because the server is APF authorized, you can get it to do anything. So, for example, you could, like, spawn a reverse. You could make it dump the, like, some sensitive data sets. Yeah. You could crash the system. With APF authorized code execution, you can do anything. Okay. And we also hosted a workshop where you can actually do this yourself, which is another reason why I like doing this on MVS 3.8j. Because you can do it yourself. You can just create a Docker with a load of labs and share it with people. So that's more interesting. And then got a number of references. So the ‑‑ yeah. Any questions? Do I just ‑‑ do I ‑‑ yeah. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . like thousands of users. So, an LPE is a little bit more, I'd say, critical on a ZOS box than a Linux box. So, let's hear it up for our speaker again.