国赛_final in武汉
第二次打线下了,出现了很多问题。现在的大比赛基本不怎么出heap相关的套路题目了(除了针对新版libc的一些利用方式和复杂的堆风水之外),比较常出的题型有vmpwn,异构架下(mips,arm)的pwn,C++程序以及一些底层机制的漏洞。同时逆向也随之增大。要加强自己的逆向功底了。
day1
第一天是awd,平台崩了,被迫停赛。我也不多bb平台了,着眼于题目吧(质量还是挺高的)。
按题目的难易程度来吧:
pwn3
一道线程pwn题。
1 | while ( 1 ) |
漏洞在于start_routine里的__printf_chk存在fmt以及栈溢出。
1 | unsigned __int64 __fastcall start_routine(void *a1) |
__printf_chk函数会检测%n以及%()$()这样的的格式,因此只能用-%p-%p-这样泄露了。由于程序存在写回ret_addr的操作,导致栈溢出覆盖返回地址的方法失效了,这也是这道题的难点所在。
重点,记笔记:
在线程中存在一种TSL机制:如果需要在一个线程内部的各个函数调用都能访问、但其它线程不能访问的变量(被称为static memory local to a thread 线程局部静态变量),就需要新的机制来实现,这就是TLS。
我们熟知的canary就是存放在tls中,在ret时和stack中的canary比较。
其结构体为:
1 | typedef struct |
在内存中如下:
1 | rsp=0x7facd4d2bea8 |
可见,tls是在stack上的(比较下面),可以通过大范围的栈溢出覆盖。
之后我们发送’a’*0x38+p64(canary)+’a’*0x400(大范围的覆盖)之后查看程序的流程变化:
1 | 0x563b14c06207 <start_routine+110> nop |
其中__call_tls_dtors函数来析构线程本地存储,diassemble看下:
1 | pwndbg> disassemble __call_tls_dtors |
其中,mov rbx,QWORD PTR fs:[rbp+0x0]使得我们通过栈溢出改写tls的内容(也即fs),进而控制rbx,使得其不跳转并继续往下执行。
rax,QWORD PTR [rbx],rax也可控,只是要经过ror rax,0x11和xor rax,QWORD PTR fs:0x30的运算后结果为one_gadget,之后call rax拿shell。
因此我们就要提前布置好fs的内容。
exp:
1 | from pwn import * |
pwn4
第二道就是一道C++的题目了,pwnable.tw上的原题CAOV。
漏洞在于浅拷贝导致的析构函数会dele存在于stack上的指针,而这个指针指向的是我们的password结构,从而在我们可以利用chang_password()形成类似于UAF的效果。
漏洞代码:
1 | unsigned __int64 edit_info() |
效果展示:
1 | 00:0000│ rsp 0x7ffc82d0fa50 —▸ 0x1985c60 ◂— 'PB19000001' |
位于rsp+0x20的位置存在的0x6032f0会被dele,由于我们提前change_password使得构造如下:
1 | pwndbg> x/20gx 0x6032e0 |
从而会将其dele到0x70的fastbin。
之后我们考虑如何泄露:
1 | 0x603270: 0x0000000000000000 0x0000000000000000 |
往上面看发现0x0000000001985c20为一个堆地址,其heap[0]位置的指针指向存放student’s name地址的地址,因此,我们可以通过UAF将堆申请到0x603280附件(正好有0x7f可以利用),然后修改0x0001985c20为一个存放read_got的地址,可以通过change_password实现。
效果:
1 | pwndbg> x/30gx 0x6032e0-0x70 |
leak之后利用uaf打malloc_hook,拿shell。
对了,add功能是通过edit info时修改name时的new实现的,dele功能是通过析构实现的。
ruan师傅的exp:
1 | from pwn import * |
ruan师傅nb!!
stackmachine
一道vm类型的题目,除了canary其他保护全开。
程序首先初始化了sp,pc,stack,data,code段,之后输入data和code,然后run(常规的VM格式)。
先来看下初始化的情况把:
1 | pwndbg> x/20xg 0x1c48000 |
接下来就是逆指令了,我只把有用到的指令代码显示了出来:
1 | case 1u: |
解释一下
0xe:将code段里数据放入stack段中,pc+=8(相当于push)。
0x1:将以data为基地址以stack为偏移的地址的内容放入stack中。
0x2:将stack顶的数据放入以data为基地址,以stack顶的后一个的数据为偏移的地址处。(由于stack的内容可控,因此存在越界写)。
0x3:将stack里的两数相加并存入栈顶。
解体思路:
1.通过观察初始的状态可以发现:data_ptr=0x01c49070,和该指针存在的结构体的位置:0x1c48030偏移为-0x1040,因此我们可以再开始时将stack中填入puts_got+offset(0x10000000000000000-0x1040),之后再执行2操作即可修改data_ptr为puts_got。
2.由于data_ptr被修改为puts_got,因此我们在stack里放入1作为偏移,再执行1操作即可将Libc里的地址写入stack里。
1 | pwndbg> x/20gx 0x807000 |
3.之后我们通过+运算将其改为one_gadget,然后再通过步骤2在stack里写入一个libc的地址,这个地址将作为后面的offset。
由于题目保护无法改写got表,ruan师傅教我了个新姿势:
1 | ────────────────────────────────────────[ DISASM ]───────────────────────────────────────── |
在执行exit函数时,一直步入进去看到即将call的位置是rtld_lock_default_lock_recursive:
0x7f118c38cf48 (_rtld_global+3848) —▸ 0x7f118c166c90 (rtld_lock_default_lock_recursive)
这种和got表的结构类似,因此我们只要改0x7f118c38cf48 (_rtld_global+3848)指向one_gadget即可拿shell,但这个地址是lib/x86_64-linux-gnu/ld-2.23.so里的地址,虽然本地是和libc基地址的偏移固定,但远程往往是不固定的,相差0x1000的整数倍,得通过爆破解决。
4.本地既然和libc偏移固定,就会和libc里的puts偏移固定,而0x0000000000601fa0又是一个固定的地址,因此只要通过’+’运算构造地址为0x7f118c38cf48 (_rtld_global+3848)-0x0000000000601fa0,最后执行2操作即可。
构造结果:
1 | pwndbg> telescope 0x0000000000e10060+0x7f0 |
exp:
1 | from pwn import * |