hacking pusage on sunfire
I was thinking about to write my self-host website for printing at soc as I had enough harassment on printing, every time I wanted to print something, I have to transfer documents to my PC , connect to soc wifi, then send jobs to printers. It would be much more convenient to be able to print from my ipad and iphone. I know that checking and removing print queue is possible on sunfire with
lprm command. And after further research on dochub here: Printing from SunA or Sunfire and Print Quota, I have consolidated the command for printing from sunfire as followed:
Printing from SunA or Sunfire
I’ve gone far off the topic……
When trying to designed the index page of website, I wanted to print the printing quota on the front page, and I was trying to use python paramiko library to connect to sunfire, then execute
pusage command to retrieve the printing quota, weirdly enough, the output was empty, but when I normally ssh to sunfire and execute the command, it is giving me the correct output as follow:
I tried various ways of executing command, and realized the output was writing to stderr and is
pusage: Unable extract your username. the same for paramiko
why there is such difference between executing command from ssh and paramiko? I decided to take a look at the pusage binary executed.
ls -al `which pusage` I get the full path of pusage:
yang-c@sunfire0:~$ ls -al `which pusage`
it is at /usr/local/bin/rpusage, it is 32-bit MSB executable SPARC Version, which I have never heard of.
so I downloaded it to my local pc and tried to decompile it using ida, but it is not giving me pseudo code, the assembly code is not complicated yet I don’t have the patience to analyse it using gdb. I tried ghidra and it can give me the pseudo code in C
the full psudo code in C of main function:
FILE * main(undefined4 param_1,undefined4 *param_2)
seems that it is because the getlogin() function is returning 0/null so the if condition is fulfilled, so
fprintf((FILE *)(__iob + 0x20),"%s: Unable extract your username.\n",*param_2); and
exit(-99); get executed and then program exits.
my first attempt is try to force the programme not to take that condictional jump to print error message and exit, so I examined the assembly code and located
be, a LAB_00010A50 is the conditional jump, then I change that line to
bne, a LAB_00010A50 and export the binary, so now the conditional jump is reverted, let’s tried to execute the binary on sunfire through ssh.
as expected, it is now throwing error as
Unable extract your username.
let’s tried to execute the binary from other ways, it should be able to bypass the check and execute the code after the check of getlogin() returned value, however, it encountered a segmentation fault =(
I went back the check the pseudo code and realized I was too naive:
__src = getlogin();
even if I bypass the getlogin() check,
__src is 0/null pointer, and when executing
strcpy(acStack1024,__src); it will throw a segmentation fault!
so firstly the the program is using getlogin() function, and if the returned value is null pointer, the program will exit;
then the program will read contents in
/local/share/etc/rpusage.cf which appears to be a host name:
lpdhost.comp.nus.edu.sg, if error, exit;
then the programme will try to resolve that host name to ip address, if error, exit;
then the programme will try to establish connection to the ip on which port?, if error, exit;
then the programme will try to send the return value of getlogin() function to the host, if error, exit, trace as
__src = getlogin(); ->
sVar3 = write(__fd,acStack1024,0x400);
after figuring out the general flow of the programme execution, it seems that I can replicate the execution if I can figure out the exact port the programme is establish the connection with.
local_810 is of type
sa_family_t which is defined in
typedef uint16_t sa_family_t , and
uint16_t is defined as
typedef unsigned short uint16_t
the pseudo code is a bit confusing, so I tried a hacky way of finding the correct port the programme is trying to connect:
- write a python programme to execute
netstatcommand to find the corresponding connection
okay, the port should be 726 or 515
actually there is an unused variable in the pseudo code
local_80e = 0x2d6; which corresponds to 726 in decimal.
nmap scan result shows that these two ports are open as well:
so I quickly crafted a python script to test it out:
it worked! I try to connect to sunfire using paramiko and try it, it should be able to work since it is purely network connection process.
and it did worked.
we can see that the root cause of the deviation in executing
pusage from normal ssh client and paramiko library is the return value of getlogin(), so what on earth is this function doing?
If getlogin() is called within a process that is not attached to a terminal, it returns a null pointer.
that explains why normal ssh client will do but paramiko library exec_command is not working. because the invoked process is not attached to any terminal!
so how can I attach the invoked process to a terminal in paramiko? there is a function called
invoke_shell will do the job. Channel
a simple demo will show how it works:
During my playing around with the pusage binary, I searched for relevant info on google and found out there is an blog post from my senior who also tried to hack this binary, unfortunately, the blog was down at that time. And after I solved the problem, I was curious about how did he solved the problem, so I emailed him and got his reply in 20 minutes, it turned out that the dns record for his blog host was not updated, and he just updated it, the blog post is accessible from https://blog.yjwong.name/2014/05/08/pusage-hacking/
after going though his blog post, I realised that he was using the way which I hate most, debugging the programme using gdb and read through the assembly codes! what a work! and his solution is also on network part.
So what is wrong with that
pusage binary? How critical is that problem? (I am not referring to the problem I encounter)