首页
社区
课程
招聘
[旧帖] [原创]反汇编学习笔记--溢出初探(存疑) 0.00雪花
发表于: 2008-9-30 11:06 3981

[旧帖] [原创]反汇编学习笔记--溢出初探(存疑) 0.00雪花

2008-9-30 11:06
3981
原题如下: 求这个函数的输出结果:
#pragma pack(1)
struct Node
{
char  a[10];
short b;
};
void func()
{
int i;
    struct Node c[2];
   
    for(i=0;i<2;i++)
    {
  c[i].b = i;
  sprintf(c[i].a,"This is a %d\n",i);
  printf("%d,  %s",c[i].b,c[i].a);
    }
}
近日学习了一点反汇编知识,看到蓬蓬发的此题,涉及到数据的内存对齐以及利用溢出(exploit)来改变
原程序的执行流程,下面是我对该程序反汇编结果以及部分注释。
注意:为了调试方便 我把循环20次改为了2次,但效果是一样的
1:    // define.cpp : Defines the entry point for the console application.
2:    //
3:
4:    #include "stdafx.h"
5:    #include <stdio.h>
6:    #include <stdlib.h>
7:
8:   
9:
10:   #pragma pack(1)        
;设置数据按1字节对齐,如果不设则按照编译器设定的值处理(编译器相关)
11:   struct Node
12:   {
13:       char  a[10];
14:       short b;
15:   };
16:
17:   void func()
18:   {
00401020   push        ebp      
00401021   mov         ebp,esp   ;这两行保存堆栈信息,函数结束时进行恢复 这是Windows调用规范
00401023   sub         esp,5Ch   ;创建该函数的内部堆栈(用于存储局部变量)
00401026   push        ebx     
00401027   push        esi
00401028   push        edi     ;这三行保存三个寄存器:ebx,esi,edi,这是C调用规范
00401029   lea         edi,[ebp-5Ch] ;将ebp-5ch,也就是sp的值放入edi
0040102C   mov         ecx,17h
00401031   mov         eax,0CCCCCCCCh
00401036   rep stos    dword ptr [edi]
;这三行是对堆栈内容进行初始化,0xCC是INT3中断,这是debug模式下的初始化内存模式
19:       int i;
20:       struct Node c[2];
21:
22:       for(i=0;i<2;i++)
00401038   mov         dword ptr [ebp-4],0  ;ebp-4即为i在堆栈的地址,以后要访问i就通过ebp-4
0040103F   jmp         func+2Ah (0040104a)  ;跳转至循环条件判断处
00401041   mov         eax,dword ptr [ebp-4]
00401044   add         eax,1
00401047   mov         dword ptr [ebp-4],eax ;这三行是控制循环变量也就是实现i++
0040104A   cmp         dword ptr [ebp-4],2  ;判断i<2
0040104E   jge         func+81h (004010a1)  ;如果>=2则跳出循环
23:       {
24:           c[i].b = i;
00401050   mov         ecx,dword ptr [ebp-4]
00401053   imul        ecx,ecx,0Ch
00401056   mov         dx,word ptr [ebp-4]
0040105A   mov         word ptr [ebp+ecx-12h],dx
;这四行是给结构体成员赋值,需要注意的结构体数组中成员访问的方式,先去i,然后i*sizeof(node),
;然后用数组的基地址+i*sizeof(node),然后再取结构体内的c[i].b成员(ebp+ecx-12h)
25:           sprintf(c[i].a,"This is a %d\n",i);
0040105F   mov         eax,dword ptr [ebp-4]
00401062   push        eax
;函数调用时的压栈: 从右->左  先压 i
00401063   push        offset string "This is a %d\n" (0041ca58)
;压入 字符串的地址
00401068   mov         ecx,dword ptr [ebp-4]
0040106B   imul        ecx,ecx,0Ch
0040106E   lea         edx,[ebp+ecx-1Ch]
00401072   push        edx
;再压入c[i].a,注意取c[i].a的方式 和上面取c[i].b的方式是一样的
00401073   call        sprintf (004011f4)    ;Call 函数调用
00401078   add         esp,0Ch         ;恢复调用后的堆栈指针
26:           printf("%d,  %s",c[i].b,c[i].a);
0040107B   mov         eax,dword ptr [ebp-4]
0040107E   imul        eax,eax,0Ch
00401081   lea         ecx,[ebp+eax-1Ch]
00401085   push        ecx           ;调用前的压栈c[i].a
00401086   mov         edx,dword ptr [ebp-4]
00401089   imul        edx,edx,0Ch
0040108C   movsx       eax,word ptr [ebp+edx-12h]
00401091   push        eax           ;调用前的压栈c[i].b
00401092   push        offset string "%d,  %s" (0041ca4c)  ;压栈
00401097   call        printf (0040119e)    ;调用
0040109C   add         esp,0Ch         ;恢复调用栈
27:       }
0040109F   jmp         func+21h (00401041)   ;跳转至循环开始处0x00401041
28:   }
004010A1   pop         edi         ;函数结束时的 恢复工作
004010A2   pop         esi
004010A3   pop         ebx         ;函数调用入口时压栈,结束时退栈,恢复原始状态的寄存器
004010A4   add         esp,5Ch       ;恢复esp
004010A7   cmp         ebp,esp      
004010A9   call        _chkesp (004012e6)  ;校验是否ebp==esp,如果不等则表示函数调用有错,会报异常
004010AE   mov         esp,ebp      
004010B0   pop         ebp         
004010B1   ret
大家可以把原程序在VC编译运行下,会发现是一个死循环的打印,从源程序看次数应该是2次才对,
为什么会是死循环了?原因就在于 sprintf 这个函数上,在func()入口初始化 i 和 Node c[2]时,
它们在堆栈中的地址是连续在一起的,sprintf函数在调用后导致的结果就是把i(ebp-4)的值清0了,
也就是说 每次i++完后,调用sprintf又会把i给置0,因此i永远是0或1。
还有一点就是被sprintf同时被覆盖的c[i].b这个值,这个值也将永远是2608或2609,为什么会这样?
原因就在于c[i].b(ebp+ecx-12h)被 30 0A 或者 31 0A,Intel的x86机器是little-ending的,
其原始值就是 2608 或 2609 了。 下面是调试期间堆栈的内存情况 此时ebp=0012FF18 i=ebp-4=0012FF14
0012FEF8  CC CC CC CC  烫烫
0012FEFC  54 68 69 73  This
0012FF00  20 69 73 20   is
0012FF04  61 20 30 0A  a 0.
0012FF08  54 68 69 73  This
0012FF0C  20 69 73 20   is
0012FF10  61 20 31 0A  a 1.
0012FF14  00 00 00 00  ....
0012FF18  78 FF 12 00  x...
好了,我的分析也就到此了,如果有感兴趣的可以更进一步交流。
我也是刚刚学习此方面内容,有些地方可能理解的不够到位或者错误的地方,还请大牛们给与指正。
本文来源于栀子博客-栀子花驿站 http://www.zhizihua.com/blog/ , 原文地址:http://www.zhizihua.com/blog/post/反汇编学习笔记--溢出初探.html

让我搞不明白的是
sprintf(c[i].a,"This is a %d\n",i);
会把 c[i].b的两个字节给覆盖掉 就像堆栈中所显示的那样
0012FF10  61 20 31 0A  a 1.  中的 31 0A 是
c[i].b的值  被 1(0x31) 和 \n (0x0A) 覆盖
但是0012FF14  00 00 00 00  ....  也就是 i 所在的位置 为什么也被覆盖成0x00了?
其中的原理是什么?
难道是返回值吗?但是从MSDN里查到的结果是sprintf 函数返回的是 成功拷贝的字符数
我跟踪了 返回值也不是 0x00......

这彻底让我迷惑了。。。。。
我在debugman的公开群里求助无果,因此发此贴来向大家请教,请巨牛们帮看看,谢谢先!

[课程]FART 脱壳王!加量不加价!FART作者讲授!

收藏
免费 0
支持
分享
最新回复 (6)
雪    币: 2067
活跃值: (82)
能力值: ( LV9,RANK:180 )
在线值:
发帖
回帖
粉丝
2
本来是 0012FF14  01 00 00 00        <- i的值
被字符串最后的 00 盖掉了, 变成
       0012FF14  00 00 00 00        <- i的值

"This is a 1",0A,00  <- 字符串
2008-9-30 11:24
0
雪    币: 233
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
注意 0A,00 覆盖掉的是  c[1].b的值 而不是 i

从跟踪的来两次循环来看
第一次的时候i不会收到影响 i++ 使其从 0变为1  c[0].b 会被覆盖成 30 0A  c[1].a中一位会被覆盖成00
第二次的时候 c[1].b 被修改成 31 0A  
sprintf 只会覆盖 c[1].b的值 i的值到底是谁改的?
是什么原理了?
2008-9-30 11:52
0
雪    币: 2067
活跃值: (82)
能力值: ( LV9,RANK:180 )
在线值:
发帖
回帖
粉丝
4
char a[10];          <- 10个byte
short b;             <- 2 个byte

This is a 1,0A,00    <- 13个byte

谁说只盖到 b ?
都盖到 i 的 low byte 了

当 i=0 :
0012FEFC  54 68 69 73  This
0012FF00  20 69 73 20   is
0012FF04  61 20 30 0A  a 0.
0012FF08  00 CC CC CC
0012FF0C  CC CC CC CC
0012FF10  CC CC CC CC
0012FF14  CC CC CC CC
当 i=1 :
0012FEFC  54 68 69 73  This
0012FF00  20 69 73 20   is
0012FF04  61 20 30 0A  a 0.
0012FF08  54 68 69 73  This
0012FF0C  20 69 73 20   is
0012FF10  61 20 31 0A  a 1.
0012FF14  00

最后这个 00 就是盖到 i 的 low byte
2008-9-30 12:16
0
雪    币: 2067
活跃值: (82)
能力值: ( LV9,RANK:180 )
在线值:
发帖
回帖
粉丝
5
[QUOTE=goodone;514745]注意 0A,00 覆盖掉的是  c[1].b的值 而不是 i 
[/QUOTE]

31 盖到 b 的 Low Byte
0A 盖到 b 的 High Byte
00 盖到 i 的 Low Byte
自己数数看吧
上传的附件:
2008-9-30 12:29
0
雪    币: 233
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
sprintf  结束后,覆盖确实覆盖了13个byte的数据
导致的结果吧 c[i].b 2byte 和 i(4个byte) 的最低一位的btye也被覆盖了
覆盖为 0 了。

这些都是由于sprintf()造成的
本以为是\n(0x0A)是字符串的结束,
但是后面似乎 还多出了一byte 0x00
难道所有的这种构造字符串的都是已 0x00结束吗?
2008-9-30 14:15
0
雪    币: 233
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
还是把题结了吧。
问题就在sprintf()函数,该函数在构造 字符串时
\n(0x0A) 会占一位 然后后面还有 0x00 做为结束符
就是这样最后多出的一位0x00把 i的值给覆盖掉了

其实文章中我的分析注释已经基本写清楚了,只是觉的
怎么会多加一位 0x00,因为在我的脑海中记得是 \n就是字符串的结束符了啊
这次是我第一次在看雪发帖。。。
文章比较粗浅,只是自己的在学习反汇编中的一篇笔记。
要学的东西好多啊,革命尚未成功,我还需继续努力啊!
谢谢 sessiondiy 的热情回帖。
2008-10-6 15:42
0
游客
登录 | 注册 方可回帖
返回
//