UnbanMii 2.0 technical writeup

Please read the whole post very carefully before even deciding to comment! This situation is already bad enough, there's no need to make it worse by not reading it fully. Thanks!


As you may have noticed, there's a big fuss around the new UnbanMii 2.0 release due to how the backend was implemented. In this post - as the author of the backend - I'll explain why was the backend implemented this way, and I'll talk about some shitty design decisions I have made.
As not everyone is competent reading code, I'll make an explaination with technical details, and one with "dumbed down" details so non-programmer people can also understand it.

I pastebin'd the modified API.php: https://pastebin.com/4pzGrbjW
(Note: the only modification I have done is I have removed the username and the password, the rest of the script is the same as the one that was used in production)
(another note: I just noticed that someone has touched the script while I was sleeping, and has changed the capitalization on the die() error messages... the rest of the script seems to be still untouched however)

The POST data (size=0x380) contained the following stuff:
- offs=0x000, size=0x110: LocalFriendCodeSeed
- offs=0x110, size=0x140: movable.sed
- offs=0x250, size=0x111: SecureInfo
- offs=0x370, size=0x010: NAND CID


so, let's start breaking down the code...

everything 'till line 15 is boring, it's just checking the 'e' parameter for valid values, loading the POST data into memory, and checking if it has a valid size

on line16 I convert the data into uppercase hexadecimal because I have a bad experience with storing binary data in a MySQL database, and other than having to multiply every value by 2, it's easier to work with the data this way

in the line 18-39 block I check if the seed is somewhat valid at least (according to 3dbrew the offs=0x100, size=8 region in the seed file is all zero on retail units, and checking it in GM9's hex editor proves that)
but, here is the first occurrance where I check data which is considered sketchy... when I discovered on 3dbrew that movable.sed contains a copy of FriendCodeSeed, I wanted to also add movable.sed to the uploaded data buffer, and I did...
so, on lines 36-38 I check if the FriendCodeSeed matches the one present in movable.sed, and error out if it doesn't match (because I didn't wanted people uploading public seed by chance if they have tampered with their LocalFriendCodeSeed)...
but when I tested on my new3DS I noticed it was always triggering, I went to GM9 to check both files, and I noticed that the LocalFriendCodeSeed didn't match the one in movable.sed. later I remembered that I systransferred my old3DS to my new3DS, but I backed up the NAND before... so I mounted my decrypted old3DS CTRNAND with ImDisk, opened up movable.sed and LocalFriendCodeSeed, and noticed not only did they match, but it was the same that was in my new3DS's movable.sed, so I came to a conclusion that LocalFriendCodeSeed is always the factory one if you don't touch it.
so ye, because of this I commented out the code in the backend, but never bothered to remove the code from the 3DS client (which was a very big fault)

moving on, on line 41-42 I check if the movable.sed has a valid magic 'SEED' (again, completely unnecessary as I no longer had the need to check movable.sed, and this is the only remaining check of movable.sed in the backend code)

lines 46-54 are cheesy... intially I wanted to use the error "Not a 3DS", but I thought that if a possible hacker/bully/whatever reached here then I thought I'd troll them a bit by faking a database error by changing to username and password to a dummy one (the only reason I used an empty password is just to make the hacker/bully/whatever even angrier that "they aren't using a password for their database?! >_>", but otherwise there was a password needed to connect, so a bit less security risk there)

I won't say line numbers anymore since the code is very dense, I'll just explain how the code works from now:
so, the database had approximately this layout (I'm saying "approximately" since the server is shut down the time of writing, so I can't screenshot the database structure):
- UID, AUTO_INCREMENT
- NANDCID, varchar(32),
this was the UNIQUE index (though was not applied in the database, so I had to handle it in code instead)
- datetime, unsigned int, just the time() of the user first using the service
- RawPOST, varchar(1730 iirc), this contained the 0x361 * 2 big data sent in POST (it was stripped of NANDCID because that's used for indexing anyways, so it was pointless to keep multiple copies of it)
- flags, unsigned int: bitmask of these values: 1="seed available for download", 2="the user is banned from using the service", 4="seed is prohibited to be made available for download"

as you can see, no matter what all this data was stored in the db before even continuing to execute the rest of the script

as you can see on line 79, there was a planned feature in UnbanMii 2.1 to restore your original seed. as you can see, even if you are banned you would've had the chance to restore your original seed in case you wanted to revert your original friendcodeseed if you called Ninty and they unbanned you for some miraculous reason. anyways, since this functionality was not fully implemented by the time this incident happened, there's no code yet to delete the record from the db after the user has successfully restored their original FriendCodeSeed.
on line 82 we can see the only use of SecureInfo: all I did is I compared the one in the POST data with the one stored in db to see if the user has region changed since the first use, and disallow them to upload or download seed to reduce the risk of the public seed getting banned due to that... otherwise it had no use

after this we check if the user wants to set their seed as public, and if they do then be paranoid before setting the bitmask to allow the download of the seed...
so:
- first I get the first available seed (though there's a potential oversight because I don't order the results, so it might glitch out if something)
- I accidently check twice if there are multiple seeds (which is a very big copypaste fail, considering I have used "LIMIT 1"... oh well, I'm not surprised, considering the approx. 19 errors/oversights fixed in ~1-2hrs)
line 93 is interesting... the magic zero NANDCID would've been used for a future website upload (since how would you get the NANDCID without an actual 3DS?), otherwise check if you're the first one to use UnbanMii with this seed (assuming the first person to use it owns that seed), and disallow you from marking it as publicly available if it's not yours
the rest in this if-block is self-explainatory

since the restore feature is not fully implemented all that's left is serving the public seed after all this crazy and retard security checks... it's really easy, just query the first seed flagged public (bitmask 1), check if there's even any, and just serve it (the data is stored as a hexadecimal string in the database, so it has to be hex2bin'd before serving it)
Note: this section doesn't explain stuff in too much detail, so if you're not satisfied with the info here then all I can say is "sorry, but this was a design choice"

so, I thought it's a good idea to use very touchy data from the user'd NAND (namely LocalFriendCodeSeed, movable.sed, and SecureInfo) for verifying the user, and verify if the data is valid or hand-crafted by a troll without having access to a 3DS to verify the integrity of the data

first, I check some things in the seed file that we can check without a 3DS, and fail if those basic checks fail (namely to see if the seed is corrupted from the start, or of the seed is from a dev 3DS (due to how security checks are implemented, dev 3DS friendcodeseeds are not supported))

earlier in development I noticed on 3dbrew (where a lot of stuff about the 3DS is documented) that there's a copy of the LocalFriendCodeSeed in the file named movable.sed... this was working and good, but then later I noticed that my new3DS triggers the "Upload of non-factory friendcodeseed is not allowed" error... after digging into it I realized that I did a system transfer from my old3DS to my new3DS, so I went to check the decrypted CTRNAND of my old3DS, and the data was matching (no wonder why it's *movable*.sed), so I commented out the code without removing the need for this file
but since I was already checking movable.sed, I added a check to see if the movable.sed magic word "SEED" is correct, otherwise fail. this is the only remaining check of movable.sed in the backend code, and is completely pointless

just before connecting I added a "troll" to troll the hackers who would try to spam the API from not a 3DS... if the User Agent (the "browser identifier" name field) didn't match the one the UnbanMii application was using it would change the database login details to fake ones, so the script would fail with a faked database error (note: the password in the fake entry is empty to make the hacker/troll/whatever even more angrier for thinking that we aren't password-protecting the database. the real database was password-protected at least, so that's a bit less security concern)

after this I used the NAND chip's ID as the way to identify unique users (I have never heard of someone replacing the NAND on their 3DS boards, so I assumed it's a good way to identify users, even though this is another touchy data)

due to the shitty design choices I made while making the backend, the first thing was to insert all of this data into the database for later use and security comparison. at this point the inserted data is marked as private, meaning nobody has access to it from the API

here it gets complicated, but I'll try my best to explain without making confusion... so, let's continue

I thought that since I'm storing all this data in the database, I could add a restore functionality to UnbanMii 2.1, but I didn't implement it fully, so there's no code yet to delete the data stored in the db after a successful restore.

if the user is not wanting to restore their original seed, immediately it's checked if the user has been banned from UnbanMii, and since SecureInfo is uploaded (which contains your region and your 3DS serial number) it's easy to check if the 3DS has been region changed, and prevent the user from using UnbanMii until they undo the region change to prevent the ban of the public seed due to region change (yes, I know you must be frightened out by me saying that, and yes, I know I'm stupid for doing that, but when I made the code my mind wasn't clear, so I didn't double-think about it at all, I was concentrating on adding the most security checks possible instead for some reason)

this section only applies if you wanted to upload your seed:
- first I query the database for the first seed which is equal to yours trying to upload
- then I check if the NAND ID matches yours (or if it was uploaded from a planned web interface)... if it's from a website upload or you're the first one to use UnbanMii with the seed you want to upload, the seed will be marked public, otherwise the "this seed is not owned by you" error is thrown
- after querying the seed, I check the flags on it to see if the seed's owner or the seed itself is banned from being made public, and if that's the case then the "this seed is banned from this service" error is returned
- if there was no error then I update the seed in the db to make it public
- later in the script it'll die with the error message "thank you for your contribution"

since restoring is not implemented fully in the script, if you were to hax UnbanMii to have the restore feature, the server would die with the error message "programming error, this should never happen!"... funny, eh?

since all security checks were passed, all that's left is to query the latest public seed from the database, and serve it... the rest is handled by the UnbanMii 3DS client

more info that's important regardless of being technical or not:
- I had no malicious intentions *at all*... the reason for uploading such touchy files was purely for very shitty security checks, and the touchy data (movable.sed and SecureInfo) was purely used by the script to check stuff
- some people let me know that the NAND CID (or as mentioned in the non-technical writeup: "NAND chip's ID") is also a touchy data... since I don't know of an occasion where people have replaced the NAND chip on their 3DS boards, I thought that this is a good way to *somehow* identify unique users
- the reason I stored the data unencrypted in the database is because all 3 people who had access to the server don't know how to SQL (or even how the database manager UI works), I didn't even think about more about it since I knew that nobody else could access the database other than me
- note to tech sawwies: I was using MySQLi prepared statements, and I don't know a way exists to exploit that... but data was checked before even a connection was made to the database, so there was absolutely no way to exploit this
- my mind wasn't clear (and it still isn't) when I worked on the code/backend, and there were ~19errors/mistakes fixed in a ~1-2hr timespan before the initial release, so ye... I was only focusing on getting the work done, and I didn't even think about how the data I'm working with was touchy, nor about how illegal it was... I'm sorry for that


so ye... my wanting to add too many safety checks went super wrong, and I'm sorry about that. the data in the database isn't used by any human at all, it's only used by the backend API code to check some data validity and eliminate possible risks for banning the public seed served by this...

edit (17/07/28 10:01): I got access to the server and did a DROP TABLE which got rid of a whopping 17 entries, 3 of which were from the team... a bit of a waste of antidepressants for just those 14 entries...
oh, also fixed the title as the typo was really triggering my OCD
  • Like
Reactions: 30 people

Comments

I read this before it went up, and I'd just like to post that I do firmly believe Sono/MarcusD had no malicious intent while doing this. I don't blame him too much for what happened in with UnbanMii. really, I think it's the others who are at more fault.
 
  • Like
Reactions: 17 people
Please cringe-worthy 'tempers' take your shit posting elsewhere. He's actually trying to be a good person here and explain the situation.
 
  • Like
Reactions: 17 people
You ever try to joke with someone but they are too uptight, so you move on to converse with people that don't suck? That's me with this community more often than I would like.

@MarcusD enough people here appreciate your work enough to not take anything at face value.
 
  • Like
Reactions: 1 person
@ihaveamac for jumping to conclusions, or.....


Because honestly, I was kind of on-board with him using it for malicious reasons, BUT i still wasnt sure for a couple of reasons.

1. he wasn't 14 (hint hint), not 14, but in that age group.
Because around the teenage phase, Kids can do stupid stuff, I could name people but im not going to.

2. Why out of all of his projects would he do it in this one.

3. You didnt seem like this person to do this type of thing.
I mean sure looks can be decieving but i mean like the other people who would "hang out" with you were mostly noteable members.

4. Motive.
This is mostly why I wasn't sure. I mean like what exactly would you have to gain from this, I mean Nintendo could have contacted you, but like i said You dont seem like that type of person who would do it for money.

But alas, right now im pretty sure you didn't do it, but can't be sure, it would make sense if you didnt.
 
  • Like
Reactions: 1 person
well, it's not *only* my work, Paul and arc13 has also worked on this, I just made the backend and the interface code for the backend in the 3DS client... oh, and the shitty URL obfuscation
 
  • Like
Reactions: 3 people
not did I not tell, but I went to sleep, so I wasn't able to give them source code while sleeping, so yeah...
 
I'm still amazed that one bored person's digging around made an uneventful Temp Thursday an all-too familiar lynching and torch-bearing. It somehow reminds me of Hulk Hogan, when audio from his sextape leaked revealing he used the "N-word," and everyone jumped on him, and it was the flavor of the month, people just move on to the next thing to shit and hate on to the point of being too overbearing and at times impatient and nonsensical. That's this forum in a nutshell, that's the 3DS hacking community from those that just consume and aren't necessarily the devs. But, Hulk Hogan to me was always a fucking asshole and idiot, so it was just another day for me to express that in the calm, almost numb manner because I'm used to him being a twat. Here, it's clear explanations were given, doesn't excuse the dodgy things in play, but at least someone stepped up. I'm just more interested in how one single person got bored and cracked this hitherto unknown mystery. Sounds like a movie. Sounds a bit like Sneakers (good movie by the way), but the protagonists weren't lazy and they did that stuff for a living.
 
  • Like
Reactions: 7 people
@ihaveamac I mean like no thought of Security checks or the like,
just immediate "This is bad, he's a bad person, who only wants money or people hurt"
Know what I mean?

Or using someone's innocence and exploit, and then using their stuff to get money.
 
  • Like
Reactions: 1 person
"but the protagonists weren't lazy and they did that stuff for a living."
If you're referring to me, I'm incredibly lazy, and I don't do any kind of reverse engineering or security research for a living :P
 
  • Like
Reactions: 2 people
I just think we all oughta calm down, move on, and don't really blame anybody. The data has been purged, astro did a good thing by pointing out the stupidity, and the devs involved have had a good learning excercise in hashing content instead of directly uploading it. Now it's time to put down our pitchforks and get back to regular business.
 
  • Like
Reactions: 7 people
Am I the only one who doesn't understand what happened? I thought it was just a app that people didn't talk about it because it had some stuff it would download or something.
 
@Al3x_10m did successfully replace his nand with an 8 GB microSD :)

As for myself, I've never really cared... so what if your (most likely banned) localfriendcodeseed was shared, it's not like there's much to lose
 

Blog entry information

Author
Sono
Views
693
Comments
54
Last update

More entries in Personal Blogs

  • 4: Reddit
    Finally, number 4! Never thought this day would come, did you? Uhh...
  • books
    1. I am cool as hell, have one million dollars 2. I am banned from...
  • Syncthing is fun!
    Having been kinda active in an Android forum I quickly got sick about...
  • Feeling at home here
    Not much to say this time. I'm depressed. Like almost always. Trying to...
  • I'll start, rate mine 1-10
    It's a very mixed bag, some rock, some rap, some video game music, a...

More entries from Sono

Share this entry

General chit-chat
Help Users
    Psionic Roshambo @ Psionic Roshambo: https://youtu.be/fFn0llYqM8w?si=s9t6Se-c4cz5UbbP