How CPUs Access Hardware - Another SerenityOS Exploit

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
In a previous video we looked at  a kernel exploit in SerenityOS,   which was the intended solution for the wisdom2  challenge from the hxp CTF. But of course   SerentiyOS is a big software project, so there  might have been more security issues. And in this   video I want to look at another solution for this  challenge. So basically another kernel exploit.  Actually our team ALLES! was the only team  that solved this challenge during the CTF. But   as always I have to mention, this was not me.  This team is insane, and I did not contribute   to this success. It just blows my mind how skilled  the ALLES! members are. Especially when you know   how young they are. In this case it was incredible  Linus Henze again solving it alone. Feelsbadman. ANYWAY! It’s crazy to think back at how much  I knew over 5 years ago when I started with   my binary exploitation course. At the time  I felt I already had some nice experience.   But you should always keep  in mind that over all those   years since then, I kept learning more stuff  and accumulated more knowledge and skills.   And I think that’s important for you to  remember, you can also learn all that stuff,   it just takes time. Literally years. And so in  this video I want to share with you one new thing   I learned. I just learned something new from  Linus’s SerenityOS exploit. And it makes me   really excited, because it’s a thing that I never  really understood, and it finally clicked for me. Like with the previous exploit, Andreas Kling,   the developer of SerenityOS, made a whole  video going over the writeup of the exploit,   explaining the vulnerability, looking at  the kernel source code and fixing the issue.   Because that video is already very technical and  detailed, I thought I will try to have a bit of a   different angle with my video - I want to focus  on my eureka moment instead. So let’s head in. Sometimes when I think about how computers work,  I get this feeling of “it’s kinda simple”. I can   see how complex code is compiled to very simple  instructions, which then get executed by a CPU.   Also maybe you know that I have spent many hours  working on Ben Eater’s 8 bit computer - and when   you develop each part of a CPU by hand, you  really start to feel like you understand it.  I have also played a lot with very  simple electronics like Arduinos,   which have an Atmega AVR microprocessor. Or  maybe you remember my series about the research   into the hardware wallet, which included a  lot of details about ARM microprocessors. But the thing is: on one side I understand how   higher level C programs can be compiled  to machine code, and how they run.   I understand how an ELF binary is  loaded into memory and then executed.   I know my way around reverse engineering  them, I know how debug them and so forth. And on the other side I understand very low level  code running on a microprocessor. For example   this basic Arduino code where I can simply set a  PIN to HIGH or LOW. And that makes an LED blink.   I understand that. I understand that there are  transistors that decode some instruction that will   then lead to a toggle of wire that goes to the  outside. To this pin. Thus turning this on or off. But there is something in the middle of those two  worlds that I have very little experience with.   And as soon as I think about this middle  part, I suddenly get this feeling that I   actually DO NOT UNDERSTAND A SINGLE THING  ABOUT COMPUTERS. And this thing in the middle   is basically the world where kernel code lies.  It’s what makes modern CPUs like an Intel CPU   so much more complex than a microprocessor. What  I’m basically referring to are the privilege   levels. Ring 0,1,2,3. You probably  have heard those terms before. But of course I have some knowledge  about operating system kernels,   so what do I already know? I have actually made  a few videos where I explained some stuff about   the Linux kernel. For example in the binary  exploitation playlist I introduced syscalls.   I have made videos about docker containers, and  the linux kernel namespace feature that enables   that. In those videos we also looked at kernel  source code to better understand how that works.   I also made a video about the Linux Device Drivers  book, which was a huge “AHA!” moment for me when I   understood some stuff about it.  And in the last SerenityOS video   you can also see how I feel comfortable reading  the syscall source code of the Serenity kernel. But all those topics that I have prior  experience with, have one thing in common.   They are very close to the userland. It  is kernel code, it is high privilege code,   and there are some quirks to that,  but it still feels and looks like   relatively “simple” code. There is  nothing really hardware specific in   those areas. And that’s the crux of the matter. I’m missing the link between this software world   and the hardware world. How exactly does Linux  talk to a hardrive. How exactly do key presses   on this physical thing end  up as the input to a program? When you look at very simple  microcontrollers, like looking at an arduino   or ben eater’s 8 bit computer that I was building,  I can understand how this microprocessor can   “talk” to hardware. I can understand that  there is a wire going out of this chip,   driven by transistor logic, and it can  “talk to this LED” to turn it on or off. But I don’t understand how a modern Intel  CPU is connected to the physical world.   How do drivers talk to a physical harddrive? And this is where we come  back to Linus’s SerenityOS   exploit. It’s a really creative exploit,  in some ways a suuuper simple exploit,   but it offered me an opportunity  to learn about this missing link. So let’s quickly hear how Andreas summarises  the kernel vulnerability abused in this exploit. The vulnerability they found is  quite interesting. But basically   they discovered another flaw  in our ptrace implementation. Quick reminder, ptrace is a syscall,  so a kernel feature, that allows   one process to debug another process.  Read write the other processes memory.   Single step execution. Change registers.  So the kind of stuff gdb implements. And the issue is, that we just accept the e-flags  coming from one process to the other. That makes   it possible to overwrite certain CPU flags that  you really shouldn’t let user programs change.   Like the I/O privilege register for example.  And that’s how they exploit it. So they change   the IOPL. The IO Privilege Level. And elevate  the IO Privilege of their own process, making   it possible for it to talk to hardware. And then  once they have hardware access they can talk to   the harddisk and extract information that way.  And that’s actually what their exploit does.   So the exploit contains a small IDE harddisk  driver. Very small. Just reads one sector. And you know the typical objective  of a CTF. read a flag from a file.   And the wisdom2 challenge actually had a second  harddrive connected, that contained this flag.   So if your program can directly talk to  hardware, they can just directly ask the   harddrive to give them data started on it. And  you completely bypass any of the permission   checks that the operating system might have  implemented in the kernel. Theoretically you   could also directly talk to the main drive,  overwrite any setuid binary, and then you can   easily get root. But here it was enough  just to read some data from the harddrive. What they do is, they attach with ptrace to a  child, then they get the registers of the remote   process. And then they write back the registers,  after modifying the flags, setting the IOPL to 3.  Which means that Ring3 is allowed  to have IO access I suppose. So what are the eflags? Let’s fire up gdb run  a basic program like /bin/ls, let the program   single step run for a bit. And then let’s  look at the info registers output. Here are   all the registers of the process we are debugging. Under the hood, GDB used ptrace to ask the kernel,   “please give me the registers of this process”.  And then gdb displayed it here. So here is the   stack pointer. Here is the instruction pointer  that points to the next code being executed.   You have other general purpose registers. So  these are just small memory cells in the CPU   that your code can use to calculate stuff. And  if you followed my binary exploitation playlist,   you are pretty familiar with those. But what’s up with the registers down here? They are a bit weird registers,  and for your regular program you   usually don’t deal with them. They sometimes  differ between operating systems how they are   used. And they are actually another thing I don’t  fully understand myself yet. But I want to focus   for now on the eflags. And the eflags are a  bit like internal CPU “housekeeping” flags.   Some of them you might be familiar with. For  example ZF. It’s the ZERO FLAG. “Set by most   instructions if the result of an operation is  binary zero.”. That’s basically how if-cases   are implemented in assembly. Let’s say  you want to jump if two values are equal.   Well jump equal has no additional operand  parameters which values to compare. Instead   the actual implementation of that instruction is  to jump IF THE ZERO FLAG is set. And before that   jump-equal instruction, you usually have a compare  instruction. And here is what compare does. “The comparison is performed by subtracting  the second operand from the first operand  and then setting the status flags in  the same manner as the SUB instruction” If you subtract the same value,   the result is zero. If the values differ,  the result is not zero. So if it’s zero,   the zero flag is set in the eflags. And then  Jump equal can check if the zero flag was set. As you can see you might not directly set them  yourself. It’s like CPU housekeeping stuff,   where the CPU wants to remember something. In  this case the result of a compare or subtraction.   But there are more flags, and I want  to focus on an eflag I didn’t know   existed. And it’s the IOPL flags. It’s two  bits. Input/Output privilege level flags. The IOPL (I/O Privilege level) flag is a  flag found on all IA-32 compatible x86 CPUs.   It occupies bits 12 and 13 in the FLAGS register.  And it’s used, in order for the  task or program to access I/O ports.  But it’s important, the IOPL can only be changed  when the current privilege level is Ring 0. So what are IO ports? Memory-mapped I/O (MMIO) and port-mapped  I/O (PMIO) are two complementary methods of   performing input/output (I/O) between the  CPU and peripheral devices in a computer. So IO Ports are the secret how intel CPUs  access hardware. I am familiar with memory   mapped IO from microprocessors. Again, I’d like  to reference my hardware wallet series, where I   explain memory mapped IO on an ARM processor. But ARM and INTEL are different processor   architectures, so they handle hardware  differently. And I this concept of PORTS   was weird to me. Maybe it’s the name and when  I hear “ports” think of network ports. But   that’s of course something COMPLETLY different. But this is where the Serenity exploit comes into   play. As you now know, there was a vulnerability  in the Serenity kernel ptrace handler for setting   registers in another process. This exploit simply  set the 12th and 13th bit, and write the changed   eflags with ptrace back to the other process. As you know, gdb uses ptrace to debug other   processes, so we could try the same on Linux.  As you can see, the zero flag is currently   not set. But with set $eflags and 0x40, we  can set the 7th bit, which is the zero flag.   And when we now check the registers, we can  see the zero flag is set. We could also just   try to set all flags to zero. Let’s do that. But when you check the registers, you see that   apparently IF is still set. IF, Interrupt Enable Flag,   is also a privileged flag you are not supposed  to change. A user program cannot just disable   all interrupts on the system. Now we could also  try to set the IOPL flags like the exploit did.   0x3000. But as you can see, it didn’t do anything. Of course, we shouldn’t be allowed to change   those flags. But because serenity OS didn’t  account for this, and the kernel runs in ring0,   this code can set the flags for us, when we call  PTRACE Set Registers with the modified eflags. Okay. So now we have the IOPL flags set.  Apparently we are now allowed to talk to   hardware via I/O ports. And as Andreas said,  this exploit implements a very basic IDE   harddisk driver which reads out a sector from  the attached drive. And when you look into this   code, specifically the inline assembly, you  can find here port_byte_in, and port_byte_out   using the in and out assembly instruction. And  this assembly instruction takes a port number.   The in instruction reads a byte from a port. And  the out instruction writes a byte to that port. And before we continue with this code, I want  to quickly jump back to my super simple Arduino   blinking lights. Because when you write arduino  C code, you use functions like digitalWrite,   to write to a pin. But under the hood, in actual  AVR assembly, there is an instruction to set   a bit in a port. And there are also IN and OUT  instructions. These instructions also write or   read a byte to or from a port. Here is another  blinking example using inline assembly with the   OUT instruction, writing 0 or 1111s as a byte to  the port number 5. And the port number references   these entire 8 pins on the arduino. 8 pins, 8  bit, 1 byte. Another port number corresponds to   other 8 bits of pins. The AVR architecture of the  arduino, and Intel architecture of a modern CPUs,   are of course COMPLETLY different. But  they both have a concept of I/O Ports. And so the exploit here, uses the OUT instruction,  to write to specific ports. And the port number we   are using is based on this base 0x1f0. And when we  look up a list of the strictly defined x86 intel   ports, we can see that the ports 0x1f0 to 0x1f7,  are connected to The primary harddisk controller.   You can imagine this as if a harddisk  is connected to those arduino pins. Isn’t this incredible? When I saw this list I also   saw those ports at 0x60 dealing with ps/2  controller. So keyboards and mice connected   to that oldschool ps/2 plug. And I found  this awesome repository by cirosantilli,   x86 bare metal examples. And here in the ps2  keyboard assembly file, you can see that it reads   a byte from the port 0x60. So it reads whatever  key you pressed on your connected ps2 keyboard. And this was a eureka moment for me. I  realized that a x86 CPU is not so much   different from an arduino microcontroller. Both  need somehow instructions to write and read bits   or bytes from wires connected to their chips.  The difference is only that modern desktop CPUs   have privilege level features,  so that only kernel code in ring0   can use instructions like IN and OUT. And the  IOPL flags are not set for ring3 processes,   so they can’t directly talk to hardware.  That’s why they need to ask the kernel to   read data from a harddisk using syscalls like  read() and write() instead. And the kernel can   then check permissions like file permissions,  to make sure you are allowed to read or not. Really really cool. I think now I  understand the link I was missing.
Info
Channel: LiveOverflow
Views: 74,787
Rating: undefined out of 5
Keywords: Live Overflow, liveoverflow, hacking tutorial, how to hack, exploit tutorial, serenityOS, privilege escalation, privesc, in out, arduino, mmio, port mapped io, i/o, input output, hardware access, peripherals, x86, avr, ports, bus
Id: 1hpqiWKFGQs
Channel Id: undefined
Length: 16min 57sec (1017 seconds)
Published: Sun Feb 14 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.