Background

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 lpq and 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
Printing from SunA or Sunfire

This page not only describes printers, but also printing commands, job restrictions and other related topics.

This page applies if you have a document you need to print on SunA or SunFire. All printers are already defined on these hosts for your conveience.

Note: Only postscript and ASCII files can be printed from SunA/SunFire.


Printing from SunA/Sunfire
You need to be logged on to SunA/Sunfire
Select a print queue. To print on both sides of the paper, simply select a printqueue without the (-sx) suffix.
Submit the file(s) (e.g. file1 and file2) to the selected print queue (e.g. psts-dx)
lpr -P psts-dx file1 file2 ...


Checking the status of your Print Job
- You need to be logged on to SunA/Sunfire
- Check that the file(s) are submitted to the print queue
lpq -P psts-dx


Duplex Printing
To print to both sides of the page, simply choose a print queue without the (-sx) suffix.
lp or lpr to send your job to a print queue
lpq to see the status of a print queue
lprm to cancel your job from a print queue


Multiple Pages On A Page
For a postscript file, use `multips` or `psnup`. For a text file, use `mpage` or `a2ps`.
eg: multips t.ps|lpr -Ppsmr
There are many options related to these commands. See their respectively man pages for details.


Locally added options to `lp` command
- oh to suppress banner printing
- o2on1 to print 2 pages on a physical page
- o4on1 to print 4 pages on a physical page
- oland to print in landscape orientation


To remove a print job:
1. Login to a Unix host (e.g. suna or sunfire)
2. Run: lpq -Pprint_queue
3. eg. lpq -Ppsts
4. depending on the print queue you sent to
5. Note the job id for the print job you wish to cancel
6. Run: lprm -Pprint_queue job
7. eg. lprm -Ppsc011 144 where 144 is the job id obtained from lpq command

Convert pdf to postscript:
use `pdf2ps` or `pdftops`

Check printing quota:
use `pusage`

I’ve gone far off the topic……

Deviation of pusage execution from ssh and paramiko

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:

image-20191220165240199

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

image-20191220165801195

why there is such difference between executing command from ssh and paramiko? I decided to take a look at the pusage binary executed.

Decompile pusage binary

by executing ls -al `which pusage` I get the full path of pusage:

1
2
3
4
yang-c@sunfire0:~[1022]$ ls -al `which pusage`
lrwxrwxrwx 1 sadm sadmg 7 Aug 14 2007 /usr/local/bin/pusage -> rpusage
yang-c@sunfire0:~[1023]$ file `which pusage`
/usr/local/bin/pusage: ELF 32-bit MSB executable SPARC Version 1, dynamically linked, not stripped

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

image-20191220170824216

the full psudo code in C of main function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
FILE * main(undefined4 param_1,undefined4 *param_2)

{
char *__src;
FILE *__stream;
hostent *phVar1;
int __fd;
int iVar2;
ssize_t sVar3;
undefined auStack3088 [1024];
sa_family_t local_810;
undefined2 local_80e;
undefined auStack2060 [12];
char local_800 [1024];
char acStack1024 [1024];

__src = getlogin();
if (__src == (char *)0x0) {
fprintf((FILE *)(__iob + 0x20),"%s: Unable extract your username.\n",*param_2);
/* WARNING: Subroutine does not return */
exit(-99);
}
strcpy(acStack1024,__src);
__stream = fopen("/local/share/etc/rpusage.cf","r");
if (__stream == (FILE *)0x0) {
fprintf((FILE *)(__iob + 0x20),"%s: Unable to read server list file.\n",*param_2);
/* WARNING: Subroutine does not return */
exit(-99);
}
while( true ) {
do {
__src = fgets(local_800,0x400,__stream);
if (__src == (char *)0x0) {
fclose(__stream);
return (FILE *)0x10c00;
}
} while (local_800[0] == '#');
sscanf(local_800,"%s",local_800);
phVar1 = gethostbyname(local_800);
if (phVar1 == (hostent *)0x0) {
fprintf((FILE *)(__iob + 0x20),"%s: Hostname error.\n",*param_2);
/* WARNING: Subroutine does not return */
exit(-99);
}
memset(&local_810,0,0x10);
local_810 = 2;
memcpy(auStack2060,*phVar1->h_addr_list,phVar1->h_length);
local_80e = 0x2d6;
__fd = socket(2,2,0);
if (__fd < 0) {
fprintf((FILE *)(__iob + 0x20),"%s: Unable to create socket.\n",*param_2);
/* WARNING: Subroutine does not return */
exit(-99);
}
iVar2 = connect(__fd,(sockaddr *)&local_810,0x10);
if (iVar2 < 0) break;
sVar3 = write(__fd,acStack1024,0x400);
if (sVar3 != 0x400) {
fprintf((FILE *)(__iob + 0x20),"%s: Unable to send to server.\n",*param_2);
/* WARNING: Subroutine does not return */
exit(-99);
}
while( true ) {
while( true ) {
sVar3 = read(__fd,auStack3088,0x400);
if (-1 < sVar3) break;
fprintf((FILE *)(__iob + 0x20),"%s: Data receive error. Continuing ....\n",*param_2);
}
auStack3088[sVar3] = 0;
if (sVar3 == 0) break;
fprintf((FILE *)(__iob + 0x10),"%s",auStack3088);
}
close(__fd);
}
fprintf((FILE *)(__iob + 0x20),"%s: Connect error - errno=%d.\n",*param_2,errno);
/* WARNING: Subroutine does not return */
exit(-99);
}

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.

Attempt 1: alter the programme execution flow (failed)

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.

image-20191220172521655

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 =(

image-20191220172715190

I went back the check the pseudo code and realized I was too naive:

1
2
3
4
5
6
7
__src = getlogin();
if (__src == (char *)0x0) {
fprintf((FILE *)(__iob + 0x20),"%s: Unable extract your username.\n",*param_2);
/* WARNING: Subroutine does not return */
exit(-99);
}
strcpy(acStack1024,__src);

even if I bypass the getlogin() check, __src is 0/null pointer, and when executing strcpy(acStack1024,__src); it will throw a segmentation fault!

image-20191220173328834

此路不通

Attempt 2, read through pseudo code and figure out what is happening (worked)

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(); -> strcpy(acStack1024,__src); -> 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.

1
2
3
4
5
6
7
8
9
10
11
12
13
memset(&local_810,0,0x10);
local_810 = 2;
memcpy(auStack2060,*phVar1->h_addr_list,phVar1->h_length);
local_80e = 0x2d6;
__fd = socket(2,2,0);
if (__fd < 0) {
fprintf((FILE *)(__iob + 0x20),"%s: Unable to create socket.\n",*param_2);
/* WARNING: Subroutine does not return */
exit(-99);
}
iVar2 = connect(__fd,(sockaddr *)&local_810,0x10);
if (iVar2 < 0) break;
sVar3 = write(__fd,acStack1024,0x400);

local_810 is of type sa_family_t which is defined in /usr/include/sys/socket_impl.h as typedef uint16_t sa_family_t , anduint16_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:

  1. write a python programme to execute pusage infinitely
  2. use netstat command to find the corresponding connection

test.py

1
2
3
4
5
6
7
import os
from time import sleep

while True:
os.system("rm -f iamhere")
os.system("pusage > iamhere")
#sleep(5)

image-20191220201640758

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:

image-20191220203313962

so I quickly crafted a python script to test it out:

pusage.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/python
from socket import *

s = socket(AF_INET, SOCK_STREAM)

host_ip = gethostbyname("lpdhost.comp.nus.edu.sg")
port = 726

quota = ""

s.connect((host_ip, port))

message = b'yang-c'+b'\x00'*(1024-len("yang-c"))
s.send(message)
resp = s.recv(4096)
while resp:
quota += resp.decode()
resp = s.recv(4096)

print(quota)

image-20191220204642151

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.

image-20191220204922148

and it did worked.

Attempt 3, figure out what is wrong with getlogin() (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?

image-20191220205324722

getcha!

1
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

image-20191220205833027

a simple demo will show how it works:

image-20191220205921994

End notes, an interesting story

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/

image-20191220211845561

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.

Food for thought

So what is wrong with that pusage binary? How critical is that problem? (I am not referring to the problem I encounter)