ღ Miranda

pwnable.kr的PWN挑战之Toddler’s Bottle(一)

pwnable.kr is a non-commercial site which provides various pwn challenges regarding system exploitation. the main purpose of pwnable.kr is fun.

refer:
https://gist.github.com/ihciah/0ca68da3e32e38818bb9
http://rickgray.me/2015/07/24/toddler-s-bottle-writeup-pwnable-kr.html
http://weaponx.site/2017/02/21/unlink-Writeup-pwnable-kr/
https://github.com/Qwaz/solved-hacking-problem/tree/master/pwnable.kr

fd-1pt

ssh fd@pwnable.kr -p2222(pw:guest)连上服务器后可以看到读取flag的源码:

提示是Mommy! what is a file descriptor in Linux?,所以考察的是与Linux文件描述符有关的知识,在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件、目录文件、链接文件和设备文件。文件描述符是一个非负整数,用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符。程序刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误。read()的定义为:

依次为文件描述符,变量,读取长度,返回值是操作成功时所读到的字节数,在文件结束时可能少于count个字节;若返回值为-1则说明出错了,返回0则表示到达文件尾端。atoi() (表示ascii to integer)是把字符串转换成整型数的一个函数。这里为了能让buf的值为我们自己输入的LETMEWIN\n从而执行system("/bin/cat flag");,所以需要fd参数值为0,也就是标准输入,由int fd = atoi( argv[1] ) - 0x1234;可知我们输入的参数一要为4660,也就是0x1234的十进制,然后输入LETMEWIN\n即可:

collision-3pt

ssh col@pwnable.kr -p2222 (pw:guest)连上服务器后可以看到读取flag的源码:

提示是cool MD5 hash collision,主函数读入长度20passcode,要求hashcode == check_password( argv[1] )check_passcde()函数将输入的字符串做整型转换后求和输出,要求和等于0x21DD09ECstrlen()函数会返回字符串的长度,但当输入的字符串含有\0,\x时表示一个整体是转义字符,会被当成一个字符判断,看下面的输出:

看出\x00标识了字符串的结尾,与\0作用相同,strlen()遇到就会停止计数。题目把字符串指针转换成了整型指针,来看看输入与输出的关系:

可以看出输入四个十六进制为一组,以小端序转换成了十六进制的数字,所以20 bytes的字符串会转换成5个十六进制数相加,所以构造的字符串要分成5个加起来为0x21DD09EC0x21DD09EC除以5取整为6c5cec8,然后6c5cec8乘以四用0x21DD09EC减得到6c5cecc,由小端序,最后要把十六进制倒序为'\xc8\xce\xc5\x06'*4+'\xcc\xce\xc5\x06':

可直接把这串作为输入会报错,需要用python做转换,用-c参数:

bof-5pt

题目的源码如下:

把下载下来的bof查一下:

是一个32位的程序,如果在64位的linux上运行会报错:

要安装一个包即可:

然后就可以用gdb调试了,这里使用gdb的插件peda来调试,安装方法如下:

用插件的pattern命令生成64长度的唯一字符串:

b mainmain()函数下断点,然后用r运行,n命令为stepping over,而s命令为stepping into。用s不断单步直到第一个函数调用,看到调用了定义的func()函数:

s单步进去后用n单步跟,直到运行到gets()函数接受输入,将生成的patter输入进去,然后就到了比较的语句:

EBP+0x8地址存的值与0xcafebabe比较,这是在程序里定义的,此时的EBP值为:

计算加8后为0xffffd6a0,所以要覆盖的地址就是0xffffd6a0,一样的话就会执行0x5655579b处的代码(/bin/sh):

查看字符串周围的堆栈情况:

0xffffd6a0地址处为AAGAAcAA2AAH,计算一下偏移:

远程发送payload :

flag-7pt

This implies that some kind of anti-debugging techniques are applied to this program.

breakpoint:

flag:

下载http://pwnable.kr/bin/flag文件,是一个linux-64bit下的程序,这题没有给源码,用Exeinfo PE查到有UPX壳,在kali里用upx工具脱壳:

然后放到IDA静态调试,发现在主程序里引用了flag这个变量:

双击flag发现这个变量有upx关键字,直接搜索就能发现flag:UPX...? sounds like a delivery service :)

passcode-10pt

http://blog.csdn.net/smalosnail/article/details/53247502?_t_t_t=0.050024056468489064

代码如下:

要注意的是这里scanf("%d", passcode1);两个读入的变量都没有加&scanf的原型为int scanf(const char * restrict format,...);,这样可能导致内存违规写入,这题考察的是有关GOT表的知识,用下面的命令将远端的可执行文件复制到本地:

gdb调试,先查看一下程序保护方式:

GOT表:每一个外部定义的符号在全局偏移表Global offset Table中有相应的条目,GOT位于ELF的数据段中,叫做GOT段。作用是把位置无关的地址计算重定位到一个绝对地址。程序首次调用某个库函数时,运行时连接编辑器rtld找到相应的符号,并将它重定位到GOT之后每次调用这个函数都会将控制权直接转向那个位置,而不再调用rtldPLT是过程连接表,一个PLT条目对应一个GOT条目,当main函数开始,会请求plt中这个函数的对应GOT地址,如果第一次调用那么GOT会重定位到plt,并向栈中压入一个偏移,程序的执行回到_init()函数,rtld得以调用就可以定位printf的符号地址,第二次运行程序再次调用这个函数时程序跳入plt,对应的GOT入口点就是真实的函数入口地址。动态连接器并不会把动态库函数在编译的时候就包含到ELF文件中,仅仅是在这个ELF被加载的时候,才会把那些动态函库数代码加载进来,之前系统只会在ELF文件中的GOT中保留一个调用地址。GOT覆写技术的原理:由于GOT表是可写的,把其中的函数地址覆盖为我们shellcode地址,在程序进行调用这个函数时就会执行shellcode,可以用objdump查看GOT

这里的思路是由于scanf没加&时会从栈中读取4个字节当scanf取的地址,并把scanf后输入的的内容存到那里,于是我们可以修改栈中的数据,写一个任意4字节的地址进去,来充当scanf取的地址。于是可以利用GOT覆写技术,用一个GOT表中的函数地址来充当scanf取的地址,然后把system("/bin/cat flag")这条指令的地址通过scanf写到这个函数中,当这个函数被调用时,就会直接跳到system("/bin/cat flag"),接下来进行gdb调试,首先看welcome函数,运行至输入部分:

可以看出,mov eax,0x80487dd"%100s"存入EAX寄存器,随后入栈,所以lea edx,[ebp-0x70]就是将scanf的接收地址存入edx寄存器随后入栈,如果scanf加了&的话,应当是lea edx,ptr [ebp-0x70],也就是ebp-0x70name的地址,单步过scanf函数输入nameaaaa后看到栈中0xffffd618 ("aaaa"):

反汇编login函数:

看出passcode1位于ebp-0x10passcode2位于ebp-0xc,单步进login函数可以发现EBP的值与welcome中的一样,所以namepasscode在同一段栈内,而且相差0x70-0x10 = 0x60 = 96

选择之前objdump命令列出的printfoffset 0804a000,把这个地址中的值改为0x080485e3payload为:

flag:

发表评论

电子邮件地址不会被公开。