hackme.inndy.tw的一些Writeup(6月3日更新)
推荐一下https://hackme.inndy.tw/scoreboard/,上边有一些很好的针对新手的题目,但网上能搜到的Writeup很少,因此开了这篇博文记录一下部分目前解出的题目(主要是pwn和re),以后会跟着解题进度的推进逐步更新,同时遵循inndy师傅的规矩,只放思路,不放flag。
zwhubuntu师傅的部分题解:http://wxzwhubuntu.club:8088/index.php/2017/06/14/inndy/
聂师傅的部分题解: http://blog.csdn.net/niexinming
pwn
0x00 catflag
送分题,nc连接,cat flag即可
0x01 homework
根据题目提示,是数组越界的漏洞,第一次遇到这种漏洞觉得利用方式很巧妙。通过搜索字符串发现了get shell的函数call_me_maybe, run_program函数中定义了一个大小为10的数组,漏洞出现在edit number的选项里,当我们输入的index大于10时,程序是不会报错的,而是会继续朝着高地址edit数据,因此只需要edit run_program栈中的ret为call_me_maybe, 这样当我们退出run_program函数时,程序就会返回到call_me_maybe函数来get_shell,原理图如下:
有两点需要注意:
- 因为edit number中修改的数据是通过%d输入的,因此需要以整形的方式输入call_me_maybe函数的地址
- 修改ret的地址之后,还需要再输入一次0来退出run_program的循环来出发ret
payload:
0x02 ROP
刚看这道题的时候觉得无从下手,程序中既没有可以利用的函数,通过file命令查看是静态链接的也不能利用libc中的函数。后来注意到下一题提到了ROPgadget这个工具,才想到可以直接利用ROPgadget直接拼凑出ROP链,如下:
这样就只需要我们通过缓冲区溢出的漏洞返回到ROPgadget构造的ropchain就可以了
payload:
0x03 ROP2
本来根据提示以为这道题需要自己拼凑出ropchain,但后来发现这一题中存在syscall这个可以实现系统调用的函数,如syscall(4, 1, &v4, 42)即相当于write(1, &v4, 42),syscall(3, 0, &v1, 1024)即相当于read(0, &v1, 1024),syscall的第一个参数是系统函数的系统调用号,之后的参数依次为对应函数的参数,32位的系统调用号定义在/usr/include/x8664-linux-gnu/asm/unistd32.h中,可以看到execve的调用号为11,因此如果我们构造syscall(11, "/bin/sh", 0, 0)就相当于执行了execve("/bin/sh", 0, 0)即可get shell
介绍一个小trick,如下图,可以看出v4是提示字符串的开头,但因为IDA没有正确识别变量的类型,把提示字符串分成了v4~v15多个变量,v4的地址为bp-0x33,v15为bp-0x9,因此字符串长度为0x33 - 0x9 = 42
这时我们在v4上y一下,然后在弹出来的窗口上填入我们推断出来的数据类型char v4[42],再点OK,这时IDA就能正确的识别出来v4的数据类型了,如下图:
payload1:
通过两次rop,实现get shell
payload2:
利用ROPgadget找到了一个pop4ret的gadget,利用pop4ret平衡堆栈,用一个ropchain实现getshell
需要注意,对于execve的第一个参数"/bin/sh",我们可以用先写入一个固定地址(如bss段)的方式迂回传参。
0x04 toooomuch
放松心情的题,用IDA很容易可以得到通过验证的passcode,然后用 二分/XJB猜 猜对数字后,就可以拿到flag
0x05 toooomuch-2
题目已经提示利用缓冲区溢出通过shellcode来get shell,IDA查看程序流程,在gets函数中存在溢出
这样只要把通过ROP把shellcode写到bss段,再返回bss段执行shellcode即可
payload:
0x06 echo
看到echo,第一反应就是格式化字符串的题,分析文件后果然发现了很明显的格式化字符串漏洞
并且可以通过while循环多次利用,很经典的利用方式,先泄露出system_got中的system函数真实地址,再覆写printf_got为system_addr,之后通过fgets读入"/bin/sh"时,printf("/bin/sh")已经相当于system("/bin/sh"),即可get shell
payload:
需要注意如果leak的payload是p32(system_got) + "%7$s"的形式,那么泄露出的system_addr是从第4位到第8位(p32(system_got))占据了前4位,另外如果p32(system_got)中有\x00等bad char截断printf的话,可以调整payload形式为%8$s + p32(system_got)
0x07 echo2
很明显这一题也是格式化字符串的漏洞,与上一题的不同在于:
- 64位,这意味着fmtstr_payload函数极有可能因为\x00不能用
- 开启了PIE,也就是说got表等地址都是随机的,但因为elf中各个部分的偏移是固定的,因此可以通过泄露elf基址的方法来确定其他部分的真实地址
通过调试看出可以利用%41$p和%43$p泄露main地址与__libc_start_main的地址(0x23 + 6, 0x25 + 6):
泄露elf_base与libc_base的函数如下:
def getAddr(): io.sendline("%41$p..%43$p..") elf_base = int(io.recvuntil("..", drop = True), 16) - 74 - 0x9b9#nm ./echo2 libc_base = int(io.recvuntil("..", drop = True), 16) - 240 - libc_offset # this is for remote # libc_base = int(io.recvuntil("..", drop = True), 16) - 241 - libc_offset #this is for local log.info("elf_base -> 0x%x" % elf_base) log.info("libc_base -> 0x%x" % libc_base) return elf_base + exit_got, libc_base + one_gadget
获得elf_base与libc_base基址后,按照正常的格式化字符串思路来就行,比如可以用one_gadget地址覆写exit函数的got表地址.
0x08 echo3
这道题目做出来后在和站主交流时发现使用了非预期解,不太容易说清楚,这阵比较忙就先只放exp,忙过了这一阵写一下对这道题的详细分析(估计要几个月后了),exp如下:
多说一句inndy人很nice
0x09 smash-the-stack
典型的canary leak,覆盖__libc_argv[0]为flag在内存中地址,触发__stack_chk_fail函数即可泄露flag
放一篇学习链接:http://veritas501.space/2017/04/28/%E8%AE%BAcanary%E7%9A%84%E5%87%A0%E7%A7%8D%E7%8E%A9%E6%B3%95/
payload:
或者可以用更暴力的方法,不计算argv[0]到缓冲区的距离,用一个较大的值直接覆盖过去即可:
payload:
需要注意的是write函数的长度是由用户输入决定的,给buf一个较小的值即可
0x0A onepunch
本来以为onepunch的意思是构造好payload一发get shell, 后来才发现是一个字节一个字节的打
题中有一个任意地址写一个字节的漏洞,刚开始觉得无从下手,后来调试的时候发现代码段为rwxp权限,才觉得柳暗花明。
既然可以在代码段任意地址写,就意味着我们可以为所欲为的修改代码流程,因此就可以将
.text:0000000000400767 jnz short loc_400773
修改为
.text:0000000000400767 jnz 0x40071D
这样就构成了一个循环,接下来在合适的位置写shellcode,然后跳转到shellcode即可
payload:
至于怎么确定要patch的地址和值,我推荐keypatch,用keypatch修改完伪代码后,通过将 Options -> General ->Number of Opcode byte修改为非0值对比修改前后的字节码即可
0x0B rsbo
这道题的利用方法倒是第一次见到,对于read和write的第一个参数fd(文件描述符),fd = 0时代表标准输入stdin,1时代表标准输出stdout,2时代表标准错误stderr,3~9则代表打开的文件。这一题的利用方式利用方式就是利用rop先用open函数打开位于/home/rsbo/的flag,然后再用read(3, )把flag写到一个固定地址上,最后用write输出
payload:
0x0C leave_msg
经过分析代码的逻辑,绕过下边的限制,就可以覆写got表:
if ( v4 <= 64 && nptr != 45 )
dword_804A060[v4] = (int)strdup(&buf);
而v4=atoi(&ntpr),经过搜索atoi函数会跳过字符串开头的空白字符,因此只要构造(空格)-16就可以同时绕过上边的两个限制来覆写puts的got表,然后再用\x00绕过strlen的限制就可以执行shellcode,payload如下:
此时的程序流程如下:
至于0x36是怎么来的,我是调试看出来的,如果哪位表哥有静态计算的方法,还请不吝赐教!
0x0D stack
这个题开了全保护,第一眼看上去挺吓人,但其实漏洞很容易发现,pop时并没有对下标作出检查,这就意味着我们可以通过一直pop利用数组越界从栈上leak,先通过调试看栈结构
可以看出,通过一直pop可以泄露_IO_2_1_stdout_的地址,进而确定libc的装载基址和one_gadget地址
1 def getBase(): 2 # debug() 3 for i in xrange(15): 4 io.sendlineafter("Cmd >>", "p") 5 io.recvuntil("-> ") 6 7 libc_base = (int(io.recvuntil("\n", drop = True)) & 0xffffffff)- libc.symbols["_IO_2_1_stdout_"] 8 info("libc_base -> 0x%x" % libc_base) 9 one_gadget = libc_base + one_gadget_offset 10 11 return one_gadget 12 # return libc_base
通过main+27, main+427等地址可以泄露elf的装载基址(当然并没有用到),并且经过观察,main+27, main+427分别是__x86_get_pc_thunk_dx和stack_push的返回地址,这样我们只需要把main+427覆盖为one_gadget的地址,再次调用stack_push时,就可以返回到one_gadget,进而get shell,有一个坑点是0xf段的地址会发生数据溢出,需要我们转成int32的类型。
虽然听起来很麻烦但只要耐心调试并不难,这个题目的flag也说明了本题的重点就是leak from stack。
关于这个题有两点值得一提:
-
刚打开文件使用F5时报错
很容易搜索到这是因为函数的参数个数不匹配,我们定位到0x78D,发现540这个函数有一个参数,但IDA并没有正确识别
手动指定一个参数后(快捷键y指定类型),这个函数的报错就解决了
同样的方法修复之后的报错,就可以F5查看伪代码了
实现查看伪代码后,可以通过查看函数的具体实现进一步识别函数和修正函数参数
如上图的578函数,可以看出实际调用了hex(8128 + 56)这个地址,而这个地址在IDA中可以看出是scanf的地址,这样我们就可以通过scanf的参数列表进一步修正参数了,附一张我修复之后的图
这样在通过对比题目给出的源码,就很容易分析程序的功能了
-
这个题目开启了PIE保护,利用pwn.gdb.attach调试的时候和没有开启PIE保护的有些不同。
以前我调试开启了PIE保护elf的方式是Uriel师傅教我的先找elf装载基址
-
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
from pwn import * import sys, os import re wordSz = 4 hwordSz = 2 bits = 32 PIE = 0 mypid=0 context(arch='amd64', os='linux', log_level='debug') def leak(address, size): with open('/proc/%s/mem' % mypid) as mem: mem.seek(address) return mem.read(size) def findModuleBase(pid, mem): name = os.readlink('/proc/%s/exe' % pid) with open('/proc/%s/maps' % pid) as maps: for line in maps: if name in line: addr = int(line.split('-')[0], 16) mem.seek(addr) if mem.read(4) == "\x7fELF": bitFormat = u8(leak(addr + 4, 1)) if bitFormat == 2: global wordSz global hwordSz global bits wordSz = 8 hwordSz = 4 bits = 64 return addr log.failure("Module's base address not found.") sys.exit(1) def debug(addr = 0): global mypid mypid = proc.pidof(r)[0] raw_input('debug:') with open('/proc/%s/mem' % mypid) as mem: moduleBase = findModuleBase(mypid, mem) gdb.attach(r, "set follow-fork-mode parent\nb *" + hex(moduleBase+addr))
但做这道题题时发现pwn.gdb.attach只有第一个参数是必须的
这样就可以先进入gdb,通过vmmap找到elf基址后在下断点进行调试了
0x0E very_overflow
这个题目给了源码,分析起来方便了不少。这个题的漏洞也很容易发现,虽然申请了长度为128*(sizeof(buffer))的缓冲区,但可以无限的add_note,这就意味着我们可以先重复add_note耗尽缓冲区,然后继续add_note和show_note时,就可以leak类似__libc_start_main这些信息来确定libc装载基址了,有了libc装载基址后,通过rop构造system("/bin/sh")或者one_gadget都可以求解
至于add_note多少次,通过调试可以很清楚的算出来
另外就是刚开始本地可以get shell,但远程连接很容易超时,后来把context.log_level换成了info,减少了打印花费的时间,又把io.sendlineafter换成了直接io.sendline,就不容易超时了
0x0F tictactoe-1
给的elf文件逆起来比较繁琐,通过反编译可以找到棋的源码,了解了程序的大体流程后,可以发现在落子时可以通过数组越界覆写GOT表
但因为程序有一个判负退出的功能,因此经过实验最多只能写三个字节,但对第一题而言,三个字节已经足够overwrite memset@got为打印flag的地址
第一题很快就解决了,至于tictatoe-2,虽然get shell拿到了flag,但根据flag形式需要用ret2dl_solve,等我用ret2dl_solve解决时再来补wp
reverse
0x00 helloworld:
0x01 simple:
都是水题,不再细说
0x03 pyyy
这一题用uncomplye6或者在线网站反编译后,得到的python代码都有大量的lambda操作,读起来十分费力,并且代码不能直接运行,仔细观察,代码中有一行
this_is = 'Y-Combinator'
似乎是在暗示什么,google了一发,在 https://rosettacode.org/wiki/Y_combinator#Python 找到了Y-Combinator的介绍,即利用lambda等操作实现递归,于是按照网站上给出的python实例修改了代码,使其能够正确执行。再仔细观察,发现只要我们每轮的输入都和程序每轮计算的值相等即可得到flag,因此直接修改python代码,让输入等于程序计算好的值,修改好的代码如下:
1 # uncompyle6 version 2.12.0 2 # Python bytecode 2.7 (62211) 3 # Decompiled from: Python 2.7.13 (default, Jan 19 2017, 14:48:08) 4 # [GCC 6.3.0 20170118] 5 # Embedded file name: pyyy.py 6 # Compiled at: 2016-06-12 01:14:31 7 from fractions import gcd 8 __import__('sys').setrecursionlimit(1048576) 9 data = 'Tt1PJbKTTP+nCqHvVwojv9K8AmPWx1q1UCC7yAxMRIpddAlH+oIHgTET7KHS1SIZshfo2DOu8dUt6wORBvNVBpUSsuHa0S78KG+SCQtB2lr4c1RPbMf0nR9SeSm1ptEY37y310SJMY28u6m4Y44qniGTi39ToHRTyxwsbHVuEjf480eeYAfSVvpWvS8Oy2bjvy0QMVEMSkyJ9p1QlGgyg3mUnNCpSb96VgCaUe4aFu4YbOnOV3HUgYcgXs7IcCELyUeUci7mN8HSvNc93sST6mKl5SDryngxuURkmqLB3azioL6MLWZTg69j6dflQIhr8RvOLNwRURYRKa1g7CKkmhN4RytXn4nyK2UM/SoR+ntja1scBJTUo0I31x1wBJpT4HjDN47FLQWIkRW+2wnB3eEwO5+uSiQpzA8VaH7VGRrlU/BFW4GqbaepzKPLdXQFBkNyBKzqzR/zA2GIrYbLIVScWJ19DqJCOyVLGeVIVXyzN1y327orYL2Ee3lRITnE3FouicRStaznIcw8xmxvukwVMRZIJ/vTu8Zc1WQIYEIFXMHozGuvzZgROZTyFihWNRCBBtoP9DJJALJb0pA1IKIb2zLh+pwGF40Y6y93D6weKejGPO+A0DBXH9vuLcCcCIvr/XPQhO3jLKCBN+h9unuJKW3dyWxyaVPdR2V+BTw10VXolo7yaTH1GbR4TiVSB308mBOMwfchwihEe7RdMXvmXgaGarKkJe0NLUCd8jwhYII+WymjxO/xOz/ppOvNfAyIQksW0sggRPQTlgXSZ7MIVA1h66sGNljJ833MoFzWof3azLabaz1OrAJFqYXBg/myDsy1tV6rULSQ82hVR/TNnSmBGvyEDJTrLSwHyj78NOrW4mUnlLGBnAgWfw6pW2lRK2jkNX9NM6DfLsRK8lwl85UP8CZSuNdcLmLwHTVMZGm/cNkZCtWRBlZqEggxGdIO44D+f4y6ysnAk5/QzEwjIuecxEOb0jyV6dFui8g0c3Oxlhzcli0X8ToJFyeQRv1N9nokYZ07tFlG6m18kCToKz1qiH1U7kljXa6SvdORur5dWYLQ//gwhwppe7JlNda/cEoh92h96wRZDv1dSK/f1vz+mUeUyUlFY0iMjfw5eBXWZppNZi3ZtJcq5kllM2ACVFcxQWI3azM3ArOcqjosoiPjNoDYgKh7w4k2Cd0kLYEHscz/njtJ1KEcwLtqs4nJ+gB2r4V9g03YgvY5E8JJtfJMKdaTedjtvEuif8FNlCK9DMnL1iLpWptJbdfO83Y7Y46XCqjZFBI5o9Qtb78nLhMEM5/YTaNOM/wE/oJl5HI/i1X6kW3PKCsVubRkOkc2xawl6NYdLETjLvmrGhhI' 10 a = 138429774382724799266162638867586769792748493609302140496533867008095173455879947894779596310639574974753192434052788523153034589364467968354251594963074151184337695885797721664543377136576728391441971163150867881230659356864392306243566560400813331657921013491282868612767612765572674016169587707802180184907L 11 b = 166973306488837616386657525560867472072892600582336170876582087259745204609621953127155704341986656998388476384268944991674622137321564169015892277394676111821625785660520124854949115848029992901570017003426516060587542151508457828993393269285811192061921777841414081024007246548176106270807755753959299347499L