 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 shit posting. So after university I got my first real job as a security consultant and at that point I had no clue what a mainframe really was but my boss's boss asked if I wanted to do a mainframe job and I wasn't going to say no. So 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 Zeta West 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 Zeta West and it kind of typifies how sometimes the Zeta West vulnerability development type thing works and so the job had a, there was a program running on Windows, Linux, and Zeta West and for Windows and Linux you had the normal requirements but for Zeta West I got told that I could send an email to someone, they could run it on their computer and they would 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 took a month long holiday and worked out that one of the binaries was vulnerable to a APF authorised 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 it 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 Zeta West. Don't let someone fool you by saying that that means it's unhackable because there's nothing there, it's because IBM Z 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 AS 400, PHP nonstop, ZOS, and Z on Linux, 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 these 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 doing a host review of ZOS but if you want to do like vulnerability research you need to know like 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 format is like beforehand so I might say that this data set is a fixed block and a L record length of 80. 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 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.bloodyblur 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 like underneath the surface it's completely different. So here is an example of a partition data set IBM user dot CNTL and the the member is ALOC STD and this is the contents of that data set or that that member and this is a this is a JCL which is a which is 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 your output this doesn't happen anymore everything is emulated the 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 so it's just how it is and you have different things on here like the job name ALOC STD 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 username and password or just the user and having access to do that the next line the step one is a is a is a is a step which runs the program IE FBR 14 that's that then also has on the next line a DD statement and what the DD statement means is that IE FBR 14 can reference a DD fix 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 Z arch 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 are 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 parts 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'll be running it to if you're wondering how ASLR works on ZOS it only moves around the private low memory so this is how I bypass ASLR as 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 uh the current the instruction register or condition code registers this is all in memory so 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 uh so for example change key or there's an instruction called uh MVCS which literally just lets you write to another address space so if you're in that it's it's quite good uh and then the key is every single page of memory is protected by a uh by a storage key and you need to have the correct key to access it so if I wanted to write to some uh key for memory I would need to be in key for and uh also if I want to read from that memory if it's fetch protected which is a way you can set it to be you also need to be in key for so uh 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 um we would want to escalate our privileges and the way we do this is uh by editing the ZOS ackee and you can think of this like a UID or a GUID but uh it's in it's in our own address space in a private high uh in a private in private high storage so it's the same across every single job so if we could write to this uh acee we're able to change our privileges in one step then in the next step um say so for example one of the privileges we would give ourselves is rackf special and what this does is it says anytime I want to uh anytime anytime I uh 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 ackee bit in one bit give ourselves special and in the next step we just say also give us access to everything and then yeah and yeah but this is stored in key zero memory so not only certain types of programs are allowed to edit this so how do we edit it um we can just use a simple zdoch assembly program which uh uses mode set which uh 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 certain or if you're running apf authorized so after we get into key zero we load our um through what's called control blocks and we find out where our acee is in memory we then just flip a bit that says we're special and we're good for this is part of the reason why um 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 so how do we how do we get in supervisor state so we could either be running an svc or a pc these are like syscalls on um on linux but just completely different um and then there's also uh what i'm going to be talking about for the majority of this talk which is apf authorized data sets um so we want 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 ac one flag has to be set so if for example we had right access to an apf authorized library all we would have to do is compile that um previous code i showed and um and compile it with that ac one flag and we would be have full access this isn't often i i i've never seen it where since since i've been testing mainframes i've never seen it where i could just write to an apf authorized data set um okay so now we're going to uh assume that there we can't write to it but there are some vulnerable program that someone has written that's ac one so how do we find these vulnerable programs well we're looking for a bend and these are kind of like seg faults in linux and there's two there's two main categories we have system and user abends we're just going to ignore user abends for the time being because that's kind people uh mainframeers normally use that to uh with the regular it's like it's it's an expected bad input that someone's given gives you a user abend a system abend is normally something that happens that the program didn't even realize that that could happen so there's the three main ones that i'm looking for uh sock four sock one and sock six sock four means that we've tried to address an area of memory that uh we either don't have the right key to or just the memory doesn't exist um then there's also sock six and so in zed arch every instruction has to be on a half word boundary and if it's not you get a sock six abend so everything needs to be on an even byte um and then sock one means that we've tried to execute an instruction but the operand in 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 abends you can read ibm's 854 page manual about every single one okay so we've now um we now know that we're looking for abends how do we work out what caused that abend um the one i normally am going for is using a sysu dump an sysu dump means a user formatted dump so we can just print this out to any random data set so we can 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 uh compiled programs in language environment i'll explain with that as later but um the problem with this for us is because we break so much stuff when we're causing abends sometimes we even break it generating the c dump um and then there is sys evan 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 sysu dump okay so how do we cause a dump to be generated we just add a dd statement um so here is a uh here's a jcl to run our ftpd server and we've added a sysu dump dd sysout equals star so now when the programmer bends we'll be able to see a long list of all the all the things that were in there um this is the instead of using instead of practicing exploit dev form dev stuff on zos i like going through nvs 3.8j 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 ten thousand dollar usb to run it and so but all of the exploit techniques are the same on nvs 3.8j as modern zos so what's actually in a sysu dump so we have our psw which is going to tell us our instruction 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 uh like sock four sock six and we also maybe i get a little uh a reason code so some extra information about why it have ended uh we also get a list of registers the um 16 general purpose registers and uh when we also have a list of sub pools and sub pools are when i showed you that memory map you're allowed to ask the system for different parts of that uh uh different parts of that memory and so this is useful for saying okay what is what what memory and where has my program got mem where is my what what memory is my program allocated and then we also get a big dump of uh all of the contents of what was in the um the uh the dumps the uh the the sub pools we actually have uh access to so it's when we dump when we when we get a dump we need to know what we're actually looking for and uh so most programs on zos will be compiled language environment what language environment is is it's kind of a uh a common way for for things to be compiled and for what what registers will contain what because yeah um so for example if you compile a c program it will be compiled language environment and then that will also have dsa's dynamic save areas if you um one with zot doesn't have a stack so we have to create a stack in memory because stacks are useful so uh 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 um nothing code that users 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 start 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 um 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 um that we're using is before our gets dsa will end up overflowing our previous dsa pointer in the gets dsa and so I've put uh for reasons I put a long list of lgbt lgbt lgbt lgbt that ended with kale and then when the 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 uh it will abend so then instead of doing this what we do is we fill it with uh 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 that it that it thought it had at the when the get when the get function was called but at the moment it's restoring from our buffer one of those uh um 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 do the reason why is because the typical way to call a function in zeta in zs ascent z arch assembly is a instruction called uh 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 authorize code we can just run our ackee flipping uh code that we've I showed before and now that's the case um in the next step we can uh uh just give ourselves privileges to access anything so that uh ftp server on the mvs 3.8 j is uh 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 um I'm not going to go through the vulnerability because it's 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 uh 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 change that previous dsa pointer point it towards our buffer and then edit the return register and then run some shell code so um if you use a mainframe uh one of the useful tools on there is something called rex it's somewhere between uh bash and python and so I often use this for generating um a deboonstring on I I can use this to generate a dataset which contains a a deboonstring and then I can then use that to try and look for vulnerabilities but we'll use it to crash the ftp server by giving it a user space deboonstring and what's um kind of curious about a uh doing an RCE against ZOS or nvs 3.8j is that most servers are expecting a ascii text from a normal host and it's going to translate that into epsi-dick and so when we've passed our our normal buffer of just the load of characters it's actually translated that into uh epsi-dick so 41 will become c1 and this is going to become this this makes RCEs on ZOS quite tricky because you have to think about this before you send your buffer so if we look at the uh the registers in our sysu dump we can see that uh register 2 has been overflown by a epsi-dick string uh we then we also see that it's a sock 4 so we can see that in our register 2 we had 88 f8 c2 88 but the actual binary that the actual bytes that we sent to it was 68 38 42 68 so what this means is that uh the translation table between ascii and epsi-dick isn't one to one so some bytes uh will both convert down to the same convert to the same uh byte so for example on the default IBM ZOS translation table 00 and 80 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 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 shell code so that everything translates properly so um how do we work out the instructions that we're bending on like what's actually causing the abends so uh I've written a uh rec script to just translate uh to disassemble uh like eight bytes to a it's z arch assembly mostly grabbed from someone else's stuff and so if we want to find out what did what what instruction we are bended on we have a look at our instruction pointer we then take that instruction pointer have a look at our um at our at our at the memory the address where it was uh where it occurred we then take that take that instruction run it through to kodi and find out it's a store multiple r12 r14 0 r2 and this is writing to um act r2 and this is why we got a sock four because it tried to write to that um that register we've overflown so how do we how do we solve this well we need to find some area of memory which uh is in key eight because if you looked at our psw at that time we were in key eight it also needs to be we then need to create a an address which will convert to something so uh I just chose this one because it converted to something that we have access to and also to note that in z os there's three types of addressing modes there's 24 bit there's 31 bit for some reason and 64 bit so this is the moment in 24 bit so any three bytes and then so we keep doing it and we see that there's another bend 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 onto 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 our 14 our 12 our 13 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 1 1 1 1 1 which means branch every time to register 14 so that's where that's why overwriting register 14 lets us control the instruction pointer okay so this is where we've now restored all of those registers from our 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 c1 82 f5 which was from our from our buffer oh and also you can see that this is sock six because f5 is not on a half word boundary so it's another 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 sock one and the reason why we got a sock one is because it tried to run some code we just try to run our our epsidic translated to broom string and that's not real z arch assembly so all we need to do now is generate some real z arch 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 uh 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 returned on register 14 and that means that the program the when it 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 one message wto where that is and where that where that is okay so now that we've got the code we want to run we now need to uh uh zore the zore that code so that we can um so that actually survives a ascii to epsidic translation and we just choose a so we need to have a zore key and a zored shell code that both survives it and luckily the xc instruction is a instruction which translates directly from ascii to epsidic so we don't have to deal with that but that just allows you to zore that the xc instruction allows you to zore any two strings of i think 255 length so now that we've um now that we've actually zored it uh 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 uh find all of the bytes that convert to the the epsidic bytes that we need one thing to know is that uh 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 um send that to the fdp server with all of those addresses we've changed and the shell code added and you'll see that it wto's the way to um so this is just a proof of concept so you don't think you don't need there's only rights to operator but because the the server is apf authorized you can get it to do anything so for example uh you could like spawn a reverse shell back to you um you could make it dump the like some sensitive data sets yeah you could crash the system yeah you with with apf authorized apf authorized code execution you can do anything okay and um we also hosted a workshop where you can actually do this yourself which is another reason why I like doing this on nvs 3.8j because you can just create a docker with a load of labs and just share it with people so I think that's more interesting and then uh got a number of references so the um yeah any questions no it's it it runs exactly the same oh okay oh okay yeah yeah if the um the emulator is runs exactly the same as like the heavy metal oh uh yeah uh so I had a month-long holiday and I by by the end of the holiday I think I was less rested than I started so about that about that long oh yeah so most most of the times main oh yeah uh the attack surface of a mainframe is mostly it mostly be if someone already had access to the like the internal network it's not really shared like there there is some but not many and a lot of most of the attacks I've found are LPEs so you need to have a user on the system to be able to escalate your privileges but I do have a couple RCEs so the um no I lost it oh yeah the um like oh the other the other thing about LPEs is that you think of a typical like Linux box and you think I don't know how many users there would actually be but on a typical mainframe box there could be like thousands of users so an LPE is a little bit more I'd say critical on a ZOS box than a Linux box