首页
社区
课程
招聘
[原创]pwnablr.kr (刷题)之 Toddler's Bottle
发表于: 2018-3-23 20:37 6436

[原创]pwnablr.kr (刷题)之 Toddler's Bottle

2018-3-23 20:37
6436

本来打算寒假的时候把这个题认真的刷一刷,结果...最近几天,把pwnable.kr上前2部分的题刷了刷,写了写解题的过程。

根据提示,本题的关键在于文件描述符
ssh连接后,查看源文件:

根据维基百科上对于 file descriptor的定义:

fd 的源码可知, 标准输入的内容为字符串LETMEWIN ,并且fd 的值为 0,所以第一个命令行参数应为0x1234 的 10进制 4660, 通过管道符|将 字符串输入程序,即可获得flag

echo 'LETMEWIN' | ./fd 4660

连接成功后,可以看到源码:

发现这个题的要求就是第一个命令行参数长度为20个字节,且经过函数check_password处理后返回的值应该为 0x21DD09EC,
所以在这里的话,我们选择 [0x21DD09EC/5]= 0x6c5cec8 + 0x6c5cecc 来作为参数输入。

可以从网站上下载下来两个文件bofbof.c,可以看到是一道简单的缓冲区溢出题:

gdb 简单的调试下,可以发现实际开辟的栈空间大小为0x48

我们只需要传入"A*52" + "0xcafebabe" 即可获得shell:

但是需要注意的一点是,系统只是调用了system函数,没有发现输入的话就会终止掉,所以我们可以向它传递一个标准输入:

(ps: 以前,我曾经一度认为函数func传的参数就是0xcafebabe,还在想这个题要干啥)

题目提示是一个加壳的文件,下载下来后看一下:

然后我们用UPX工具脱壳

给文件添加运行权限chmod u+x flag:

gdb简单地 在main处下个断点,发现运行到0x401184 <main+32>: mov rdx,QWORD PTR [rip+0x2c0ee5] # 0x6c2070 <flag>这条指令时,将flag的内容传入了RDX寄存器:

got表覆盖技术

原理:由于got表是可写的,把其中的函数地址覆盖为我们shellcode地址,在程序进行调用这个函数时就会执行shellcode。由于本题中函数scanf 的 第二个参数没有加&,传递的是值而不是地址。程序首先需要输入name,然后输入passcode1passcode2,相等的话会输出flag。

经过分析, namepasscode1间差了96个bytes,最后四个字节填充为got表中printf的地址(0x804a000),这样就能在读取passcode1的时候,将system('/bin/sh')的地址(0x80485e3)写入got表中,得以执行。

这个题 比较简单,程序随机化时不指定seed 的话,生成的随机数都是一样的。本地测试下,随机数为1804289383,将其与0xdeadbeef 异或,就可以得到正确的key = 3039230856

这个题考察了5种输入方式:

这题需要完成的就是实现这5步输入,然后就可以看到flag。

1.stage 1

编译运行一下,可以看到第一阶段已经完成:

2.stage 2

第二步是标准输入read(0, buf, 4),这个在第一题已经见过,但是由于\x00\x0a\x00\xff无法通过命令行输入,stderr也是这样,所以需要重定向输入,即fork创建子进程,pipe进行管道传输可以参考这篇文章,理解管道传输,目前的代码为:

这一步用到了fork()函数,作用是从父进程中打开一个子进程,这个函数会有2个返回值,一次是父进程,一次是子进程,而且 fork() 函数在子进程中返回 0,而在父进程中返回子进程的 PID,所以可以通过判断 fork() 函数返回值来分别对父/子进程进行操作。

3.stage 3

第三步是环境变量,char *envp[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL};

4.stage 4

第四步是文件读写,需要在文件\x0a中读取字符,判断是否为”\x00\x00\x00\x00″,所以把源码中的读操作改为写操作即可:

5.stage 5

第五步是socket 编程,和上一步类似,只需要把接收改为发送即可,但需要注意的是端口号port是通过argv['C']控制的,代码如下:

下面是完整的exp.c:

(ps:在网上也发现了python版本的实现,比较简洁,如http://www.akashtrehan.com/writeups/pwnablekr_todders_bottle/,但是我在测试的时候,发现第5步老是出问题...)

我们可以从网站上下载下来leg.cleg.asm 2个文件,简单地看下leg.c的源码,可以知道这题的关键是找到key1()key2()key3() 三个函数的 返回值:

所以直接看3个函数的汇编代码:

key1()

以前做的都是x86汇编,没有做过arm相关的,所以简单地查了一下相关的信息:

PC代表程序计数器,流水线使用三个阶段,因此指令分为三个阶段执行:
1.取指(从存储器装载一条指令);
2.译码(识别将要被执行的指令);
3.执行(处理指令并将结果写回寄存器)。

而R15(PC)总是指向“正在取指”的指令,而不是指向“正在执行”的指令或正在“译码”的指令。一般来说,人们习惯性约定将“正在执行的指令作为参考点”,称之为当前第一条指令,因此PC总是指向第三
条指令。当ARM状态时,每条指令为4字节长,所以PC始终指向该指令地址加8字节的地址,即:PC值=当前程序执行位置+8;

ARM指令是三级流水线,取指,译指,执行时同时执行的,现在PC指向的是正在取指的地址,那么cpu正在译指的指令地址是PC-4(假设在ARM状态下,一个指令占4个字节),cpu正在执行的指令地
址是PC-8,也就是说PC所指向的地址和现在所执行的指令地址相差8。

key()1 中,返回地址为r0, r0 = pc,所以r0 的地址为0x8cdc + 0x8

key2():

返回地址为r3,bx r6跳转为thumb指令,r0为0x8d04 + 0x4 + 0x4,即0x8d0c

key3():

r0的值 为 lr 的 值,lx寄存器 在函数调用时,将下一条指令地址传给lx寄存器,等待函数返回时调用,所以r0 = 0x8d80

三个返回值加起来 = 0x1a770,也就是108400

看下题目源码,又看到了熟悉的read(fd,pw_buf,PW_LEN)

由于操作符的优先级问题,=为14,而 <为6
在下面这条语句中
if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0){fd 的值为0,

所以当执行到if(!(len=read(fd,pw_buf,PW_LEN) > 0))时,实际上是从标准输入流读入了10个字节的内容:

`if(!(len=read(0,pw_buf,PW_LEN) > 0))``
这样,我们只需要先输入10个字符,再输入10个字符异或后,与前者相同。
我们可以考虑一种简单的情况,比如先输入10个0,再输入10个1。

根据百度百科介绍,Shellshock,又称Bashdoor,是在Unix中广泛使用的Bash shell中的一个安全漏洞。我们可以用export x="() { :;}; /bin/cat flag",添加以(){开头的环境变量,后面执行./shellshock的时候,会以root权限执行之前的命令。

nc pwnable.kr 9007 后,看到这个题是一个二分法的题目,找到假硬币,但是要在30s 内解100 次 才会print flag,直接放上python脚本:

另外,远程连接的话可能会超时,所以可以放到服务器的/tmp 文件夹下运行一下,即可。

这个题的第一印象就是源码太长了,让人很难看下去。。

百度了一下,才知道其实这个是一种扑克牌游戏。black jack是扑克牌游戏中的一种游戏规则,中文名为21点或者黑杰克。

检查了下代码中的输入,发现了betting函数中存在漏洞:

用户输入bet后,会判断是否大于500,却没有判断是否小于0。所以,我们输入-1000000,再输掉的话,实际上就会赢得1000000,这样就可以获得flag了。

这个题意思也比较明显,就是说只要我们输入的字符中有一个和随机生成的lotto数组中的字符相等的话,就会获得flag。所以,我们只需要输入6个一样的字符,直到获得flag为止:

可以看到,在cmd1中,没有对/字符进行过滤,因此我们可以传入绝对路径。只要想办法将/bin/cat flag作为第一个命令行参数输入,绕过过滤的话,即可查看flag的内容,比如一种做法就是/bin/cat fla*

可以看到,在cmd2中,对/字符进行了过滤,所以我们要想办法构造出来/字符,而pwd命令可以打印出当前路径,如果我们切换到根目录下的话,不就能够利用/字符了吗?

所以,我们切换到根目录下,执行下面的命令:

ps: 其实这个题的解法还是特别多的,比如下面这种

根据题目提示,是UAF漏洞,下载源码:

用64 位的IDA打开UAF, 可以看到Woman对象大小为24个字节:

在下面的rodata数据段,可以看到虚表的地址:

而函数的调用就是这个虚表指针+偏移量,比如Human->give_shell就是 vTable_ptr + 0,所以只需要修改一个指针,我们只需要调用give_shell函数,就可以得到shell,后面函数需要调用introduce函数,地址为vTable + 8, 即 0x0000000000401568。所以,我们先将这个地址写入文件中,然后作为文件输入:

然后 3->2->2->1 即可获取到shell:

根据题目,我们可以下载memcpy.c

简单看下文件,两个字节复制函数slow_memcpyfast_memcpy,前者是逐字节复制,后者是利用xmm寄存器无cache复制,不足64个字节的话调用slow_memcpy

使用代码第一行所注释的编译选项gcc -o memcpy memcpy.c -m32 -lm,编译文件:

运行程序,看看发生了什么:

使用gdb调试程序,发现执行到fash_memcpy的时候,发生了异常:

fash_memcpy函数用到了[movntps](https://baike.baidu.com/item/SSE%E6%8C%87%E4%BB%A4/8857860),参见百度百科解释,大部分涉及到128位内存变量操作的,内存变量首地址必须要对齐16字节,也就是内存地址低4位为0,否则会引起CPU异常,导致指令执行失败,此错误编译器不检查。

所以本题的关键就在于malloc 函数 所申请的字节大小,下图程序可以计算malloc程序实际申请的内存大小,只要size可以整除16,就满足本题的要求:

下面是示例代码:

根据目录下的文件readme提示:我们只要连接到本地服务器的9026端口,执行asm 程序可以获取到flag文件,flag文件名称为this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong

程序首先申请一块内存,用0x90(nop)清零,再将字符串stub 的 内容复制到刚刚申请的内存空间中去,读取用户输入,执行程序。反汇编一下stub,发现其功能是将全部的寄存器清零。

之后执行sandbox函数(seccomp是一种安全运行模式),由于沙盒只允许运行open、read、write函数,所以我们可以使用x64的rax, rsp寄存器存放我们需要的内容,然后输出到标准输出流。我们可以使用pwntools工具中的shellcraft模块来编写我们的shellcode,来读取/home/asm_pwn目录下的flag文件中的内容(关于shellcraft模块的使用,可以参考官方文档)。

完整的 exp 脚本如下:

to be ...

(其实,关于第一部分的wp,网上特别多,我写本篇文章的目的权当记录一下学习过程吧)

 
 
 

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2018-3-23 20:38 被fyb波编辑 ,原因:
收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//