首页
社区
课程
招聘
[分享] Linux PWN从入门到熟练
发表于: 2018-12-27 15:12 39026

[分享] Linux PWN从入门到熟练

2018-12-27 15:12
39026

本文首发于安全客: https://www.anquanke.com/post/id/164530

最近在复习pwn的一些知识。主要涉及到当堆栈开启了保护的时候,我们不能够直接将shellcode覆盖到堆栈中执行,而需要利用程序其他部分的可执行的小片段来连接成最终的shellcode。此小片段就是gadgets。本文主要通过练习题的方式讲述如何寻找gadgets,如何利用现有的工具来加速自己的pwn的效率。Gadgets的类型和难度也逐步变化。下面带来手把手教你linux pwn。让你的pwn技术从入门到熟练。练习题的难度逐步加大。

第一关的gadgets较为简单,包含了一个直接可以利用的,可返回shell的函数。我们只要计算好覆盖的偏移,将可返回shell函数的地址覆盖到相应的位置即可以。程序下载:Pwn1

我们首先来查看一下该程序的保护情况,发现开启了堆栈保护。即NX enabled。且是32bit的程序。因此需要在32位的linux环境下测试。

这里涉及到一个工具,chechsec。该工具专门用来检测程序中受保护的情况,我们可以根据程序受保护的情况来选择对应的pwn策略。
下载以后,直接在命令行中建立符号链接就可以在terminal中直接使用了

接下来我们利用IDA查看一下程序的源代码:


可以发现漏洞出现在gets里面,gets函数存在缓冲区溢出漏洞,我们可以通过超长的字符串来覆盖缓冲区,从而修改ROP。为了达到这个目的,我们需要首先计算,输入的&s的堆栈地址位置距离堆栈的底部ebp的位置。Ebp的下一个地址,就是记录了返回地址的位置。在32位的程序中,就是ebp+4。其中,Esp是栈顶指针,ebp是栈底指针。Esp -> ebp, 地址从小到大。小地址栈顶,大地址栈底。

我们有两种方法可以得到s距离返回地址的偏移:徒手计算和利用patternoffset产生字符串。

首先第一种方法,徒手计算。我们利用gdb的辅助工具gef来辅助查看esp地址。
注意,这里需要按照这个辅助工具,gef,该工具会提供更加丰富的调试信息。包括堆栈信息,寄存器信息等。按照完毕之后,使用gdb –q *.elf执行就可以。
启动的程序之后,我们在上述get函数的位置下断点,即0x080486AE


可以看到 esp 为 0xbfffeed0,ebp为0xbfffef58,同时 s 相对于 esp 的索引为[esp+80h-64h]= [esp+0x1c]。所以s的地址为 0xbfffeeec,所以 s 相对于 ebp 的偏移为 0x6C(108),所以相对于返回地址的偏移为 0x6c+4(112)。

另外一种方法是利用patternoffset执行来计算。借助到这个工具patternoffset。下载下来直接作为python脚本使用。利用下面的命令产生字符串到test的文件中:

接着远程IDA挂载调试,在程序的返回位置下断点,即retn的位置。


它会在远程的服务器端等待我的输入

在这个位置,我就把产生的pattern计算字符串复制进去。(注意,如果这里始终没有让程序停下来让你输入对应的字符串进去的话,就断开ubuntu的server,然后重新连接一下,就会停下来等待我们的字符串输入)

接着,查看程序覆盖的寄存器ebp的内容为0x41366441


再利用offset的脚本计算一下输入的缓冲区地址距离EBP相差多少的字节,相差的是108个字节。EBP之后,存储的就是返回的地址,所以要加上108+4=112字节的偏移。


得到的结果和上面是一致的。

接下来,我们需要找到可以利用的系统调用函数。在IDA中搜索(alt+T)可以利用来的系统sh调用函数:


最后,将需要覆盖的地址0x0804863A填入指定的位置覆盖,在利用pwntools来验证攻击。这里利用到了一个pwntools工具。推荐使用基于源代码的安装方式,可以更为方便。

安装方式为:

验证:

使用下面的脚本来验证攻击:

在这一关中,没有可以直接利用的system()函数让我们直接调用了。我们可以学习使用系统调用来进行操作。系统调用的背景知识在这里
Pwn2

Syscall的函数调用规范为: execve(“/bin/sh”, 0,0);
它对应的汇编代码为:

同样的,首先利用工具来查看程序保护情况:


查看程序的代码,发现同样是gets造成的函数溢出。

因此我们这里需要人为的构造了。这里需要用到一个工具,来查到能够控制eax,ebx,ecx,edx。就是ROPgadget。下载之后,直接安装

就可以使用了。执行命令,来查找对一个的汇编指令:

其中—binary 表示目标二进制的路径,--only 表示只显示指定的汇编语句, grep可以展示想要的寄存器。

针对eax选择,0x080bb196 : pop eax ; ret


针对ebx和ecx选择,0x0806eb91 : pop ecx ; pop ebx ; ret


针对edx,选择,0x0806eb6a : pop edx ; ret


执行命令,筛选int 0x80的系统调用, 选择:0x08049421


执行命令,筛选字符串,得到:0x080be408


这里选择的每一个gadgets都含有ret是为了能够使得程序自动持续的选择堆栈中的指令依次执行。在构造这些gadgets之前,我们通过下面的堆栈指针移动图,来分析一下eip指针的移动,以及对应获取的数据内容。ret指令可以理解成去栈顶的数据作为下次跳转的位置。即,
eip = [esp];
esp = esp+4;
或者简单理解成: pop eip;

上图中,左边显示的堆栈的内容,右边是对应的代码。数字表示的是,运行到特定的汇编指令的时候,esp指针的位置。总结下来,我们通过pop指令来移动esp指针获取数据,比如字符串/bin/sh,我们通过ret指令来同样移动esp指针来获取下一条执行的命令。这样,我们就能够在不需要与堆栈中执行程序的情况下,顺利的控制程序控制流的执行。

最终形成的shellcode利用pwntools的代码为:

这一关中,我们主要通过导入函数里面的system(“/bin/sh”)函数来完成调用。
Pwn3


发现它的保护也是类似的。该程序与之前类似,都是在gets函数存在漏洞。

首先查找system函数是否存在,利用IDA查看。
查看导入函数表,发现有system的外部调用函数在列表里面,


从而确定地址为0x08048460。

在利用下面的命令查找”/bin/sh”的字符串,确定了字符串的地址为0x08048720

那么就可以依葫芦画瓢的构造shellcode了。

这里解释一下,为什么会有4个字节空余的部分。
这里的部分,在正常调用system函数的时候,堆栈位置的system_plt之后的内容为system函数的返回地址,在之后才是新的堆栈的栈顶位置,因此在system_plt和sh_addr之间增加了4个字符来进行填充。


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2019-3-5 09:22 被二当家a编辑 ,原因: 笔误,修正。
收藏
免费 6
支持
分享
最新回复 (29)
雪    币: 26245
活跃值: (63297)
能力值: (RANK:135 )
在线值:
发帖
回帖
粉丝
2
感谢分享!
2018-12-27 16:24
0
雪    币: 14517
活跃值: (17538)
能力值: ( LV12,RANK:290 )
在线值:
发帖
回帖
粉丝
3
LZ,第一个PWN的链接挂了,麻烦重发一下好吗,谢谢
2018-12-27 20:30
0
雪    币: 6303
活跃值: (2051)
能力值: ( LV7,RANK:150 )
在线值:
发帖
回帖
粉丝
4
pureGavin LZ,第一个PWN的链接挂了,麻烦重发一下好吗,谢谢
ok,更新了
2018-12-27 20:39
0
雪    币: 14517
活跃值: (17538)
能力值: ( LV12,RANK:290 )
在线值:
发帖
回帖
粉丝
5
二当家a ok,更新了
Thanks♪(・ω・)ノ
2018-12-27 21:44
0
雪    币: 1421
活跃值: (162)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
想问下LZ这个错误是什么原因造成的,一直这样
[+] Starting local process './messageb0x': pid 124
[*] '/mnt/c/Users/123/Desktop/messageb0x'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
Traceback (most recent call last):
  File "messageb0x.py", line 19, in <module>
    write_addr = u32(r.recv()[0:4])
  File "/usr/local/lib/python2.7/dist-packages/pwnlib/context/__init__.py", line 1392, in setter
    return function(*a)
  File "/usr/local/lib/python2.7/dist-packages/pwnlib/util/packing.py", line 334, in routine
    ("big",    False):  bu}[endian, signed](number)
  File "/usr/local/lib/python2.7/dist-packages/pwnlib/util/packing.py", line 299, in routine
    return ops[op](fmt,data)
  File "/usr/local/lib/python2.7/dist-packages/pwnlib/util/packing.py", line 284, in <lambda>
    ops   = {'p': struct.pack, 'u': lambda *a: struct.unpack(*a)[0]}
struct.error: unpack requires a string argument of length 4
[*] Stopped process './messageb0x' (pid 124)

2018-12-28 10:34
0
雪    币: 18
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
你u32里面的字符串不够4字节就报这个错
2018-12-30 13:18
0
雪    币: 545
活跃值: (247)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
8
mark
2019-1-17 20:17
0
雪    币: 228
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
贴主。第一个python代码中的target。。。换成sh吧。。否则新手肯定蒙
2019-2-23 01:46
0
雪    币: 6303
活跃值: (2051)
能力值: ( LV7,RANK:150 )
在线值:
发帖
回帖
粉丝
10
GodofOrange 贴主。第一个python代码中的target。。。换成sh吧。。否则新手肯定蒙
多谢,已改。
2019-2-23 09:23
0
雪    币: 359
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
第一个pwn 如果只用IDA的话 char s; // [esp+1Ch] [ebp-64h]
如何计算 返回函数地址?
64h+4? 不对呀...
2019-3-4 11:53
0
雪    币: 359
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
pwn2中ROPgadget寻找gadget。
从pop_eax_ret到int+0x80都与帖子相同
唯独 /bin/sh字符串找到的地址是0x080BE42C,而题目中找到的是0x080BE408.
我用自己找到的0x080BE42C就不能getshell,用题目的0x080BE408就能getshell。
这是为什么呢
2019-3-4 13:42
0
雪    币: 359
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13

pwn1的截图
之前见了一个栈概念图
ESP在上EBP在下
返回地址应该是EBP+4
请问大佬这里是特殊情况,还是写错了?
QAQ

2019-3-4 13:53
0
雪    币: 6303
活跃值: (2051)
能力值: ( LV7,RANK:150 )
在线值:
发帖
回帖
粉丝
14
第一个问题。直接IDA 看偏移,有的时候确实会不对。 所以用工具溢出覆盖算偏移是最准确的。可以尝试这个工具:https://github.com/desword/shellcode_tools/blob/master/getOverFlowOffset.py

第二个问题:也许是ROPgadget的问题?你可以尝试gdb调试,然后看看载入字符串地址之后,对应的内容是不是bin/sh

第三个,笔误,应该是EBP,已更正。
2019-3-5 09:20
1
雪    币: 51
活跃值: (607)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
15
谢谢大佬的入门提点,在这四道题中收获很多,学到了很多知识.
2019-3-30 19:12
0
雪    币: 359
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16

见鬼了 同样是ROPgadget。py脚本的和pip install的 结果不一样
图片描述

最后于 2019-5-16 22:46 被leepandapia编辑 ,原因:
2019-5-16 22:45
0
雪    币: 1760
活跃值: (823)
能力值: ( LV3,RANK:38 )
在线值:
发帖
回帖
粉丝
17
你好,楼主,这里通过ALT+T来搜索,如果搜索'/bin/sh'关键字找不到的话,搜索其它的关键字应该搜索什么呢?
2019-6-6 14:06
0
雪    币: 1760
活跃值: (823)
能力值: ( LV3,RANK:38 )
在线值:
发帖
回帖
粉丝
18
入门精贴,感谢楼主
2019-6-7 20:11
0
雪    币: 14517
活跃值: (17538)
能力值: ( LV12,RANK:290 )
在线值:
发帖
回帖
粉丝
19
leepandapia 见鬼了 同样是ROPgadget。py脚本的和pip install的 结果不一样 ![图片描述](upload/tmp/847238_655QSR5XAUZUF98.png)
是不是开了ASLR??
2019-6-7 21:09
0
雪    币: 20
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
楼主,链接又挂了,可以再补下链接吗?感谢
2019-7-7 19:53
0
雪    币: 6303
活跃值: (2051)
能力值: ( LV7,RANK:150 )
在线值:
发帖
回帖
粉丝
21
社区送温暖 楼主,链接又挂了,可以再补下链接吗?感谢[em_84]
哪条链接?
2019-7-8 10:47
0
雪    币: 20
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
二当家a 哪条链接?
貌似都挂了,每次点击后跳转到https://raw.githubusercontent.com/desword/pwn_execrise/master/pwn_basic_rop/pwnx,刷新不出来。
2019-7-8 19:00
0
雪    币: 6303
活跃值: (2051)
能力值: ( LV7,RANK:150 )
在线值:
发帖
回帖
粉丝
23
社区送温暖 貌似都挂了,每次点击后跳转到https://raw.githubusercontent.com/desword/pwn_execrise/master/pwn_basic_rop/pwnx,刷新不出来 ...
我这里试着都可以的。直接就会下载文件了。你直接在这里下载也可以:https://github.com/desword/pwn_execrise/tree/master/pwn_basic_rop
2019-7-8 20:41
0
雪    币: 159
活跃值: (695)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
24
还是不能理解为什么ret2libc的时候 system_plt 和  arg0 中间为啥要用4字节的padding
2019-11-1 16:43
0
雪    币: 159
活跃值: (695)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
25
xiaozhu头 还是不能理解为什么ret2libc的时候 system_plt 和 arg0 中间为啥要用4字节的padding
噢噢,因为 main函数的 ret之前有个leave。先恢复ebp,再恢复eip
2019-11-1 17:09
0
游客
登录 | 注册 方可回帖
返回
//