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 )

等待更新~