下载程序(注意要用bash,zsh报错zsh: no matches found: [email protected]:/home/input2/*)
scp -P 2222 -p [email protected]:/home/input2/* ./
昨晚阅读了一下pwnable.kr的官网,里面提到"the main purpose of pwnable.kr is 'fun'. please consider each of the challenges as a game",感觉挺有意思的。ok,记录一下本关的通关过程。
本关一共有五个小关卡,分为5个stage。
第一关
本关对argc、argv[65]、argv[66]做了判断,本来考虑在bash使用python传递参数给程序,但是\x00实在不知道如何传递,参考网上WP编写C程序,调用execve时传递argv给子程序。
v16 = argv;
v24 = __readfsqword(0x28u);
puts("Welcome to pwnable.kr");
puts("Let's see if you know how to give input to program");
puts("Just give me correct inputs then you will get the flag :)");
if ( argc != 100 )
return 0;
if ( *argv[65] )
return 0;
v4 = __CFADD__(argv, 528LL);
v5 = argv + 66 == 0LL;
v6 = 4LL;
v7 = argv[66];
v8 = " \n\r";
do
{
if ( !v6 )
break;
v4 = (const unsigned __int8)*v7 < *v8;
v5 = *v7++ == *v8++;
--v6;
}
while ( v5 );
if ( (!v4 && !v5) != v4 )
return 0;
puts("Stage 1 clear!");
argc是arguments count,程序运行时参数的数量。没有参数时argc为1。
argv是argument value,传递给程序的argv长度为argc,长度为argc。argv[0]是程序本身,注意argv[argc]需为NULL。
在execve的man文档中也有说明:
#include <unistd.h>
void main(int argc, char **argv, char **env){
char *arg[101]={"/tmp/input2",[1 ... 99]="A", NULL};
arg['A'] = "\x00";
arg['B'] = "\x20\x0a\x0d";
execve(arg[0],arg,NULL);
}
第二关
程序从0 stdin和2 stderr读取buf并和字符串比较,因此需要想办法重定向子进程的stdin和stderr。dup2函数可以做这件事情。
重定向了stdin和stderr之后,父进程向子进程写入对于数据即可。
read(0, &buf, 4uLL);
if ( memcmp(&buf, &unk_400E3D, 4uLL) ) // "\x00\x0a\x00\xff"
return 0;
read(2, &buf, 4uLL);
if ( memcmp(&buf, &unk_400E42, 4uLL) ) // "\x00\x0a\x02\xff"
return 0;
puts("Stage 2 clear!");
linux c如何实现进程通信呢?这里有一份很好的资料可以参考。
https://akaedu.github.io/book/ch30s04.html#id2867812
通过该文章我们可以知道,进程有自己独立的地址空间,需要通过kernel来交换数据。
pipe函数可以在kernel中建立一个半双工的管道用于单向传递数据。一种利用pipe来传递数据的思路如下所示:
1. 进程调用pipe创建自身的读写管道
2. 调用fork生成子进程并close父进程的写端和子进程的读端建立进程间半双工管道
3. 开始传递数据(参考上文链接中的图片)
知道了如何在进程之间传递数据之后,只需要重定向子进程的描述符即可。
dup2函数可以复制描述符,如下程序所示。
#include <unistd.h>
#include <stdio.h>
void main(){
dup2(1,3);
write(3,"123",3);//程序输出123
}
因此,stage2的exp为:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
void main(int argc, char **argv, char **env){
char *arg[101]={"/tmp/input2",[1 ... 99]="A", NULL};
arg['A'] = "\x00";
arg['B'] = "\x20\x0a\x0d";
pid_t pid;
int pipefd1[2], pipefd2[2];
//int n;
//char buf[100]={0};
if(pipe(pipefd1)<0 || pipe(pipefd2)<0){
exit(-1);
}
pid = fork();
if(pid == -1){
printf("fork error");
return;
}else if(pid >0){
close(pipefd1[0]);
close(pipefd2[0]);
write(pipefd1[1],"\x00\x0a\x00\xff",4);
write(pipefd2[1],"\x00\x0a\x02\xff",4);
// wait(NULL);
}else if(pid==0){
close(pipefd1[1]);
close(pipefd2[1]);
dup2(pipefd1[0],0); //pipefd1[0] and 0
dup2(pipefd2[0],2); //pipefd2[0] and 2
//n = read(0,buf,13);
//write(1, buf, n);
execve(arg[0],arg,NULL);
}
}
第三关
本关获取环境变量并调用strcmp(IDA这种伪代码看多了就感觉是这函数了)来比较,因此传递环境变量给子程序即可。
v11 = getenv(&s2); // \xDE\xAD\xBE\xEF
v12 = 5LL;
v13 = &unk_400E5B; // \xCA\xFE\xBA\xBE
v14 = v11;
do
{
if ( !v12 )
break;
v9 = *v13 < (unsigned __int8)*v14;
v10 = *v13++ == *v14++;
--v12;
}
while ( v10 );
if ( (!v9 && !v10) != v9 )
return 0;
puts("Stage 3 clear!");
envp is an array of strings, conventionally of the form
key=value, which are
passed as environment to the new program. The argv and envp arrays
must each include a null pointer at the end of the array.
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
void main(){
...
char *env[2] = {"\xDE\xAD\xBE\xEF=\xCA\xFE\xBA\xBE", NULL};
...
execve(argv[0],argv, env);
}
}
第四关
本关调用fopen打开文件,读取4byte。
stream = fopen("\n", "r");
if ( !stream )
return 0;
if ( fread(&buf, 4uLL, 1uLL, stream) != 1 )
return 0;
if ( memcmp(&buf, &unk_400E73, 4uLL) ) // \x00\x00\x00\x00
return 0;
fclose(stream);
puts("Stage 4 clear!");
因此调用fopen打开文件,fwrite写入相应数据最后fclose即可。(如果是在windows给linux的共享目录下操作需要拷贝到tmp下运行程序)
//stage 4
FILE *fp = fopen("\x0a", "w+");
if (fp == NULL) {
perror("Open file recfile");
exit(-1);
}
char *data = "\x00\x00\x00\x00";
fwrite(data, sizeof(char), 4, fp);
fclose(fp);第五关
第五关
这关IDA伪代码可以看,不过不太友好,直接看源码吧。程序从argv['C']获取端口绑定并接受4byte,比较成功后get flag。
// network
int sd, cd;
struct sockaddr_in saddr, caddr;
sd = socket(AF_INET, SOCK_STREAM, 0);
if(sd == -1){
printf("socket error, tell admin\n");
return 0;
}
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_port = htons( atoi(argv['C']) );
if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
printf("bind error, use another port\n");
return 1;
}
listen(sd, 1);
int c = sizeof(struct sockaddr_in);
cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
if(cd < 0){
printf("accept error, tell admin\n");
return 0;
}
if( recv(cd, buf, 4, 0) != 4 ) return 0;
if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
printf("Stage 5 clear!\n");
// here's your flag
system("/bin/cat flag");
socket编程相关的函数说明参考这里吧,没什么想说的。https://akaedu.github.io/book/ch37s02.html
// stage 5
argv['C'] = "7777";
...
sleep(1);
struct sockaddr_in servaddr;
int sockfd;
char *str = "\xde\xad\xbe\xef";
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(atoi(argv['C']));
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
write(sockfd, str, strlen(str));
close(sockfd);
EXP
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
void main(){
char *argv[101]={"/home/input2/input",[1 ... 99]="A", NULL};
argv['A'] = "\x00";
argv['B'] = "\x20\x0a\x0d";
argv['C'] = "7777";
//stage 3
char *env[2] = {"\xDE\xAD\xBE\xEF=\xCA\xFE\xBA\xBE", NULL};
//stage 4
FILE *fp = fopen("\x0a", "wb");
if (fp == NULL) {
perror("Open file recfile");
exit(-1);
}
char *data = "\x00\x00\x00\x00";
fwrite(data, sizeof(char), 4, fp);
fclose(fp);
//stage2
pid_t pid;
int pipefd1[2], pipefd2[2];
//int n;
//char buf[100]={0};
if(pipe(pipefd1)<0 || pipe(pipefd2)<0){
exit(-1);
}
pid = fork();
if(pid == -1){
printf("fork error");
return;
}else if(pid >0){
close(pipefd1[0]);
close(pipefd2[0]);
write(pipefd1[1],"\x00\x0a\x00\xff",4);
write(pipefd2[1],"\x00\x0a\x02\xff",4);
// wait(NULL);
// stage 5
sleep(1);
struct sockaddr_in servaddr;
int sockfd;
char *str = "\xde\xad\xbe\xef";
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(atoi(argv['C']));
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
write(sockfd, str, strlen(str));
close(sockfd);
}else if(pid==0){
close(pipefd1[1]);
close(pipefd2[1]);
dup2(pipefd1[0],0); //pipefd1[0] and 0
dup2(pipefd2[0],2); //pipefd2[0] and 2
//n = read(0,buf,13);
//write(1, buf, n);
execve(argv[0],argv, env);
}
}
最后修改程序路径,上传程序运行即可。
scp -P 2222 -p exp [email protected]:/tmp/exp/
恭喜通关。