← All talks

How To Pwn A Zune

BSides Newcastle · 202526:2721 viewsPublished 2025-12Watch on YouTube ↗
Speakers
Tags
About this talk
Callum Thomson demonstrates practical attacks against Microsoft's Zune media players, detailing kernel-level vulnerabilities in the Windows CE operating system that runs them. The talk covers sandbox escapes through XNA framework bugs, driver-based privilege escalation, and browser exploits, showing how to achieve full device compromise for digital preservation purposes.
Show transcript [en]

Um, so hi. Um, I'm going to be telling you about Zooms for a bit. [clears throat] Um, so my name's Call, but I also go by cooped online. So you might see that referenced on here. Uh, first things first, I will go through who I am, what a Zoom is, and then we're going to hack some Z and hopefully take some questions. Should be nice. Um, so I'm Callum. I'm a leader researcher. Uh, I'm known for hacking the iPod now. See my talk at B exit from earlier this year. Um, and that's my email if you want to get in touch after. Uh, so first, what is a Zoom? Well, here's one as an example. I actually props. Um,

it's an MP3 player. Uh, Microsoft released them from about 2006 to about 2011. Um they made three different versions of them, but really there's only two. Um we'll get into that in a second. Famously, they lost to the iPod. Um basically nobody had one, especially not uh over here in the UK. They're incredibly hard to get. All of mine I've had to import from Ireland, which was not exactly cheap. Um but let's have a look at some zooms. So, this was the first Doom D 30. Uh, came out in November 2006. Uh, it has 30 gigs of storage. It's actually got a spin hard drive in here. Um, has Wi-Fi, but no Bluetooth. No one

had AirPods back then. Um, funny thing is they wanted it to look a bit like an iPod, so they put that wheel at the bottom. That's not a wheel. That's a D-pad. They just want it to look like iPod. Um, it has a Frecale IMX 31L CPU which has a staggering 64 megs of RAM. Um, and famously came in brown. I don't know why they decided to do that. Um, and then they did update uh increasing the capacity to 80 or 120 gigs, made the screen it bigger. Um, this came out in November 2007. Um, they also then followed that up with the 4,8 gig portals that have uh flash storage so they don't weigh a ton and have better

battery life. Um, and they replaced that pad with a proper trackpad uh which is a lot easier to use. Um, internally exactly the same as the Zoom 30. So, uh, we group kind of all of those together as Zoom SD generation. Um, and again they still didn't put Bluetooth in them. Then they brought out Zoom HD a few years later in 2009. They came up to 64 gigs of storage. Still didn't have Bluetooth. Uh, and have Nvidia socks in them for some reason. It's actually the second generation ever. Nvidia Tegra. Um, and that screen is pretty cool. It's a multi-touch uh OLED touchcreen which back in 2009 was really rare. Um this has kind of led into Microsoft's Windows

phone era if anybody remembers that. Um I specifically didn't talk about the software though. Um because obviously Microsoft released these it had to run Windows CE [laughter] which is really really weird OS. doesn't come up very often. Um, if you went to the operational technology talk, uh, some of their kit is probably running Windows C that they look at, but it's, uh, compact embedded. It was designed as a heavily embedded version Windows specifically for use in like embedded industrial medical applications. Uh, so it's still used in place like that. And point of view terminals, if you've paid for something ever, you've probably interacted with Windows CE. Um, it does have some shared link with Windows. Um, from talking to

some people who've got more knowledge about this than I do, um, they think it may have came from a fork of Windows 95 or 98. Um, but the, uh, kernel is very different to modern Windows internally. Um, the Zoom S is something between version five and six. It's kind of a weird uh midpoint uh where it doesn't sort of line up with either. Um and then the Zoom HD is on standard kind of 6.0. Um so we can add some apps. This is app stuff. Um you can write apps uh even if you're not uh have been authorized by Microsoft. It has like dev apps and they're written in C uh using the XNA framework which is a gamedev framework.

um they're sandboxed so you can't run any kind of natives, you can't run AC or anything fancy like that. Um the release apps that were available on the store, uh some of them for free, some paid are encrypted to protect kind of the IP in those apps. Um and if you uh run a dev app on one of these devices, when you quit that dev signed app, it will reboot the device. Um because when you go load a dev app, it actually deletes some crypto that's used to those apps to prevent you from using a dev app to leak these uh possibly paid apps. Um to try and get it back to a clean state. Um and

this is the main kind of target for what I'm trying to get is I want these apps out for preservation purposes. Um because you can't really buy as soon anymore and there's a bit of history back there. Um, another uh funny is that rebooting uh method was actually done by just adding a registry key. So you kind of delete that registry key um from your app and it will let you quit it again but you [snorts] can't open normal apps anymore. Um now in 2010 somebody called it's not a big truck found uh a bug um in the XNA framework that allows you to actually escape this sandbox to get code running. Um, in essence, XNA or C more specifically

allows you to get pointers to variables through some unsafe APIs, but they've blocked access to read or read to them from the C# runtime. Um, there are SDK functions that don't have those [clears throat] uh limitations and there's even one that is just wrapper around the standard M copy. Um, so if you put something on the stack, you can essentially cause a stack overflow and we don't expect you to ever be able to do this. So you can get code execution through that. Um, with this tech, you can run kind of native code on at least as soon as that this works on. um security wise they ASLR on the to randomize the location of structures in

memory but they didn't randomize the location of any of the code and that'll be relevant later um they split user base and kernel to prevent you from accessing uh privileged data code and they did actually employ everywhere except the uh XNA framework cookies uh stack cookies to protect against flows Um, and they do have nonexecute permission checked as well, so you can't execute code unless you uh mark it as execute explicitly. As a weird side effect though, the kernel can patch itself and has ax perms, but only when you're coming from the kernel, which is kind of useful later. Um, the Zoom HD and and the SD uh have a lot of third party vendor supply drivers. This was

the first area I started looking at when I wanted to kind of own these devices. Um on C on Windows C5, these drivers are always random user space, but because of this weird 5.0 uh 5.5 version running on the SD, they've actually moved those drivers into the kernel. Um, so this protection of running all the drivers who use doesn't exist. Um there's uh you can identify these because they've got um a K prefix essentially on the files which tells you they run in kernel space. Um and that's kind of when you where to start looking. Um Microsoft did write some pretty good docs on how not to put bugs in your drivers. Um they've [clears throat] got

quite a lot of documents like this. I'll highlight device drivers should not unexpectedly crush or unexpectedly elevate privilege which is a hint. Um but they also this interesting point which is that you should check access permissions on nested pointers in io calls. Um so when you open a file on the wind32 API you can do something called an IO control which is an operation on these files that get handled based on some command ID. Um, and the arguments that you provide, the input and output data is checked to make sure you have permission to read and write to that data and you're not trying to do anything you shouldn't. But if those structures uh that are pointed to then

point to other structures, um, there's no validation on that because it's not aware. It's an opaque memory block. Um, so you have to explicitly do those checks. This is what they're highlighting here is that you need to use those kind of extra check functions before you use them. Do you think Microsoft can follow their own rules? No, they can't. Um, so they actually got it wrong in the same driver twice, which is very impressive. Um, so this is our first bug. Um, it has a hardware accelerated uh Wav file decoder in the kernel for some reason. Um, and this is kind of the entry point to that uh that [clears throat] I control handler. Um,

and there's this command ID of 1D control C. Um, where it then calls through to this function and normally this from user space in both. We don't need to do any checks on this cuz uh the OS will check that for us. So this is all safe up until this point. Um then it comes to this function where it pulls out some fields uh from that safe pointer. Again, there's nothing wrong with this code. There's no bug here. Um this is completely safe as long as these aren't pointers. The problem is it uses them as pointers. Um so the the actually doesn't come in until the second case here. uh this first one simply takes a value that

we've given it treats it as an op value and stores it as a member or some structure. Um but when we send this command after it will actually take a value that we provided that isn't checked and treat it as a privileged kernel pointer and we'll copy that written value out again. This is essentially the read uh version of this other operation above. Um so what we can do with this by doing these two balls um is store a value somewhere and then write to anywhere we want in memory. Uh which the code to do that looks a bit like this. We open the file which just requires some read permissions uh which we have. Um we send

this command which will save that value. Um and then send this command which will write that value out to any address of our choice. Um and it gives us uh this kind of write four a four by value we can write um it also does some other corruption that I'm not including for time. Um, thankfully because of this weird uh weird 5.5 uh version of WindC, um it has a uh config file that is to find people that build the OS that can essentially turn off the kernel user mode separation. I think for performance reasons, um that file should be read only and only read once, but we can write anything we want. So we can turn

it off um or turn it on. Um so by simply flipping that to on and restarting our program, it will now run in kernel mode and we have uh coupe access to the device. Um works on every Zun SD uh on every uh Zun SDA version as well which is quite nice and you can even do it purely from C. You don't need the C uh the C++ C. Um, but we do it by C for uh reasons. Um, and we can use the same bug to clean up that kind of corruption. I mentioned uh getting us to kind of a clean state where we've only flipped that one flag. Um, we can move on to

Zoom HD now. Uh, now that we've done the early one, um, you'd think it will be harder. The problem is, uh, this might look familiar. It's the same handler in a different driver. This one's written by Nvidia rather than being written by scale. Um, but [clears throat] has essentially the same code. Control flow comes into here. This one's actually nicer um because when we send in command 13, it just writes to a fixed offset from a pointer that we control um a value that we control which is very nice. Uh we don't even need two calls. Uh so this is yet in the right fold. There is a limitation on this. Um we can only do this once um due to some other

uh other checks. Uh but we only need to do it once. Uh the export looks like that. Open the file. Send the command. Um this doesn't work in the Zoom SD only works in Zoom HD because Zoom uh HD [snorts] is the only one in Nvidia sock. Um the question then comes of where do we write with this single use right? Um, but the one has this very nice table it maintains where it tracks the types of arguments to system calls. And the reason it has this is to kind of check if those are sensible values when they're coming in. So if you pass uh something marked as a cable or kernel pointer um it will check that it's

actually a kernel bunt and not just something that you've provided. Um, it also has whole fork just an int type that doesn't get any checks. And thankfully int is type zero. So if we can write a zero into this table to anything that returns into a pointer, we can allow it to write to any uh any pointer including kernel memory. um which uh there's a lovely function called get exit code thread which simply takes the return value which you can set uh if you want a thread and puts it at a point. So that's the one we we use for this. Uh so the code looks like this. You create a thread uh passing in a value

that val gets passed in as x into that function. We then call exit thread and just essentially return that f value um and then call get exit code thread but can pass kernel addresses to write to those kernel addresses. This gives us a right for that we can multiple times but we're still limited to only writing and we'll be writing four bytes at a time uh which is a little bit annoying. So we can use that to put some code that gives us a single write read and write function and just find one of the unused sys calls that normal apps aren't supposed to be able to use and uh overwrite it with uh this code. Um so

this gives us control of the kernel of both the SD and the Zoom HD. Now the question is could we use this to apps? It would be really nice if we could find apps that use native code. Um, so we can untether um this these bugs from having to go through. Sadly, it doesn't work. They're all written in the shop. Um, and as I mentioned, they're encrypted. Maybe we can dump them by just launching them and reading their files off from memory. The problem is this hardware crypto engine essentially the keys are stored in this hardware unit and when you load evap those keys are nuked from memory and you have to restart the device to

get them back which requires going through a fully secure boot chain to get all the way to the OS. So we need a different way in. Thankfully browsers are hard. Browsers are really hard. browsers are buggy as crap to be honest. And the Zoom HD has a browser and it's based on Internet Explorer 6, which I mean at the time to be fair that was not as bad, but [snorts] it also has a lot of CVEs, but we don't have to find our own. Um, this is a CV that actually found in the wild by project zero, I believe. Um, essentially the goal here is there's this arguments variable in JavaScript that when you call function contains all the arguments

to your function as an array. Um, but you're not really supposed to write it. It's supposed to be read only. And if you write an object to it, it will remember that object, but it doesn't tell the garbage collector in JavaScript that that value is referenced there. Um, and this is kind of a problem because the garbage collector is going to come around and look at that object and not think anything points to it when something does. Uh, and it's going to delete it. And that creates a condition that's called a use after free. Um, and use after free are very very nice when it comes to generating exploits like this. Um, so we can use this to build a really

uh really powerful read primitive. And essentially we have strings that consist of a tag and a pointer uh in memory and then that pointer points to uh for a string points to a large number and then it looks after that number that many bytes and that's the string data. So if we simply find a large number which we can do that by looking at the code which isn't protected by ASLR um we can create a object store an argument it will then get deleted create lots of variables and hope that one of those variables that we create will overlap that now deleted object um and then try and use that arguments value afterwards and hopefully it will pick up the value

that we placed in memory and start using the pointer that we give it as a string. And because we can create a very very large string, we can create a string that covers 4 gigs of memory. We only have 64 megs, so we cover all of RAM. Um, which is very very nice. Uh, we can't read things that aren't paged in and there's some other limitations, but it doesn't really matter. This is kind of the perfect read primitive. um building on top of that we uh can kind of start to leak pointers to structures. So this is a bit complicated but uh there is thread local storage. Uh the browser uh the browser JavaScript engine is multi-threaded. So

it stores the kind of root class that handles all the garbage collection in thread local storage uh which [clears throat] is at a spec place. So we can go find that. We then find this garbage collector. Uh we go through that garbage collector uh and has these structures called scavengers. And scavengers uh track variables. Essentially they contain all the variables that are being monitored. Um and there's a specific type of scavenger called a scaral list which tracks an array of variables. Um so if we go through this list and we find a scalar list which contains magic values that we can put in a list um we can then find the pointer for an arbitrary object uh

in JavaScript which is very very powerful. Um the main trick here is at the bottom of creating a sort call back um which is uh [clears throat] how you sort a list in JavaScript um because it will take the list that it's given and create a dedicated scar list to track that list so that it's not deleted while it's running function that does the comparison operation in the uh in the loop. Um so with this we can create we can we can place objects in memory get their pointers and use those to build structures that reference other structures in memory. Um this kind of primitive generic primitive being able to get pointer to a JavaScript object

often called address of or a [sighs] dur. Um if you've read other JavaScript bugs it comes up a lot. Um so now we can actually get some code execution because we can create a relatively convoluted stack of objects by uh creating a fake pointer table uh having a native object that points to that having a variable that points to that. um and reusing the use after free from before we can spray that fake variable. And hopefully if we try to do operations on that um it will have overlaid our fake variable over the deleted variable um and if you try to use the type of function in JS the way that works is if it's a known object type like a string

which is a from before it will uh [clears throat] simply return the string string but if you have a native object which is a special object used to define JavaScript classes that are implemented as part of the JavaScript runtime. So things like reg x or the string class itself. Um it will call a pointer on that class uh to ask it to describe itself. Um and we can abuse that by making that fake v table we create making the uh type of function on that v table point do anything we want. So we can control the program counter making it jump to something uh that we control. Uh it's worth mentioning have to do this

kind of backwards the order it is here because you need to create the fake v table to get pointer to then put it into the native object etc. Um finally we can do a bit of rop. So ro is return orientated programming. Um and essentially this is we build a fake stack in memory uh and have it execute as if it's returning from a very deep whole stack where it will return we'll do some operations and return up the stack uh to the next uh function which we will make point to something interesting. Uh so I can use this gadget uh which is essentially loads all the registers uh which is the perfect gadget. Um we will set our stack up such

that PC point to this virtual protect function which is uh the m protect function of windows. We can change the functions of memory to make executable. This is how we bypass the uh no execute mentioned earlier. Um we give it some code which we will uh simply create a big string with some code in it. Um and get its pointer using the address of primitive. Um we can set uh that one is one by which will be rounded up to the minimum size which in this case is I think one page which is I think 4,96 bytes. Um and then set it to be rewrite and executable which is kind of the key thing here. And then because we also

control the link register here uh which is the return address uh [clears throat] register used to always store the return address from a function call we can set that to the code that is just protected. So in effect virtual protect will think it was called from the code it just protected and will return into that code uh and start executing it and from that we can run whatever code we want. For example, uh we could place a file uh like an .exe file onto the uh on the storage and simply load that file. Because that exe hasn't been loaded through XNA, the keys are still intact. Um and we can dump the apps that we want

to dump. So, I have a demo and hopefully the video works if I can find those. There it is.

Give it a second. And there we go. Um, so you can see there at the top it says leak leak work. That's it string. And then these pointers it's printing out where it prints various addresses. This is it dumping the addresses of those structures as it's building them in memory. Um, and then right at the bottom, which you can't see under the notification there, it's saying that it's going to try execute something and it was running random FTP server test. Um, and then if we out of this, open up app, we can use that FTP server to dump the contents of that app. And now we have a full backup of all the apps from uh the Zoom 80, at least all

the apps that I find. Um, In conclusion, zoom mode. [applause]