本题目来自:http://pwnable.kr/play.php
首先根据题目提示使用
ssh [email protected] -p2222
来连接到目标服务器,密码为guest
ls一下发现当前目录下有三个文件,一看到flag字样的文件名就下意识的cat flag但是发现没有读取权限,而除了flag文件外还有一个源码和一个可执行文件,显然作者的意图是让玩家分析源代码再通过可执行文件读取flag里面的内容。
fd.c内容如下
#include
#include
#include
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;
}
现在我们来分析一下代码
首先main函数使用了带参数的形式:int main(int argc, char* argv[], char* envp[])
argc: 参数的个数,不给main()函数传递参数时默认值为1,即至少有一个参数为该可执行文件的文件名(含目录)。
argv: 为指针数组,分别指向各个字符串参数的首地址,其中argv[0]存储的是可执行文件的文件名的首地址 。
envp:存放系统的环境变量 。
假设有一个文件名为1.sh的文件中主函数声明为int main(int argc , char* argv[] )
的形式,如果调用时使用root@kali-linux:~# ./1.sh hahaha xixixi hehehe
的形式,则此时argc的值为4,argv[0]为“1.sh” ,argv[1]为“hahaha”,argv[2]为“xixixi”,argv[3]为“hehehe” 。
那么这个代码块:
if(argc<2){
printf("pass argv[1] a number\n");
return 0;
}
表示的意思是运行fd的时候需要加参数。
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);
}
以上代码将argv[1]即第一个参数做了转整型的处理,使用的是atoi()函数:
int atoi(const char *str) 把参数 str 所指向的字符串转换为一个整数(类型为 int 型)
而后面又使用read(fd, buf, 32)
把fd(文件描述)所指的文件传送32个字节到buf所指的内存中,read()函数返回值为实际读取到的字节数, 如果返回0, 表示已到达文件尾或是无可读取的数据。若参数count 为0, 则read()不会有作用并返回0。
关于文件描述符:
文件描述符的值 | 意义 |
---|---|
0 | 标准输入 |
1 | 标准输出 |
2 | 错误 |
strcmp("LETMEWIN\n", buf)
将用户输入的字符串与“LETMEWIN”进行比较,如果一致则输出flag的内容,因此我们需要找到输入的点,因此本题的关键点在于通过控制argv[1] (第一个参数)来修改文件的描述符为0从而修改文件状态为标准输入,再输入“LETMEWIN”来得到flag,此时我们需要考虑如何时文件描述符为0,int fd = atoi( argv[1] ) - 0x1234
文件描述符在程序中的变量名为fd(file descriptor),因此我们需要将atoi( argv[1] ) - 0x1234
的值设为0,0x1234为16进制数,如果我们在参数输入时输入0x1234,atoi()函数会将0x1234中的x转为整型数字,因此数值会发生变化,此时应该使用进制转换将0x1234转换为十进制的4660再输入“LETMEWIN”即可获得flag。
fd@ubuntu:~$ ./fd 4660
LETMEWIN
good job :)
mommy! I think I know what a file descriptor is!!