ctf场景下的场景漏洞格式
C语言漏洞
数组越界漏洞
v0定义为unsigned int,scanf输入是 “%d” (正确的是 “%u” ),if 判断语句只判断上界,不判断下界(漏洞根基)。
void sub_400DD6()
{
unsigned int v0; // [rsp+Ch] [rbp-14h] BYREF
void *buf; // [rsp+10h] [rbp-10h]
unsigned __int64 v2; // [rsp+18h] [rbp-8h]
v2 = __readfsqword(0x28u);
v0 = 0;
printf("Index: ");
__isoc99_scanf("%d", &v0);
if ( v0 <= 2 && qword_602140[v0] )
{
buf = (void *)qword_602140[v0];
write(1, buf, 0x10uLL);
puts("\nDone");
}
else
{
puts("error.");
}
}格式化字符串漏洞
printf(format)
fprintf(stdout,format,"%s")栈溢出漏洞
sprintf 写法漏洞
程序从字符串构造文件名,为打开文件做准备
#include <stdio.h>
...
void func(const char *name) {
char filename[128];
sprintf(filename, "%s.txt", name);
}修复:
snprintf(filename, sizeof(filename), "%s.txt", name);堆漏洞
off-by-null漏洞:
关键在于退出时序号是什么值明确即可。
for循环判断处有=:
先写入再加序号
for循环的condition 里存在等于(=)数量,下标溢出1个
...
v3 = malloc((int)v1);
printf("des info:");
for ( i = 0; i <= (int)v1; ++i )
//漏洞点:循环从0开始,有 '=' 就会多写一个byte
{
read(0, &v3[i], 1uLL);
if ( v3[i] == '\n' )
{
v3[i] = 0;
break;
}
}
// 循环执行完退出时i为v1+1
v3[i] = 0;
...循环内退出有=:
先写入再加序号
ida反汇编的代码里,将for循环的condition 退出写成if… break;
漏洞代码: if 序号==len(array) 退出,此时退出时,buf写入到buf+number,
signed __int64 __fastcall my_read(_BYTE *ptr, int number)
{
int i; // [rsp+14h] [rbp-Ch]
_BYTE *buf; // [rsp+18h] [rbp-8h]
if ( number <= 0 )
return 0LL;
buf = ptr;
for ( i = 0; ; ++i )
{
if ( (unsigned int)read(0, buf, 1uLL) != 1 )
return 1LL;
if ( *buf == '\n' )
break;
++buf;
// 循环从0开始,有 '=' 就会多写一个byte
if ( i == number )
// 此时退出,i还没加,buf已经加了
break;
}
// 循环执行完退出时i为number, buf为ptr+number+1
*buf = 0;
return 0LL;
}堆溢出漏洞:
漏洞点:编辑写入时能指定写入的大小
编辑堆块时:
__int64 __fastcall fill(__int64 a1)
{
__int64 result; // rax
int v2; // [rsp+18h] [rbp-8h]
int v3; // [rsp+1Ch] [rbp-4h]
printf("Index: ");
result = my_read_7();
// 临时变量,存储索引
v2 = result;
// 索引要小于16
if ( result <= 0xF )
{
// a1 是管理堆的数据结构的首地址
result = *(24LL * result + a1);
// 如果堆块是使用状态
if ( result == 1 )
{
printf("Size: ");
// 漏洞点: 可自定义堆块写入大小
result = my_read_7();
// 临时变量,存储自定义堆块写入大小
v3 = result;
if ( result > 0 )
{
printf("Content: ");
return read_vuln(*(24LL * v2 + a1 + 16), v3);
}
}
}
return result;
}UAF漏洞
删除堆块时:
出现在删除申请的堆块的代码中:
void del_note()
{
char buf[4]; // [esp+8h] [ebp-10h] BYREF
int v1; // [esp+Ch] [ebp-Ch]
printf("Index :");
read(0, buf, 4u);
v1 = atoi(buf);
if ( v1 < 0 || v1 >= count )
{
puts("Out of bound!");
_exit(0);
}
if ( *(¬elist + v1) )
{
free(*(*(¬elist + v1) + 4));
free(*(¬elist + v1));
puts("Success");
}
}ida识别的怪代码
很怪的read写法(一):
// a1 是要写入的buf地址,a2是字节数Bytes
unsigned __int64 __fastcall func_read(__int64 a1, unsigned __int64 a2)
{
unsigned __int64 v3; // [rsp+10h] [rbp-10h]
ssize_t v4; // [rsp+18h] [rbp-8h]
if ( !a2 )
return 0LL;
v3 = 0LL;
//
while ( v3 < a2 )
{
v4 = read(0, (v3 + a1), a2 - v3);
// 如果read不报错
if ( v4 > 0 )
{
v3 += v4;
// 执行完后v3 = a2
}
// 如果read报错
else if ( *_errno_location() != 11 && *_errno_location() != 4 )
{
return v3;
}
}
// 退出循环,v3=a2=输入的字节数
// 缺陷: 不加截断*(_BYTE *)(v3 + a1) = 0;
return v3;
}
用数字给字符串赋值
由于内部逻辑的原因,内存里的二进制数据均用小端序存储。对于一个字符串赋值,往往正确方法用的是strcpy系统api为我们完成转换。但也可以这样:
char dest[8];
*(_QWORD *)dest = 0x20676E6970LL;// ' gnip'将dest指针解释为8字节,赋值十六进制数字,在内存中被小端序存储,十六进制正好就是内存中数字存储形态,故在ASCII码解释下,形成了字符串'ping '
内存中数据均是以数字的逻辑存在着!
pwndbg> x/10gx 0x7fffffffda00
0x7fffffffda00: 0x00000020676e6970 0x0000000000000000ctf场景下的场景漏洞格式
https://k3ppf0r.pages.dev/2021/03/11/二进制安全/PWN/ctf场景下的场景漏洞格式/