Tony Hawk's Pro Strcpy
icode4.coffee
icode4.coffee
A random 2048-bit integer has a moderate chance of being trivially factorizeable (I don't know the precise odds but we can infer that it's roughly on the order of 2^-32 (for some definition of trivial) without doing any real math). Presumably, they wrote code that did something like this:
while true:
randomly tweak/increment 4 bytes of the public modulus
spend 1 millisecond trying to factor it
did it work? if yes, we're done here.
else, try again.
The resulting public modulus likely has lots of smaller factors (it should be possible to verify this, if anyone knows where I can find the "habibi public key"?). Although an RSA modulus normally has exactly 2 prime factors, the math still works out if you have more (as long as e is coprime).EDIT: Wikipedia says "The structure of the RSA public key requires that N be a large semiprime (i.e., a product of two large prime numbers), that 2 < e < N, that e be coprime to φ(N), and that 0 ≤ C < N." and later "the same algorithm allows anyone who factors N to obtain the private key."
which in the contest of the Xbox hack means that if you force N to be divisible by the prime 3, then the other prime which is used for generating the private key has to be N/3 => You have successfully factored it.
EDIT2: Here's code for signing with the Habibi key: https://github.com/XboxDev/xbedump/blob/b8cd5cd0f8b1cbc4e64f...
As you can see, it'll replace the last 4 bytes with 0x89, 0x9c, 0x90, 0x6b and then start by dividing it by 3 and using that to generate a suitable private key.
Here's the original public modulus as an integer: http://factordb.com/index.php?query=207401193272587237602760... (which can't be factored, at least not any time soon)
And here's the patched version: http://factordb.com/index.php?query=173718524353649322341982...
And exactly as you say, it's divisible by 3, leaving behind a single large prime (so I was wrong about there being more factors)
https://www.usenix.org/system/files/conference/usenixsecurit...
The chance is way, way, WAY larger than 2*-32. Consider the following code:
primes = [2, 3, 5, 7, ..., 499]
def miller_rabin(n, k): ... # your fast primarity test of choice
def is_prime_trivial(n):
for p in primes:
while n % p == 0:
n //= p
if n == 1:
return True
return miller_rabin(n, 20)
It fully factors a random 2048bit integer in around 100 tries, for me.Xbox 360...._softmod_.... via the park name on a Tony Hawk game.
24 segment ROP chain :')
His rightful lamentation for the hypervisor, concise functional write up, and immediate thoughts of an x360 botnet make this the greatest xbox 360 nostalgia gut-punch of all time.
kudos++
it really ought to have been strncpy, I'm sure Tony Hawk who's lauded for his advocacy of safety gear would prefer to be associated with safer string copying
basically you should almost never use strncpy; it's specifically for fixed-size fields like this:
struct dirent { unsigned short inode; char name[14]; };
and in those cases more often than not the pad byte should be a space rather than a nulstrncpy should never have been added to the standard library
The real issue I've ran into is that "strncpy" assumes the source is null-terminated.
Or move up from the 1970's Bell Labs, adopt C++ with the respective compiler switches to have bounds checking enabled for operator[]().
Better yet, use something else instead of one of those two, pick whatver is your fancy.
(Separately, there's a discussion about how many bytes you are allowed to read from the _source_, but to fix that, you need something like the Linux kernel's strscpy(), which isn't really widely supported in userspace.)
It is a simple refactoring to change strcpy/strncpy to strlcpy and, though it doesn't solve truncation issues, it's a solid improvement by eliminating memory overruns and lack of null termination.
It was added to OpenBSD in 1998 and then in FreeBSD, Mac OS X, Solaris, IRIX, etc. but its adoption was hampered by glibc stubbornly refusing to add it (until 2023 apparently).
What I wish the standard library had:
ssize_t strscpy(char* dst, const char* src, ssize_t dsize);
Copies src into dst, stopping when it either reaches a \0 byte in src or on copying dsize - 1 bytes, whichever happens first. dst is then null terminated.If the copy is not truncated the strscpy returns the number of bytes copied. If the copy is truncated dsize is returned.
Returns a negative value and sets errno if either dst or src is NULL or dsize is < 1.
they actually started out with your design and then fixed it:
> The return values started out as the number of characters copied, since this was trivial to get as a side effect of the copy or concatenation. We soon decided that a return value with the same semantics as snprintf()’s was a better choice since it gives the programmer the most flexibility with respect to truncation detection and recovery.
basically they wanted to treat string truncation due to insufficient space as an error condition, so they designed the interface to make it easy to check (code from the paper, with syntax corrections):
len = strlcpy(path, homedir, sizeof(path));
if (len >= sizeof(path)) return (ENAMETOOLONG);
len = strlcat(path, "/", sizeof(path));
if (len >= sizeof(path)) return (ENAMETOOLONG);
your proposal does permit such simple error checking for strscpy, although it is marginally less efficient: len = strscpy(path, homedir, sizeof(path));
if (len <= strlen(homedir)) return (ENAMETOOLONG);
but i can't think of anything your corresponding strscat could return to permit a similarly simple check. is there anything?(note: the following code is probably buggy with off by one errors and doesn't check return value properly)
copied_bytes = strlcpy(dst, src, size);
if ( copied_bytes >= size )
{
realloc(dst, copied_bytes);
size = strlcat(dst, src + size, copied_bytes);
}
I can appreciate that sentiment, but I think it was a mistake. That behavior is still possible where it makes sense by using strlen() first, but I'd argue that in most cases if this is possible then strdup() was the better solution all along. Basically they made a tradeoff that makes one relatively uncommon use case easier at the expense of making the function explode in other cases.You don't need strlen() to check for truncation:
if ( strscpy(path, homedir, sizeof(path) == sizeof(path) ) return (ENAMETOOLONG);
strscat() would have a similar syntax. Return values would be the same, including returning the size parameter in the case of truncation, making it easy to check. if ( strscat(path, "/", sizeof(path) == sizeof(path) ) return (ENAMETOOLONG);
On a side note I always cringe when I see people using sizeof() on strings in C. I left them in here to make the comparison easier, but I wouldn't normally do it this way. That's a gun pointed directly at your foot when this bit of code gets refactored out to a function and that string degrades to a pointer.strdup() is not helpful in examples like the one i quoted from the paper, where you are building up a string by concatenating substrings, but something like stralloc is. (see other subthread) the paper recommends the libmib astring functions, which are something like stralloc: http://www.mibsoftware.com/libmib/astring/. they definitely were not recommending that people copy and paste those six lines of code with slight changes every time they wanted to copy a string
i don't agree that it makes the function explode in other use cases. if you're okay with truncation then strlcpy() will silently truncate your strings if you don't check its return value
your strscpy() example has a parse error; i think you meant
if ( strscpy(path, homedir, sizeof(path)) == sizeof(path) ) return (ENAMETOOLONG);
which leads me to think that you mean that if strlen(homedir) is 12 and sizeof(path) is 13, strscpy copies 12 characters (not counting the nul) and returns 12, not 13, while if strlen(homedir) is 13 in that case, it also copies 12 characters, but returns 13. i agree that that would work; it is so similar to the flawed design rejected in the strlcpy paper that i thought you meant the same thing, but you evidently meant something subtly different. i agree that that design would also work for strscatat that point, though, it might be better to return -1 or INT_MAX rather than dsize on truncation; you can't use the return value you've specified for anything before you check whether it's equal to dsize or not. (this is also true of strlcpy!) actually you also specified to return a negative value on certain other errors, which means you have to check the return value twice before using it for anything; possibly this was a mistake
i also agree that using sizeof on arrays is a footgun for exactly the reason you say, although in this case the most likely result would be that you'd notice the bug and fix it, since pointers are too short for most strings
if (!addrparse(arg)) { err_syntax(); return; }
flagbarf = bmfcheck();
seenmail = 1;
if (!stralloc_copys(&rcptto,"")) die_nomem();
if (!stralloc_copys(&mailfrom,addr.s)) die_nomem();
if (!stralloc_0(&mailfrom)) die_nomem();
out("250 ok\r\n");
basically you have a struct with buffer-pointer, length, and capacity fields, like a golang slice, and you modify it with a small number of functions which reallocate the buffer if it isn't big enough. the ones you see here are stralloc_copys, which sets the buffer contents to the contents of a nul-terminated string, and stralloc_0, which appends a nul to the buffer. there are also functions for appending an arbitrary byte, for copying one stralloc to another, for copying counted strings into strallocs, and for concatenation, for determining whether one is a prefix of another, etc., but depending on the application, you may or may not need to implement thesethe whole stralloc library is 97 lines of k&r c, so reimplementing the part you need for a given program is pretty trivial. it's in the public domain
for most programs, a disadvantage of the particular way that stralloc is implemented in qmail is that you have to check every single copy or concatenation operation for an out-of-memory error, as you see above. this makes your code a lot longer. many applications are better off just aborting inside the memory allocation function if they run out of memory; getting out-of-memory handling correct is very difficult, especially if you don't devote a massive amount of effort to testing out-of-memory conditions (because they won't occur often enough just by chance to test your error-handling path)
(another disadvantage of the way stralloc is implemented is that you probably don't really want to use unsigned int for the two length fields on lp64 platforms)
for some applications you might prefer just using strdup() (or xstrdup()) or non-owning string-view types (a pointer and a length, perhaps into an input file you've mapped into memory), or lisp-style symbol interning (plus some kind of buffer management probably). arena allocation, if you can afford it, makes dynamic memory allocation for strings a much more reasonable thing to do: no risk of a memory leak, fast allocation, instant deallocation. but again some applications do poorly with arena allocation
but please don't fill your program with fifty zillion string buffers of arbitrarily chosen sizes and then try to separately pass the right size in seventy zillion string-processing function calls. your code will be hard to read, buggy, and probably insecure. factor string buffer length handling into a small part of your program so that most of your code never has to think about string buffer lengths
You could do that. Or you could put a field in the struct that stores an error flag. If flag is set, all `stralloc` functions return immediately. When they fail, they set the flag and then return.
This lets you do:
stralloc_copys(&rcptto,""));
stralloc_copys(&mailfrom,addr.s));
stralloc_0(&mailfrom));
if (stralloc_error(rcptto) || stralloc_error(mailfrom)) {
die_nomem();
}
I'd go one further and make the error checker function take variable arguments, so that the last line looks like this: if (stralloc_error (rcptto, mailfrom, NULL)) {
die_nomem();
}
IME, forgetting to terminate the parameter list with a NULL almost always causes the program to blow up on the very first execution.> but please don't fill your program with fifty zillion string buffers of arbitrarily chosen sizes and then try to separately pass the right size in seventy zillion string-processing function calls. your code will be hard to read, buggy, and probably insecure. factor string buffer length handling into a small part of your program so that most of your code never has to think about string buffer lengths
I agree, but after years and years of looking at and writing idiomatic safe C code, I am now of the opinion that a string library is, while a better approach to slinging around raw strings, still very much the wrong approach.
Nothing stops the developer from doing Parse, Don't Validate! in C, and this means that seeing C strings being used anywhere other than at the boundaries to the system evokes my code-smell senses.
i do think parse, don't validate is much more difficult in c; c's type system is not strong enough to give you the kinds of soundness guarantees you get from ocaml or haskell. if you forget a type case in a switch, there's not a whole lot you can do to get the compiler to complain about it
the code i quoted above is from qmail-smtpd.c, which is 373 lines of code like the above and contains all of qmail's smtp input logic except for ip_scanbracket, which parses strings like [127.0.0.1] and is shared with dns.c. it's not clear to me that a parse, don't validate approach would consist of much more than just the parser
maybe using a parser generator for all your input and output handling would help? i'm still skeptical that something like a text editor or a macro processor is going to have a large body of code that is free of string handling
Sure, I agree, but we're talking about strings here. Instead of a function taking or returning an email address in a generic string type, it can take or return an email address type.
For example, construction of a value of type `email_t` can take a parameter of raw string. Then any function in the rest of the code that receives an email would receive an `email_t`, not a `char ` or some other generic string type.
> it's not clear to me that a parse, don't validate approach would consist of much more than just the parser
It might often be nothing but* a parser, such as `email_t`, but it means that no `str()` function would then be used by the caller - any operation on the `email_t`, if `email_t` is an opaque pointer, would used the `email_type_()` functions, because the users of any `email_t` value cannot access, or even see, the fields inside an `email_t` value.
This means that passing an email to a function expecting a name would cause a compiler error.
For the IP address example you mention, that definitely should be parsed only once into the quad-byte or quad-quad integer fields.
I mean, I'm looking over my previous projects: every single instance of a string I am using is actually not just "generic string"; there's an associated type with it (name, description, comment, whatever). Making those into different types with their own operations means that the compiler will generate errors if I try to use a `description_t` where a `name_t` is expected.
indeed it is the case in qmail that ip_scanbracket populates a struct ip_address. but rcptto, where the destination email address goes, is just a byte buffer in a very simple ad-hoc format which, if i understand correctly, gets written to a pipe; qmail's privilege separation design, which its author to a significant extent came to regret, adds some extra difficulties here by requiring things to run in separate processes
what you read from or write to a pipe is, at that point, necessarily just a generic string. you could write some kind of generic serialization layer, but doing that in c requires a preprocessor, and unmarshaling things in a statically type-safe way really requires compiler support for sum types, which c doesn't have
aside from that, i think it's pretty likely that trying to parse the email address in the qmail-smtpd process would have made the code more bug-prone rather than less so
> ...
> what you read from or write to a pipe is, at that point, necessarily just a generic string.
Aren't the reader and writer of the pipe part of the same software package?
If they are, then safe[1] functions for that type make sense:
bool email_to_bytes(email_t *src, uint8_t **dst, size_t *len);
bool email_from_bytes(email_t *dst, uint8_t *src, size_t len);
This still means that you're only ever passing around `email_t` values, not `char *` values.On the other hand, if the reader and writer of the pipe are in different packages, then the pipe is the boundary for each of them, and you wouldn't be passing language native types without first serialising to a language independent representation anyway.
[1] By "safe" I mean that they don't overflow and that the actual binary format allows the `from_bytes` function to determine when the input could be malicious.
so when i implied that 'trying to parse the email address in the qmail-smtpd process' was not a thing that was already being done, i was wrong, so plausibly your recommendation is in fact applicable; maybe it would have been better to parse the email address into a struct with user and host fields, then have email_to_bytes represent the email <kragen@gentle.dyn.ml.org> as T6:kragen,17:gentle.dyn.ml.org, instead of as Tkragen@gentle.dyn.ml.org\0. i mean you could generate email_to_bytes with a code generator (an idl compiler) instead of writing it
then you wouldn't have to worry about the possibility that you'd accidentally left an @ in one part or the other—unless you did relay the mail over smtp to some other host, in which case you would have to worry about it anyway
In this case, StringCchCopyW(), or StringCbCopyW() would be a better choice than strcpy.
https://m.youtube.com/watch?v=ob0dI05Xz8s
Koston did not invent the thing but has been a major popularity contributor to it.
https://www.surfertoday.com/skateboarding/why-do-skateboarde...
The mod itself is over 10 years old now, and I think the original developers are gone, explaining why no one was interested in fixing it when Ryan reported it. But this means that now the mod is unusable, no one is going to want to risk a full privilege exploit taking over their PC.
Hopefully this article reaches someone who's a bit more interested in patching the mod.
A question I have is where/when/how the corresponding private habibi key was released/leaked, if the story about it being used exclusively by the linux console group to prevent pirated content from being used is true. OP clearly was able to patch the four byte difference between the MS key and the habibi key to then run "unsigned" (but, actually, signed with the habibi private key) executables, so they clearly got their hands on it.
I've been doing some PSX decompiling and there are lots of similar things there as well. Interestingly, something like `memmove` is linked in using an SDK library[0], but `strcpy` is a function provided by the BIOS. Later version of the SDK could patch that out for a library version, but as late as 1997 it hadn't been.
0 - https://github.com/Xeeynamo/sotn-decomp/blob/master/src/main...
Now there are the N64 games being ported to PC with decompilers, I can only hope. Inb4 "there are native PC versions of these, you know"... most recompiled N64 games with the FX's being 'deshaderized' to pure textures (or simpler FX's) can be run in toasters such as cheap netbooks from 2009 and nearly anywhere.
They even ported Super Mario 64 to the 3DFX API. I know, the most complex games accesing the N64 framebuffer with complex FX will require OpenGL 3.3 to mimic that microcode; but, as I said before, when the engines run uber-fast on anything post Pentium III, is not something difficult to 'mimic' these in software while the rest it's running GL 2.1 accelerated.
That's... not surprising. UltraHLE ran SM64 like a dream, and the HLE bit referred to the fact that the emulator translated 3D calls to the Glide API rather than attempting to emulate the 3D hardware directly.
Once your run the games at 1024x768 and up that doesn't matter at all:
sm64ex, perfectdark, zeldaoot for PC...
If you use one of those hires texture packs then it'd be fine.
Great article!
(Completely separate from that, consoles shouldn't be treating users as the adversary, but given that they do, games are failing to have a security mindset consistent with that stance.)
I would 100% agree with this when talking about a normal computer, but I kind of feel differently about consoles. How do you prevent cheating in online games if you don't restrict what users can do?
2) Group players by apparent capability and observed behavior, such that cheaters end up only playing with other cheaters.
2. is a research project that will probably never pan out.
This is not a hypothetical or a research project; some games do exactly this.
https://en.wikipedia.org/wiki/Cheating_in_online_games#Playe...
https://en.wikipedia.org/wiki/Cheating_in_online_games#Banni...
> Certain games are known to identify cheaters and "shadow ban" them by placing them in matchmaking with other cheaters only, so as not to let the cheaters know that they have been identified.
The banning section mentions companies that employ very invasive software to find and ban cheaters. Read up on how the following softwares actually work:
> There are many facets of cheating in online games which make the creation of a system to stop cheating very difficult; however, game developers and third-party software developers have created or are developing[22][23] technologies that attempt to prevent cheating. Such countermeasures are commonly used in video games, with notable anti-cheat software being BattlEye, GameGuard, PunkBuster, Valve Anti-Cheat (specifically used on games on the Steam platform),[citation needed] and EasyAntiCheat.
Because the only sources you have provided are anecdotal, its entirely possible you are falling for the illusion.
To use your phrases, 'certain games companies' are known to totally lie about their anti cheat techniques and methods to throw people off the scent of the real methods.
Therefore without actually decompiling something to prove whats going on, you have no real idea what techniques are being used at all.
A single cheater can often ruin games for hundreds or even thousands of players very easily. For experienced players, seeing a single flying aimbot shithead in your lobby just means there are 10 other cheaters in the lobby too -- ones using subtle ESP/wallhacks that can be extremely difficult to detect, by design. Shady websites like G2A or GMG that sell keys (which are almost 100% hot keys, to really make it all come full circle) mean that even if you get banned, buying new keys for a new copy of the game is extremely cheap, especially when many of these games have items that can be sold for IRL cash in various ways, games like Rust. For many parts of the world, selling/trading rare items to players can net you plenty of actual income -- and getting banned means nothing as a result. Instant banning cheaters the second they are confirmed leaks information to the cheater and cheat creator, today most games like Warzone or Destiny have to play psyops and shroud their exact detection techniques in part by doing "ban waves" only when they accumulate a mass amount of confirmed cheaters. The cheater that ruined your top score may, necessarily and by design, be allowed to run free for a while.
The net result of all this is that designers and -- importantly, even though people on Hacker News don't want to hear it -- PLAYERS tend to overwhelmingly prefer prevention instead of reaction. They are both needed. Players are not morons who love installing rootkits. But on the whole, preventative measures tend to be more valuable to players and creators than reactionary ones, even if they are all ultimately imperfect.
In a funny twist, games like Tarkov and Rust do have a gameplay mechanic that reduces the long-term psychological devastation of cheaters and is not invasive at all: they reset all content in the game to "neutral" every once in a while, so basically all your stuff gets deleted, and everyone starts over again. (This non-permanence is probably one of the reasons players stick with the game, despite cheaters, which are incredibly infuriating.)
Can I ask if you seriously play any online competitive games, at a high level or otherwise? Because I do, and I'll be honest: I've been hearing it all for 20 years. These types of approaches have had success (CS's player review system, certain shadowban systems, "trip wires" that trigger on impossible game behavior), but there is no single approach that has proven itself to be the ultimate universal solution. There is no universal, wibbly wobbly bullshit stats algorithm you run on your servers to "solve" this. These problems are not solved. I don't like it. I don't run certain games with certain forms of anticheat. But it is what it is.
(Which is presumably "the first recorded" but I'm guessing if someone had done it, they'd have been shouting about it and -probably- the only kind of person who could pull it off would be a pro skater anyway?)
The rarity of seeing a 720 or above has as much to do with the fact that most skaters don’t skate vert - instead skating street or smaller transition - as the trick’s difficulty. Outsiders tend to imagine large spins are the holy grail of skate moves but almost all skaters aren’t interested in them for aesthetic reasons among others.
Arguably, this feat is easier for a small child, but still... insane talent at a young age.