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 ( *(&notelist + v1) )
  {
    free(*(*(&notelist + v1) + 4));
    free(*(&notelist + 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      0x0000000000000000

ctf场景下的场景漏洞格式
https://k3ppf0r.pages.dev/2021/03/11/二进制安全/PWN/ctf场景下的场景漏洞格式/
作者
k3ppf0r
发布于
2021年3月11日
许可协议