This is a followup to this blog post, where the author corrects tw_cli to read the effective uid of a process instead of the real uid.

We have a use case where we want an external monitoring system to run as an unprivileged user, and still monitor parts of the system that are more privileged (3ware RAID status is one of them). The system works great under IA-32; we were able to successfully follow the steps in the above blog post to hack tw_cli to allow an unprivileged user to read the status of the system’s RAID.

We hit a problem with the steps the author described after we migrated a server to x86_64 architecture. tw_cli has a check to make sure it’s running on the correct architecture as well. And of course, repeating the exact process on x86_64 was futile, since it’s compiled differently.

Before I begin this, I’m going to reiterate the following warning:

If you do any of these hacks, be sure that you do NOT install the tw_cli program itself setuid root; use sudo, or another wrapper that filters user access to running tw_cli as root. If you do not take appropriate precautions, any user will be able to run tw_cli, bugs and all, and have all the powers of root while doing so!

First, I wanted to know exactly what was going on when I tried to run tw_cli as an unprivileged user. For that, I turned to strace:

$ sudo -u monitoring-user strace -vvtf -s 1024 ./tw_cli info c4

This is the most important bit I got back:

07:54:39 getuid()                       = 102
07:54:39 brk(0x246a000)                 = 0x246a000
07:54:39 brk(0x245d000)                 = 0x245d000
07:54:39 write(2, "Error: (CLI:001) Only root/administrator can run this program.\n", 63Error: (CLI:001) Only root/administrator can run this program.
) = 63

According to strace, the system ran getuid(), found that my current uid is 102, and bailed out due to its internal checking. I proceeded to use gdb to see if I could get any more information.

$ sudo -u monitoring-user gdb ./tw_cli

So gdb has a pretty useful function called catch, where you can catch different types of functions and have it break you out. I set a catch on getuid, to inspect the stack frame where it calls that function:

(gdb) catch syscall getuid
Catchpoint 1 (syscall 'getuid' [102])

I ran the program with my catch statement set:

(gdb) r
Starting program: /opt/monitor/sbin/tw_cli

Catchpoint 1 (call to syscall 'getuid'), 0x00000000005a4099 in ?? ()

So it caught my call to getuid(). Look at that address. That’s the address in the program where the getuid() call is located. Wouldn’t it be nice if we could skip this getuid call(), or skip the conditional where it checks? I disassembled the source to find this location:

$ objdump -d ./tw_cli >tw.dump

So now I had a dump of the disassembled program. Searching for 5a4099 yielded:

5a4090:       48 c7 c0 66 00 00 00    mov    $0x66,%rax
5a4097:       0f 05                   syscall
5a4099:       c3                      retq</pre>

Here, the system is moving 0x66 into %RAX, and then doing a syscall on it (syscall pulls the value from the %RAX).

So what’s going on here? This is my getuid() call, in its disassembled form. It is throwing 0x66 at the operating system (number for getuid()), and then returning the value. How do I know that 0x66 is getuid? Well, if you browse the linux source (/arch/x86/include/asm/unistd_64.h, Linux 3.2 in my case), you will see the following code:

#define __NR_getuid 102 __SYSCALL(__NR_getuid, sys_getuid)

0x66 converts to 102 decimal.

One problem though, is that this seemed to be a function due to the use of retq (returning to the address on the top of the stack.) So I could either hack this function, or hack something around where the caller was calling the function.

I grabbed a backtrace from gdb and inspected it:

Catchpoint 1 (call to syscall 'getuid'), 0x00000000005a4099 in ?? ()
(gdb) bt
#0  0x00000000005a4099 in ?? ()
#1  0x0000000000484cb8 in ?? ()
#2  0x000000000044fdde in ?? ()
#3  0x0000000000578371 in ?? ()
#4  0x000000000040018a in ?? ()
#5  0x00007fff274ee758 in ?? ()
#6  0x0000000000000000 in ?? ()

0x5a4099 (#0) was where I just was in that function. I looked further back at 0x484cb8 (#1).

484cb3:       e8 d8 f3 11 00          callq  0x5a4090
484cb8:       85 c0                   test   %eax,%eax
484cba:       0f 94 c0                sete   %al
484cbd:       88 45 e7                mov    %al,-0x19(%rbp)
484cc0:       80 7d e7 00             cmpb   $0x0,-0x19(%rbp)
484cc4:       75 57                   jne    0x484d1d

Here’s where 0x5a4090 is called. I looked down towards the cmpb and the jne. I was pretty sure that this cmpb was where it was testing if we were root, and the jne would jump if we were root. (I could have tested this assumption by running it as root in gdb using the ni (next instruction) command). I wanted to change this so it would jump no matter what. I grabbed a copy of shed, and ran it against my binary:

$ shed ./tw_cli

I should note that in shed, I searched for 80 7d e7 00 75 57 (the last two lines of the above code). I got the following back:

00543936:     80  128 200 10000000
00543937:  }  7D  125 175 01111101
00543938:     E7  231 347 11100111
00543939:     00  000 000 00000000
00543940:  u  75  117 165 01110101
00543941:  W  57  087 127 01010111

So! Using my prior knowledge of assembler, I already knew that 0x75 is the jne command, and was informed that 0xeb is the jmp command (easily searchable via your favorite search engine). I changed the 75 to eb:

00543936:     80  128 200 10000000
00543937:  }  7D  125 175 01111101
00543938:     E7  231 347 11100111
00543939:     00  000 000 00000000
00543940:     EB  235 353 11101011
00543941:  W  57  087 127 01010111

Testing…

$ sudo -u monitoring-user ./tw_cli info c4

Unit  UnitType  Status         %RCmpl  %V/I/M  Stripe  Size(GB)  Cache  AVrfy
——————————————————————————
u0    RAID-10   OK             -       -       64K     931.303   ON     OFF

Great success!

Further Hacking 3ware’s Management For setuid Programs