纵横杯 2020 线下 Pwn

三道Pwn,比赛时均没有写出利用,看出并修上两个题的洞,另外一个没看出洞,当然也没修上。

最终排名:

image

攻防详情:

image

异或不在的星期天

很明显的main函数栈溢出,但是encode函数太大,无法F5,也就看不懂了(后经同学提醒是个魔改的AES),所以也就不知道如何走到return进而触发栈溢出:

image

虽然不会打,但是咱还是会修的,修的方式比较暴力,直接干掉了return,patch成exit(0):

image

  • 用exit直接退出的原因是:main函数如果正常return则会返回到libc_start_main,进而执行一些收尾工作,比如fini_array啥的,如果这里面没有开发逻辑,直接退出无伤大雅。
  • 清空rdi的原因是:不知道怎么check,原来的程序是return 0返回,故保证程序返回和原来一致,防止checker可能不过。

比赛时用keypatch修的,当然也可以用pwntools来生成相应的机器码,首先是清rdi寄存器:

  python
>>> from pwn import *
>>> context(arch='amd64')
>>> asm('xor rdi,rdi').encode('hex')
'4831ff'

然后是call指令,这句没法用pwntools直接生成,因为其不支持相对地址的汇编指令生成,本质是因为pwntools是调用了相应架构的as去编译一段汇编,然后提取,并不是按照机器码的生成规则算出一段机器码。不过我们可以直接算出来这个指令,再算之前首先要知道call指令的格式:

所以首先我们要算出这个相对地址,xor rdi,rdi的起始地址是0x676f4d,长度是3个字节,call 一个相对地址的机器码长度是5个字节,所以call指令执行时rip的值为0x676f4d+3+5,找到exit的函数地址如下:

image

做差:

  python
>>> hex(0x676f4d+3+5-0x400450)
'0x276b05'

然后需要转换成补码,即负数在计算机中的存储格式:python 负数 和 任意位数 补码 互转

  fu2bu -0x276b05 32
input         : -2583301
hex           : 0xffd894fb
bin           : 11111111110110001001010011111011
------------------------------------------------
bit           : 32
min signed    : 0x80000000, -2147483648
max signed    : 0x7fffffff,  2147483647
max unsigned  : 0xffffffff,  4294967295

大小端转换后,开头接上call的操作码E8,即为call exit的机器码:

E8 FB 94 D8 FF

指令的机器码可以参考以下两张cheatsheet:

image

image

pwntools可以反编译这句:

  python
>>> from pwn import *
>>> context(arch='amd64')
>>> disasm("\xE8\xFB\x94\xD8\xFF")
'   0:   e8 fb 94 d8 ff          call   0xffffffffffd89500'

如果你就想用pwntools来生成跳转语句,也可以。方法是:使用寄存器间接保存目标地址,比如call rax这种,不过注意这里使用的就是绝对地址了:

  python 
>>> from pwn import *
>>> context(arch='amd64')
>>> asm('mov rax,0x400450;call rax').encode('hex')
'48c7c050044000ffd0'

baby_httpd

本题是一个将python打包的ELF程序,所以需要反解成原始的python代码,否则看不出程序逻辑:

首先安装需要打包与反编译python字节码的库:

  sudo pip install pyinstaller
  sudo pip install uncompyle6

解开ELF中的python代码:

➜  objcopy --dump-section pydata=pydata.dump pwn
➜  python pyinstxtractor.py pydata.dump

找到input_httpd文件,按上文补齐文件的头部magic并增加.pyc后缀,最后使用uncompyle6反编译即可导出原始python文件:

➜  uncompyle6 input_httpd.pyc

找到一个溢出和一个格串,patch如下,溢出没有完全修上,但是官方的exp应该是失效了:

 diff ./http.py ./patch/http.py
199,200c199,200
<                 string = ctypes.c_buffer(1024)
<                 self.libc.sprintf(string,"%s",value)
---
>                 string = ctypes.c_buffer(2014)
>                 self.libc.strcpy(string,value)

patch完之后,重打包回去即可:

➜  pyinstaller -F ./http.py 

dwarf