hackme.inndy.tw的一些Writeup(5月30更新)

hackme.inndy.tw的一些Writeup(6月3日更新)

原文链接:http://www.cnblogs.com/WangAoBo/p/7706719.html

推荐一下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

如果看了解题思路仍有问题,可以在我的github找到完整的脚本:pwnreverse,欢迎star和follow

pwn

0x00 catflag

送分题,nc连接,cat flag即可

0x01 homework

根据题目提示,是数组越界的漏洞,第一次遇到这种漏洞觉得利用方式很巧妙。通过搜索字符串发现了get shell的函数call_me_maybe, run_program函数中定义了一个大小为10的数组,漏洞出现在edit number的选项里,当我们输入的index大于10时,程序是不会报错的,而是会继续朝着高地址edit数据,因此只需要edit run_program栈中的retcall_me_maybe, 这样当我们退出run_program函数时,程序就会返回到call_me_maybe函数来get_shell,原理图如下:

有两点需要注意:

  1. 因为edit number中修改的数据是通过%d输入的,因此需要以整形的方式输入call_me_maybe函数的地址
  2. 修改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

很明显这一题也是格式化字符串的漏洞,与上一题的不同在于:

  1. 64位,这意味着fmtstr_payload函数极有可能因为\x00不能用
  2. 开启了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_echo3

多说一句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。

关于这个题有两点值得一提:

  1. 刚打开文件使用F5时报错

    很容易搜索到这是因为函数的参数个数不匹配,我们定位到0x78D,发现540这个函数有一个参数,但IDA并没有正确识别

    手动指定一个参数后(快捷键y指定类型),这个函数的报错就解决了

    同样的方法修复之后的报错,就可以F5查看伪代码了

    实现查看伪代码后,可以通过查看函数的具体实现进一步识别函数和修正函数参数

    如上图的578函数,可以看出实际调用了hex(8128 + 56)这个地址,而这个地址在IDA中可以看出是scanf的地址,这样我们就可以通过scanf的参数列表进一步修正参数了,附一张我修复之后的图

    这样在通过对比题目给出的源码,就很容易分析程序的功能了

    1. 这个题目开启了PIE保护,利用pwn.gdb.attach调试的时候和没有开启PIE保护的有些不同。

      以前我调试开启了PIE保护elf的方式是Uriel师傅教我的先找elf装载基址

    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))    
View Code

 

但做这道题题时发现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
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值