pwnable.kr小白题解
题目一(fd - 1 pt )
打开题目,使用SSH链接到目标服务器。发现有三个文件分别为fd、fd.c 、flag。
fd@ubuntu:~$ ls
fd fd.c flag
fd@ubuntu:~$ cat flag
cat: flag: Permission denied
fd@ubuntu:~$ cat fd.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[]){
if(argc<2){
printf("pass argv[1] a number\n");
return 0;
}
int fd = atoi( argv[1] ) - 0x1234;
int len = 0;
len = read(fd, buf, 32);
if(!strcmp("LETMEWIN\n", buf)){
printf("good job :)\n");
system("/bin/cat flag");
exit(0);
}
printf("learn about Linux file IO\n");
return 0;
}
fd@ubuntu:~$ ./fd
pass argv[1] a number
fd@ubuntu:~$ ./fd 3
learn about Linux file IO
fd@ubuntu:~$ ./fd 2
learn about Linux file IO
fd@ubuntu:~$ ./fd 1
learn about Linux file IO
尝试输入,猜测漏洞点为Linux下read的第一参数可控,查阅资料可得(以下内容摘自https://www.cnblogs.com/xiehongfeng100/p/4619451.html):
在Linux下read函数定义如下:
#include <unistd>
ssize_t read(int filedes, void *buf, size_t nbytes);
// 返回:若成功则返回读到的字节数,若已到文件末尾则返回0,若出错则返回-1
// filedes:文件描述符[0-标准输入(stdin),1-标准输出(stdout),2-标准错误输出(stderr)]
// buf:读取数据缓存区
// nbytes:要读取的字节数
有几种情况可使实际读到的字节数少于要求读的字节数:
1)读普通文件时,在读到要求字节数之前就已经达到了文件末端。例如,若在到达文件末端之前还有30个字节,而要求读100个字节,则read返回30,下一次再调用read时,它将返回0(文件末端)。
2)当从终端设备读时,通常一次最多读一行。
3)当从网络读时,网络中的缓存机构可能造成返回值小于所要求读的字结束。
4)当从管道或FIFO读时,如若管道包含的字节少于所需的数量,那么read将只返回实际可用的字节数。
5)当从某些面向记录的设备(例如磁带)读时,一次最多返回一个记录。
6)当某一个信号造成中断,而已经读取了部分数据。
在《UNIX网络编程 卷1》中,作者将该函数进行了封装,以确保数据读取的完整,具体程序如下:
1 ssize_t /* Read "n" bytes from a descriptor. */
2 readn(int fd, void *vptr, size_t n)
3 {
4 size_t nleft;
5 ssize_t nread;
6 char *ptr;
7
8 ptr = vptr;
9 nleft = n;
10 while (nleft > 0) {
11 if ( (nread = read(fd, ptr, nleft)) < 0) {
12 if (errno == EINTR)
13 nread = 0; /* and call read() again */
14 else
15 return(-1);
16 } else if (nread == 0)
17 break; /* EOF */
18
19 nleft -= nread;
20 ptr += nread;
21 }
22 return(n - nleft); /* return >= 0 */
23 }
24 /* end readn */
25
26 ssize_t
27 Readn(int fd, void *ptr, size_t nbytes)
28 {
29 ssize_t n;
30
31 if ( (n = readn(fd, ptr, nbytes)) < 0)
32 err_sys("readn error");
33 return(n);
34 }
因此,我们只需要使得read函数的filedes(文件描述符)为0即可对buf变量进行最大32字节的填充,这里只需要填充"LETMEWIN"即可顺利cat flag。
题目二(collision - 3 pt )
与第一题一样,看文件,读源码,尝试无参运行。
col@ubuntu:~$ ls
col col.c flag
col@ubuntu:~$ cat col.c
#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p){
int* ip = (int*)p;
int i;
int res=0;
for(i=0; i<5; i++){
res += ip[i];
}
return res;
}
int main(int argc, char* argv[]){
if(argc<2){
printf("usage : %s [passcode]\n", argv[0]);
return 0;
}
if(strlen(argv[1]) != 20){
printf("passcode length should be 20 bytes\n");
return 0;
}
if(hashcode == check_password( argv[1] )){
system("/bin/cat flag");
return 0;
}
else
printf("wrong passcode.\n");
return 0;
}
col@ubuntu:~$ ./col
usage : ./col [passcode]
这个程序的逻辑相对简单,即为输入20字节长度的参数,经过check_password的变换,输出int变量与0x21DD09EC比较,相等即输出flag。
为了说明数据在内存中的存储方式,在这里我们用一个简单的方式给予说明。
#include<bits/stdc++.h>
using namespace std;
int main()
{
char str[20]="ABCDEFGHIJKLMNOPQRS";
int *p=(int *)str;
for(int i=0; i<5; i++)
cout<<p[i]<<endl;
return 0;
}
发现输出
1145258561
1212630597
1280002633
1347374669
5460561
--------------------------------
Process exited with return value 0
Press any key to continue . . .
转换为16进制发现即为
0x44434241
0x48474645
0x4C4B4A49
0x504F4E4D
0x535251
那么尝试
#include<bits/stdc++.h>
using namespace std;
int main()
{
char str[20]={'\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x00'};
int *p=(int *)str;
for(int i=0; i<5; i++){
cout<<dec<<p[i]<<endl;
cout<<hex<<p[i]<<endl;
}
return 0;
}
果然结果为
16843009
1010101
16843009
1010101
16843009
1010101
16843009
1010101
65793
10101
--------------------------------
Process exited with return value 0
Press any key to continue . . .
那么我们尝试构造payload。
0x21DD09EC=568134124 33686018*4=134744072 568134124-134744072=433390052
33686018=0x02020202 433390052=0x19D501E4
所以payload是'\xe4\x01\xd5\x19\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
实验
#include<bits/stdc++.h>
using namespace std;
int main()
{
char str[20]={'\x19','\xd5','\x01','\xe4','\x02','\x02','\x02','\x02','\x02','\x02','\x02','\x02','\x02','\x02','\x02','\x02','\x02','\x02','\x02','\x02'};
int *p=(int *)str;
int tmp;
for(int i=0; i<5; i++)
tmp+=p[i];
cout<<dec<<tmp;
cout<<hex<<tmp;
return 0;
}
结果
433390052
33686018
33686018
33686018
33686018
568134124
21dd09ec
--------------------------------
Process exited with return value 0
Press any key to continue . . .
显然正确,那么尝试带参数运行程序。
col@ubuntu:~$ ./col `python -c "print '\x02\x02\x02\x02'*4+'\xE4\x01\xD5\x19'"`
拿到flag。
题目三(bof - 5 pt )
等待更新~