avatar

pwnable.kr-写题记录-random与input

反正是找手感,直接打开我扔了很久的pwnable.kr,之前写到random,那么这次就从这里开始

random

程序的逻辑很简单,让我们输入一个数值,程序用rand()生成一个随机数与我们的值异或,判断通过就cat flag,看起来很难完成,但实际上由于题目并没有设置随机数种子,所以每次的值都是一样的,我们只需要得到这个值就好了。

但是我找了很久…最后才发现原来服务器上其实是有gdb的,那么我们调试一下就好了,可以得到默认的值是0x6b8b4567,异或一下输入就好了

input

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main(int argc, char* argv[], char* envp[]){
printf("Welcome to pwnable.kr\n");
printf("Let's see if you know how to give input to program\n");
printf("Just give me correct inputs then you will get the flag :)\n");

// argv
if(argc != 100) return 0;
if(strcmp(argv['A'],"\x00")) return 0;
if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
printf("Stage 1 clear!\n");

// stdio
char buf[4];
read(0, buf, 4);
if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
read(2, buf, 4);
if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
printf("Stage 2 clear!\n");

// env
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");

// file
FILE* fp = fopen("\x0a", "r");
if(!fp) return 0;
if( fread(buf, 4, 1, fp)!=1 ) return 0;
if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
fclose(fp);
printf("Stage 4 clear!\n");

// 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");
return 0;
}

分析

粗看得知,题目需要过五关才能获得flag,然后需要满足各种输入

  1. 需要有100个参数,同时第65、66个参数还需要满足特定的条件,因为中间有例如”\x00”、回车等字符,故我们在服务器上写个程序编译执行应该会方便一点,布局好之后用execve传参即可。
  2. 需要从stdin和stderr中获取字符串,但是这些字符串并没有办法直接从命令行输入,我们只能使用 I/O重定向来进行。这里需要fork一个子进程,然后用pipe实现。参考 linux编程之pipe()函数
  3. env的值默认是系统环境变量,这里我们可以指定env并通过API——execve(“input”,argv,env)传递。C 库函数 - getenv()
  4. 读文件,这里我们整一个这样的文件就行
  5. socket编程,这里我们需要建立套接字连接到server上并发送信息。

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main()
{
int pipe_stdin[2] = {-1,-1}, pipe_stderr[2] = {-1, -1};
char *argv[101] = {0};
/* stage 3 */
char *envp[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL};
FILE *fp = NULL;
pid_t pid_child;

/* stage 1 */
argv[0] = "/home/input2/input";
for(int i=1;i<100;i++)
{
argv[i] = "a";
}
argv['A'] = "\x00";
argv['B'] = "\x20\x0a\x0d";
argv['C'] = "55555";
argv[100] = NULL;

/* stage 4 */
fp = fopen("\x0a","wb");
if(!fp)
{
perror("Cannot open file.");
exit(-1);
}
fwrite("\x00\x00\x00\x00",4,1,fp);
fclose(fp);
fp = NULL;

/* stage 2 */
if(pipe(pipe_stdin) < 0 || pipe(pipe_stderr) < 0)
{
perror("Cannot create pipe!");
exit(-1);
}

if((pid_child = fork()) < 0)
{
perror("Cannot create child process!");
exit(-1);
}

if(pid_child == 0)
{
//子进程先等待父进程重定向pipe read,然后关闭不使用的pipe read
//继而向两个pipe write对应的字符串,父进程此时已经把pipe read重定向到stdin和stderr
//最终由input程序接收
sleep(1);
close(pipe_stdin[0]);
close(pipe_stderr[0]);
write(pipe_stdin[1], "\x00\x0a\x00\xff", 4);
write(pipe_stderr[1], "\x00\x0a\x02\xff", 4);
}
else
{
//父进程无需pipe write操作,首先close掉,然后把两个pipe read重定向到0和2
//也就是stdin和stderr
close(pipe_stdin[1]);
close(pipe_stderr[1]);
dup2(pipe_stdin[0], 0);
dup2(pipe_stderr[0], 2);

execve("/home/input2/input", argv, envp);
}

/* stage 5 */
sleep(5);
int sockfd;
struct sockaddr_in saddr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd == -1)
{
perror("Cannot create socket!");
exit(-1);
}
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
saddr.sin_port = htons(55555);
if(connect(sockfd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0)
{
perror("Cannot connect to server!");
exit(-1);
}
write(sockfd, "\xde\xad\xbe\xef", 4);
close(sockfd);

return 0;
}

在/tmp下建一个我们的目录input,编写test.c并gcc test.c -o test编译,注意该目录下没有flag,所以要先通过ln -s /home/input2/flag flag来建一个软链接。

之所以新建目录而不是在tmp下操作是因为tmp目录我们本身也没有权限,这会限制最后一步的system(“/bin/cat flag”)。

ps:草tmp目录和input目录下全是大家的文件,亏tmp目录还特意限制了ls

参考:

文章作者: 0bs3rver
文章链接: http://yoursite.com/2021/04/18/pwnable-kr-%E5%86%99%E9%A2%98%E8%AE%B0%E5%BD%95-random%E4%B8%8Einput/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 0bs3rver的小屋