Patrick O'Callaghan wrote:
Running sed on an executable is unlikely to work:
$ file /usr/bin/kwrite
/usr/bin/kwrite: ELF 64-bit LSB shared object, x86-64, version 1
(GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2,
for GNU/Linux 2.6.32, BuildID[sha1]=3c378cb60be1e2a76f98
1ba5c15c1096f1b6a5b5, stripped
Also, are you sure you mean 'getpid'?
OK, so let me explain how this hack works (*) (I wanted to let you try and
be amazed first :-p):
* The first thing to observe is that the replacement string has the same
length as the string being replaced. (In fact, I only replace one character
with another, but this does not even matter, as long as the length does not
change.) sed actually has no issues at all with operating on binary files.
The only problem is, if you change the length of the string, you break all
the offsets in the executable and it will stop working. So it is extremely
important that the replacement string also has 6 characters like "getuid".
* Now compare the prototypes of "getuid" and "getpid":
uid_t getuid(void);
pid_t getpid(void);
Notice that they are almost the same, only the return type looks different
at first glance. But both uid_t and pid_t are actually 32-bit integers
(technically, uid_t is unsigned and pid_t is signed, but that does not
matter at all at assembly level), so that does not matter. Therefore, it is
safe to call one function instead of the other.
* Symbol versioning might get in your way. But if you check, you will find
that the current symbol version of both "getuid" and "getpid" on
x86_64 is
GLIBC_2.2.5, so that will not get in your way either.
* Now look at how the check for root actually works: it does something like:
if (getuid() == 0) fail;
My hack changes this to:
if (getpid() == 0) fail;
The interesting thing is, getpid() == 0 is always false! The PID is never
zero, PID 0 is reserved by the Linux kernel. So the whole check becomes a
no-op and kwrite will always run.
So, all in all, I just had to look for a libc function that has a 6-letter
name, a compatible prototype with getuid, and will never return 0. getpid
was the perfect match. (By the way, if upstream decides to change the check
to check for geteuid instead, which has 7 characters, then you can replace
that with getppid, which is also never 0.)
So next time you should at least try my command before claiming that it will
not work. :-) You should trust an experienced developer with assembly
knowledge like me more than you do. ;-)
Note:
(*) To be sure, I have now just tested it on the kwrite binary from
kwrite-17.04.1-1.fc26.x86_64.rpm, and yes, it does work – but I was almost
sure it would work even before testing it. :-) "I have only proved it
correct, not tried it." as Donald Knuth once wrote. ;-) But now I did try it
and it works indeed.
Kevin Kofler